From 70c35be1b775446a0843844c92404137053c51c8 Mon Sep 17 00:00:00 2001 From: Adam Prins Date: Wed, 18 Aug 2021 13:03:39 -0400 Subject: [PATCH 1/5] reformat fieldRequirements the object is now formatted using yup createValidationSchema can create a schema from the passed keys custom validation schemas can be created with getRequirment and schemaToValidation --- frontend/src/utilities/fieldRequirements.js | 145 ++++++++++++-------- 1 file changed, 90 insertions(+), 55 deletions(-) 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, } From 9a5977131c5fa311b0b002b926cb5a4a5b78e275 Mon Sep 17 00:00:00 2001 From: Adam Prins Date: Wed, 18 Aug 2021 13:05:17 -0400 Subject: [PATCH 2/5] updates code to use new fieldRequirments --- frontend/src/admin/AdminDomains.js | 23 +------- frontend/src/admin/OrganizationInformation.js | 29 ++++----- frontend/src/admin/UserListModal.js | 29 ++++----- frontend/src/auth/CreateUserPage.js | 36 +++-------- frontend/src/auth/SignInPage.js | 16 +++-- .../src/auth/TwoFactorAuthenticatePage.js | 11 +--- .../CreateOrganizationPage.js | 59 +++++++------------ frontend/src/domains/ScanDomain.js | 18 ++---- .../src/domains/__tests__/ScanDomain.test.js | 8 +-- frontend/src/user/EditableUserDisplayName.js | 12 +--- frontend/src/user/EditableUserEmail.js | 12 +--- frontend/src/user/EditableUserLanguage.js | 12 +--- frontend/src/user/EditableUserPassword.js | 28 ++------- frontend/src/user/EditableUserPhoneNumber.js | 25 +------- frontend/src/user/PhoneNumberField.js | 12 +--- frontend/src/user/UserPage.js | 17 ++---- 16 files changed, 92 insertions(+), 255 deletions(-) diff --git a/frontend/src/admin/AdminDomains.js b/frontend/src/admin/AdminDomains.js index e994fc6407..c4e53adb4f 100644 --- a/frontend/src/admin/AdminDomains.js +++ b/frontend/src/admin/AdminDomains.js @@ -25,11 +25,6 @@ import { AddIcon, EditIcon, MinusIcon, PlusSquareIcon } from '@chakra-ui/icons' import { useMutation } from '@apollo/client' import { useLingui } from '@lingui/react' import { number, string } from 'prop-types' -import { - array as yupArray, - object as yupObject, - string as yupString, -} from 'yup' import { AdminDomainModal } from './AdminDomainModal' import { AdminDomianCard } from './AdminDomianCard' @@ -39,7 +34,7 @@ import { LoadingMessage } from '../components/LoadingMessage' import { ErrorFallbackMessage } from '../components/ErrorFallbackMessage' import { RelayPaginationControls } from '../components/RelayPaginationControls' import { useDebouncedFunction } from '../utilities/useDebouncedFunction' -import { fieldRequirements } from '../utilities/fieldRequirements' +import { createValidationSchema } from '../utilities/fieldRequirements' import { usePaginatedCollection } from '../utilities/usePaginatedCollection' import { PAGINATED_ORG_DOMAINS_ADMIN_PAGE as FORWARD } from '../graphql/queries' import { REMOVE_DOMAIN } from '../graphql/mutations' @@ -141,20 +136,6 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId }) { }, ) - const updatedDomainValidationSchema = yupObject().shape({ - domainUrl: yupString().required( - i18n._(fieldRequirements.domainUrl.required.message), - ), - selectors: yupArray().of( - yupString() - .required(i18n._(fieldRequirements.selector.required.message)) - .matches( - fieldRequirements.selector.matches.regex, - i18n._(fieldRequirements.selector.matches.message), - ), - ), - }) - if (error) return const adminDomainList = loading ? ( @@ -279,7 +260,7 @@ export function AdminDomains({ orgSlug, domainsPerPage, orgId }) { { // Submit update role mutation if (mutation === 'update') { @@ -170,7 +165,7 @@ export function UserListModal({ variables: { orgId: orgId, role: values.role, - userName: values.userName, + userName: values.email, }, }) } else if (mutation === 'create') { @@ -178,7 +173,7 @@ export function UserListModal({ variables: { orgId: orgId, requestedRole: values.role, - userName: values.userName, + userName: values.email, preferredLang: 'ENGLISH', }, }) @@ -204,15 +199,13 @@ export function UserListModal({ {editingUserName} ) : ( - + {({ field, form }) => ( - + User: @@ -222,15 +215,13 @@ export function UserListModal({ - - {form.errors.userName} - + {form.errors.email} )} 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 ( requestScan({ variables: { - domainUrl: values.domain.toLowerCase(), + domainUrl: values.domainURL.toLowerCase(), }, }) } @@ -182,13 +175,14 @@ export function ScanDomain() { > Request a domain to be scanned: - +