diff --git a/frontend/src/admin/AdminDomainModal.js b/frontend/src/admin/AdminDomainModal.js
index f4c1632065..5644a80f99 100644
--- a/frontend/src/admin/AdminDomainModal.js
+++ b/frontend/src/admin/AdminDomainModal.js
@@ -6,7 +6,6 @@ import {
Button,
FormControl,
FormErrorMessage,
- FormLabel,
Grid,
IconButton,
Input,
@@ -26,6 +25,7 @@ import { array, bool, func, object, string } from 'prop-types'
import { Field, FieldArray, Formik } from 'formik'
import { useMutation } from '@apollo/client'
+import { DomainField } from '../domains/DomainField'
import { CREATE_DOMAIN, UPDATE_DOMAIN } from '../graphql/mutations'
export function AdminDomainModal({
@@ -193,30 +193,11 @@ export function AdminDomainModal({
-
- {({ field, form }) => (
-
-
- New Domain URL:
-
-
-
-
- {form.errors.domainUrl}
-
-
- )}
-
+
const adminDomainList = loading ? (
@@ -279,7 +260,7 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId }) {
{
// Submit update role mutation
if (mutation === 'update') {
@@ -170,7 +158,7 @@ export function UserListModal({
variables: {
orgId: orgId,
role: values.role,
- userName: values.userName,
+ userName: values.email,
},
})
} else if (mutation === 'create') {
@@ -178,7 +166,7 @@ export function UserListModal({
variables: {
orgId: orgId,
requestedRole: values.role,
- userName: values.userName,
+ userName: values.email,
preferredLang: 'ENGLISH',
},
})
@@ -204,36 +192,7 @@ export function UserListModal({
{editingUserName}
) : (
-
- {({ field, form }) => (
-
-
-
- User:
-
-
-
-
-
-
-
-
-
- {form.errors.userName}
-
-
- )}
-
+
)}
diff --git a/frontend/src/admin/__tests__/AdminDomains.test.js b/frontend/src/admin/__tests__/AdminDomains.test.js
index aa4e8938c5..dc78efdc36 100644
--- a/frontend/src/admin/__tests__/AdminDomains.test.js
+++ b/frontend/src/admin/__tests__/AdminDomains.test.js
@@ -229,7 +229,7 @@ describe('', () => {
},
]
- const { getByText, getByRole, findByRole } = render(
+ const { getByText, getByRole } = render(
', () => {
,
)
- const domainUrlInput = await findByRole('textbox', {
+ const domainUrlInput = getByRole('textbox', {
name: 'Search by Domain URL',
})
diff --git a/frontend/src/auth/CreateUserPage.js b/frontend/src/auth/CreateUserPage.js
index 366f6dca64..e5016e5d32 100644
--- a/frontend/src/auth/CreateUserPage.js
+++ b/frontend/src/auth/CreateUserPage.js
@@ -9,11 +9,9 @@ import {
useToast,
} from '@chakra-ui/react'
import { useMutation } from '@apollo/client'
-import { object, string } from 'yup'
import { Link as RouteLink, useParams } from 'react-router-dom'
import { Formik } from 'formik'
import { t, Trans } from '@lingui/macro'
-import { i18n } from '@lingui/core'
import { ArrowForwardIcon, CheckCircleIcon } from '@chakra-ui/icons'
import { LanguageSelect } from './LanguageSelect'
@@ -22,7 +20,7 @@ import { EmailField } from '../components/EmailField'
import { DisplayNameField } from '../components/DisplayNameField'
import { PasswordConfirmation } from '../components/PasswordConfirmation'
import { LoadingMessage } from '../components/LoadingMessage'
-import { fieldRequirements } from '../utilities/fieldRequirements'
+import { createValidationSchema } from '../utilities/fieldRequirements'
import { useUserVar } from '../utilities/userState'
import { activate } from '../utilities/i18n.config'
import TermsConditionsPage from '../termsConditions/TermsConditionsPage'
@@ -34,30 +32,6 @@ export default function CreateUserPage() {
const userOrgToken = useParams().userOrgToken || ''
const [showVerifyMessage, setShowVerifyMessage] = useState(false)
- const validationSchema = object().shape({
- email: string()
- .required(i18n._(fieldRequirements.email.required.message))
- .email(i18n._(fieldRequirements.email.email.message)),
- displayName: string().required(
- i18n._(fieldRequirements.displayName.required.message),
- ),
- password: string()
- .required(i18n._(fieldRequirements.password.required.message))
- .min(
- fieldRequirements.password.min.minLength,
- i18n._(fieldRequirements.password.min.message),
- ),
- confirmPassword: string()
- .required(i18n._(fieldRequirements.confirmPassword.required.message))
- .oneOf(
- fieldRequirements.confirmPassword.oneOf.types,
- i18n._(fieldRequirements.confirmPassword.oneOf.message),
- ),
- lang: string()
- .required(i18n._(fieldRequirements.lang.required.message))
- .oneOf(fieldRequirements.lang.oneOf.types),
- })
-
const [signUp, { loading }] = useMutation(SIGN_UP, {
onError(error) {
toast({
@@ -159,7 +133,13 @@ export default function CreateUserPage() {
return (
- {meta.error}
+ {meta.error}
)
diff --git a/frontend/src/createOrganization/CreateOrganizationField.js b/frontend/src/createOrganization/CreateOrganizationField.js
index b6fccb9687..c2e1a1eac9 100644
--- a/frontend/src/createOrganization/CreateOrganizationField.js
+++ b/frontend/src/createOrganization/CreateOrganizationField.js
@@ -1,36 +1,25 @@
import React from 'react'
import { func, object, oneOfType, shape, string } from 'prop-types'
-import {
- FormControl,
- FormErrorMessage,
- FormLabel,
- Input,
-} from '@chakra-ui/react'
-import { useField } from 'formik'
+
+import { FormField } from '../components/FormField'
function OrganizationCreateField({
name,
label,
language,
forwardedRef,
+ inputProps,
...props
}) {
- const [field, meta] = useField(name)
-
return (
-
-
- {label} ({language})
-
-
- {meta.error}
-
+
)
}
@@ -38,6 +27,7 @@ OrganizationCreateField.propTypes = {
name: string.isRequired,
label: string,
language: string,
+ inputProps: object,
forwardedRef: oneOfType([func, shape({ current: object })]),
}
diff --git a/frontend/src/createOrganization/CreateOrganizationPage.js b/frontend/src/createOrganization/CreateOrganizationPage.js
index 001fa8db96..62ef18e5be 100644
--- a/frontend/src/createOrganization/CreateOrganizationPage.js
+++ b/frontend/src/createOrganization/CreateOrganizationPage.js
@@ -12,14 +12,16 @@ import { t, Trans } from '@lingui/macro'
import { useMutation } from '@apollo/client'
import { Formik } from 'formik'
import { Link as RouteLink, useHistory } from 'react-router-dom'
-import { object, string } from 'yup'
import { useLingui } from '@lingui/react'
import { CreateOrganizationField } from './CreateOrganizationField'
import { InfoButton, InfoBox, InfoPanel } from '../components/InfoPanel'
import { LoadingMessage } from '../components/LoadingMessage'
-import { fieldRequirements } from '../utilities/fieldRequirements'
+import {
+ getRequirment,
+ schemaToValidation,
+} from '../utilities/fieldRequirements'
import { CREATE_ORGANIZATION } from '../graphql/mutations'
export default function CreateOrganizationPage() {
@@ -31,43 +33,22 @@ export default function CreateOrganizationPage() {
isVisible: false,
})
- const validationSchema = object().shape({
- nameEN: string().required(i18n._(fieldRequirements.field.required.message)),
- nameFR: string().required(i18n._(fieldRequirements.field.required.message)),
- acronymEN: string()
- .matches(
- fieldRequirements.acronym.matches.regex,
- i18n._(fieldRequirements.acronym.matches.message),
- )
- .max(
- fieldRequirements.acronym.max.maxLength,
- i18n._(fieldRequirements.acronym.max.message),
- )
- .required(i18n._(fieldRequirements.field.required.message)),
- acronymFR: string()
- .matches(
- fieldRequirements.acronym.matches.regex,
- i18n._(fieldRequirements.acronym.matches.message),
- )
- .max(
- fieldRequirements.acronym.max.maxLength,
- i18n._(fieldRequirements.acronym.max.message),
- )
- .required(i18n._(fieldRequirements.field.required.message)),
- cityEN: string().required(i18n._(fieldRequirements.field.required.message)),
- cityFR: string().required(i18n._(fieldRequirements.field.required.message)),
- provinceEN: string().required(
- i18n._(fieldRequirements.field.required.message),
- ),
- provinceFR: string().required(
- i18n._(fieldRequirements.field.required.message),
- ),
- countryEN: string().required(
- i18n._(fieldRequirements.field.required.message),
- ),
- countryFR: string().required(
- i18n._(fieldRequirements.field.required.message),
- ),
+ const fieldRequirement = getRequirment('field')
+ const acronymRequirement = getRequirment('acronym').required(
+ i18n._(t`This field cannot be empty`),
+ )
+
+ const validationSchema = schemaToValidation({
+ nameEN: fieldRequirement,
+ nameFR: fieldRequirement,
+ acronymEN: acronymRequirement,
+ acronymFR: acronymRequirement,
+ cityEN: fieldRequirement,
+ cityFR: fieldRequirement,
+ provinceEN: fieldRequirement,
+ provinceFR: fieldRequirement,
+ countryEN: fieldRequirement,
+ countryFR: fieldRequirement,
})
const [createOrganization, { loading }] = useMutation(CREATE_ORGANIZATION, {
diff --git a/frontend/src/createOrganization/__tests__/CreateOrganizationField.test.js b/frontend/src/createOrganization/__tests__/CreateOrganizationField.test.js
index 31791ee9ad..fcdde00ae5 100644
--- a/frontend/src/createOrganization/__tests__/CreateOrganizationField.test.js
+++ b/frontend/src/createOrganization/__tests__/CreateOrganizationField.test.js
@@ -47,8 +47,8 @@ describe('', () => {
,
)
- const createOrgInput = getByRole('textbox', { name: /Create Org Field/ })
- fireEvent.blur(createOrgInput)
+ const input = getByRole('textbox', { name: /Create Org Field/ })
+ fireEvent.blur(input)
await waitFor(() => {
expect(getByText(/sadness/)).toBeInTheDocument()
diff --git a/frontend/src/domains/DomainField.js b/frontend/src/domains/DomainField.js
index e679b85fb3..f0f86ba0dd 100644
--- a/frontend/src/domains/DomainField.js
+++ b/frontend/src/domains/DomainField.js
@@ -1,44 +1,43 @@
import React from 'react'
import { func, object, oneOfType, shape, string } from 'prop-types'
-import { t, Trans } from '@lingui/macro'
-import {
- FormControl,
- FormErrorMessage,
- FormLabel,
- Input,
- InputGroup,
-} from '@chakra-ui/react'
-import { useField } from 'formik'
+import { t } from '@lingui/macro'
-function DomainField({ name, label, forwardedRef, ...props }) {
- const [field, meta] = useField(name)
-
- const labelText = label === undefined ? Domain URL: : label
+import { FormField } from '../components/FormField'
+function DomainField({
+ name,
+ label,
+ placeholder,
+ forwardedRef,
+ inputProps,
+ ...props
+}) {
return (
-
-
- {labelText}
-
-
-
-
- {meta.error}
-
+
)
}
DomainField.propTypes = {
- name: string.isRequired,
+ name: string,
label: string,
+ placeholder: string,
+ inputProps: object,
forwardedRef: oneOfType([func, shape({ current: object })]),
}
+DomainField.defaultProps = {
+ name: 'domainURL',
+ label: t`Domain URL:`,
+ placeholder: t`Domain URL`,
+}
+
const withForwardedRef = React.forwardRef((props, ref) => {
return
})
diff --git a/frontend/src/domains/ScanDomain.js b/frontend/src/domains/ScanDomain.js
index 8482e7fe22..0aea352998 100644
--- a/frontend/src/domains/ScanDomain.js
+++ b/frontend/src/domains/ScanDomain.js
@@ -1,6 +1,5 @@
import React from 'react'
import { t, Trans } from '@lingui/macro'
-import { i18n } from '@lingui/core'
import { Formik } from 'formik'
import {
Accordion,
@@ -24,7 +23,6 @@ import {
} from '@chakra-ui/react'
import { WarningTwoIcon } from '@chakra-ui/icons'
import { useMutation, useQuery } from '@apollo/client'
-import { object, string } from 'yup'
import { DomainField } from './DomainField'
import { StatusBadge } from './StatusBadge'
@@ -32,17 +30,12 @@ import { StatusBadge } from './StatusBadge'
import { ScanCategoryDetails } from '../guidance/ScanCategoryDetails'
import { LoadingMessage } from '../components/LoadingMessage'
import { StatusIcon } from '../components/StatusIcon'
-import { fieldRequirements } from '../utilities/fieldRequirements'
+import { createValidationSchema } from '../utilities/fieldRequirements'
import { GET_ONE_TIME_SCANS } from '../graphql/queries'
import { REQUEST_SCAN } from '../graphql/mutations'
export function ScanDomain() {
const toast = useToast()
- const validationSchema = object().shape({
- domain: string().required(
- i18n._(fieldRequirements.domainUrl.required.message),
- ),
- })
const [requestScan, { loading }] = useMutation(REQUEST_SCAN, {
onError(error) {
@@ -156,12 +149,12 @@ export function ScanDomain() {
return (
requestScan({
variables: {
- domainUrl: values.domain.toLowerCase(),
+ domainUrl: values.domainUrl.toLowerCase(),
},
})
}
@@ -182,13 +175,14 @@ export function ScanDomain() {
>
Request a domain to be scanned:
-
+
- {errorText}
+ {meta.error}
)
}
diff --git a/frontend/src/user/UserPage.js b/frontend/src/user/UserPage.js
index 33cf8fbba5..05eee1ea3c 100644
--- a/frontend/src/user/UserPage.js
+++ b/frontend/src/user/UserPage.js
@@ -23,7 +23,6 @@ import { t, Trans } from '@lingui/macro'
import { useLingui } from '@lingui/react'
import { Formik } from 'formik'
import { string } from 'prop-types'
-import { object, string as yupString } from 'yup'
import { EditableUserLanguage } from './EditableUserLanguage'
import { EditableUserDisplayName } from './EditableUserDisplayName'
@@ -35,7 +34,7 @@ import { EditableUserPhoneNumber } from './EditableUserPhoneNumber'
import { FormField } from '../components/FormField'
import { LoadingMessage } from '../components/LoadingMessage'
import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage'
-import { fieldRequirements } from '../utilities/fieldRequirements'
+import { createValidationSchema } from '../utilities/fieldRequirements'
import { useUserVar } from '../utilities/userState'
import {
SEND_EMAIL_VERIFICATION,
@@ -167,12 +166,6 @@ export default function UserPage() {
phoneValidated,
} = queryUserData?.userPage
- const closeAccountValidationSchema = object().shape({
- userNameConfirm: yupString()
- .required(i18n._(fieldRequirements.field.required.message))
- .matches(userName, t`User email does not match.`),
- })
-
return (
@@ -239,12 +232,12 @@ export default function UserPage() {
{
await closeAccount({})
signOut()
@@ -272,7 +265,7 @@ export default function UserPage() {
diff --git a/frontend/src/utilities/fieldRequirements.js b/frontend/src/utilities/fieldRequirements.js
index f945a1481d..4b60b9aca8 100644
--- a/frontend/src/utilities/fieldRequirements.js
+++ b/frontend/src/utilities/fieldRequirements.js
@@ -1,58 +1,93 @@
-import { ref } from 'yup'
+import { array, number, object, ref, string } from 'yup'
import { t } from '@lingui/macro'
+import { i18n } from '@lingui/core'
+import {
+ array as arrayProp,
+ object as objectProp,
+ string as stringProp,
+} from 'prop-types'
-export const fieldRequirements = {
- email: {
- required: { message: t`Email cannot be empty` },
- email: { message: t`Invalid email` },
- },
- displayName: { required: { message: t`Display name cannot be empty` } },
- password: {
- required: { message: t`Password cannot be empty` },
- min: {
- minLength: 12,
- message: t`Password must be at least 12 characters long`,
- },
- },
- confirmPassword: {
- required: { message: t`Password confirmation cannot be empty` },
- oneOf: { types: [ref('password')], message: t`Passwords must match` },
- },
- lang: {
- required: { message: t`Please choose your preferred language` },
- oneOf: { types: ['ENGLISH', 'FRENCH'], message: '' },
- },
- twoFactorCode: {
- typeError: { message: t`Verification code must only contains numbers` },
- required: { message: t`Code field must not be empty` },
- },
- domainUrl: {
- required: { message: t`Domain url field must not be empty` },
- },
- phoneNumber: {
- matches: {
- message: t`Phone number must be a valid phone number that is 10-15 digits long`,
- },
- required: { message: t`Phone number field must not be empty` },
- },
- acronym: {
- matches: {
- regex: /^[A-Z]+(?:_[A-Z]+)*$/gm,
- message: t`Acronyms can only use upper case letters and underscores`,
- },
- max: {
- maxLength: 50,
- message: t`Acronyms must be at most 50 characters`,
- },
- },
- field: {
- required: { message: t`This field cannot be empty` },
- },
- selector: {
- required: { message: t`Selector cannot be empty` },
- matches: {
- regex: /^([\S]+)([.]_domainkey)$/gm,
- message: t`Selector must be string ending in '._domainkey'`,
- },
- },
+const getSchema = () => {
+ return {
+ email: string()
+ .required(i18n._(t`Email cannot be empty`))
+ .email(i18n._(t`Invalid email`)),
+ confirmEmail: string()
+ .required(i18n._(t`Email cannot be empty`))
+ .oneOf([ref('userName')], t`User email does not match`),
+ displayName: string().required(i18n._(t`Display name cannot be empty`)),
+ password: string()
+ .required(i18n._(t`Password cannot be empty`))
+ .min(12, i18n._(t`Password must be at least 12 characters long`)),
+ passwordSignIn: string().required(i18n._(t`Password cannot be empty`)),
+ confirmPassword: string()
+ .required(i18n._(t`Password confirmation cannot be empty`))
+ .oneOf([ref('password')], t`Passwords must match`),
+ currentPassword: string().required(
+ i18n._(t`Please enter your current password.`),
+ ),
+ lang: string()
+ .required(i18n._(t`Please choose your preferred language`))
+ .oneOf(['ENGLISH', 'FRENCH'], ''),
+ twoFactorCode: number()
+ .typeError(i18n._(t`Verification code must only contains numbers`))
+ .required(i18n._(t`Code field must not be empty`)),
+ domainUrl: string().required(i18n._(t`Domain url field must not be empty`)),
+ phoneNumber: string()
+ .required(i18n._(t`Phone number field must not be empty`))
+ .matches(
+ /^[1-9]\d{9,14}$/,
+ i18n._(
+ t`Phone number must be a valid phone number that is 10-15 digits long`,
+ ),
+ ),
+ acronym: string()
+ .matches(
+ /^[A-Z]+(?:_[A-Z]+)*$/gm,
+ i18n._(t`Acronyms can only use upper case letters and underscores`),
+ )
+ .max(50, i18n._(t`Acronyms must be at most 50 characters`)),
+ field: string().required(i18n._(t`This field cannot be empty`)),
+ selectors: array().of(
+ string()
+ .required(i18n._(t`Selector cannot be empty`))
+ .matches(
+ /^([\S]+)([.]_domainkey)$/gm,
+ i18n._(t`Selector must be string ending in '._domainkey'`),
+ ),
+ ),
+ }
+}
+
+const filterSchema = (keyArray) => {
+ const schema = getSchema()
+
+ return keyArray.reduce((selectedSchema, currentKey) => {
+ selectedSchema[currentKey] = schema[currentKey]
+ return selectedSchema
+ }, {})
+}
+
+export const getRequirment = (key) => {
+ return getSchema()[key]
+}
+
+export const schemaToValidation = (schema) => {
+ return object().shape(schema)
+}
+
+export const createValidationSchema = (keyArray) => {
+ return schemaToValidation(filterSchema(keyArray))
+}
+
+getRequirment.propTypes = {
+ keyArray: stringProp.isRequired,
+}
+
+schemaToValidation.propTypes = {
+ schema: objectProp.isRequired,
+}
+
+createValidationSchema.propTypes = {
+ keyArray: arrayProp.isRequired,
}