mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 20:26:49 +00:00
Merge branch 'release-stable' of https://github.com/KieIO/grocery-vercel-commerce into feature/m1-get-product-detail
This commit is contained in:
30
framework/vendure/schema.d.ts
vendored
30
framework/vendure/schema.d.ts
vendored
@@ -3095,6 +3095,36 @@ export type LoginMutation = { __typename?: 'Mutation' } & {
|
|||||||
>)
|
>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type VerifyCustomerAccountVariables = Exact<{
|
||||||
|
token: Scalars['String']
|
||||||
|
password?: Maybe<Scalars['String']>
|
||||||
|
}>
|
||||||
|
|
||||||
|
export type VerifyCustomerAccountMutation = { __typename?: 'Mutation' } & {
|
||||||
|
verifyCustomerAccount:
|
||||||
|
| ({ __typename: 'CurrentUser' } & Pick<CurrentUser, 'id'>)
|
||||||
|
| ({ __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 LogoutMutationVariables = Exact<{ [key: string]: never }>
|
||||||
|
|
||||||
export type LogoutMutation = { __typename?: 'Mutation' } & {
|
export type LogoutMutation = { __typename?: 'Mutation' } & {
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
export const verifyCustomerAccountMutaton = /* GraphQL */ `
|
||||||
|
mutation verifyCustomerAccount($token: String!, $password: String) {
|
||||||
|
verifyCustomerAccount( token: $token, password: $password) {
|
||||||
|
__typename
|
||||||
|
...on CurrentUser {
|
||||||
|
id
|
||||||
|
identifier
|
||||||
|
}
|
||||||
|
... on ErrorResult {
|
||||||
|
errorCode
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
@@ -29,6 +29,7 @@
|
|||||||
"email-validator": "^2.0.4",
|
"email-validator": "^2.0.4",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-config-next": "^11.1.2",
|
"eslint-config-next": "^11.1.2",
|
||||||
|
"formik": "^2.2.9",
|
||||||
"immutability-helper": "^3.1.1",
|
"immutability-helper": "^3.1.1",
|
||||||
"js-cookie": "^2.2.1",
|
"js-cookie": "^2.2.1",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
@@ -51,7 +52,8 @@
|
|||||||
"swr": "^0.5.6",
|
"swr": "^0.5.6",
|
||||||
"tabbable": "^5.2.0",
|
"tabbable": "^5.2.0",
|
||||||
"tailwindcss": "^2.2.2",
|
"tailwindcss": "^2.2.2",
|
||||||
"uuidv4": "^6.2.10"
|
"uuidv4": "^6.2.10",
|
||||||
|
"yup": "^0.32.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "^1.21.5",
|
"@graphql-codegen/cli": "^1.21.5",
|
||||||
|
@@ -1,15 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Layout } from 'src/components/common';
|
|
||||||
import { AccountSignIn } from 'src/components/modules/account';
|
|
||||||
|
|
||||||
const AccountNotLogin = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<AccountSignIn/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
AccountNotLogin.Layout = Layout
|
|
||||||
|
|
||||||
export default AccountNotLogin;
|
|
@@ -1,13 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react'
|
||||||
import { Layout } from 'src/components/common';
|
import { Layout } from 'src/components/common'
|
||||||
import { AccountPage } from 'src/components/modules/account';
|
import { useActiveCustomer } from 'src/components/hooks/auth'
|
||||||
|
import { AccountPage, AccountSignIn } from 'src/components/modules/account'
|
||||||
|
|
||||||
const Account = () => {
|
const Account = () => {
|
||||||
return (
|
const { customer } = useActiveCustomer()
|
||||||
<AccountPage/>
|
if (customer) {
|
||||||
);
|
return <AccountPage />
|
||||||
};
|
}
|
||||||
|
return <AccountSignIn />
|
||||||
|
}
|
||||||
|
|
||||||
Account.Layout = Layout
|
Account.Layout = Layout
|
||||||
|
|
||||||
export default Account;
|
export default Account
|
||||||
|
@@ -1,85 +1,16 @@
|
|||||||
import {
|
import { Layout } from 'src/components/common'
|
||||||
FeaturedProductCard,
|
import { useMessage } from 'src/components/contexts'
|
||||||
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';
|
|
||||||
|
|
||||||
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() {
|
export default function Test() {
|
||||||
|
const { showMessageError } = useMessage()
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
showMessageError("Create account successfully")
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FeaturedProductCard
|
<button onClick={handleClick}>Click me</button>
|
||||||
imageSrc={data.imageSrc}
|
|
||||||
title="Sale 25% coffee bean"
|
|
||||||
subTitle="50 first orders within a day"
|
|
||||||
price={data.price}
|
|
||||||
originPrice="$20.00" />
|
|
||||||
<HomeBanner/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
8
pages/verify.tsx
Normal file
8
pages/verify.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { Layout } from 'src/components/common'
|
||||||
|
import { VerifyCustomerAccount } from 'src/components/modules/verify-customer'
|
||||||
|
|
||||||
|
export default function VerifyCustomer() {
|
||||||
|
return <VerifyCustomerAccount />
|
||||||
|
}
|
||||||
|
|
||||||
|
VerifyCustomer.Layout = Layout
|
@@ -41,4 +41,5 @@ const Banner = memo(({ data }: Props) => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Banner.displayName = 'Banner'
|
||||||
export default Banner
|
export default Banner
|
||||||
|
@@ -2,6 +2,32 @@
|
|||||||
|
|
||||||
.buttonCommon {
|
.buttonCommon {
|
||||||
@apply shape-common;
|
@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 {
|
.inner {
|
||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem;
|
||||||
@apply bg-primary transition-all duration-200 text-white font-bold;
|
@apply bg-primary transition-all duration-200 text-white font-bold;
|
||||||
@@ -14,37 +40,19 @@
|
|||||||
@screen lg {
|
@screen lg {
|
||||||
padding: 1.6rem 3.2rem;
|
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 {
|
&.loading {
|
||||||
.inner {
|
.inner {
|
||||||
&::after {
|
&::after {
|
||||||
content: "";
|
content: "";
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 1.6rem;
|
width: 1.8rem;
|
||||||
height: 1.6rem;
|
height: 1.8rem;
|
||||||
border: 3px solid rgba(170, 170, 170, 0.5);
|
border: 3px solid rgba(170, 170, 170, 0.5);
|
||||||
border-top: 3px solid var(--white);
|
border-top: 3px solid var(--white);
|
||||||
-webkit-animation: spin 2s linear infinite;
|
-webkit-animation: spin 2s linear infinite;
|
||||||
animation: spin 2s linear infinite;
|
animation: spin 2s linear infinite;
|
||||||
margin-right: 0.8rem;
|
margin-left: 0.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,7 +68,7 @@
|
|||||||
&.loading {
|
&.loading {
|
||||||
.inner {
|
.inner {
|
||||||
&::after {
|
&::after {
|
||||||
border-top-color: var(--primary);
|
border-top-color: var(--text-active);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +95,7 @@
|
|||||||
}
|
}
|
||||||
&.loading {
|
&.loading {
|
||||||
.inner::after {
|
.inner::after {
|
||||||
border-top-color: var(--text-active);
|
border-top-color: var(--primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,14 +113,14 @@
|
|||||||
}
|
}
|
||||||
&.small {
|
&.small {
|
||||||
.inner {
|
.inner {
|
||||||
padding: .5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
&.onlyIcon {
|
&.onlyIcon {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
@screen md {
|
@screen md {
|
||||||
padding: .8rem 1.6rem;
|
padding: 0.8rem 1.6rem;
|
||||||
&.onlyIcon {
|
&.onlyIcon {
|
||||||
padding: .8rem;
|
padding: 0.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,38 +3,51 @@ import React, { memo } from 'react'
|
|||||||
import s from './ButtonCommon.module.scss'
|
import s from './ButtonCommon.module.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: React.ReactNode,
|
children?: React.ReactNode
|
||||||
type?: 'primary' | 'light' | 'ghost' | 'lightBorderNone',
|
type?: 'primary' | 'light' | 'ghost' | 'lightBorderNone'
|
||||||
size?: 'default' | 'large' | 'small',
|
HTMLType?: "submit" | "button" | "reset"
|
||||||
icon?: React.ReactNode,
|
size?: 'default' | 'large' | 'small'
|
||||||
isIconSuffix?: boolean,
|
icon?: React.ReactNode
|
||||||
loading?: boolean,
|
isIconSuffix?: boolean
|
||||||
disabled?: boolean,
|
loading?: boolean
|
||||||
onClick?: () => void,
|
disabled?: boolean
|
||||||
|
onClick?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ButtonCommon = memo(({ type = 'primary', size = 'default', loading = false, isIconSuffix = false,
|
const ButtonCommon = memo(
|
||||||
icon, disabled, children, onClick }: Props) => {
|
({
|
||||||
|
type = 'primary',
|
||||||
|
HTMLType,
|
||||||
|
size = 'default',
|
||||||
|
loading = false,
|
||||||
|
isIconSuffix = false,
|
||||||
|
icon,
|
||||||
|
disabled,
|
||||||
|
children,
|
||||||
|
onClick,
|
||||||
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
<button className={classNames({
|
<button
|
||||||
[s.buttonCommon]: true,
|
className={classNames({
|
||||||
[s[type]]: !!type,
|
[s.buttonCommon]: true,
|
||||||
[s[size]]: !!size,
|
[s[type]]: !!type,
|
||||||
[s.loading]: loading,
|
[s[size]]: !!size,
|
||||||
[s.preserve]: isIconSuffix,
|
[s.loading]: loading,
|
||||||
[s.onlyIcon]: icon && !children,
|
[s.preserve]: isIconSuffix,
|
||||||
|
[s.onlyIcon]: icon && !children,
|
||||||
})}
|
})}
|
||||||
disabled={disabled}
|
disabled={disabled || loading}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
type={HTMLType}
|
||||||
<div className={s.inner}>
|
>
|
||||||
{
|
<div className={s.inner}>
|
||||||
icon && <span className={s.icon}>{icon}</span>
|
{icon && <span className={s.icon}>{icon}</span>}
|
||||||
}
|
<span className={s.label}>{children}</span>
|
||||||
<span className={s.label}>{children}</span>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
</button>
|
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
ButtonCommon.displayName = 'ButtonCommon'
|
||||||
export default ButtonCommon
|
export default ButtonCommon
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import classNames from 'classnames'
|
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 { useModalCommon } from 'src/components/hooks'
|
||||||
import { CartDrawer } from '..'
|
|
||||||
import ModalAuthenticate from '../ModalAuthenticate/ModalAuthenticate'
|
import ModalAuthenticate from '../ModalAuthenticate/ModalAuthenticate'
|
||||||
import ModalCreateUserInfo from '../ModalCreateUserInfo/ModalCreateUserInfo'
|
import ModalCreateUserInfo from '../ModalCreateUserInfo/ModalCreateUserInfo'
|
||||||
import HeaderHighLight from './components/HeaderHighLight/HeaderHighLight'
|
import HeaderHighLight from './components/HeaderHighLight/HeaderHighLight'
|
||||||
@@ -17,6 +16,7 @@ interface props {
|
|||||||
const Header = memo(({ toggleFilter, visibleFilter }: props) => {
|
const Header = memo(({ toggleFilter, visibleFilter }: props) => {
|
||||||
const headeFullRef = useRef<HTMLDivElement>(null)
|
const headeFullRef = useRef<HTMLDivElement>(null)
|
||||||
const [isFullHeader, setIsFullHeader] = useState<boolean>(true)
|
const [isFullHeader, setIsFullHeader] = useState<boolean>(true)
|
||||||
|
const [isModeAuthenRegister, setIsModeAuthenRegister] = useState<boolean>(false)
|
||||||
const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false })
|
const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false })
|
||||||
const { visible: visibleModalInfo, closeModal: closeModalInfo, openModal: openModalInfo } = useModalCommon({ initialValue: false })
|
const { visible: visibleModalInfo, closeModal: closeModalInfo, openModal: openModalInfo } = useModalCommon({ initialValue: false })
|
||||||
|
|
||||||
@@ -32,7 +32,17 @@ const Header = memo(({ toggleFilter, visibleFilter }: props) => {
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('scroll', handleScroll)
|
window.removeEventListener('scroll', handleScroll)
|
||||||
}
|
}
|
||||||
}, [headeFullRef.current])
|
}, [])
|
||||||
|
|
||||||
|
const openModalRegister = () => {
|
||||||
|
setIsModeAuthenRegister(true)
|
||||||
|
openModalAuthen()
|
||||||
|
}
|
||||||
|
|
||||||
|
const openModalLogin = () => {
|
||||||
|
setIsModeAuthenRegister(false)
|
||||||
|
openModalAuthen()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -43,7 +53,8 @@ const Header = memo(({ toggleFilter, visibleFilter }: props) => {
|
|||||||
<HeaderMenu
|
<HeaderMenu
|
||||||
isStickyHeader={true}
|
isStickyHeader={true}
|
||||||
toggleFilter={toggleFilter}
|
toggleFilter={toggleFilter}
|
||||||
openModalAuthen={openModalAuthen}
|
openModalLogin={openModalLogin}
|
||||||
|
openModalRegister={openModalRegister}
|
||||||
openModalInfo={openModalInfo} />
|
openModalInfo={openModalInfo} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -54,16 +65,17 @@ const Header = memo(({ toggleFilter, visibleFilter }: props) => {
|
|||||||
isFull={isFullHeader}
|
isFull={isFullHeader}
|
||||||
visibleFilter={visibleFilter}
|
visibleFilter={visibleFilter}
|
||||||
toggleFilter={toggleFilter}
|
toggleFilter={toggleFilter}
|
||||||
openModalAuthen={openModalAuthen}
|
openModalLogin={openModalLogin}
|
||||||
openModalInfo={openModalInfo} />
|
openModalRegister = {openModalRegister}
|
||||||
|
openModalInfo={openModalInfo}
|
||||||
|
/>
|
||||||
<HeaderSubMenu />
|
<HeaderSubMenu />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<HeaderSubMenuMobile />
|
<HeaderSubMenuMobile />
|
||||||
<ModalAuthenticate visible={visibleModalAuthen} closeModal={closeModalAuthen} />
|
<ModalAuthenticate visible={visibleModalAuthen} closeModal={closeModalAuthen} mode={isModeAuthenRegister? 'register': ''} />
|
||||||
<ModalCreateUserInfo demoVisible={visibleModalInfo} demoCloseModal={closeModalInfo} />
|
<ModalCreateUserInfo demoVisible={visibleModalInfo} demoCloseModal={closeModalInfo} />
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@@ -6,115 +6,164 @@ import { ButtonCommon } from 'src/components/common'
|
|||||||
import InputSearch from 'src/components/common/InputSearch/InputSearch'
|
import InputSearch from 'src/components/common/InputSearch/InputSearch'
|
||||||
import MenuDropdown from 'src/components/common/MenuDropdown/MenuDropdown'
|
import MenuDropdown from 'src/components/common/MenuDropdown/MenuDropdown'
|
||||||
import { useCartDrawer } from 'src/components/contexts'
|
import { useCartDrawer } from 'src/components/contexts'
|
||||||
import { IconBuy, IconFilter, IconHeart, IconHistory, IconUser } from 'src/components/icons'
|
import {
|
||||||
import { ACCOUNT_TAB, FILTER_PAGE, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
|
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 Logo from '../../../Logo/Logo'
|
||||||
import s from './HeaderMenu.module.scss'
|
import s from './HeaderMenu.module.scss'
|
||||||
|
import { useLogout } from '../../../../hooks/auth'
|
||||||
|
import { useActiveCustomer } from 'src/components/hooks/auth'
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: any,
|
children?: any
|
||||||
isFull?: boolean,
|
isFull?: boolean
|
||||||
isStickyHeader?: boolean,
|
isStickyHeader?: boolean
|
||||||
visibleFilter?: boolean,
|
visibleFilter?: boolean
|
||||||
openModalAuthen: () => void,
|
openModalLogin: () => void
|
||||||
openModalInfo: () => void,
|
openModalRegister: () => void
|
||||||
toggleFilter: () => void,
|
openModalInfo: () => void
|
||||||
|
toggleFilter: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HeaderMenu = memo(
|
||||||
const HeaderMenu = memo(({ isFull, isStickyHeader, visibleFilter, openModalAuthen, openModalInfo, toggleFilter }: Props) => {
|
({
|
||||||
|
isFull,
|
||||||
|
isStickyHeader,
|
||||||
|
visibleFilter,
|
||||||
|
openModalLogin,
|
||||||
|
openModalRegister,
|
||||||
|
openModalInfo,
|
||||||
|
toggleFilter,
|
||||||
|
}: Props) => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { toggleCartDrawer } = useCartDrawer()
|
const { toggleCartDrawer } = useCartDrawer()
|
||||||
|
const { customer } = useActiveCustomer()
|
||||||
|
|
||||||
const optionMenu = useMemo(() => [
|
const { logout } = useLogout()
|
||||||
{
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
|
|
||||||
], [openModalAuthen])
|
const optionMenuNotAuthen = useMemo(
|
||||||
return (
|
() => [
|
||||||
<section className={classNames({
|
{
|
||||||
[s.headerMenu]: true,
|
onClick: openModalLogin,
|
||||||
[s.small]: isStickyHeader,
|
name: 'Sign in',
|
||||||
[s.full]: isFull,
|
},
|
||||||
})}>
|
{
|
||||||
<div className={s.left}>
|
onClick: openModalRegister,
|
||||||
<div className={s.top}>
|
name: 'Create account',
|
||||||
<Logo />
|
},
|
||||||
<div className={s.iconGroup}>
|
],
|
||||||
{
|
[openModalLogin, openModalRegister]
|
||||||
FILTER_PAGE.includes(router.pathname) && (
|
|
||||||
<button className={s.iconFilter} onClick={toggleFilter}>
|
|
||||||
<IconFilter />
|
|
||||||
<div className={classNames({ [s.dot]: true, [s.isShow]: visibleFilter })}></div>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<button className={`${s.iconCart} ${s.btnCart}`} onClick={toggleCartDrawer}>
|
|
||||||
<IconBuy />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className={s.searchWrap}>
|
|
||||||
<div className={s.inputSearch}>
|
|
||||||
<InputSearch />
|
|
||||||
</div>
|
|
||||||
<div className={s.buttonSearch}>
|
|
||||||
<ButtonCommon>Search</ButtonCommon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul className={s.menu}>
|
|
||||||
<li>
|
|
||||||
<Link href={`${ROUTE.ACCOUNT}?${QUERY_KEY.TAB}=${ACCOUNT_TAB.ORDER}`}>
|
|
||||||
<a>
|
|
||||||
<IconHistory />
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<Link href={`${ROUTE.ACCOUNT}?${QUERY_KEY.TAB}=${ACCOUNT_TAB.FAVOURITE}`}>
|
|
||||||
<a className={s.iconFavourite}>
|
|
||||||
<IconHeart />
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<MenuDropdown options={optionMenu} isHasArrow={false}><IconUser /></MenuDropdown>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button className={s.btnCart} onClick={toggleCartDrawer}>
|
|
||||||
<IconBuy />
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
)
|
)
|
||||||
})
|
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<section
|
||||||
|
className={classNames({
|
||||||
|
[s.headerMenu]: true,
|
||||||
|
[s.small]: isStickyHeader,
|
||||||
|
[s.full]: isFull,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={s.left}>
|
||||||
|
<div className={s.top}>
|
||||||
|
<Logo />
|
||||||
|
<div className={s.iconGroup}>
|
||||||
|
{FILTER_PAGE.includes(router.pathname) && (
|
||||||
|
<button className={s.iconFilter} onClick={toggleFilter}>
|
||||||
|
<IconFilter />
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
[s.dot]: true,
|
||||||
|
[s.isShow]: visibleFilter,
|
||||||
|
})}
|
||||||
|
></div>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className={`${s.iconCart} ${s.btnCart}`}
|
||||||
|
onClick={toggleCartDrawer}
|
||||||
|
>
|
||||||
|
<IconBuy />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={s.searchWrap}>
|
||||||
|
<div className={s.inputSearch}>
|
||||||
|
<InputSearch />
|
||||||
|
</div>
|
||||||
|
<div className={s.buttonSearch}>
|
||||||
|
<ButtonCommon>Search</ButtonCommon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul className={s.menu}>
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
href={`${ROUTE.ACCOUNT}?${QUERY_KEY.TAB}=${ACCOUNT_TAB.ORDER}`}
|
||||||
|
>
|
||||||
|
<a>
|
||||||
|
<IconHistory />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link
|
||||||
|
href={`${ROUTE.ACCOUNT}?${QUERY_KEY.TAB}=${ACCOUNT_TAB.FAVOURITE}`}
|
||||||
|
>
|
||||||
|
<a className={s.iconFavourite}>
|
||||||
|
<IconHeart />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<MenuDropdown options={customer ? optionMenu : optionMenuNotAuthen} isHasArrow={false}>
|
||||||
|
<IconUser />
|
||||||
|
</MenuDropdown>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button className={s.btnCart} onClick={toggleCartDrawer}>
|
||||||
|
<IconBuy />
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
HeaderMenu.displayName = 'HeaderMenu'
|
||||||
export default HeaderMenu
|
export default HeaderMenu
|
||||||
|
@@ -1,100 +1,5 @@
|
|||||||
@import "../../../styles/utilities";
|
@import "../../../styles/form";
|
||||||
|
|
||||||
.inputWrap {
|
.inputWrap {
|
||||||
.inputInner {
|
@extend .formInputWrap;
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,95 +1,127 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames'
|
||||||
import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react';
|
import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'
|
||||||
import { IconCheck, IconError } from 'src/components/icons';
|
import { IconCheck, IconError } from 'src/components/icons'
|
||||||
import { KEY } from 'src/utils/constanst.utils';
|
import { KEY } from 'src/utils/constanst.utils'
|
||||||
import s from './InputCommon.module.scss';
|
import s from './InputCommon.module.scss'
|
||||||
|
|
||||||
type Ref = {
|
type Ref = {
|
||||||
focus: () => void
|
focus: () => void
|
||||||
getValue: () => string | number
|
getValue: () => string | number
|
||||||
} | null;
|
} | null
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: React.ReactNode,
|
children?: React.ReactNode
|
||||||
value?: string | number,
|
value?: string | number
|
||||||
placeholder?: string,
|
placeholder?: string
|
||||||
type?: 'text' | 'number' | 'email' | 'password',
|
type?: 'text' | 'number' | 'email' | 'password'
|
||||||
styleType?: 'default' | 'custom',
|
styleType?: 'default' | 'custom'
|
||||||
backgroundTransparent?: boolean,
|
backgroundTransparent?: boolean
|
||||||
icon?: React.ReactNode,
|
icon?: React.ReactNode
|
||||||
isIconSuffix?: boolean,
|
isIconSuffix?: boolean
|
||||||
isShowIconSuccess?: boolean,
|
isShowIconSuccess?: boolean
|
||||||
error?: string,
|
error?: string
|
||||||
onChange?: (value: string | number) => void,
|
onChange?: (value: string | number) => void
|
||||||
onEnter?: (value: string | number) => void,
|
onChangeEvent?: (e: React.ChangeEvent<HTMLInputElement>) => void
|
||||||
|
onBlur?: (e: any) => void
|
||||||
|
onEnter?: (value: string | number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputCommon = forwardRef<Ref, Props>(({ value, placeholder, type, styleType = 'default', icon, backgroundTransparent = false,
|
const InputCommon = forwardRef<Ref, Props>(
|
||||||
isIconSuffix, isShowIconSuccess, error,
|
(
|
||||||
onChange, onEnter }: Props, ref) => {
|
{
|
||||||
const inputElementRef = useRef<HTMLInputElement>(null);
|
value,
|
||||||
|
placeholder,
|
||||||
|
type,
|
||||||
|
styleType = 'default',
|
||||||
|
icon,
|
||||||
|
backgroundTransparent = false,
|
||||||
|
isIconSuffix,
|
||||||
|
isShowIconSuccess,
|
||||||
|
error,
|
||||||
|
onChange,
|
||||||
|
onChangeEvent,
|
||||||
|
onEnter,
|
||||||
|
onBlur,
|
||||||
|
}: Props,
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const inputElementRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
const iconElement = useMemo(() => {
|
const iconElement = useMemo(() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return <span className={s.icon}><IconError /> </span>
|
return (
|
||||||
} else if (isShowIconSuccess) {
|
<span className={s.icon}>
|
||||||
return <span className={s.icon}><IconCheck /> </span>
|
<IconError />{' '}
|
||||||
} else if (icon) {
|
</span>
|
||||||
return <span className={s.icon}>{icon} </span>
|
)
|
||||||
}
|
} else if (isShowIconSuccess) {
|
||||||
return <></>
|
return (
|
||||||
|
<span className={s.icon}>
|
||||||
|
<IconCheck />{' '}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
} else if (icon) {
|
||||||
|
return <span className={s.icon}>{icon} </span>
|
||||||
|
}
|
||||||
|
return <></>
|
||||||
}, [icon, error, isShowIconSuccess])
|
}, [icon, error, isShowIconSuccess])
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
focus: () => {
|
focus: () => {
|
||||||
inputElementRef.current?.focus();
|
inputElementRef.current?.focus()
|
||||||
},
|
},
|
||||||
getValue: () => {
|
getValue: () => {
|
||||||
const value = inputElementRef.current?.value || ''
|
const value = inputElementRef.current?.value || ''
|
||||||
return value
|
return value
|
||||||
}
|
},
|
||||||
}));
|
}))
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (onChangeEvent) {
|
||||||
|
onChangeEvent(e)
|
||||||
|
} else {
|
||||||
onChange && onChange(e.target.value)
|
onChange && onChange(e.target.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyDown = (e: any) => {
|
const handleKeyDown = (e: any) => {
|
||||||
if (e.key === KEY.ENTER && onEnter) {
|
if (e.key === KEY.ENTER && onEnter) {
|
||||||
const value = inputElementRef.current?.value || ''
|
const value = inputElementRef.current?.value || ''
|
||||||
onEnter(value)
|
onEnter(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames({
|
<div
|
||||||
[s.inputWrap]: true,
|
className={classNames({
|
||||||
[s[styleType]]: true,
|
[s.inputWrap]: true,
|
||||||
[s.bgTransparent]: backgroundTransparent
|
[s[styleType]]: true,
|
||||||
|
[s.bgTransparent]: backgroundTransparent,
|
||||||
})}>
|
})}
|
||||||
<div className={classNames({
|
>
|
||||||
[s.inputInner]: true,
|
<div
|
||||||
[s.preserve]: isIconSuffix,
|
className={classNames({
|
||||||
[s.success]: isShowIconSuccess,
|
[s.inputInner]: true,
|
||||||
[s.error]: !!error,
|
[s.preserve]: isIconSuffix,
|
||||||
})}>
|
[s.success]: isShowIconSuccess,
|
||||||
{iconElement}
|
[s.error]: !!error,
|
||||||
<input
|
})}
|
||||||
ref={inputElementRef}
|
>
|
||||||
value={value}
|
{iconElement}
|
||||||
type={type}
|
<input
|
||||||
placeholder={placeholder}
|
ref={inputElementRef}
|
||||||
onChange={handleChange}
|
value={value}
|
||||||
onKeyDown={handleKeyDown}
|
type={type}
|
||||||
className={s.inputCommon}
|
placeholder={placeholder}
|
||||||
/>
|
onChange={handleChange}
|
||||||
</div>
|
onKeyDown={handleKeyDown}
|
||||||
{
|
onBlur={onBlur}
|
||||||
error && <div className={s.errorMessage}>{error}</div>
|
/>
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
{error && <div className={s.errorMessage}>{error}</div>}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
})
|
InputCommon.displayName = 'InputCommon'
|
||||||
|
|
||||||
export default InputCommon
|
export default InputCommon
|
||||||
|
@@ -0,0 +1,5 @@
|
|||||||
|
@import "../../../styles/form";
|
||||||
|
|
||||||
|
.inputWrap {
|
||||||
|
@extend .formInputWrap;
|
||||||
|
}
|
109
src/components/common/InputFiledInForm/InputFiledInForm.tsx
Normal file
109
src/components/common/InputFiledInForm/InputFiledInForm.tsx
Normal file
@@ -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<HTMLInputElement>) => void
|
||||||
|
onBlur?: (e: any) => void
|
||||||
|
onEnter?: (value: string | number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputFiledInForm = forwardRef<Ref, Props>(({
|
||||||
|
name,
|
||||||
|
placeholder,
|
||||||
|
type,
|
||||||
|
styleType = 'default',
|
||||||
|
icon,
|
||||||
|
backgroundTransparent = false,
|
||||||
|
isIconSuffix = true,
|
||||||
|
isShowIconSuccess,
|
||||||
|
error,
|
||||||
|
onEnter,
|
||||||
|
}: Props, ref) => {
|
||||||
|
const inputElementRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
focus: () => {
|
||||||
|
inputElementRef.current?.focus()
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const iconElement = useMemo(() => {
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<span className={s.icon}>
|
||||||
|
<IconError />{' '}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
} else if (isShowIconSuccess) {
|
||||||
|
return (
|
||||||
|
<span className={s.icon}>
|
||||||
|
<IconCheck />{' '}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
} else if (icon) {
|
||||||
|
return <span className={s.icon}>{icon} </span>
|
||||||
|
}
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
[s.inputWrap]: true,
|
||||||
|
[s[styleType]]: true,
|
||||||
|
[s.bgTransparent]: backgroundTransparent,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={classNames({
|
||||||
|
[s.inputInner]: true,
|
||||||
|
[s.preserve]: isIconSuffix,
|
||||||
|
[s.success]: isShowIconSuccess,
|
||||||
|
[s.error]: !!error,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{iconElement}
|
||||||
|
<Field
|
||||||
|
name={name}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
type={type}
|
||||||
|
innerRef={inputElementRef}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{error && <div className={s.errorMessage}>{error}</div>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
InputFiledInForm.displayName = 'InputFiledInForm'
|
||||||
|
export default InputFiledInForm
|
@@ -0,0 +1,10 @@
|
|||||||
|
.iconPassword {
|
||||||
|
all: unset;
|
||||||
|
cursor: pointer;
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--text-active);
|
||||||
|
}
|
||||||
|
}
|
@@ -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<boolean>(false)
|
||||||
|
const toggleShowPassword = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
setIsShowPassword(!isShowPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputFiledInForm
|
||||||
|
name={name}
|
||||||
|
type={isShowPassword ? 'text' : 'password'}
|
||||||
|
styleType={styleType}
|
||||||
|
error={error}
|
||||||
|
placeholder={placeholder}
|
||||||
|
icon={
|
||||||
|
<button className={s.iconPassword} onClick={toggleShowPassword}>
|
||||||
|
{isShowPassword ? <IconPassword /> : <IconPasswordCross />}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
isIconSuffix={true}
|
||||||
|
onChange={onChange}
|
||||||
|
onEnter={onEnter}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InputPasswordFiledInForm
|
@@ -1,25 +1,24 @@
|
|||||||
import { CommerceProvider } from '@framework'
|
import { CommerceProvider } from '@framework'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { CartDrawerProvider } from 'src/components/contexts'
|
import { CartDrawerProvider, MessageProvider } from 'src/components/contexts'
|
||||||
import LayoutContent from './LayoutContent/LayoutContent'
|
import LayoutContent from './LayoutContent/LayoutContent'
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string
|
className?: string
|
||||||
children?: any
|
children?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
const Layout: FC<Props> = ({ children }) => {
|
const Layout: FC<Props> = ({ children }) => {
|
||||||
const { locale = 'en-US' } = useRouter()
|
const { locale = 'en-US' } = useRouter()
|
||||||
return (
|
return (
|
||||||
<CommerceProvider locale={locale}>
|
<CommerceProvider locale={locale}>
|
||||||
<CartDrawerProvider>
|
<CartDrawerProvider>
|
||||||
<LayoutContent>
|
<MessageProvider>
|
||||||
{children}
|
<LayoutContent>{children}</LayoutContent>
|
||||||
</LayoutContent>
|
</MessageProvider>
|
||||||
</CartDrawerProvider>
|
</CartDrawerProvider>
|
||||||
</CommerceProvider>
|
</CommerceProvider>
|
||||||
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Layout
|
export default Layout
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
|
import { useMessage } from 'src/components/contexts'
|
||||||
import { useModalCommon } from 'src/components/hooks'
|
import { useModalCommon } from 'src/components/hooks'
|
||||||
import { BRAND, CATEGORY, FEATURED, FILTER_PAGE, ROUTE } from 'src/utils/constanst.utils'
|
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 Header from '../../Header/Header'
|
||||||
import MenuNavigationProductList from '../../MenuNavigationProductList/MenuNavigationProductList'
|
import MenuNavigationProductList from '../../MenuNavigationProductList/MenuNavigationProductList'
|
||||||
import s from './LayoutContent.module.scss'
|
import s from './LayoutContent.module.scss'
|
||||||
@@ -16,6 +17,7 @@ const LayoutContent: FC<Props> = ({ children }) => {
|
|||||||
const { pathname } = useRouter()
|
const { pathname } = useRouter()
|
||||||
const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false })
|
const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false })
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const {messages, removeMessage} = useMessage()
|
||||||
|
|
||||||
const toggleFilter = () => {
|
const toggleFilter = () => {
|
||||||
if (visibleFilter) {
|
if (visibleFilter) {
|
||||||
@@ -44,6 +46,7 @@ const LayoutContent: FC<Props> = ({ children }) => {
|
|||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
<CartDrawer />
|
<CartDrawer />
|
||||||
|
<MessageCommon messages={messages} onRemove={removeMessage}/>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
)
|
)
|
||||||
|
@@ -1,15 +1,17 @@
|
|||||||
import React from "react";
|
import React from 'react'
|
||||||
import s from './LoadingCommon.module.scss'
|
import s from './LoadingCommon.module.scss'
|
||||||
|
|
||||||
const LoadingCommon = () => {
|
interface Props {
|
||||||
|
description?: string
|
||||||
return (
|
|
||||||
<div className={s.wrapper}>
|
|
||||||
<div className={s.loadingCommon}>
|
|
||||||
</div>
|
|
||||||
<p className={s.text}>Loading...</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoadingCommon
|
const LoadingCommon = ({ description = 'Loading...' }: Props) => {
|
||||||
|
return (
|
||||||
|
<div className={s.wrapper}>
|
||||||
|
<div className={s.loadingCommon}></div>
|
||||||
|
<p className={s.text}>{description}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoadingCommon
|
||||||
|
@@ -24,7 +24,7 @@ const MenuNavigationProductList = ({categories,brands,featured,visible,onClose}:
|
|||||||
setDataSort({...dataSort,...value});
|
setDataSort({...dataSort,...value});
|
||||||
}
|
}
|
||||||
function filter(){
|
function filter(){
|
||||||
console.log(dataSort)
|
// console.log(dataSort)
|
||||||
}
|
}
|
||||||
return(
|
return(
|
||||||
<>
|
<>
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
.messageCommon {
|
||||||
|
@apply fixed;
|
||||||
|
top: 2.4rem;
|
||||||
|
left: 50%;
|
||||||
|
z-index: 20000;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
27
src/components/common/MessageCommon/MessageCommon.tsx
Normal file
27
src/components/common/MessageCommon/MessageCommon.tsx
Normal file
@@ -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 (
|
||||||
|
<div className={s.messageCommon}>
|
||||||
|
{messages.reverse().map((item) => (
|
||||||
|
<MessageItem
|
||||||
|
key={item.id}
|
||||||
|
id={item.id}
|
||||||
|
type={item.type}
|
||||||
|
content={item.content}
|
||||||
|
onRemove={onRemove}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
MessageCommon.displayName = 'MessageCommon'
|
||||||
|
export default MessageCommon
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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<boolean>()
|
||||||
|
const [isMouseOver, setIsMouseOver] = useState(false)
|
||||||
|
|
||||||
|
const iconElement = useMemo(() => {
|
||||||
|
switch (type) {
|
||||||
|
case 'info':
|
||||||
|
return <IconInfo />
|
||||||
|
case 'success':
|
||||||
|
return <IconCheck />
|
||||||
|
case 'error':
|
||||||
|
return <IconError />
|
||||||
|
case 'warning':
|
||||||
|
return <IconError />
|
||||||
|
default:
|
||||||
|
return <IconInfo />
|
||||||
|
}
|
||||||
|
}, [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 (
|
||||||
|
<div
|
||||||
|
className={classNames(s.messageItem, s[type], {
|
||||||
|
[s.hide]: isHide && !isMouseOver,
|
||||||
|
})}
|
||||||
|
onMouseOver={onMouseOver}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
<span className={s.icon}>{iconElement}</span>
|
||||||
|
<span className={s.content}>{content}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
MessageItem.displayName = 'MessageItem'
|
||||||
|
export default MessageItem
|
@@ -1,7 +1,7 @@
|
|||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import useActiveCustomer from 'src/components/hooks/useActiveCustomer'
|
import { useActiveCustomer } from 'src/components/hooks/auth'
|
||||||
import { ROUTE } from 'src/utils/constanst.utils'
|
import { ROUTE } from 'src/utils/constanst.utils'
|
||||||
import ModalCommon from '../ModalCommon/ModalCommon'
|
import ModalCommon from '../ModalCommon/ModalCommon'
|
||||||
import FormLogin from './components/FormLogin/FormLogin'
|
import FormLogin from './components/FormLogin/FormLogin'
|
||||||
@@ -32,7 +32,7 @@ const ModalAuthenticate = ({ visible, mode, closeModal }: Props) => {
|
|||||||
closeModal()
|
closeModal()
|
||||||
router.push(ROUTE.ACCOUNT)
|
router.push(ROUTE.ACCOUNT)
|
||||||
}
|
}
|
||||||
}, [customer, visible])
|
}, [customer, visible, closeModal, router])
|
||||||
|
|
||||||
const onSwitch = () => {
|
const onSwitch = () => {
|
||||||
setIsLogin(!isLogin)
|
setIsLogin(!isLogin)
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
@import '../../../../styles/utilities';
|
@import '../../../../styles/utilities';
|
||||||
|
|
||||||
.formAuthen {
|
.formAuthen {
|
||||||
@apply bg-white w-full u-form;
|
@apply bg-white w-full;
|
||||||
.inner {
|
.inner {
|
||||||
@screen md {
|
@screen md {
|
||||||
width: 60rem;
|
width: 60rem;
|
||||||
|
@@ -1,9 +1,13 @@
|
|||||||
|
import { Form, Formik } from 'formik'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef } from 'react'
|
||||||
import { ButtonCommon, Inputcommon, InputPassword } from 'src/components/common'
|
import { ButtonCommon, InputFiledInForm, InputPasswordFiledInForm } from 'src/components/common'
|
||||||
|
import { useMessage } from 'src/components/contexts'
|
||||||
|
import useLogin from 'src/components/hooks/auth/useLogin'
|
||||||
import { ROUTE } from 'src/utils/constanst.utils'
|
import { ROUTE } from 'src/utils/constanst.utils'
|
||||||
|
import { LANGUAGE } from 'src/utils/language.utils'
|
||||||
import { CustomInputCommon } from 'src/utils/type.utils'
|
import { CustomInputCommon } from 'src/utils/type.utils'
|
||||||
import useLogin from 'src/components/hooks/useLogin'
|
import * as Yup from 'yup'
|
||||||
import s from '../FormAuthen.module.scss'
|
import s from '../FormAuthen.module.scss'
|
||||||
import SocialAuthen from '../SocialAuthen/SocialAuthen'
|
import SocialAuthen from '../SocialAuthen/SocialAuthen'
|
||||||
import styles from './FormLogin.module.scss'
|
import styles from './FormLogin.module.scss'
|
||||||
@@ -13,15 +17,17 @@ interface Props {
|
|||||||
onSwitch: () => void
|
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 FormLogin = ({ onSwitch, isHide }: Props) => {
|
||||||
const emailRef = useRef<CustomInputCommon>(null)
|
const emailRef = useRef<CustomInputCommon>(null)
|
||||||
const { loading, login, error } = useLogin()
|
const { loading, login } = useLogin()
|
||||||
const [email, setEmail] = useState('')
|
const { showMessageSuccess, showMessageError } = useMessage()
|
||||||
const [password, setPassword] = useState('')
|
|
||||||
|
|
||||||
const onLogin = () => {
|
|
||||||
login({ username: email, password })
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isHide) {
|
if (!isHide) {
|
||||||
@@ -29,42 +35,71 @@ const FormLogin = ({ onSwitch, isHide }: Props) => {
|
|||||||
}
|
}
|
||||||
}, [isHide])
|
}, [isHide])
|
||||||
|
|
||||||
useEffect(() => {
|
const onLogin = (values: { email: string; password: string }) => {
|
||||||
if (error) {
|
login({ username: values.email, password: values.password }, onLoginCallBack)
|
||||||
alert(error.message)
|
}
|
||||||
|
|
||||||
|
const onLoginCallBack = (isSuccess: boolean, message?: string) => {
|
||||||
|
if (isSuccess) {
|
||||||
|
showMessageSuccess("Login successfully!", 4000)
|
||||||
|
} else {
|
||||||
|
showMessageError(message || LANGUAGE.MESSAGE.ERROR)
|
||||||
}
|
}
|
||||||
}, [error])
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={s.formAuthen}>
|
<section className={s.formAuthen}>
|
||||||
<div className={s.inner}>
|
<div className={s.inner}>
|
||||||
<div className={s.body}>
|
<div className={s.body}>
|
||||||
<Inputcommon
|
<Formik
|
||||||
placeholder="Email Address"
|
initialValues={{
|
||||||
value={email}
|
password: '',
|
||||||
onChange={(val) => setEmail(val.toString())}
|
email: '',
|
||||||
type="email"
|
}}
|
||||||
ref={emailRef}
|
validationSchema={DisplayingErrorMessagesSchema}
|
||||||
/>
|
onSubmit={onLogin}
|
||||||
|
|
||||||
|
>
|
||||||
|
{({ errors, touched, isValid, submitForm }) => (
|
||||||
|
<Form className="u-form">
|
||||||
|
<div className="body">
|
||||||
|
<InputFiledInForm
|
||||||
|
name="email"
|
||||||
|
placeholder="Email Address"
|
||||||
|
ref={emailRef}
|
||||||
|
error={
|
||||||
|
touched.email && errors.email
|
||||||
|
? errors.email.toString()
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
isShowIconSuccess={touched.email && !errors.email}
|
||||||
|
/>
|
||||||
|
<InputPasswordFiledInForm
|
||||||
|
name="password"
|
||||||
|
placeholder="Password"
|
||||||
|
error={
|
||||||
|
touched.password && errors.password
|
||||||
|
? errors.password.toString()
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
onEnter={isValid ? submitForm : undefined}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.bottom}>
|
||||||
|
<Link href={ROUTE.FORGOT_PASSWORD}>
|
||||||
|
<a href="" className={styles.forgotPassword}>
|
||||||
|
Forgot Password?
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
<ButtonCommon HTMLType='submit' loading={loading} size="large">
|
||||||
|
Sign in
|
||||||
|
</ButtonCommon>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* <Inputcommon placeholder='Email Address' type='email' ref={emailRef}
|
|
||||||
isShowIconSuccess={true} isIconSuffix={true} /> */}
|
|
||||||
<InputPassword
|
|
||||||
placeholder="Password"
|
|
||||||
value={password}
|
|
||||||
onChange={(val) => setPassword(val.toString())}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.bottom}>
|
|
||||||
<Link href={ROUTE.FORGOT_PASSWORD}>
|
|
||||||
<a href="" className={styles.forgotPassword}>
|
|
||||||
Forgot Password?
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
<ButtonCommon onClick={onLogin} loading={loading} size="large">
|
|
||||||
Sign in
|
|
||||||
</ButtonCommon>
|
|
||||||
</div>
|
|
||||||
<SocialAuthen />
|
<SocialAuthen />
|
||||||
<div className={s.others}>
|
<div className={s.others}>
|
||||||
<span>Don't have an account?</span>
|
<span>Don't have an account?</span>
|
||||||
|
@@ -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 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 { CustomInputCommon } from 'src/utils/type.utils'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
import { useSignup } from '../../../../hooks/auth'
|
||||||
|
import s from '../FormAuthen.module.scss'
|
||||||
|
import SocialAuthen from '../SocialAuthen/SocialAuthen'
|
||||||
|
import styles from './FormRegister.module.scss'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isHide: boolean,
|
isHide: boolean
|
||||||
onSwitch: () => void
|
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 FormRegister = ({ onSwitch, isHide }: Props) => {
|
||||||
const emailRef = useRef<CustomInputCommon>(null)
|
const emailRef = useRef<CustomInputCommon>(null)
|
||||||
|
const { loading, signup } = useSignup()
|
||||||
|
const { showMessageSuccess, showMessageError } = useMessage()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isHide) {
|
if (!isHide) {
|
||||||
emailRef.current?.focus()
|
emailRef.current?.focus()
|
||||||
}
|
}
|
||||||
}, [isHide])
|
}, [isHide])
|
||||||
|
|
||||||
return (
|
const onSignup = (values: { email: string; password: string }) => {
|
||||||
<section className={classNames({
|
signup({ email: values.email, password: values.password }, onSignupCallBack)
|
||||||
[s.formAuthen]: true,
|
}
|
||||||
[styles.formRegister]: true,
|
|
||||||
})}>
|
const onSignupCallBack = (isSuccess: boolean, message?: string) => {
|
||||||
<div className={s.inner}>
|
if (isSuccess) {
|
||||||
<div className={s.body}>
|
showMessageSuccess("Create account successfully. Please verify your email to login.")
|
||||||
<Inputcommon placeholder='Email Address' type='email' ref={emailRef}/>
|
} else {
|
||||||
<InputPassword placeholder='Password'/>
|
showMessageError(message || LANGUAGE.MESSAGE.ERROR)
|
||||||
<div className={styles.passwordNote}>
|
}
|
||||||
Must contain 8 characters with at least 1 uppercase and 1 lowercase letter and either 1 number or 1 special character.
|
}
|
||||||
</div>
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className={classNames({
|
||||||
|
[s.formAuthen]: true,
|
||||||
|
[styles.formRegister]: true,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={s.inner}>
|
||||||
|
<div className={s.body}>
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
password: '',
|
||||||
|
email: '',
|
||||||
|
}}
|
||||||
|
validationSchema={DisplayingErrorMessagesSchema}
|
||||||
|
onSubmit={onSignup}
|
||||||
|
>
|
||||||
|
{({ errors, touched }) => (
|
||||||
|
<Form className="u-form">
|
||||||
|
<div className="body">
|
||||||
|
<InputFiledInForm
|
||||||
|
name="email"
|
||||||
|
placeholder="Email Address"
|
||||||
|
ref = {emailRef}
|
||||||
|
error={
|
||||||
|
touched.email && errors.email
|
||||||
|
? errors.email.toString()
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
isShowIconSuccess={touched.email && !errors.email}
|
||||||
|
/>
|
||||||
|
<InputPasswordFiledInForm
|
||||||
|
name="password"
|
||||||
|
placeholder="Password"
|
||||||
|
error={
|
||||||
|
touched.password && errors.password
|
||||||
|
? errors.password.toString()
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className={styles.passwordNote}>
|
||||||
|
Must contain 8 characters with at least 1 uppercase and 1
|
||||||
|
lowercase letter and either 1 number or 1 special character.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.bottom}>
|
<div className={styles.bottom}>
|
||||||
<ButtonCommon size='large'>Create Account</ButtonCommon>
|
<ButtonCommon
|
||||||
|
HTMLType="submit"
|
||||||
|
size="large"
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
Create Account
|
||||||
|
</ButtonCommon>
|
||||||
</div>
|
</div>
|
||||||
<SocialAuthen />
|
</Form>
|
||||||
<div className={s.others}>
|
)}
|
||||||
<span>Already an account?</span>
|
</Formik>
|
||||||
<button onClick={onSwitch}>Sign In</button>
|
</div>
|
||||||
</div>
|
<SocialAuthen />
|
||||||
</div>
|
<div className={s.others}>
|
||||||
</section>
|
<span>Already an account?</span>
|
||||||
)
|
<button onClick={onSwitch}>Sign In</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FormRegister
|
export default FormRegister
|
||||||
|
@@ -47,4 +47,9 @@ export { default as StaticImage} from './StaticImage/StaticImage'
|
|||||||
export { default as EmptyCommon} from './EmptyCommon/EmptyCommon'
|
export { default as EmptyCommon} from './EmptyCommon/EmptyCommon'
|
||||||
export { default as CustomShapeSvg} from './CustomShapeSvg/CustomShapeSvg'
|
export { default as CustomShapeSvg} from './CustomShapeSvg/CustomShapeSvg'
|
||||||
export { default as RecommendedRecipes} from './RecommendedRecipes/RecommendedRecipes'
|
export { default as RecommendedRecipes} from './RecommendedRecipes/RecommendedRecipes'
|
||||||
export { default as LayoutCheckout} from './LayoutCheckout/LayoutCheckout'
|
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'
|
||||||
|
|
||||||
|
|
||||||
|
27
src/components/contexts/Message/MessageContext.tsx
Normal file
27
src/components/contexts/Message/MessageContext.tsx
Normal file
@@ -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<MessageContextType>(
|
||||||
|
DEFAULT_MESSAGE_CONTEXT
|
||||||
|
)
|
||||||
|
|
||||||
|
export function useMessage() {
|
||||||
|
return useContext(MessageContext)
|
||||||
|
}
|
67
src/components/contexts/Message/MessageProvider.tsx
Normal file
67
src/components/contexts/Message/MessageProvider.tsx
Normal file
@@ -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<number>(0)
|
||||||
|
const [messages, setMessages] = useState<MessageItemProps[]>([])
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<MessageContext.Provider
|
||||||
|
value={{
|
||||||
|
messages,
|
||||||
|
removeMessage,
|
||||||
|
showMessageSuccess,
|
||||||
|
showMessageInfo,
|
||||||
|
showMessageError,
|
||||||
|
showMessageWarning,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</MessageContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
@@ -1,2 +1,5 @@
|
|||||||
export * from './CartDrawer/CartDrawerContext'
|
export * from './CartDrawer/CartDrawerContext'
|
||||||
export * from './CartDrawer/CartDrawerProvider'
|
export * from './CartDrawer/CartDrawerProvider'
|
||||||
|
|
||||||
|
export * from './Message/MessageContext'
|
||||||
|
export * from './Message/MessageProvider'
|
||||||
|
6
src/components/hooks/auth/index.ts
Normal file
6
src/components/hooks/auth/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export { default as useSignup } from './useSignup'
|
||||||
|
export { default as useLogin } from './useLogin'
|
||||||
|
export { default as useLogout } from './useLogout'
|
||||||
|
export { default as useVerifyCustomer } from './useVerifyCustomer'
|
||||||
|
export { default as useActiveCustomer } from './useActiveCustomer'
|
||||||
|
|
11
src/components/hooks/auth/useActiveCustomer.tsx
Normal file
11
src/components/hooks/auth/useActiveCustomer.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { ActiveCustomerQuery } from '@framework/schema'
|
||||||
|
import { activeCustomerQuery } from '@framework/utils/queries/active-customer-query'
|
||||||
|
import gglFetcher from 'src/utils/gglFetcher'
|
||||||
|
import useSWR from 'swr'
|
||||||
|
|
||||||
|
const useActiveCustomer = () => {
|
||||||
|
const { data, ...rest } = useSWR<ActiveCustomerQuery>([activeCustomerQuery], gglFetcher)
|
||||||
|
return { customer: data?.activeCustomer, ...rest }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useActiveCustomer
|
@@ -1,24 +1,11 @@
|
|||||||
import { gql } from 'graphql-request'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import useActiveCustomer from './useActiveCustomer'
|
import useActiveCustomer from './useActiveCustomer'
|
||||||
import { CommonError } from 'src/domains/interfaces/CommonError'
|
import { CommonError } from 'src/domains/interfaces/CommonError'
|
||||||
import rawFetcher from 'src/utils/rawFetcher'
|
import rawFetcher from 'src/utils/rawFetcher'
|
||||||
import { LoginMutation } from '@framework/schema'
|
import { LoginMutation } from '@framework/schema'
|
||||||
|
import { LOCAL_STORAGE_KEY } from 'src/utils/constanst.utils'
|
||||||
const query = gql`
|
import { errorMapping } from 'src/utils/errrorMapping'
|
||||||
mutation login($username: String!, $password: String!) {
|
import { loginMutation } from '@framework/utils/mutations/log-in-mutation'
|
||||||
login(username: $username, password: $password) {
|
|
||||||
__typename
|
|
||||||
... on CurrentUser {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
... on ErrorResult {
|
|
||||||
errorCode
|
|
||||||
message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
interface LoginInput {
|
interface LoginInput {
|
||||||
username: string
|
username: string
|
||||||
@@ -30,24 +17,30 @@ const useLogin = () => {
|
|||||||
const [error, setError] = useState<CommonError | null>(null)
|
const [error, setError] = useState<CommonError | null>(null)
|
||||||
const { mutate } = useActiveCustomer()
|
const { mutate } = useActiveCustomer()
|
||||||
|
|
||||||
const login = (options: LoginInput) => {
|
const login = (options: LoginInput,
|
||||||
|
fCallBack: (isSuccess: boolean, message?: string) => void
|
||||||
|
) => {
|
||||||
setError(null)
|
setError(null)
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
rawFetcher<LoginMutation>({
|
rawFetcher<LoginMutation>({
|
||||||
query,
|
query: loginMutation,
|
||||||
variables: options,
|
variables: options,
|
||||||
})
|
})
|
||||||
.then(({ data, headers }) => {
|
.then(({ data, headers }) => {
|
||||||
if (data.login.__typename !== 'CurrentUser') {
|
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')
|
const authToken = headers.get('vendure-auth-token')
|
||||||
if (authToken != null) {
|
if (authToken != null) {
|
||||||
localStorage.setItem('token', authToken)
|
localStorage.setItem(LOCAL_STORAGE_KEY.TOKEN, authToken)
|
||||||
return mutate()
|
mutate()
|
||||||
}
|
}
|
||||||
|
fCallBack(true)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setError(error)
|
||||||
|
fCallBack(false, error.message)
|
||||||
})
|
})
|
||||||
.catch(setError)
|
|
||||||
.finally(() => setLoading(false))
|
.finally(() => setLoading(false))
|
||||||
}
|
}
|
||||||
|
|
34
src/components/hooks/auth/useLogout.tsx
Normal file
34
src/components/hooks/auth/useLogout.tsx
Normal file
@@ -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<CommonError | null>(null)
|
||||||
|
const { mutate } = useActiveCustomer()
|
||||||
|
|
||||||
|
const logout = () => {
|
||||||
|
setError(null)
|
||||||
|
setLoading(true)
|
||||||
|
rawFetcher<LogoutMutation>({
|
||||||
|
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
|
@@ -1,29 +1,14 @@
|
|||||||
import { gql } from 'graphql-request'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import useActiveCustomer from './useActiveCustomer'
|
import useActiveCustomer from './useActiveCustomer'
|
||||||
import { SignupMutation } from '@framework/schema'
|
import { SignupMutation } from '@framework/schema'
|
||||||
import fetcher from 'src/utils/fetcher'
|
import fetcher from 'src/utils/fetcher'
|
||||||
import { CommonError } from 'src/domains/interfaces/CommonError'
|
import { CommonError } from 'src/domains/interfaces/CommonError'
|
||||||
|
import { signupMutation } from '@framework/utils/mutations/sign-up-mutation'
|
||||||
const query = gql`
|
|
||||||
mutation signup($input: RegisterCustomerInput!) {
|
|
||||||
registerCustomerAccount(input: $input) {
|
|
||||||
__typename
|
|
||||||
... on Success {
|
|
||||||
success
|
|
||||||
}
|
|
||||||
... on ErrorResult {
|
|
||||||
errorCode
|
|
||||||
message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
interface SignupInput {
|
interface SignupInput {
|
||||||
email: string
|
email: string
|
||||||
firstName: string
|
firstName?: string
|
||||||
lastName: string
|
lastName?: string
|
||||||
password: string
|
password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,11 +17,14 @@ const useSignup = () => {
|
|||||||
const [error, setError] = useState<Error | null>(null)
|
const [error, setError] = useState<Error | null>(null)
|
||||||
const { mutate } = useActiveCustomer()
|
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)
|
setError(null)
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
fetcher<SignupMutation>({
|
fetcher<SignupMutation>({
|
||||||
query,
|
query: signupMutation,
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
firstName,
|
firstName,
|
||||||
@@ -53,11 +41,15 @@ const useSignup = () => {
|
|||||||
data.registerCustomerAccount.errorCode
|
data.registerCustomerAccount.errorCode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
console.log(data)
|
|
||||||
mutate()
|
mutate()
|
||||||
|
fCallBack(true)
|
||||||
return data
|
return data
|
||||||
})
|
})
|
||||||
.catch(setError)
|
.catch((error) => {
|
||||||
|
setError(error)
|
||||||
|
fCallBack(false, error.message)
|
||||||
|
})
|
||||||
.finally(() => setLoading(false))
|
.finally(() => setLoading(false))
|
||||||
}
|
}
|
||||||
|
|
51
src/components/hooks/auth/useVerifyCustomer.tsx
Normal file
51
src/components/hooks/auth/useVerifyCustomer.tsx
Normal file
@@ -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 useActiveCustomer from './useActiveCustomer'
|
||||||
|
import { verifyCustomerAccountMutaton } from '@framework/utils/mutations/verify-customer-account-mutation'
|
||||||
|
|
||||||
|
interface VerifyInput {
|
||||||
|
token: string
|
||||||
|
password?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const useVerifyCustomer = () => {
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<CommonError | null>(null)
|
||||||
|
const { mutate } = useActiveCustomer()
|
||||||
|
|
||||||
|
const verify = (
|
||||||
|
options: VerifyInput,
|
||||||
|
fCallBack?: (isSuccess: boolean) => void
|
||||||
|
) => {
|
||||||
|
setError(null)
|
||||||
|
setLoading(true)
|
||||||
|
rawFetcher<VerifyCustomerAccountMutation>({
|
||||||
|
query: verifyCustomerAccountMutaton,
|
||||||
|
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
|
@@ -1,22 +0,0 @@
|
|||||||
import { ActiveCustomerQuery } from '@framework/schema'
|
|
||||||
import { gql } from 'graphql-request'
|
|
||||||
import gglFetcher from 'src/utils/gglFetcher'
|
|
||||||
import useSWR from 'swr'
|
|
||||||
|
|
||||||
const query = gql`
|
|
||||||
query activeCustomer {
|
|
||||||
activeCustomer {
|
|
||||||
id
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
emailAddress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const useActiveCustomer = () => {
|
|
||||||
const { data, ...rest } = useSWR<ActiveCustomerQuery>([query], gglFetcher)
|
|
||||||
return { customer: data?.activeCustomer, ...rest }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useActiveCustomer
|
|
1
src/components/hooks/useProduct.tsx
Normal file
1
src/components/hooks/useProduct.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
// here
|
@@ -87,7 +87,7 @@ const AccountPage = ({ defaultActiveContent="orders" } : AccountPageProps) => {
|
|||||||
const query = router.query[QUERY_KEY.TAB] as string
|
const query = router.query[QUERY_KEY.TAB] as string
|
||||||
const index = getTabIndex(query)
|
const index = getTabIndex(query)
|
||||||
setActiveTab(index)
|
setActiveTab(index)
|
||||||
}, [router.query[QUERY_KEY.TAB]])
|
}, [router.query])
|
||||||
|
|
||||||
function showModal() {
|
function showModal() {
|
||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
|
@@ -5,7 +5,7 @@ import Image from 'next/image'
|
|||||||
import avatar from '../../assets/avatar.png'
|
import avatar from '../../assets/avatar.png'
|
||||||
|
|
||||||
import { ButtonCommon } from 'src/components/common'
|
import { ButtonCommon } from 'src/components/common'
|
||||||
import useActiveCustomer from 'src/components/hooks/useActiveCustomer'
|
import { useActiveCustomer } from 'src/components/hooks/auth'
|
||||||
|
|
||||||
interface AccountProps {
|
interface AccountProps {
|
||||||
name: string
|
name: string
|
||||||
|
@@ -12,8 +12,6 @@ interface EditInfoModalProps {
|
|||||||
const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoModalProps) => {
|
const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoModalProps) => {
|
||||||
|
|
||||||
function saveInfo() {
|
function saveInfo() {
|
||||||
console.log("saved !!!");
|
|
||||||
|
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,7 +16,6 @@ const FormSubscribe = () => {
|
|||||||
e.preventDefault && e.preventDefault()
|
e.preventDefault && e.preventDefault()
|
||||||
value = inputElementRef.current?.getValue()?.toString() || ''
|
value = inputElementRef.current?.getValue()?.toString() || ''
|
||||||
}
|
}
|
||||||
console.log("email here: ", value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,71 @@
|
|||||||
|
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 } from 'src/components/hooks'
|
||||||
|
import { useVerifyCustomer } from 'src/components/hooks/auth'
|
||||||
|
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<boolean>(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 (
|
||||||
|
<div className={s.verifyCustomerAccount}>
|
||||||
|
{loading || !isVerified ? (
|
||||||
|
<div>
|
||||||
|
<LoadingCommon description="Verifing your account ...." />
|
||||||
|
</div>
|
||||||
|
) : error ? (
|
||||||
|
<div className={s.result}>
|
||||||
|
<div className={s.message}>Error: {error?.message}</div>
|
||||||
|
<Link href={ROUTE.HOME}>
|
||||||
|
<a href="">
|
||||||
|
<ButtonCommon>Back to home</ButtonCommon>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={s.result}>
|
||||||
|
<div className={s.message}>
|
||||||
|
Congratulation! Verified account successfully
|
||||||
|
</div>
|
||||||
|
<div className={s.bottom}>
|
||||||
|
<Link href={ROUTE.HOME}>
|
||||||
|
<a href="">
|
||||||
|
<ButtonCommon type="light">Back to home</ButtonCommon>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<ButtonCommon onClick={openModalAuthen}>
|
||||||
|
{LANGUAGE.BUTTON_LABEL.SIGNIN}
|
||||||
|
</ButtonCommon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ModalAuthenticate
|
||||||
|
visible={visibleModalAuthen}
|
||||||
|
closeModal={closeModalAuthen}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
2
src/components/modules/verify-customer/index.tsx
Normal file
2
src/components/modules/verify-customer/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as VerifyCustomerAccount } from './VerifyCustomerAccount/VerifyCustomerAccount'
|
||||||
|
|
100
src/styles/_form.scss
Normal file
100
src/styles/_form.scss
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -6,4 +6,5 @@
|
|||||||
@import "~tailwindcss/utilities";
|
@import "~tailwindcss/utilities";
|
||||||
@import './utilities';
|
@import './utilities';
|
||||||
|
|
||||||
|
@import './form';
|
||||||
@import './pages'
|
@import './pages'
|
||||||
|
@@ -38,6 +38,10 @@ export const ACCOUNT_TAB = {
|
|||||||
FAVOURITE: 'wishlist',
|
FAVOURITE: 'wishlist',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const LOCAL_STORAGE_KEY = {
|
||||||
|
TOKEN: 'token'
|
||||||
|
}
|
||||||
|
|
||||||
export const QUERY_KEY = {
|
export const QUERY_KEY = {
|
||||||
TAB: 'tab',
|
TAB: 'tab',
|
||||||
CATEGORY: 'category',
|
CATEGORY: 'category',
|
||||||
|
14
src/utils/errrorMapping.ts
Normal file
14
src/utils/errrorMapping.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
import { request } from 'graphql-request'
|
import { request } from 'graphql-request'
|
||||||
import { RequestDocument, Variables } from 'graphql-request/dist/types'
|
import { RequestDocument, Variables } from 'graphql-request/dist/types'
|
||||||
|
import { LOCAL_STORAGE_KEY } from './constanst.utils'
|
||||||
|
|
||||||
interface QueryOptions {
|
interface QueryOptions {
|
||||||
query: RequestDocument
|
query: RequestDocument
|
||||||
@@ -10,11 +11,7 @@ interface QueryOptions {
|
|||||||
|
|
||||||
const fetcher = async <T>(options: QueryOptions): Promise<T> => {
|
const fetcher = async <T>(options: QueryOptions): Promise<T> => {
|
||||||
const { query, variables } = options
|
const { query, variables } = options
|
||||||
console.log('query')
|
const token = localStorage.getItem(LOCAL_STORAGE_KEY.TOKEN)
|
||||||
console.log(options)
|
|
||||||
const token = localStorage.getItem('token')
|
|
||||||
console.log('token')
|
|
||||||
console.log(token)
|
|
||||||
const res = await request<T>(
|
const res = await request<T>(
|
||||||
process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL as string,
|
process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL as string,
|
||||||
query,
|
query,
|
||||||
|
@@ -9,5 +9,8 @@ export const LANGUAGE = {
|
|||||||
},
|
},
|
||||||
PLACE_HOLDER: {
|
PLACE_HOLDER: {
|
||||||
SEARCH: 'Search',
|
SEARCH: 'Search',
|
||||||
|
},
|
||||||
|
MESSAGE: {
|
||||||
|
ERROR: 'Something went wrong! Please try again!'
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
import { rawRequest } from 'graphql-request'
|
import { rawRequest } from 'graphql-request'
|
||||||
import { RequestDocument, Variables } from 'graphql-request/dist/types'
|
import { RequestDocument, Variables } from 'graphql-request/dist/types'
|
||||||
|
import { LOCAL_STORAGE_KEY } from './constanst.utils'
|
||||||
|
|
||||||
interface QueryOptions {
|
interface QueryOptions {
|
||||||
query: RequestDocument
|
query: RequestDocument
|
||||||
@@ -14,10 +15,12 @@ const rawFetcher = <T>({
|
|||||||
onLoad = () => true,
|
onLoad = () => true,
|
||||||
}: QueryOptions): Promise<{ data: T; headers: any }> => {
|
}: QueryOptions): Promise<{ data: T; headers: any }> => {
|
||||||
onLoad(true)
|
onLoad(true)
|
||||||
|
const token = localStorage.getItem(LOCAL_STORAGE_KEY.TOKEN)
|
||||||
return rawRequest<T>(
|
return rawRequest<T>(
|
||||||
process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL as string,
|
process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL as string,
|
||||||
query as string,
|
query as string,
|
||||||
variables
|
variables,
|
||||||
|
token ? { Authorization: 'Bearer ' + token } : {}
|
||||||
)
|
)
|
||||||
.then(({ data, headers }) => {
|
.then(({ data, headers }) => {
|
||||||
return { data, headers }
|
return { data, headers }
|
||||||
|
79
yarn.lock
79
yarn.lock
@@ -445,7 +445,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.2"
|
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"
|
version "7.15.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
|
||||||
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
|
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
|
||||||
@@ -1213,6 +1213,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.173.tgz#9d3b674c67a26cf673756f6aca7b429f237f91ed"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.173.tgz#9d3b674c67a26cf673756f6aca7b429f237f91ed"
|
||||||
integrity sha512-vv0CAYoaEjCw/mLy96GBTnRoZrSxkGE0BKzKimdR8P3OzrNYNvBgtW7p055A+E8C31vXNUhWKoFCbhq7gbyhFg==
|
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":
|
"@types/lru-cache@4.1.1":
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-4.1.1.tgz#b2d87a5e3df8d4b18ca426c5105cd701c2306d40"
|
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"
|
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
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:
|
defer-to-connect@^1.0.1:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
|
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"
|
combined-stream "^1.0.8"
|
||||||
mime-types "^2.1.12"
|
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:
|
fraction.js@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.1.tgz#ac4e520473dae67012d618aab91eda09bcb400ff"
|
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-assert "^1.0.0"
|
||||||
minimalistic-crypto-utils "^1.0.1"
|
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:
|
hosted-git-info@^2.1.4:
|
||||||
version "2.8.9"
|
version "2.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
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:
|
dependencies:
|
||||||
p-locate "^4.1.0"
|
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:
|
lodash.camelcase@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
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"
|
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
||||||
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
|
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:
|
nanoid@^3.1.23:
|
||||||
version "3.1.25"
|
version "3.1.25"
|
||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152"
|
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"
|
object-assign "^4.1.1"
|
||||||
react-is "^16.8.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:
|
public-encrypt@^4.0.0:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
|
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"
|
object-assign "^4.1.1"
|
||||||
scheduler "^0.20.2"
|
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:
|
react-fast-compare@^3.0.1:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
|
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"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
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"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
@@ -6878,6 +6928,11 @@ timers-browserify@2.0.12, timers-browserify@^2.0.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
setimmediate "^1.0.4"
|
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:
|
title-case@^3.0.3:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982"
|
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"
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
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:
|
totalist@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
|
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"
|
minimist "^1.2.0"
|
||||||
strip-bom "^3.0.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"
|
version "1.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
@@ -7419,3 +7479,16 @@ yocto-queue@^0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
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"
|
||||||
|
Reference in New Issue
Block a user