diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 9d6b53c52..b0b0170d7 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -3095,6 +3095,36 @@ export type LoginMutation = { __typename?: 'Mutation' } & { >) } +export type VerifyCustomerAccountVariables = Exact<{ + token: Scalars['String'] + password?: Maybe +}> + +export type VerifyCustomerAccountMutation = { __typename?: 'Mutation' } & { + verifyCustomerAccount: + | ({ __typename: 'CurrentUser' } & Pick) + | ({ __typename: 'VerificationTokenInvalidError' } & Pick< + VerificationTokenInvalidError, + 'errorCode' | 'message' + >) + | ({ __typename: 'VerificationTokenExpiredError' } & Pick< + VerificationTokenExpiredError, + 'errorCode' | 'message' + >) + | ({ __typename: 'MissingPasswordError' } & Pick< + MissingPasswordError, + 'errorCode' | 'message' + >) + | ({ __typename: 'PasswordAlreadySetError' } & Pick< + PasswordAlreadySetError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NativeAuthStrategyError' } & Pick< + NativeAuthStrategyError, + 'errorCode' | 'message' + >) +} + export type LogoutMutationVariables = Exact<{ [key: string]: never }> export type LogoutMutation = { __typename?: 'Mutation' } & { diff --git a/package.json b/package.json index bb78b5cf1..84a77cf71 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "email-validator": "^2.0.4", "eslint": "^7.32.0", "eslint-config-next": "^11.1.2", + "formik": "^2.2.9", "immutability-helper": "^3.1.1", "js-cookie": "^2.2.1", "lodash.debounce": "^4.0.8", @@ -51,7 +52,8 @@ "swr": "^0.5.6", "tabbable": "^5.2.0", "tailwindcss": "^2.2.2", - "uuidv4": "^6.2.10" + "uuidv4": "^6.2.10", + "yup": "^0.32.9" }, "devDependencies": { "@graphql-codegen/cli": "^1.21.5", diff --git a/pages/account-not-login.tsx b/pages/account-not-login.tsx deleted file mode 100644 index cbda16486..000000000 --- a/pages/account-not-login.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { Layout } from 'src/components/common'; -import { AccountSignIn } from 'src/components/modules/account'; - -const AccountNotLogin = () => { - return ( - <> - - - ); -}; - -AccountNotLogin.Layout = Layout - -export default AccountNotLogin; \ No newline at end of file diff --git a/pages/account.tsx b/pages/account.tsx index 7da102c52..df4121aa7 100644 --- a/pages/account.tsx +++ b/pages/account.tsx @@ -1,13 +1,16 @@ -import React from 'react'; -import { Layout } from 'src/components/common'; -import { AccountPage } from 'src/components/modules/account'; +import React from 'react' +import { Layout } from 'src/components/common' +import useActiveCustomer from 'src/components/hooks/useActiveCustomer' +import { AccountPage, AccountSignIn } from 'src/components/modules/account' const Account = () => { - return ( - - ); -}; + const { customer } = useActiveCustomer() + if (customer) { + return + } + return +} Account.Layout = Layout -export default Account; \ No newline at end of file +export default Account diff --git a/pages/test.tsx b/pages/test.tsx index 9d0cb6129..b60fe63c7 100644 --- a/pages/test.tsx +++ b/pages/test.tsx @@ -1,85 +1,16 @@ -import { - FeaturedProductCard, - Layout -} from 'src/components/common'; -import { HomeBanner } from 'src/components/modules/home'; -// import { RecipeListPage } from 'src/components/modules/recipes'; -import { OPTION_ALL, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'; -import { PRODUCT_DATA_TEST, PRODUCT_DATA_TEST_PAGE } from 'src/utils/demo-data'; +import { Layout } from 'src/components/common' +import { useMessage } from 'src/components/contexts' -const CATEGORY = [ - { - name: 'All', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=${OPTION_ALL}`, - }, - { - name: 'Veggie', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=veggie`, - }, - { - name: 'Seafood', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=seafood`, - }, - { - name: 'Frozen', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=frozen`, - }, - { - name: 'Coffee Bean', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=coffee-bean`, - }, - { - name: 'Sauce', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=sauce`, - }, -] -const BRAND = [ - { - name: 'Maggi', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=veggie`, - }, - { - name: 'Cholimes', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=seafood`, - }, - { - name: 'Chinsu', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=frozen`, - }] - -const FEATURED = [ - - { - name: 'Best Sellers', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=veggie`, - }, - { - name: 'Sales', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=seafood`, - }, - { - name: 'New Item', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=frozen`, - }, - { - name: 'Viewed', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=viewed`, - } -]; - - - -const data = PRODUCT_DATA_TEST[0] export default function Test() { + const { showMessageError } = useMessage() + + const handleClick = () => { + showMessageError("Create account successfully") + } + return ( <> - - + ) } diff --git a/pages/verify.tsx b/pages/verify.tsx new file mode 100644 index 000000000..97d9743b1 --- /dev/null +++ b/pages/verify.tsx @@ -0,0 +1,8 @@ +import { Layout } from 'src/components/common' +import { VerifyCustomerAccount } from 'src/components/modules/verify-customer' + +export default function VerifyCustomer() { + return +} + +VerifyCustomer.Layout = Layout diff --git a/src/components/common/Banner/Banner.tsx b/src/components/common/Banner/Banner.tsx index dc95c2146..8a98fe3a6 100644 --- a/src/components/common/Banner/Banner.tsx +++ b/src/components/common/Banner/Banner.tsx @@ -41,4 +41,5 @@ const Banner = memo(({ data }: Props) => { ) }) +Banner.displayName = 'Banner' export default Banner diff --git a/src/components/common/ButtonCommon/ButtonCommon.module.scss b/src/components/common/ButtonCommon/ButtonCommon.module.scss index 32cf65655..318180ede 100644 --- a/src/components/common/ButtonCommon/ButtonCommon.module.scss +++ b/src/components/common/ButtonCommon/ButtonCommon.module.scss @@ -2,6 +2,32 @@ .buttonCommon { @apply shape-common; + &:hover { + .inner { + @apply shadow-md; + &:not(:disabled) { + filter: brightness(1.05); + } + } + } + + &:disabled { + cursor: not-allowed; + .inner { + filter: brightness(0.8) !important; + color: var(--disabled); + } + } + + &:focus { + outline: none; + .inner { + filter: brightness(1.05); + } + } + &:focus-visible { + outline: 2px solid var(--text-active); + } .inner { padding: 1rem 2rem; @apply bg-primary transition-all duration-200 text-white font-bold; @@ -14,37 +40,19 @@ @screen lg { padding: 1.6rem 3.2rem; } - &:disabled { - filter: brightness(0.9); - cursor: not-allowed; - color: var(--disabled); - } - &:hover { - @apply shadow-md; - &:not(:disabled) { - filter: brightness(1.05); - } - } - &:focus { - outline: none; - filter: brightness(1.05); - } - &:focus-visible { - outline: 2px solid var(--text-active); - } } &.loading { .inner { &::after { content: ""; border-radius: 50%; - width: 1.6rem; - height: 1.6rem; + width: 1.8rem; + height: 1.8rem; border: 3px solid rgba(170, 170, 170, 0.5); border-top: 3px solid var(--white); -webkit-animation: spin 2s linear infinite; animation: spin 2s linear infinite; - margin-right: 0.8rem; + margin-left: 0.8rem; } } } @@ -60,7 +68,7 @@ &.loading { .inner { &::after { - border-top-color: var(--primary); + border-top-color: var(--text-active); } } } @@ -87,7 +95,7 @@ } &.loading { .inner::after { - border-top-color: var(--text-active); + border-top-color: var(--primary); } } } @@ -105,14 +113,14 @@ } &.small { .inner { - padding: .5rem 1rem; + padding: 0.5rem 1rem; &.onlyIcon { padding: 1rem; } @screen md { - padding: .8rem 1.6rem; + padding: 0.8rem 1.6rem; &.onlyIcon { - padding: .8rem; + padding: 0.8rem; } } } diff --git a/src/components/common/ButtonCommon/ButtonCommon.tsx b/src/components/common/ButtonCommon/ButtonCommon.tsx index d83621d30..862adf71d 100644 --- a/src/components/common/ButtonCommon/ButtonCommon.tsx +++ b/src/components/common/ButtonCommon/ButtonCommon.tsx @@ -3,38 +3,51 @@ import React, { memo } from 'react' import s from './ButtonCommon.module.scss' interface Props { - children?: React.ReactNode, - type?: 'primary' | 'light' | 'ghost' | 'lightBorderNone', - size?: 'default' | 'large' | 'small', - icon?: React.ReactNode, - isIconSuffix?: boolean, - loading?: boolean, - disabled?: boolean, - onClick?: () => void, + children?: React.ReactNode + type?: 'primary' | 'light' | 'ghost' | 'lightBorderNone' + HTMLType?: "submit" | "button" | "reset" + size?: 'default' | 'large' | 'small' + icon?: React.ReactNode + isIconSuffix?: boolean + loading?: boolean + disabled?: boolean + onClick?: () => void } -const ButtonCommon = memo(({ type = 'primary', size = 'default', loading = false, isIconSuffix = false, - icon, disabled, children, onClick }: Props) => { +const ButtonCommon = memo( + ({ + type = 'primary', + HTMLType, + size = 'default', + loading = false, + isIconSuffix = false, + icon, + disabled, + children, + onClick, + }: Props) => { return ( - + disabled={disabled || loading} + onClick={onClick} + type={HTMLType} + > +
+ {icon && {icon}} + {children} +
+ ) -}) + } +) +ButtonCommon.displayName = 'ButtonCommon' export default ButtonCommon diff --git a/src/components/common/Header/Header.tsx b/src/components/common/Header/Header.tsx index a0b220c08..f3a514970 100644 --- a/src/components/common/Header/Header.tsx +++ b/src/components/common/Header/Header.tsx @@ -1,7 +1,6 @@ import classNames from 'classnames' -import React, { memo, useEffect, useMemo, useRef, useState } from 'react' +import React, { memo, useEffect, useRef, useState } from 'react' import { useModalCommon } from 'src/components/hooks' -import { CartDrawer } from '..' import ModalAuthenticate from '../ModalAuthenticate/ModalAuthenticate' import ModalCreateUserInfo from '../ModalCreateUserInfo/ModalCreateUserInfo' import HeaderHighLight from './components/HeaderHighLight/HeaderHighLight' @@ -17,6 +16,7 @@ interface props { const Header = memo(({ toggleFilter, visibleFilter }: props) => { const headeFullRef = useRef(null) const [isFullHeader, setIsFullHeader] = useState(true) + const [isModeAuthenRegister, setIsModeAuthenRegister] = useState(false) const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false }) const { visible: visibleModalInfo, closeModal: closeModalInfo, openModal: openModalInfo } = useModalCommon({ initialValue: false }) @@ -32,7 +32,17 @@ const Header = memo(({ toggleFilter, visibleFilter }: props) => { return () => { window.removeEventListener('scroll', handleScroll) } - }, [headeFullRef.current]) + }, []) + + const openModalRegister = () => { + setIsModeAuthenRegister(true) + openModalAuthen() + } + + const openModalLogin = () => { + setIsModeAuthenRegister(false) + openModalAuthen() + } return ( <> @@ -43,7 +53,8 @@ const Header = memo(({ toggleFilter, visibleFilter }: props) => { @@ -54,16 +65,17 @@ const Header = memo(({ toggleFilter, visibleFilter }: props) => { isFull={isFullHeader} visibleFilter={visibleFilter} toggleFilter={toggleFilter} - openModalAuthen={openModalAuthen} - openModalInfo={openModalInfo} /> + openModalLogin={openModalLogin} + openModalRegister = {openModalRegister} + openModalInfo={openModalInfo} + /> - + - ) }) diff --git a/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx b/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx index 2060d5996..a4977b198 100644 --- a/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx +++ b/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx @@ -6,115 +6,164 @@ import { ButtonCommon } from 'src/components/common' import InputSearch from 'src/components/common/InputSearch/InputSearch' import MenuDropdown from 'src/components/common/MenuDropdown/MenuDropdown' import { useCartDrawer } from 'src/components/contexts' -import { IconBuy, IconFilter, IconHeart, IconHistory, IconUser } from 'src/components/icons' -import { ACCOUNT_TAB, FILTER_PAGE, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils' +import { + IconBuy, + IconFilter, + IconHeart, + IconHistory, + IconUser, +} from 'src/components/icons' +import { + ACCOUNT_TAB, + FILTER_PAGE, + QUERY_KEY, + ROUTE, +} from 'src/utils/constanst.utils' import Logo from '../../../Logo/Logo' import s from './HeaderMenu.module.scss' +import { useLogout } from '../../../../hooks/auth' +import useActiveCustomer from 'src/components/hooks/useActiveCustomer' interface Props { - children?: any, - isFull?: boolean, - isStickyHeader?: boolean, - visibleFilter?: boolean, - openModalAuthen: () => void, - openModalInfo: () => void, - toggleFilter: () => void, + children?: any + isFull?: boolean + isStickyHeader?: boolean + visibleFilter?: boolean + openModalLogin: () => void + openModalRegister: () => void + openModalInfo: () => void + toggleFilter: () => void } - -const HeaderMenu = memo(({ isFull, isStickyHeader, visibleFilter, openModalAuthen, openModalInfo, toggleFilter }: Props) => { +const HeaderMenu = memo( + ({ + isFull, + isStickyHeader, + visibleFilter, + openModalLogin, + openModalRegister, + openModalInfo, + toggleFilter, + }: Props) => { const router = useRouter() const { toggleCartDrawer } = useCartDrawer() + const { customer } = useActiveCustomer() - const optionMenu = useMemo(() => [ - { - onClick: openModalAuthen, - name: 'Login (Demo)', - }, - { - onClick: openModalInfo, - name: 'Create User Info (Demo)', - }, - { - link: '/account-not-login', - name: 'Account Not Login (Demo)', - }, - { - link: '/demo', - name: 'Notifications Empty (Demo)', - }, - { - link: ROUTE.NOTIFICATION, - name: 'Notifications', - }, - { - link: ROUTE.ACCOUNT, - name: 'Account', - }, - { - link: '/', - name: 'Logout', - }, + const { logout } = useLogout() - ], [openModalAuthen]) - return ( -
-
-
- -
- { - FILTER_PAGE.includes(router.pathname) && ( - - ) - } - -
- -
-
-
- -
-
- Search -
-
-
- -
+ const optionMenuNotAuthen = useMemo( + () => [ + { + onClick: openModalLogin, + name: 'Sign in', + }, + { + onClick: openModalRegister, + name: 'Create account', + }, + ], + [openModalLogin, openModalRegister] ) -}) + const optionMenu = useMemo( + () => [ + // { + // onClick: openModalInfo, + // name: 'Create User Info (Demo)', + // }, + { + link: '/demo', + name: 'Notifications Empty (Demo)', + }, + { + link: ROUTE.NOTIFICATION, + name: 'Notifications', + }, + { + link: ROUTE.ACCOUNT, + name: 'Account', + }, + { + link: '/', + name: 'Logout', + onClick: logout, + }, + ], + [logout] + ) + return ( +
+
+
+ +
+ {FILTER_PAGE.includes(router.pathname) && ( + + )} + +
+
+
+
+ +
+
+ Search +
+
+
+
    +
  • + + + + + +
  • +
  • + + + + + +
  • +
  • + + + +
  • +
  • + +
  • +
+
+ ) + } +) + +HeaderMenu.displayName = 'HeaderMenu' export default HeaderMenu diff --git a/src/components/common/InputCommon/InputCommon.module.scss b/src/components/common/InputCommon/InputCommon.module.scss index 5471b2169..b62048bb4 100644 --- a/src/components/common/InputCommon/InputCommon.module.scss +++ b/src/components/common/InputCommon/InputCommon.module.scss @@ -1,100 +1,5 @@ -@import "../../../styles/utilities"; +@import "../../../styles/form"; .inputWrap { - .inputInner { - @apply flex items-center relative; - .icon { - @apply absolute flex justify-center items-center; - content: ""; - left: 1.6rem; - margin-right: 1.6rem; - svg path { - fill: currentColor; - } - } - .icon + .inputCommon { - padding-left: 4.8rem; - } - - .inputCommon { - @apply block w-full transition-all duration-200 bg-white; - border-radius: .8rem; - padding: 1.6rem; - border: 1px solid var(--border-line); - &:hover, - &:focus, - &:active { - outline: none; - border: 1px solid var(--primary); - @apply shadow-md; - } - - &::placeholder { - @apply text-label; - } - } - - &.preserve { - @apply flex-row-reverse; - .icon { - left: unset; - right: 1.6rem; - margin-left: 1.6rem; - margin-right: 0; - svg path { - fill: var(--text-label); - } - } - .icon + .inputCommon { - padding-left: 1.6rem; - padding-right: 4.8rem; - } - } - &.success { - .icon { - svg path { - fill: var(--primary); - } - } - } - - &.error { - .icon { - svg path { - fill: var(--negative); - } - } - input { - border-color: var(--negative) !important; - } - } - } - .errorMessage { - @apply caption; - color: var(--negative); - margin-top: 0.4rem; - } - - &.custom { - @apply shape-common; - .inputCommon { - border: none; - background: var(--background-gray); - &:hover, - &:focus, - &:active { - @apply shadow-md; - border: none; - } - } - } - &.bgTransparent { - .inputCommon { - background: rgb(227, 242, 233, 0.3); - color: var(--white); - &::placeholder { - color: var(--white); - } - } - } + @extend .formInputWrap; } diff --git a/src/components/common/InputCommon/InputCommon.tsx b/src/components/common/InputCommon/InputCommon.tsx index 7ef9d886f..2300e9b80 100644 --- a/src/components/common/InputCommon/InputCommon.tsx +++ b/src/components/common/InputCommon/InputCommon.tsx @@ -1,95 +1,127 @@ -import classNames from 'classnames'; -import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; -import { IconCheck, IconError } from 'src/components/icons'; -import { KEY } from 'src/utils/constanst.utils'; -import s from './InputCommon.module.scss'; +import classNames from 'classnames' +import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react' +import { IconCheck, IconError } from 'src/components/icons' +import { KEY } from 'src/utils/constanst.utils' +import s from './InputCommon.module.scss' type Ref = { - focus: () => void - getValue: () => string | number -} | null; + focus: () => void + getValue: () => string | number +} | null interface Props { - children?: React.ReactNode, - value?: string | number, - placeholder?: string, - type?: 'text' | 'number' | 'email' | 'password', - styleType?: 'default' | 'custom', - backgroundTransparent?: boolean, - icon?: React.ReactNode, - isIconSuffix?: boolean, - isShowIconSuccess?: boolean, - error?: string, - onChange?: (value: string | number) => void, - onEnter?: (value: string | number) => void, + children?: React.ReactNode + value?: string | number + placeholder?: string + type?: 'text' | 'number' | 'email' | 'password' + styleType?: 'default' | 'custom' + backgroundTransparent?: boolean + icon?: React.ReactNode + isIconSuffix?: boolean + isShowIconSuccess?: boolean + error?: string + onChange?: (value: string | number) => void + onChangeEvent?: (e: React.ChangeEvent) => void + onBlur?: (e: any) => void + onEnter?: (value: string | number) => void } -const InputCommon = forwardRef(({ value, placeholder, type, styleType = 'default', icon, backgroundTransparent = false, - isIconSuffix, isShowIconSuccess, error, - onChange, onEnter }: Props, ref) => { - const inputElementRef = useRef(null); +const InputCommon = forwardRef( + ( + { + value, + placeholder, + type, + styleType = 'default', + icon, + backgroundTransparent = false, + isIconSuffix, + isShowIconSuccess, + error, + onChange, + onChangeEvent, + onEnter, + onBlur, + }: Props, + ref + ) => { + const inputElementRef = useRef(null) const iconElement = useMemo(() => { - if (error) { - return - } else if (isShowIconSuccess) { - return - } else if (icon) { - return {icon} - } - return <> + if (error) { + return ( + + {' '} + + ) + } else if (isShowIconSuccess) { + return ( + + {' '} + + ) + } else if (icon) { + return {icon} + } + return <> }, [icon, error, isShowIconSuccess]) useImperativeHandle(ref, () => ({ - focus: () => { - inputElementRef.current?.focus(); - }, - getValue: () => { - const value = inputElementRef.current?.value || '' - return value - } - })); + focus: () => { + inputElementRef.current?.focus() + }, + getValue: () => { + const value = inputElementRef.current?.value || '' + return value + }, + })) const handleChange = (e: React.ChangeEvent) => { + if (onChangeEvent) { + onChangeEvent(e) + } else { onChange && onChange(e.target.value) + } } const handleKeyDown = (e: any) => { - if (e.key === KEY.ENTER && onEnter) { - const value = inputElementRef.current?.value || '' - onEnter(value) - } + if (e.key === KEY.ENTER && onEnter) { + const value = inputElementRef.current?.value || '' + onEnter(value) + } } return ( -
-
- {iconElement} - -
- { - error &&
{error}
- } +
+
+ {iconElement} +
+ {error &&
{error}
} +
) + } +) -}) - +InputCommon.displayName = 'InputCommon' export default InputCommon diff --git a/src/components/common/InputFiledInForm/InputFiledInForm.module.scss b/src/components/common/InputFiledInForm/InputFiledInForm.module.scss new file mode 100644 index 000000000..b62048bb4 --- /dev/null +++ b/src/components/common/InputFiledInForm/InputFiledInForm.module.scss @@ -0,0 +1,5 @@ +@import "../../../styles/form"; + +.inputWrap { + @extend .formInputWrap; +} diff --git a/src/components/common/InputFiledInForm/InputFiledInForm.tsx b/src/components/common/InputFiledInForm/InputFiledInForm.tsx new file mode 100644 index 000000000..22389b78d --- /dev/null +++ b/src/components/common/InputFiledInForm/InputFiledInForm.tsx @@ -0,0 +1,109 @@ +import classNames from 'classnames' +import { Field } from 'formik' +import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react' +import { IconCheck, IconError } from 'src/components/icons' +import { KEY } from 'src/utils/constanst.utils' +import s from './InputFiledInForm.module.scss' + +type Ref = { + focus: () => void +} | null +interface Props { + placeholder?: string + type?: 'text' | 'number' | 'email' | 'password' + styleType?: 'default' | 'custom' + backgroundTransparent?: boolean + icon?: React.ReactNode + isIconSuffix?: boolean + isShowIconSuccess?: boolean + name: string + error?: string + onChange?: (value: string | number) => void + onChangeEvent?: (e: React.ChangeEvent) => void + onBlur?: (e: any) => void + onEnter?: (value: string | number) => void +} + +const InputFiledInForm = forwardRef(({ + name, + placeholder, + type, + styleType = 'default', + icon, + backgroundTransparent = false, + isIconSuffix = true, + isShowIconSuccess, + error, + onEnter, +}: Props, ref) => { + const inputElementRef = useRef(null) + + useImperativeHandle(ref, () => ({ + focus: () => { + inputElementRef.current?.focus() + }, + })) + + const iconElement = useMemo(() => { + if (error) { + return ( + + {' '} + + ) + } else if (isShowIconSuccess) { + return ( + + {' '} + + ) + } else if (icon) { + return {icon} + } + return <> + }, [icon, error, isShowIconSuccess]) + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === KEY.ENTER) { + e.stopPropagation() + e.preventDefault() + if (onEnter) { + const value = inputElementRef.current?.value || '' + onEnter(value) + } + } + } + + return ( +
+
+ {iconElement} + + +
+ {error &&
{error}
} +
+ ) +}) + +InputFiledInForm.displayName = 'InputFiledInForm' +export default InputFiledInForm diff --git a/src/components/common/InputPasswordFiledInForm/InputPasswordFiledInForm.module.scss b/src/components/common/InputPasswordFiledInForm/InputPasswordFiledInForm.module.scss new file mode 100644 index 000000000..598620891 --- /dev/null +++ b/src/components/common/InputPasswordFiledInForm/InputPasswordFiledInForm.module.scss @@ -0,0 +1,10 @@ +.iconPassword { + all: unset; + cursor: pointer; + &:focus { + outline: none; + } + &:focus-visible { + outline: 2px solid var(--text-active); + } +} diff --git a/src/components/common/InputPasswordFiledInForm/InputPasswordFiledInForm.tsx b/src/components/common/InputPasswordFiledInForm/InputPasswordFiledInForm.tsx new file mode 100644 index 000000000..3cdc48353 --- /dev/null +++ b/src/components/common/InputPasswordFiledInForm/InputPasswordFiledInForm.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react' +import { IconPassword, IconPasswordCross } from 'src/components/icons' +import InputFiledInForm from '../InputFiledInForm/InputFiledInForm' +import s from './InputPasswordFiledInForm.module.scss' + +interface Props { + name?: string + placeholder?: string + styleType?: 'default' | 'custom' + error?: string + onChange?: (value: string | number) => void + onEnter?: (value: string | number) => void +} + +const InputPasswordFiledInForm = ({ + name = 'password', + placeholder, + styleType = 'default', + error, + onChange, + onEnter, +}: Props) => { + const [isShowPassword, setIsShowPassword] = useState(false) + const toggleShowPassword = (e: React.MouseEvent) => { + e.stopPropagation() + e.preventDefault() + setIsShowPassword(!isShowPassword) + } + + return ( + + {isShowPassword ? : } + + } + isIconSuffix={true} + onChange={onChange} + onEnter={onEnter} + /> + ) +} + +export default InputPasswordFiledInForm diff --git a/src/components/common/Layout/Layout.tsx b/src/components/common/Layout/Layout.tsx index e7b0c88e4..ec5a1646d 100644 --- a/src/components/common/Layout/Layout.tsx +++ b/src/components/common/Layout/Layout.tsx @@ -1,25 +1,24 @@ import { CommerceProvider } from '@framework' import { useRouter } from 'next/router' import { FC } from 'react' -import { CartDrawerProvider } from 'src/components/contexts' +import { CartDrawerProvider, MessageProvider } from 'src/components/contexts' import LayoutContent from './LayoutContent/LayoutContent' interface Props { - className?: string - children?: any + className?: string + children?: any } const Layout: FC = ({ children }) => { - const { locale = 'en-US' } = useRouter() - return ( - - - - {children} - - - - - ) + const { locale = 'en-US' } = useRouter() + return ( + + + + {children} + + + + ) } export default Layout diff --git a/src/components/common/Layout/LayoutContent/LayoutContent.tsx b/src/components/common/Layout/LayoutContent/LayoutContent.tsx index 7c096be0a..77eac0a2a 100644 --- a/src/components/common/Layout/LayoutContent/LayoutContent.tsx +++ b/src/components/common/Layout/LayoutContent/LayoutContent.tsx @@ -1,8 +1,9 @@ import { useRouter } from 'next/router' import { FC } from 'react' +import { useMessage } from 'src/components/contexts' import { useModalCommon } from 'src/components/hooks' import { BRAND, CATEGORY, FEATURED, FILTER_PAGE, ROUTE } from 'src/utils/constanst.utils' -import { CartDrawer, Footer, ScrollToTop } from '../..' +import { CartDrawer, Footer, MessageCommon, ScrollToTop } from '../..' import Header from '../../Header/Header' import MenuNavigationProductList from '../../MenuNavigationProductList/MenuNavigationProductList' import s from './LayoutContent.module.scss' @@ -16,6 +17,7 @@ const LayoutContent: FC = ({ children }) => { const { pathname } = useRouter() const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false }) const router = useRouter() + const {messages, removeMessage} = useMessage() const toggleFilter = () => { if (visibleFilter) { @@ -44,6 +46,7 @@ const LayoutContent: FC = ({ children }) => {
+ ) diff --git a/src/components/common/LoadingCommon/LoadingCommon.tsx b/src/components/common/LoadingCommon/LoadingCommon.tsx index 1e8ffe340..f75bc370b 100644 --- a/src/components/common/LoadingCommon/LoadingCommon.tsx +++ b/src/components/common/LoadingCommon/LoadingCommon.tsx @@ -1,15 +1,17 @@ -import React from "react"; +import React from 'react' import s from './LoadingCommon.module.scss' -const LoadingCommon = () => { - - return ( -
-
-
-

Loading...

-
- ) +interface Props { + description?: string } -export default LoadingCommon \ No newline at end of file +const LoadingCommon = ({ description = 'Loading...' }: Props) => { + return ( +
+
+

{description}

+
+ ) +} + +export default LoadingCommon diff --git a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx index 590368da4..026710bbf 100644 --- a/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx +++ b/src/components/common/MenuNavigationProductList/MenuNavigationProductList.tsx @@ -24,7 +24,7 @@ const MenuNavigationProductList = ({categories,brands,featured,visible,onClose}: setDataSort({...dataSort,...value}); } function filter(){ - console.log(dataSort) + // console.log(dataSort) } return( <> diff --git a/src/components/common/MessageCommon/MessageCommon.module.scss b/src/components/common/MessageCommon/MessageCommon.module.scss new file mode 100644 index 000000000..99137e828 --- /dev/null +++ b/src/components/common/MessageCommon/MessageCommon.module.scss @@ -0,0 +1,7 @@ +.messageCommon { + @apply fixed; + top: 2.4rem; + left: 50%; + z-index: 20000; + transform: translateX(-50%); +} diff --git a/src/components/common/MessageCommon/MessageCommon.tsx b/src/components/common/MessageCommon/MessageCommon.tsx new file mode 100644 index 000000000..f15f8f443 --- /dev/null +++ b/src/components/common/MessageCommon/MessageCommon.tsx @@ -0,0 +1,27 @@ +import React, { memo, useEffect } from 'react' +import s from './MessageCommon.module.scss' +import MessageItem, { MessageItemProps } from './MessageItem/MessageItem' + +interface Props { + messages: MessageItemProps[] + onRemove?: (id: number) => void +} + +const MessageCommon = memo(({ messages, onRemove }: Props) => { + return ( +
+ {messages.reverse().map((item) => ( + + ))} +
+ ) +}) + +MessageCommon.displayName = 'MessageCommon' +export default MessageCommon diff --git a/src/components/common/MessageCommon/MessageItem/MessageItem.module.scss b/src/components/common/MessageCommon/MessageItem/MessageItem.module.scss new file mode 100644 index 000000000..bc0ee8e46 --- /dev/null +++ b/src/components/common/MessageCommon/MessageItem/MessageItem.module.scss @@ -0,0 +1,65 @@ +@import "../../../../styles/utilities"; + +.messageItem { + @apply shadow-sm flex justify-center items-center cursor-default; + width: fit-content; + padding: 0.8rem 2.4rem; + margin: 0 auto .8rem; + border-radius: 0.8rem; + transition: all .5s; + animation: showMessage .5s; + width: max-content; + + &.hide { + display: none; + } + .icon { + @apply flex justify-center items-center; + margin-right: 0.8rem; + svg { + width: 2rem; + height: 2rem; + } + } + &.info { + @apply bg-info-light; + color: var(--info-dark); + .icon svg path { + fill: var(--info); + } + } + &.success { + @apply bg-positive-light; + color: var(--positive-dark); + .icon svg path { + fill: var(--positive); + } + } + &.error { + @apply bg-negative-light; + color: var(--negative-dark); + .icon svg path { + fill: var(--negative); + } + } + &.warning { + @apply bg-warning-light; + color: var(--warning-dark); + .icon svg path { + fill: var(--warning); + } + } +} + + +@keyframes showMessage { + 0% { + transform: translateY(-2rem); + opacity: .5; + } + + 100% { + transform: none; + opacity: 1; + } +} diff --git a/src/components/common/MessageCommon/MessageItem/MessageItem.tsx b/src/components/common/MessageCommon/MessageItem/MessageItem.tsx new file mode 100644 index 000000000..89f9b6600 --- /dev/null +++ b/src/components/common/MessageCommon/MessageItem/MessageItem.tsx @@ -0,0 +1,71 @@ +import classNames from 'classnames' +import React, { memo, useEffect, useMemo, useState } from 'react' +import { IconCheck, IconError, IconInfo } from 'src/components/icons' +import s from './MessageItem.module.scss' + +export interface MessageItemProps { + id?: number + content?: React.ReactNode + type?: 'info' | 'success' | 'error' | 'warning' + timeout?: number + onRemove?: (id: number) => void +} + +const MessageItem = memo( + ({ id, content, type = 'success', timeout = 3000, onRemove }: MessageItemProps) => { + const [isHide, setIsHide] = useState() + const [isMouseOver, setIsMouseOver] = useState(false) + + const iconElement = useMemo(() => { + switch (type) { + case 'info': + return + case 'success': + return + case 'error': + return + case 'warning': + return + default: + return + } + }, [type]) + + useEffect(() => { + setIsHide(false) + setTimeout(() => { + setIsHide(true) + }, timeout) + }, [timeout, isMouseOver]) + + useEffect(() => { + if (isHide && !isMouseOver && onRemove) { + onRemove(id || 0) + } + }, [isHide, isMouseOver, onRemove, id]) + + const onMouseOver = () => { + setIsMouseOver(true) + } + + const onMouseLeave = () => { + setIsMouseOver(false) + } + + return ( +
+ {iconElement} + {content} +
+ ) + } +) + +MessageItem.displayName = 'MessageItem' +export default MessageItem diff --git a/src/components/common/ModalAuthenticate/ModalAuthenticate.tsx b/src/components/common/ModalAuthenticate/ModalAuthenticate.tsx index b086098cc..08cbc96e6 100644 --- a/src/components/common/ModalAuthenticate/ModalAuthenticate.tsx +++ b/src/components/common/ModalAuthenticate/ModalAuthenticate.tsx @@ -32,7 +32,7 @@ const ModalAuthenticate = ({ visible, mode, closeModal }: Props) => { closeModal() router.push(ROUTE.ACCOUNT) } - }, [customer, visible]) + }, [customer, visible, closeModal, router]) const onSwitch = () => { setIsLogin(!isLogin) diff --git a/src/components/common/ModalAuthenticate/components/FormAuthen.module.scss b/src/components/common/ModalAuthenticate/components/FormAuthen.module.scss index 2ec8bf91f..eb28ba509 100644 --- a/src/components/common/ModalAuthenticate/components/FormAuthen.module.scss +++ b/src/components/common/ModalAuthenticate/components/FormAuthen.module.scss @@ -1,7 +1,7 @@ @import '../../../../styles/utilities'; .formAuthen { - @apply bg-white w-full u-form; + @apply bg-white w-full; .inner { @screen md { width: 60rem; diff --git a/src/components/common/ModalAuthenticate/components/FormLogin/FormLogin.tsx b/src/components/common/ModalAuthenticate/components/FormLogin/FormLogin.tsx index 9dd5296f8..713b78629 100644 --- a/src/components/common/ModalAuthenticate/components/FormLogin/FormLogin.tsx +++ b/src/components/common/ModalAuthenticate/components/FormLogin/FormLogin.tsx @@ -1,9 +1,13 @@ +import { Form, Formik } from 'formik' import Link from 'next/link' -import React, { useEffect, useRef, useState } from 'react' -import { ButtonCommon, Inputcommon, InputPassword } from 'src/components/common' -import { ROUTE } from 'src/utils/constanst.utils' -import { CustomInputCommon } from 'src/utils/type.utils' +import React, { useEffect, useRef } from 'react' +import { ButtonCommon, InputFiledInForm, InputPasswordFiledInForm } from 'src/components/common' +import { useMessage } from 'src/components/contexts' import useLogin from 'src/components/hooks/useLogin' +import { ROUTE } from 'src/utils/constanst.utils' +import { LANGUAGE } from 'src/utils/language.utils' +import { CustomInputCommon } from 'src/utils/type.utils' +import * as Yup from 'yup' import s from '../FormAuthen.module.scss' import SocialAuthen from '../SocialAuthen/SocialAuthen' import styles from './FormLogin.module.scss' @@ -13,15 +17,17 @@ interface Props { onSwitch: () => void } +const DisplayingErrorMessagesSchema = Yup.object().shape({ + email: Yup.string().email('Your email was wrong').required('Required'), + password: Yup.string() + .max(30, 'Password is too long') + .required('Required'), +}) + const FormLogin = ({ onSwitch, isHide }: Props) => { const emailRef = useRef(null) - const { loading, login, error } = useLogin() - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') - - const onLogin = () => { - login({ username: email, password }) - } + const { loading, login } = useLogin() + const { showMessageSuccess, showMessageError } = useMessage() useEffect(() => { if (!isHide) { @@ -29,42 +35,71 @@ const FormLogin = ({ onSwitch, isHide }: Props) => { } }, [isHide]) - useEffect(() => { - if (error) { - alert(error.message) + const onLogin = (values: { email: string; password: string }) => { + login({ username: values.email, password: values.password }, onLoginCallBack) + } + + const onLoginCallBack = (isSuccess: boolean, message?: string) => { + if (isSuccess) { + showMessageSuccess("Login successfully!", 4000) + } else { + showMessageError(message || LANGUAGE.MESSAGE.ERROR) } - }, [error]) + } return (
- setEmail(val.toString())} - type="email" - ref={emailRef} - /> + + {({ errors, touched, isValid, submitForm }) => ( +
+
+ + +
+
+ + + Forgot Password? + + + + Sign in + +
+
+ )} +
+
- {/* */} - setPassword(val.toString())} - /> -
-
- - - Forgot Password? - - - - Sign in - -
Don't have an account? diff --git a/src/components/common/ModalAuthenticate/components/FormRegister/FormRegister.tsx b/src/components/common/ModalAuthenticate/components/FormRegister/FormRegister.tsx index 66ec1f8a4..7f63ef4e0 100644 --- a/src/components/common/ModalAuthenticate/components/FormRegister/FormRegister.tsx +++ b/src/components/common/ModalAuthenticate/components/FormRegister/FormRegister.tsx @@ -1,49 +1,125 @@ -import React, { useEffect, useRef } from 'react' -import { ButtonCommon, Inputcommon, InputPassword } from 'src/components/common' -import s from '../FormAuthen.module.scss' -import styles from './FormRegister.module.scss' -import SocialAuthen from '../SocialAuthen/SocialAuthen' import classNames from 'classnames' +import { Form, Formik } from 'formik' +import React, { useEffect, useRef } from 'react' +import { + ButtonCommon, + InputFiledInForm, + InputPasswordFiledInForm, +} from 'src/components/common' +import { useMessage } from 'src/components/contexts' +import { LANGUAGE } from 'src/utils/language.utils' import { CustomInputCommon } from 'src/utils/type.utils' +import * as Yup from 'yup' +import { useSignup } from '../../../../hooks' +import s from '../FormAuthen.module.scss' +import SocialAuthen from '../SocialAuthen/SocialAuthen' +import styles from './FormRegister.module.scss' interface Props { - isHide: boolean, - onSwitch: () => void + isHide: boolean + onSwitch: () => void } +const DisplayingErrorMessagesSchema = Yup.object().shape({ + email: Yup.string().email('Your email was wrong').required('Required'), + password: Yup.string() + .matches( + /^(?=.{8,})(?=.*[a-z])(?=.*[A-Z])((?=.*[0-9!@#$%^&*()\-_=+{};:,<.>]){1}).*$/, + 'Must contain 8 characters with at least 1 uppercase and 1 lowercase letter and either 1 number or 1 special character.' + ) + .max(30, 'Password is too long') + .required('Required'), +}) + const FormRegister = ({ onSwitch, isHide }: Props) => { - const emailRef = useRef(null) + const emailRef = useRef(null) + const { loading, signup } = useSignup() + const { showMessageSuccess, showMessageError } = useMessage() - useEffect(() => { - if (!isHide) { - emailRef.current?.focus() - } - }, [isHide]) + useEffect(() => { + if (!isHide) { + emailRef.current?.focus() + } + }, [isHide]) - return ( -
-
-
- - -
- Must contain 8 characters with at least 1 uppercase and 1 lowercase letter and either 1 number or 1 special character. -
+ const onSignup = (values: { email: string; password: string }) => { + signup({ email: values.email, password: values.password }, onSignupCallBack) + } + + const onSignupCallBack = (isSuccess: boolean, message?: string) => { + if (isSuccess) { + showMessageSuccess("Create account successfully. Please verify your email to login.") + } else { + showMessageError(message || LANGUAGE.MESSAGE.ERROR) + } + } + + return ( +
+
+
+ + {({ errors, touched }) => ( +
+
+ + +
+ Must contain 8 characters with at least 1 uppercase and 1 + lowercase letter and either 1 number or 1 special character. +
- Create Account + + Create Account +
- -
- Already an account? - -
-
-
- ) + + )} + +
+ +
+ Already an account? + +
+
+
+ ) } -export default FormRegister \ No newline at end of file +export default FormRegister diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 7b82399f6..eaa33176c 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -47,4 +47,9 @@ export { default as StaticImage} from './StaticImage/StaticImage' export { default as EmptyCommon} from './EmptyCommon/EmptyCommon' export { default as CustomShapeSvg} from './CustomShapeSvg/CustomShapeSvg' export { default as RecommendedRecipes} from './RecommendedRecipes/RecommendedRecipes' -export { default as LayoutCheckout} from './LayoutCheckout/LayoutCheckout' \ No newline at end of file +export { default as LayoutCheckout} from './LayoutCheckout/LayoutCheckout' +export { default as InputPasswordFiledInForm} from './InputPasswordFiledInForm/InputPasswordFiledInForm' +export { default as InputFiledInForm} from './InputFiledInForm/InputFiledInForm' +export { default as MessageCommon} from './MessageCommon/MessageCommon' + + diff --git a/src/components/contexts/Message/MessageContext.tsx b/src/components/contexts/Message/MessageContext.tsx new file mode 100644 index 000000000..f9399498e --- /dev/null +++ b/src/components/contexts/Message/MessageContext.tsx @@ -0,0 +1,27 @@ +import { createContext, useContext } from 'react' +import { MessageItemProps } from 'src/components/common/MessageCommon/MessageItem/MessageItem' + +export type MessageContextType = { + messages: MessageItemProps[] + removeMessage: (id: number) => void + showMessageSuccess: (content: string, timeout?: number) => void + showMessageInfo: (content: string, timeout?: number) => void + showMessageError: (content: string, timeout?: number) => void + showMessageWarning: (content: string, timeout?: number) => void +} +export const DEFAULT_MESSAGE_CONTEXT: MessageContextType = { + messages: [], + removeMessage: () => {}, + showMessageSuccess: () => {}, + showMessageInfo: () => {}, + showMessageError: () => {}, + showMessageWarning: () => {}, +} + +export const MessageContext = createContext( + DEFAULT_MESSAGE_CONTEXT +) + +export function useMessage() { + return useContext(MessageContext) +} diff --git a/src/components/contexts/Message/MessageProvider.tsx b/src/components/contexts/Message/MessageProvider.tsx new file mode 100644 index 000000000..0ccde8609 --- /dev/null +++ b/src/components/contexts/Message/MessageProvider.tsx @@ -0,0 +1,67 @@ +import { ReactNode, useCallback, useState } from 'react' +import { MessageItemProps } from 'src/components/common/MessageCommon/MessageItem/MessageItem' +import { MessageContext } from './MessageContext' + +type Props = { + children: ReactNode +} + +export function MessageProvider({ children }: Props) { + const [currentId, setCurrentId] = useState(0) + const [messages, setMessages] = useState([]) + + const createNewMessage = ( + content: string, + timeout?: number, + type?: 'info' | 'success' | 'error' | 'warning' + ) => { + const item: MessageItemProps = { + id: currentId + 1, + content, + type, + timeout, + } + setCurrentId(currentId + 1) + setMessages([...messages, item]) + } + + const showMessageSuccess = (content: string, timeout?: number) => { + createNewMessage(content, timeout, 'success') + } + + const showMessageInfo = (content: string, timeout?: number) => { + createNewMessage(content, timeout, 'info') + } + + const showMessageError = (content: string, timeout?: number) => { + createNewMessage(content, timeout, 'error') + } + + const showMessageWarning = (content: string, timeout?: number) => { + createNewMessage(content, timeout, 'warning') + } + + const removeMessage = (id: number) => { + const newMessages = messages.filter((item) => item.id !== id) + setMessages(newMessages) + + if (newMessages.length === 0) { + setCurrentId(0) + } + } + + return ( + + {children} + + ) +} diff --git a/src/components/contexts/index.ts b/src/components/contexts/index.ts index a34c91d85..795c9161c 100644 --- a/src/components/contexts/index.ts +++ b/src/components/contexts/index.ts @@ -1,2 +1,5 @@ export * from './CartDrawer/CartDrawerContext' -export * from './CartDrawer/CartDrawerProvider' \ No newline at end of file +export * from './CartDrawer/CartDrawerProvider' + +export * from './Message/MessageContext' +export * from './Message/MessageProvider' diff --git a/src/components/hooks/auth/index.ts b/src/components/hooks/auth/index.ts new file mode 100644 index 000000000..494c974de --- /dev/null +++ b/src/components/hooks/auth/index.ts @@ -0,0 +1,3 @@ +export { default as useLogout } from './useLogout' + + diff --git a/src/components/hooks/auth/useLogout.tsx b/src/components/hooks/auth/useLogout.tsx new file mode 100644 index 000000000..5b1c095e1 --- /dev/null +++ b/src/components/hooks/auth/useLogout.tsx @@ -0,0 +1,34 @@ +import { LogoutMutation } from '@framework/schema' +import { logoutMutation } from '@framework/utils/mutations/log-out-mutation' +import { useState } from 'react' +import { CommonError } from 'src/domains/interfaces/CommonError' +import { LOCAL_STORAGE_KEY } from 'src/utils/constanst.utils' +import rawFetcher from 'src/utils/rawFetcher' +import useActiveCustomer from '../useActiveCustomer' + +const useLogout = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { mutate } = useActiveCustomer() + + const logout = () => { + setError(null) + setLoading(true) + rawFetcher({ + query: logoutMutation, + }) + .then(({ data }) => { + if (!data.logout.success) { + throw CommonError.create('Logout fail') + } + localStorage.setItem(LOCAL_STORAGE_KEY.TOKEN, '') + mutate() + }) + .catch(setError) + .finally(() => setLoading(false)) + } + + return { loading, logout, error } +} + +export default useLogout diff --git a/src/components/hooks/index.ts b/src/components/hooks/index.ts index cf83feb42..4a25fd98e 100644 --- a/src/components/hooks/index.ts +++ b/src/components/hooks/index.ts @@ -1 +1,3 @@ export { default as useModalCommon } from './useModalCommon' +export { default as useVerifyCustomer } from './useVerifyCustomer' +export { default as useSignup } from './useSignup' diff --git a/src/components/hooks/useLogin.tsx b/src/components/hooks/useLogin.tsx index 0e056c2f7..0b97e8d8b 100644 --- a/src/components/hooks/useLogin.tsx +++ b/src/components/hooks/useLogin.tsx @@ -4,6 +4,8 @@ import useActiveCustomer from './useActiveCustomer' import { CommonError } from 'src/domains/interfaces/CommonError' import rawFetcher from 'src/utils/rawFetcher' import { LoginMutation } from '@framework/schema' +import { LOCAL_STORAGE_KEY } from 'src/utils/constanst.utils' +import { errorMapping } from 'src/utils/errrorMapping' const query = gql` mutation login($username: String!, $password: String!) { @@ -30,7 +32,9 @@ const useLogin = () => { const [error, setError] = useState(null) const { mutate } = useActiveCustomer() - const login = (options: LoginInput) => { + const login = (options: LoginInput, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { setError(null) setLoading(true) rawFetcher({ @@ -39,15 +43,19 @@ const useLogin = () => { }) .then(({ data, headers }) => { if (data.login.__typename !== 'CurrentUser') { - throw CommonError.create(data.login.message, data.login.errorCode) + throw CommonError.create(errorMapping(data.login.message), data.login.errorCode) } const authToken = headers.get('vendure-auth-token') if (authToken != null) { - localStorage.setItem('token', authToken) - return mutate() + localStorage.setItem(LOCAL_STORAGE_KEY.TOKEN, authToken) + mutate() } + fCallBack(true) + }) + .catch((error) => { + setError(error) + fCallBack(false, error.message) }) - .catch(setError) .finally(() => setLoading(false)) } diff --git a/src/components/hooks/useProduct.tsx b/src/components/hooks/useProduct.tsx new file mode 100644 index 000000000..ab2eb4eb0 --- /dev/null +++ b/src/components/hooks/useProduct.tsx @@ -0,0 +1 @@ +// here diff --git a/src/components/hooks/useSignup.tsx b/src/components/hooks/useSignup.tsx index b06781e88..4842915e1 100644 --- a/src/components/hooks/useSignup.tsx +++ b/src/components/hooks/useSignup.tsx @@ -22,8 +22,8 @@ const query = gql` interface SignupInput { email: string - firstName: string - lastName: string + firstName?: string + lastName?: string password: string } @@ -32,7 +32,10 @@ const useSignup = () => { const [error, setError] = useState(null) const { mutate } = useActiveCustomer() - const signup = ({ firstName, lastName, email, password }: SignupInput) => { + const signup = ( + { firstName, lastName, email, password }: SignupInput, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { setError(null) setLoading(true) fetcher({ @@ -53,11 +56,15 @@ const useSignup = () => { data.registerCustomerAccount.errorCode ) } - console.log(data) + mutate() + fCallBack(true) return data }) - .catch(setError) + .catch((error) => { + setError(error) + fCallBack(false, error.message) + }) .finally(() => setLoading(false)) } diff --git a/src/components/hooks/useVerifyCustomer.tsx b/src/components/hooks/useVerifyCustomer.tsx new file mode 100644 index 000000000..d86a5b8ed --- /dev/null +++ b/src/components/hooks/useVerifyCustomer.tsx @@ -0,0 +1,51 @@ +import { VerifyCustomerAccountMutation } from '@framework/schema' +import { useState } from 'react' +import { CommonError } from 'src/domains/interfaces/CommonError' +import rawFetcher from 'src/utils/rawFetcher' +import { VERIFY_CUSTOMER_ACCOUNT } from '../../graphql/mutation' +import useActiveCustomer from './useActiveCustomer' + +interface VerifyInput { + token: string + password?: string +} + +const useVerifyCustomer = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { mutate } = useActiveCustomer() + + const verify = ( + options: VerifyInput, + fCallBack?: (isSuccess: boolean) => void + ) => { + setError(null) + setLoading(true) + rawFetcher({ + query: VERIFY_CUSTOMER_ACCOUNT, + variables: options, + }) + .then(({ data }) => { + if (data.verifyCustomerAccount.__typename !== 'CurrentUser') { + throw CommonError.create( + data.verifyCustomerAccount.message, + data.verifyCustomerAccount.errorCode + ) + } + fCallBack && fCallBack(true) + mutate() + return data + }) + .catch((err) => { + setError(err) + fCallBack && fCallBack(false) + }) + .finally(() => { + setLoading(false) + }) + } + + return { loading, verify, error } +} + +export default useVerifyCustomer diff --git a/src/components/modules/account/AccountPage/AccountPage.tsx b/src/components/modules/account/AccountPage/AccountPage.tsx index 47f5cf955..db5801235 100644 --- a/src/components/modules/account/AccountPage/AccountPage.tsx +++ b/src/components/modules/account/AccountPage/AccountPage.tsx @@ -87,7 +87,7 @@ const AccountPage = ({ defaultActiveContent="orders" } : AccountPageProps) => { const query = router.query[QUERY_KEY.TAB] as string const index = getTabIndex(query) setActiveTab(index) - }, [router.query[QUERY_KEY.TAB]]) + }, [router.query]) function showModal() { setModalVisible(true); diff --git a/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx b/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx index 4f1b8296b..06e6b2124 100644 --- a/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx +++ b/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx @@ -12,8 +12,6 @@ interface EditInfoModalProps { const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoModalProps) => { function saveInfo() { - console.log("saved !!!"); - closeModal(); } diff --git a/src/components/modules/home/HomeSubscribe/FormSubscribe/FormSubscribe.tsx b/src/components/modules/home/HomeSubscribe/FormSubscribe/FormSubscribe.tsx index c6b459a2a..ad30990f9 100644 --- a/src/components/modules/home/HomeSubscribe/FormSubscribe/FormSubscribe.tsx +++ b/src/components/modules/home/HomeSubscribe/FormSubscribe/FormSubscribe.tsx @@ -16,7 +16,6 @@ const FormSubscribe = () => { e.preventDefault && e.preventDefault() value = inputElementRef.current?.getValue()?.toString() || '' } - console.log("email here: ", value) } return ( diff --git a/src/components/modules/verify-customer/VerifyCustomerAccount/VerifyCustomerAccount.module.scss b/src/components/modules/verify-customer/VerifyCustomerAccount/VerifyCustomerAccount.module.scss new file mode 100644 index 000000000..64431e892 --- /dev/null +++ b/src/components/modules/verify-customer/VerifyCustomerAccount/VerifyCustomerAccount.module.scss @@ -0,0 +1,27 @@ +@import "../../../../styles/utilities"; + +.verifyCustomerAccount { + @apply spacing-horizontal; + min-height: 25rem; + margin: 5rem auto 10rem; + display: flex; + justify-content: center; + + .result { + @apply flex flex-col justify-center items-center; + .message { + margin-bottom: 1.6rem; + } + .bottom { + @apply flex justify-center items-center flex-col; + a { + margin-right: 1.6rem; + margin-bottom: 1.6rem; + width: 100%; + } + button { + @apply w-full; + } + } + } +} diff --git a/src/components/modules/verify-customer/VerifyCustomerAccount/VerifyCustomerAccount.tsx b/src/components/modules/verify-customer/VerifyCustomerAccount/VerifyCustomerAccount.tsx new file mode 100644 index 000000000..e2c93db2e --- /dev/null +++ b/src/components/modules/verify-customer/VerifyCustomerAccount/VerifyCustomerAccount.tsx @@ -0,0 +1,70 @@ +import { useRouter } from 'next/router' +import { useEffect, useState } from 'react' +import { ButtonCommon } from 'src/components/common' +import LoadingCommon from 'src/components/common/LoadingCommon/LoadingCommon' +import { useModalCommon, useVerifyCustomer } from 'src/components/hooks' +import { ROUTE } from 'src/utils/constanst.utils' +import s from './VerifyCustomerAccount.module.scss' +import Link from 'next/link' +import { LANGUAGE } from 'src/utils/language.utils' +import ModalAuthenticate from 'src/components/common/ModalAuthenticate/ModalAuthenticate' + +export default function VerifyCustomerAccount() { + const router = useRouter() + const [isVerified, setIsVerified] = useState(false) + const { error, loading, verify } = useVerifyCustomer() + const { + visible: visibleModalAuthen, + closeModal: closeModalAuthen, + openModal: openModalAuthen, + } = useModalCommon({ initialValue: false }) + + useEffect(() => { + const token = router.query.token + if (token && !isVerified) { + setIsVerified(true) + verify({ token: token.toString() }) + } + }, [router, verify, isVerified]) + + return ( +
+ {loading || !isVerified ? ( +
+ +
+ ) : error ? ( +
+
Error: {error?.message}
+ + + Back to home + + +
+ ) : ( +
+
+ Congratulation! Verified account successfully +
+
+ + + Back to home + + + + + {LANGUAGE.BUTTON_LABEL.SIGNIN} + +
+
+ )} + + +
+ ) +} diff --git a/src/components/modules/verify-customer/index.tsx b/src/components/modules/verify-customer/index.tsx new file mode 100644 index 000000000..8b8fe4dbe --- /dev/null +++ b/src/components/modules/verify-customer/index.tsx @@ -0,0 +1,2 @@ +export { default as VerifyCustomerAccount } from './VerifyCustomerAccount/VerifyCustomerAccount' + diff --git a/src/graphql/mutation/index.ts b/src/graphql/mutation/index.ts new file mode 100644 index 000000000..7ea0d4f82 --- /dev/null +++ b/src/graphql/mutation/index.ts @@ -0,0 +1 @@ +export * from './user.mutation' diff --git a/src/graphql/mutation/user.mutation.ts b/src/graphql/mutation/user.mutation.ts new file mode 100644 index 000000000..40812db19 --- /dev/null +++ b/src/graphql/mutation/user.mutation.ts @@ -0,0 +1,20 @@ +import { gql } from 'graphql-request' + + +export const VERIFY_CUSTOMER_ACCOUNT = gql` +mutation verifyCustomerAccount($token: String!, $password: String) { + verifyCustomerAccount( token: $token, password: $password) { + __typename + ...on CurrentUser { + id + identifier + } + ... on ErrorResult { + errorCode + message + } + } + } +` + + diff --git a/src/graphql/query/index.ts b/src/graphql/query/index.ts new file mode 100644 index 000000000..47c53bc6d --- /dev/null +++ b/src/graphql/query/index.ts @@ -0,0 +1 @@ +// export * from './user.mutation' diff --git a/src/graphql/query/user.query.ts b/src/graphql/query/user.query.ts new file mode 100644 index 000000000..e9e7409b4 --- /dev/null +++ b/src/graphql/query/user.query.ts @@ -0,0 +1 @@ +// query here \ No newline at end of file diff --git a/src/styles/_form.scss b/src/styles/_form.scss new file mode 100644 index 000000000..0ec3b4a96 --- /dev/null +++ b/src/styles/_form.scss @@ -0,0 +1,100 @@ +@import './utilities'; + +.formInputWrap { + .inputInner { + @apply flex items-center relative; + .icon { + @apply absolute flex justify-center items-center; + content: ""; + left: 1.6rem; + margin-right: 1.6rem; + svg path { + fill: currentColor; + } + } + .icon + input { + padding-left: 4.8rem; + } + + input { + @apply block w-full transition-all duration-200 bg-white; + border-radius: .8rem; + padding: 1.6rem; + border: 1px solid var(--border-line); + &:hover, + &:focus, + &:active { + outline: none; + border: 1px solid var(--primary); + @apply shadow-md; + } + + &::placeholder { + @apply text-label; + } + } + + &.preserve { + @apply flex-row-reverse; + .icon { + left: unset; + right: 1.6rem; + margin-left: 1.6rem; + margin-right: 0; + svg path { + fill: var(--text-label); + } + } + .icon + input { + padding-left: 1.6rem; + padding-right: 4.8rem; + } + } + &.success { + .icon { + svg path { + fill: var(--primary); + } + } + } + + &.error { + .icon { + svg path { + fill: var(--negative); + } + } + input { + border-color: var(--negative) !important; + } + } + } + .errorMessage { + @apply caption; + color: var(--negative); + margin-top: 0.4rem; + } + + &.custom { + @apply shape-common; + input { + border: none; + background: var(--background-gray); + &:hover, + &:focus, + &:active { + @apply shadow-md; + border: none; + } + } + } + &.bgTransparent { + input { + background: rgb(227, 242, 233, 0.3); + color: var(--white); + &::placeholder { + color: var(--white); + } + } + } + } \ No newline at end of file diff --git a/src/styles/main.scss b/src/styles/main.scss index 51391b33f..e742ca4be 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -6,4 +6,5 @@ @import "~tailwindcss/utilities"; @import './utilities'; +@import './form'; @import './pages' diff --git a/src/utils/constanst.utils.ts b/src/utils/constanst.utils.ts index 9f9e0830f..f77991604 100644 --- a/src/utils/constanst.utils.ts +++ b/src/utils/constanst.utils.ts @@ -38,6 +38,10 @@ export const ACCOUNT_TAB = { FAVOURITE: 'wishlist', } +export const LOCAL_STORAGE_KEY = { + TOKEN: 'token' +} + export const QUERY_KEY = { TAB: 'tab', CATEGORY: 'category', diff --git a/src/utils/errrorMapping.ts b/src/utils/errrorMapping.ts new file mode 100644 index 000000000..f216e1d57 --- /dev/null +++ b/src/utils/errrorMapping.ts @@ -0,0 +1,14 @@ +import { LANGUAGE } from "./language.utils"; + +export function errorMapping(message?: string) { + if (!message) { + return LANGUAGE.MESSAGE.ERROR + } + + switch (message) { + case 'The provided credentials are invalid': + return 'The email address or password is incorrect!' + default: + return LANGUAGE.MESSAGE.ERROR + } +} \ No newline at end of file diff --git a/src/utils/fetcher.ts b/src/utils/fetcher.ts index e92696970..a86367398 100644 --- a/src/utils/fetcher.ts +++ b/src/utils/fetcher.ts @@ -1,5 +1,6 @@ import { request } from 'graphql-request' import { RequestDocument, Variables } from 'graphql-request/dist/types' +import { LOCAL_STORAGE_KEY } from './constanst.utils' interface QueryOptions { query: RequestDocument @@ -10,11 +11,7 @@ interface QueryOptions { const fetcher = async (options: QueryOptions): Promise => { const { query, variables } = options - console.log('query') - console.log(options) - const token = localStorage.getItem('token') - console.log('token') - console.log(token) + const token = localStorage.getItem(LOCAL_STORAGE_KEY.TOKEN) const res = await request( process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL as string, query, diff --git a/src/utils/language.utils.ts b/src/utils/language.utils.ts index dfd2b9604..921c29435 100644 --- a/src/utils/language.utils.ts +++ b/src/utils/language.utils.ts @@ -9,5 +9,8 @@ export const LANGUAGE = { }, PLACE_HOLDER: { SEARCH: 'Search', + }, + MESSAGE: { + ERROR: 'Something went wrong! Please try again!' } } \ No newline at end of file diff --git a/src/utils/rawFetcher.ts b/src/utils/rawFetcher.ts index 8ee6a05ee..c26b2ab20 100644 --- a/src/utils/rawFetcher.ts +++ b/src/utils/rawFetcher.ts @@ -1,5 +1,6 @@ import { rawRequest } from 'graphql-request' import { RequestDocument, Variables } from 'graphql-request/dist/types' +import { LOCAL_STORAGE_KEY } from './constanst.utils' interface QueryOptions { query: RequestDocument @@ -14,10 +15,12 @@ const rawFetcher = ({ onLoad = () => true, }: QueryOptions): Promise<{ data: T; headers: any }> => { onLoad(true) + const token = localStorage.getItem(LOCAL_STORAGE_KEY.TOKEN) return rawRequest( process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL as string, query as string, - variables + variables, + token ? { Authorization: 'Bearer ' + token } : {} ) .then(({ data, headers }) => { return { data, headers } diff --git a/yarn.lock b/yarn.lock index b69068411..5964ce61a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -445,7 +445,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.14.0": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.14.0": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== @@ -1213,6 +1213,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.173.tgz#9d3b674c67a26cf673756f6aca7b429f237f91ed" integrity sha512-vv0CAYoaEjCw/mLy96GBTnRoZrSxkGE0BKzKimdR8P3OzrNYNvBgtW7p055A+E8C31vXNUhWKoFCbhq7gbyhFg== +"@types/lodash@^4.14.165": + version "4.14.175" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.175.tgz#b78dfa959192b01fae0ad90e166478769b215f45" + integrity sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw== + "@types/lru-cache@4.1.1": version "4.1.1" resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-4.1.1.tgz#b2d87a5e3df8d4b18ca426c5105cd701c2306d40" @@ -2572,6 +2577,11 @@ deepmerge@4.2.2, deepmerge@^4.0.0, deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +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== + defer-to-connect@^1.0.1: version "1.1.3" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" @@ -3279,6 +3289,19 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +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" + fraction.js@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.1.tgz#ac4e520473dae67012d618aab91eda09bcb400ff" @@ -3595,6 +3618,13 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -4415,6 +4445,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +lodash-es@^4.17.15, 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.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -4752,6 +4787,11 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +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: version "3.1.25" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" @@ -5795,6 +5835,11 @@ prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +property-expr@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910" + integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg== + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -5921,6 +5966,11 @@ react-dom@^17.0.2: object-assign "^4.1.1" scheduler "^0.20.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: version "3.2.0" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" @@ -5936,7 +5986,7 @@ react-is@17.0.2: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^16.8.1: +react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -6878,6 +6928,11 @@ timers-browserify@2.0.12, timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +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== + title-case@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982" @@ -6926,6 +6981,11 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= + totalist@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" @@ -6970,7 +7030,7 @@ tsconfig-paths@^3.11.0, tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -7419,3 +7479,16 @@ yocto-queue@^0.1.0: version "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.9: + version "0.32.9" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.9.tgz#9367bec6b1b0e39211ecbca598702e106019d872" + integrity sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg== + dependencies: + "@babel/runtime" "^7.10.5" + "@types/lodash" "^4.14.165" + lodash "^4.17.20" + lodash-es "^4.17.15" + nanoclone "^0.2.1" + property-expr "^2.0.4" + toposort "^2.0.2"