diff --git a/.gitignore b/.gitignore index c4e7b0e..56ebe57 100644 --- a/.gitignore +++ b/.gitignore @@ -209,4 +209,10 @@ sketch # Support for Project snippet scope -# End of https://www.toptal.com/developers/gitignore/api/react,macos,nextjs,visualstudiocode,node \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/react,macos,nextjs,visualstudiocode,node + +# firebase +firebaseConfig.ts +.firebaserc +firebase.json +.firebase \ No newline at end of file diff --git a/package.json b/package.json index 1ec4ac5..d73b795 100644 --- a/package.json +++ b/package.json @@ -14,13 +14,16 @@ "dependencies": { "@emotion/react": "^11.8.2", "@emotion/styled": "^11.8.1", + "@types/yup": "^0.29.13", "emotion-normalize": "^11.0.1", "firebase": "^9.6.8", + "formik": "^2.2.9", "next": "12.1.0", "react": "17.0.2", "react-dom": "17.0.2", "react-icons": "^4.3.1", - "react-loading-icons": "^1.0.8" + "react-loading-icons": "^1.0.8", + "yup": "^0.32.11" }, "devDependencies": { "@babel/core": "^7.17.7", diff --git a/src/api/requestAuth.ts b/src/api/requestAuth.ts new file mode 100644 index 0000000..7121678 --- /dev/null +++ b/src/api/requestAuth.ts @@ -0,0 +1,53 @@ +import { initializeApp } from 'firebase/app'; +import { getFirestore, setDoc, doc, Timestamp } from 'firebase/firestore'; +import { + getAuth, + signInWithEmailAndPassword, + createUserWithEmailAndPassword, + signOut, + onAuthStateChanged, +} from 'firebase/auth'; +import { SignInFormValues, SignUpFormValues } from 'components/Auth/Auth.types'; +import { firebaseConfig } from './firebaseConfig'; + +initializeApp(firebaseConfig); + +const auth = getAuth(); +const db = getFirestore(); + +export const getAuthStatus = async () => { + return new Promise((resolve, reject) => { + try { + onAuthStateChanged(auth, (user) => resolve(user)); + } catch (e) { + reject(e); + } + }); +}; + +export const signIn = async ({ email, password }: SignInFormValues) => { + try { + const { user } = await signInWithEmailAndPassword(auth, email, password); + return user; + } catch (error) { + throw new Error(error.code); + } +}; + +export const signUp = async ({ username, email, password }: SignUpFormValues) => { + try { + const { user } = await createUserWithEmailAndPassword(auth, email, password); + const docRef = await setDoc(doc(db, 'users', user.uid), { + username, + email, + createdAt: Timestamp.fromDate(new Date()), + }); + return user; + } catch (error) { + throw new Error(error.code); + } +}; + +export const logOut = () => { + signOut(auth); +}; diff --git a/src/components/Auth/Auth.stories.tsx b/src/components/Auth/Auth.stories.tsx new file mode 100644 index 0000000..7f404a1 --- /dev/null +++ b/src/components/Auth/Auth.stories.tsx @@ -0,0 +1,14 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Auth } from './Auth'; + +export default { + title: 'Auth', + component: Auth, + args: { currentForm: 'signin' }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const SignInForm = Template.bind({}); +export const SignUpForm = Template.bind({}); +SignUpForm.args = { currentForm: 'signup' }; diff --git a/src/components/Auth/Auth.styled.tsx b/src/components/Auth/Auth.styled.tsx new file mode 100644 index 0000000..833ba72 --- /dev/null +++ b/src/components/Auth/Auth.styled.tsx @@ -0,0 +1,19 @@ +import styled from '@emotion/styled'; +import { pxToRem } from 'utils'; +import { StyleInputProps } from './Auth.types'; + +export const StyledFieldError = styled.div` + height: ${pxToRem(32)}; + line-height: 1.8; + font-size: ${pxToRem(16)}; + color: ${({ theme }) => theme.color.primaryOrange}; + padding: 0 ${pxToRem(20)}; +`; + +export const StyledInput = styled.input` + padding: 0 ${pxToRem(24)}; + height: ${pxToRem(36)}; + border: none; + border-radius: ${pxToRem(5)} ${pxToRem(5)}; + ${({ $warning, theme }) => $warning && `box-shadow: 0 0 1px 5px ${theme.color.primaryOrange};`} +`; diff --git a/src/components/Auth/Auth.tsx b/src/components/Auth/Auth.tsx new file mode 100644 index 0000000..682b691 --- /dev/null +++ b/src/components/Auth/Auth.tsx @@ -0,0 +1,42 @@ +import { FormikProps, withFormik } from 'formik'; +import { FormValues, FormProps } from './Auth.types'; +import { StyledInput, StyledFieldError } from './Auth.styled'; +import { AUTH_FUNC, SCHEMA, INITIAL_VALUES, FIELDS, HEADING, PLACEHOLDER, TYPE } from './AuthServices'; + +const AuthForm = (props: FormProps & FormikProps): JSX.Element => { + const { currentForm, values, errors, dirty, touched, isValid, handleChange, handleBlur, handleSubmit } = props; + return ( +
+ {FIELDS[currentForm].map((field): JSX.Element => ( + <> + + + {touched[field] && errors[field]} + + ))} + + + + ); +}; + +export const Auth = withFormik({ + mapPropsToValues: ({ currentForm }) => INITIAL_VALUES[currentForm], + validationSchema: ({ currentForm }: FormProps) => SCHEMA[currentForm], + handleSubmit(values: FormValues, { props: { currentForm } }) { + AUTH_FUNC[currentForm](values); + }, +})(AuthForm); diff --git a/src/components/Auth/Auth.types.ts b/src/components/Auth/Auth.types.ts new file mode 100644 index 0000000..471488b --- /dev/null +++ b/src/components/Auth/Auth.types.ts @@ -0,0 +1,23 @@ +export interface SignInFormValues { + password: string; + email: string; +} + +export interface SignUpFormValues extends SignInFormValues { + username: string; + passwordConfirm: string; +} + +export type FormValues = SignInFormValues | SignUpFormValues; + +export interface FormProps { + initialEmail?: string; + initialPassword?: string; + initialPasswordConfirm?: string; + initialUsername?: string; + currentForm: 'signin' | 'signup'; +} + +export interface StyleInputProps { + $warning: boolean; +} diff --git a/src/components/Auth/AuthServices.ts b/src/components/Auth/AuthServices.ts new file mode 100644 index 0000000..b81ebbd --- /dev/null +++ b/src/components/Auth/AuthServices.ts @@ -0,0 +1,69 @@ +import { signIn, signUp } from 'api/requestAuth'; +import * as Yup from 'yup'; + +export const TOGGLE_MESSAGE = { + signin: 'Not registered yet? Sign up here!', + signup: 'Already a member? Sign in here!', +} as const; + +export const HEADING = { + signin: 'Sign In', + signup: 'Sign Up', +} as const; + +export const PLACEHOLDER = { + username: 'Username', + email: 'Email', + password: 'Password', + passwordConfirm: 'Confirm password', +} as const; + +export const TYPE = { + username: 'text', + email: 'email', + password: 'password', + passwordConfirm: 'password', +} as const; + +export const INITIAL_VALUES = { + signin: { + password: '', + email: '', + }, + signup: { + username: '', + password: '', + passwordConfirm: '', + email: '', + }, +} as const; + +export const SCHEMA = { + signin: Yup.object({ + password: Yup.string().min(8, 'Must be 8 characters or more').required('Required'), + email: Yup.string().email('Invalid email address').required('Required'), + }), + signup: Yup.object({ + username: Yup.string().min(3, 'Must be 3 characters or more').required('Required'), + password: Yup.string().min(8, 'Must be 8 characters or more').required('Required'), + passwordConfirm: Yup.string() + .oneOf([Yup.ref('password'), null], 'Passwords must match') + .required('Required'), + email: Yup.string().email('Invalid email address').required('Required'), + }), +} as const; + +export const AUTH_ERROR_MSG = { + signin: 'Sign in failed. Please try again.', + signup: 'Sign up failed. Please try again.', +} as const; + +export const FIELDS = { + signin: ['email', 'password'], + signup: ['username', 'email', 'password', 'passwordConfirm'], +} as const; + +export const AUTH_FUNC = { + signin: signIn, + signup: signUp, +} as const; diff --git a/src/components/EmptyPage/EmptyPage.stories.tsx b/src/components/EmptyPage/EmptyPage.stories.tsx index c8717ab..eaf6555 100644 --- a/src/components/EmptyPage/EmptyPage.stories.tsx +++ b/src/components/EmptyPage/EmptyPage.stories.tsx @@ -1,8 +1,5 @@ import { ComponentMeta, ComponentStory } from '@storybook/react'; import Link from 'next/link'; -import { ThemeProvider } from '@emotion/react'; -import { theme } from 'theme/theme'; -import { GlobalStyle } from 'styles/GlobalStyle'; import { EmptyPage } from './EmptyPage'; export default { @@ -16,14 +13,6 @@ export default { ), }, - decorators: [ - (Story) => ( - - - - - ), - ], } as ComponentMeta; const Template: ComponentStory = (args) => ; diff --git a/src/components/Pagination/Pagination.stories.tsx b/src/components/Pagination/Pagination.stories.tsx index c28db39..29b9682 100644 --- a/src/components/Pagination/Pagination.stories.tsx +++ b/src/components/Pagination/Pagination.stories.tsx @@ -1,7 +1,4 @@ -import { ThemeProvider } from '@emotion/react'; import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { GlobalStyle } from 'styles/GlobalStyle'; -import { theme } from 'theme/theme'; import { Pagination } from './Pagination'; export default { @@ -13,14 +10,6 @@ export default { totalResults: 100, limit: 5, }, - decorators: [ - (Story) => ( - - - - - ), - ], } as ComponentMeta; const Template: ComponentStory = (args) => ; diff --git a/src/components/Pagination/Pagination.styled.tsx b/src/components/Pagination/Pagination.styled.tsx index 9499dfb..cb9d08f 100644 --- a/src/components/Pagination/Pagination.styled.tsx +++ b/src/components/Pagination/Pagination.styled.tsx @@ -1,6 +1,28 @@ -export interface PaginationProps { - currentPage: number; - onClick: (currentPage: number) => void; - totalResults: number; - limit: number; -} +import styled from '@emotion/styled'; +import { pxToRem } from 'utils'; + +export const StyledPaginationControl = styled.div` + display: flex; + justify-contents: center; + align-items: center; + gap: ${pxToRem(20)}; + ul { + display: flex; + justify-contents: center; + align-items: center; + gap: ${pxToRem(10)}; + } +`; + +export const StyledPageButton = styled.button` + color: ${({ theme, current }) => (current ? theme.color.white : theme.color.primaryOrange)}; + border: 1px solid ${({ theme }) => theme.color.primaryOrange}; + border-radius: ${pxToRem(5)}; + background-color: ${({ theme, current }) => (current ? theme.color.primaryOrange : theme.color.white)}; + width: ${pxToRem(32)}; + height: ${pxToRem(32)}; + margin: ${pxToRem(6)}; + &:hover { + opacity: 0.5; + } +`; diff --git a/src/components/Pagination/Pagination.tsx b/src/components/Pagination/Pagination.tsx index ea92c04..0c31005 100644 --- a/src/components/Pagination/Pagination.tsx +++ b/src/components/Pagination/Pagination.tsx @@ -1,11 +1,10 @@ import { IoIosArrowBack, IoIosArrowForward } from 'react-icons/io'; -import { PaginationProps } from './Pagination.styled'; -import { StyledPageButton, StyledPaginationControl } from './Pagination.types'; +import { usePageNum } from 'hooks/usePageNum'; +import { PaginationProps } from './Pagination.types'; +import { StyledPageButton, StyledPaginationControl } from './Pagination.styled'; export const Pagination = ({ limit, currentPage, onClick: handleClick, totalResults }: PaginationProps) => { - const lastPageNum = Math.ceil(totalResults / limit); - const pageStartNum = Math.max(currentPage - 2, 1); - const pageEndNum = Math.min(currentPage + 2, lastPageNum); + const { pageStartNum, pageEndNum } = usePageNum({ currentPage, totalResults, limit }); return ( (current ? theme.color.white : theme.color.primaryOrange)}; - border: 1px solid ${({ theme }) => theme.color.primaryOrange}; - border-radius: ${pxToRem(5)}; - background-color: ${({ theme, current }) => (current ? theme.color.primaryOrange : theme.color.white)}; - width: ${pxToRem(32)}; - height: ${pxToRem(32)}; - margin: ${pxToRem(6)}; - &:hover { - opacity: 0.5; - } -`; +export interface PaginationProps { + currentPage: number; + onClick: (currentPage: number) => void; + totalResults: number; + limit: number; +} \ No newline at end of file diff --git a/src/hooks/usePageNum.ts b/src/hooks/usePageNum.ts new file mode 100644 index 0000000..b494d5a --- /dev/null +++ b/src/hooks/usePageNum.ts @@ -0,0 +1,18 @@ +import { useMemo } from 'react'; + +export const usePageNum = ({ + totalResults, + limit, + currentPage, +}: { + totalResults: number; + limit: number; + currentPage: number; +}) => { + return useMemo(() => { + const lastPageNum = Math.ceil(totalResults / limit); + const pageStartNum = Math.max(currentPage - 2, 1); + const pageEndNum = Math.min(currentPage + 2, lastPageNum); + return { pageStartNum, pageEndNum }; + }, [currentPage, totalResults, limit]); +}; diff --git a/yarn.lock b/yarn.lock index 6a71ac6..c04ae18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1082,6 +1082,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.15.4": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" + integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.12.7", "@babel/template@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -3148,6 +3155,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash@^4.14.175": + version "4.14.180" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670" + integrity sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g== + "@types/long@^4.0.0", "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" @@ -3311,6 +3323,11 @@ dependencies: "@types/yargs-parser" "*" +"@types/yup@^0.29.13": + version "0.29.13" + resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.13.tgz#21b137ba60841307a3c8a1050d3bf4e63ad561e9" + integrity sha512-qRyuv+P/1t1JK1rA+elmK1MmCL1BapEzKKfbEhDBV/LMMse4lmhZ/XbgETI39JveDJRpLjmToOI6uFtMW/WR2g== + "@typescript-eslint/eslint-plugin@^5.15.0": version "5.15.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.15.0.tgz#c28ef7f2e688066db0b6a9d95fb74185c114fb9a" @@ -5598,6 +5615,11 @@ deep-object-diff@^1.1.0: resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.7.tgz#348b3246f426427dd633eaa50e1ed1fc2eafc7e4" integrity sha512-QkgBca0mL08P6HiOjoqvmm6xOAl2W6CT2+34Ljhg0OeFan8cwlcdq8jrLKsBBuUFAZLsN5b6y491KdKEoSo9lg== +deepmerge@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== + deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" @@ -7075,6 +7097,19 @@ format@^0.2.0: resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= +formik@^2.2.9: + version "2.2.9" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0" + integrity sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA== + dependencies: + deepmerge "^2.1.1" + hoist-non-react-statics "^3.3.0" + lodash "^4.17.21" + lodash-es "^4.17.21" + react-fast-compare "^2.0.1" + tiny-warning "^1.0.2" + tslib "^1.10.0" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -9138,6 +9173,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + lodash._isnative@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/lodash._isnative/-/lodash._isnative-2.4.1.tgz#3ea6404b784a7be836c7b57580e1cdf79b14832c" @@ -9813,6 +9853,11 @@ nan@^2.12.1, nan@^2.15.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== +nanoclone@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== + nanoid@^3.1.23, nanoid@^3.1.30: version "3.3.1" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" @@ -10913,6 +10958,11 @@ prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +property-expr@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4" + integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA== + property-information@^5.0.0, property-information@^5.3.0: version "5.6.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" @@ -11203,6 +11253,11 @@ react-element-to-jsx-string@^14.3.4: is-plain-object "5.0.0" react-is "17.0.2" +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + react-fast-compare@^3.0.1, react-fast-compare@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" @@ -12728,6 +12783,11 @@ timers-ext@^0.1.5, timers-ext@^0.1.7: es5-ext "~0.10.46" next-tick "1" +tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + tmp@0.0.33, tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -12804,6 +12864,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= + tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -12869,7 +12934,7 @@ tsconfig-paths@^3.12.0, tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.8.1: +tslib@^1.10.0, tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -13774,6 +13839,19 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yup@^0.32.11: + version "0.32.11" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.11.tgz#d67fb83eefa4698607982e63f7ca4c5ed3cf18c5" + integrity sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg== + dependencies: + "@babel/runtime" "^7.15.4" + "@types/lodash" "^4.14.175" + lodash "^4.17.21" + lodash-es "^4.17.21" + nanoclone "^0.2.1" + property-expr "^2.0.4" + toposort "^2.0.2" + zip-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.0.tgz#51dd326571544e36aa3f756430b313576dc8fc79"