From a4c23a218151310f9c294f734cc671d81c53ea50 Mon Sep 17 00:00:00 2001 From: Tan Le Date: Thu, 30 Sep 2021 15:47:57 +0700 Subject: [PATCH 01/28] feat: getProductDetail --- framework/vendure/schema.d.ts | 2 +- next-env.d.ts | 3 -- src/components/hooks/product/index.tsx | 1 + .../hooks/product/useProductDetail.tsx | 31 +++++++++++++++++++ .../ProductImgItem/ProductImgItem.tsx | 8 ++--- .../components/ProductImgs/ProductImgs.tsx | 4 ++- .../components/ProductInfo/ProductInfo.tsx | 10 +++--- 7 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 src/components/hooks/product/index.tsx create mode 100644 src/components/hooks/product/useProductDetail.tsx diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 9d6b53c52..b664d8fa1 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -3206,7 +3206,7 @@ export type GetProductQuery = { __typename?: 'Query' } & { variants: Array< { __typename?: 'ProductVariant' } & Pick< ProductVariant, - 'id' | 'priceWithTax' | 'currencyCode' + 'id' | 'priceWithTax' | 'currencyCode' | 'price' > & { options: Array< { __typename?: 'ProductOption' } & Pick< diff --git a/next-env.d.ts b/next-env.d.ts index 9bc3dd46b..c6643fda1 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,3 @@ /// /// /// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/src/components/hooks/product/index.tsx b/src/components/hooks/product/index.tsx new file mode 100644 index 000000000..bfe1abac2 --- /dev/null +++ b/src/components/hooks/product/index.tsx @@ -0,0 +1 @@ +export { default as useProductDetail } from './useProductDetail' \ No newline at end of file diff --git a/src/components/hooks/product/useProductDetail.tsx b/src/components/hooks/product/useProductDetail.tsx new file mode 100644 index 000000000..6a147d263 --- /dev/null +++ b/src/components/hooks/product/useProductDetail.tsx @@ -0,0 +1,31 @@ +import { GetProductQuery } from '@framework/schema' +import { gql } from 'graphql-request' +import gglFetcher from 'src/utils/gglFetcher' +import useSWR from 'swr' + +const query = gql` + query GetProductDetail($slug: String! = "hand-trowel") { + product(slug: $slug) { + name + description + variants { + price + priceWithTax + } + assets { + preview + name + } + } +} +` +interface ProductDetail { + slug: string +} + +const useProductDetail = () => { + const { data, ...rest } = useSWR([query],gglFetcher) + return { productDetail: data?.product, ...rest } +} + +export default useProductDetail \ No newline at end of file diff --git a/src/components/modules/product-detail/ProductInfoDetail/components/ProductImgItem/ProductImgItem.tsx b/src/components/modules/product-detail/ProductInfoDetail/components/ProductImgItem/ProductImgItem.tsx index 95236266c..931b0a36c 100644 --- a/src/components/modules/product-detail/ProductInfoDetail/components/ProductImgItem/ProductImgItem.tsx +++ b/src/components/modules/product-detail/ProductInfoDetail/components/ProductImgItem/ProductImgItem.tsx @@ -3,15 +3,15 @@ import { ImgWithLink } from 'src/components/common' import s from './ProductImgItem.module.scss' export interface ProductImgItemProps { - src: string - alt?: string + preview: string + name?: string } -const ProductImgItem = ({ src, alt }: ProductImgItemProps) => { +const ProductImgItem = ({ preview, name }: ProductImgItemProps) => { return (
- +
) } diff --git a/src/components/modules/product-detail/ProductInfoDetail/components/ProductImgs/ProductImgs.tsx b/src/components/modules/product-detail/ProductInfoDetail/components/ProductImgs/ProductImgs.tsx index 475d0f22e..365faa73a 100644 --- a/src/components/modules/product-detail/ProductInfoDetail/components/ProductImgs/ProductImgs.tsx +++ b/src/components/modules/product-detail/ProductInfoDetail/components/ProductImgs/ProductImgs.tsx @@ -2,6 +2,7 @@ import React from 'react' import { ResponsiveType } from 'react-multi-carousel' import { CarouselCommon } from 'src/components/common' import ProductImgItem, { ProductImgItemProps } from '../ProductImgItem/ProductImgItem' +import { useProductDetail } from 'src/components/hooks/product' import s from './ProductImgs.module.scss' interface Props { @@ -32,10 +33,11 @@ const RESPONSIVE: ResponsiveType = { }, } const ProductImgs = ({ }: Props) => { + const { productDetail } = useProductDetail() return (
- data={DATA} + data={productDetail?.assets ?? []} itemKey="product-detail-img" Component={ProductImgItem} responsive={RESPONSIVE} diff --git a/src/components/modules/product-detail/ProductInfoDetail/components/ProductInfo/ProductInfo.tsx b/src/components/modules/product-detail/ProductInfoDetail/components/ProductInfo/ProductInfo.tsx index 4abb62568..40779083c 100644 --- a/src/components/modules/product-detail/ProductInfoDetail/components/ProductInfo/ProductInfo.tsx +++ b/src/components/modules/product-detail/ProductInfoDetail/components/ProductInfo/ProductInfo.tsx @@ -2,6 +2,7 @@ import React from 'react' import { ButtonCommon, LabelCommon, QuanittyInput } from 'src/components/common' import { IconBuy } from 'src/components/icons' import { LANGUAGE } from 'src/utils/language.utils' +import { useProductDetail } from 'src/components/hooks/product' import s from './ProductInfo.module.scss' interface Props { @@ -10,20 +11,21 @@ interface Props { } const ProductInfo = ({ }: Props) => { + const {productDetail} = useProductDetail() return (
SEAFOOD -

SeaPAk

+

{productDetail?.name}

- Rp 32.000 + Rp {productDetail?.variants[0].priceWithTax} -15%
-
Rp 27.500
+
Rp {productDetail?.variants[0].price}
- In a large non-reactive dish, mix together the orange juice, soy sauce, olive oil, lemon juice, parsley + {productDetail?.description}
From cf2260869a0c23dee4790b8632bb8b24eeb069de Mon Sep 17 00:00:00 2001 From: Tan Le Date: Thu, 30 Sep 2021 15:52:18 +0700 Subject: [PATCH 02/28] refactor: move query into get-product-query --- .../utils/queries/get-product-query.ts | 16 ++++++++++++++ .../hooks/product/useProductDetail.tsx | 21 +++---------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/framework/vendure/utils/queries/get-product-query.ts b/framework/vendure/utils/queries/get-product-query.ts index b2c502da9..82aa5dc41 100644 --- a/framework/vendure/utils/queries/get-product-query.ts +++ b/framework/vendure/utils/queries/get-product-query.ts @@ -39,3 +39,19 @@ export const getProductQuery = /* GraphQL */ ` } } ` +export const getProductDetailQuery = /* GraphQL */ ` + query GetProductDetail($slug: String! = "hand-trowel") { + product(slug: $slug) { + name + description + variants { + price + priceWithTax + } + assets { + preview + name + } + } +} +` \ No newline at end of file diff --git a/src/components/hooks/product/useProductDetail.tsx b/src/components/hooks/product/useProductDetail.tsx index 6a147d263..a68b1449d 100644 --- a/src/components/hooks/product/useProductDetail.tsx +++ b/src/components/hooks/product/useProductDetail.tsx @@ -1,30 +1,15 @@ import { GetProductQuery } from '@framework/schema' -import { gql } from 'graphql-request' +import { getProductDetailQuery } from '@framework/utils/queries/get-product-query'; import gglFetcher from 'src/utils/gglFetcher' import useSWR from 'swr' -const query = gql` - query GetProductDetail($slug: String! = "hand-trowel") { - product(slug: $slug) { - name - description - variants { - price - priceWithTax - } - assets { - preview - name - } - } -} -` + interface ProductDetail { slug: string } const useProductDetail = () => { - const { data, ...rest } = useSWR([query],gglFetcher) + const { data, ...rest } = useSWR([getProductDetailQuery],gglFetcher) return { productDetail: data?.product, ...rest } } From 6238026b14a3c859643341d4e57937603f821ff2 Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Fri, 1 Oct 2021 10:26:06 +0700 Subject: [PATCH 03/28] khong co gitthay doi --- next-env.d.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/next-env.d.ts b/next-env.d.ts index 9bc3dd46b..c6643fda1 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,3 @@ /// /// /// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. From 19c826722057c1d0cfb5df77203838e13d565c18 Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Fri, 1 Oct 2021 18:28:17 +0700 Subject: [PATCH 04/28] feat: create RequestPasswordReset --- framework/vendure/schema.d.ts | 39 ++++++++++++++++++- .../request-password-reset-mutation.ts | 14 +++++++ .../mutations/reset-password-mutation.ts | 15 +++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 framework/vendure/utils/mutations/request-password-reset-mutation.ts create mode 100644 framework/vendure/utils/mutations/reset-password-mutation.ts diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index b0b0170d7..af6eb5051 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -1,3 +1,5 @@ +import { ResetPassword } from './schema.d'; +import { requestPasswordReset } from '@framework/schema'; export type Maybe = T | null export type Exact = { [K in keyof T]: T[K] @@ -3095,6 +3097,36 @@ export type LoginMutation = { __typename?: 'Mutation' } & { >) } +export type ResetPasswordMutation = { __typename?: 'Mutation' } & { + resetPassword: + | ({ __typename: 'CurrentUser' } & Pick) + | ({ __typename: 'PasswordResetTokenInvalidError' } & Pick< + PasswordResetTokenInvalidError, + 'errorCode' | 'message' + >) + | ({ __typename: 'PasswordResetTokenExpiredError' } & Pick< + PasswordResetTokenExpiredError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NativeAuthStrategyError' } & Pick< + NativeAuthStrategyError, + 'errorCode' | 'message' + >) +} + +export type SignupMutation = { __typename?: 'Mutation' } & { + registerCustomerAccount: + | ({ __typename: 'Success' } & Pick) + | ({ __typename: 'MissingPasswordError' } & Pick< + MissingPasswordError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NativeAuthStrategyError' } & Pick< + NativeAuthStrategyError, + 'errorCode' | 'message' + >) +} + export type VerifyCustomerAccountVariables = Exact<{ token: Scalars['String'] password?: Maybe @@ -3148,8 +3180,9 @@ export type SignupMutationVariables = Exact<{ input: RegisterCustomerInput }> -export type SignupMutation = { __typename?: 'Mutation' } & { - registerCustomerAccount: + +export type RequestPasswordReset = { __typename?: 'Mutation' } & { + requestPasswordReset: | ({ __typename: 'Success' } & Pick) | ({ __typename: 'MissingPasswordError' } & Pick< MissingPasswordError, @@ -3161,6 +3194,8 @@ export type SignupMutation = { __typename?: 'Mutation' } & { >) } + + export type ActiveCustomerQueryVariables = Exact<{ [key: string]: never }> export type ActiveCustomerQuery = { __typename?: 'Query' } & { diff --git a/framework/vendure/utils/mutations/request-password-reset-mutation.ts b/framework/vendure/utils/mutations/request-password-reset-mutation.ts new file mode 100644 index 000000000..474d8f33f --- /dev/null +++ b/framework/vendure/utils/mutations/request-password-reset-mutation.ts @@ -0,0 +1,14 @@ +export const requestPasswordReset = /* GraphQL */ ` +mutation RequestPasswordReset($emailAddress: String!) { + requestPasswordReset(emailAddress: $emailAddress) { + __typename + ...on Success{ + success + } + ...on ErrorResult{ + errorCode + message + } + } +} +` \ No newline at end of file diff --git a/framework/vendure/utils/mutations/reset-password-mutation.ts b/framework/vendure/utils/mutations/reset-password-mutation.ts new file mode 100644 index 000000000..8ff4058ed --- /dev/null +++ b/framework/vendure/utils/mutations/reset-password-mutation.ts @@ -0,0 +1,15 @@ +export const resetPasswordMutation = /* GraphQL */ ` +mutation resetPassword($token: String!,$password: String!){ + resetPassword(token: $token,password: $password){ + __typename + ...on CurrentUser{ + id + identifier + } + ...on ErrorResult{ + errorCode + message + } + } +} +` \ No newline at end of file From 7f927a0ba34960c17b4726796e5a7824f5c6c14a Mon Sep 17 00:00:00 2001 From: Quangnhankie Date: Mon, 4 Oct 2021 09:14:58 +0700 Subject: [PATCH 05/28] feat: RequestPasswordReset --- pages/forgot-password.tsx | 10 ++ pages/reset-password.tsx | 10 ++ .../FormForgot/FormForgot.module.scss | 22 ++++ .../ForgotPassword/FormForgot/FormForgot.tsx | 89 +++++++++++++++ .../FormResetPassword.module.scss | 27 +++++ .../FormResetPassword/FormResetPassword.tsx | 108 ++++++++++++++++++ .../components/HeaderMenu/HeaderMenu.tsx | 4 + src/components/common/index.ts | 2 + src/components/hooks/auth/index.ts | 2 + .../hooks/auth/useRequestPasswordReset.tsx | 50 ++++++++ .../hooks/auth/useResetPassword.tsx | 52 +++++++++ 11 files changed, 376 insertions(+) create mode 100644 pages/forgot-password.tsx create mode 100644 pages/reset-password.tsx create mode 100644 src/components/common/ForgotPassword/FormForgot/FormForgot.module.scss create mode 100644 src/components/common/ForgotPassword/FormForgot/FormForgot.tsx create mode 100644 src/components/common/ForgotPassword/FormResetPassword/FormResetPassword.module.scss create mode 100644 src/components/common/ForgotPassword/FormResetPassword/FormResetPassword.tsx create mode 100644 src/components/hooks/auth/useRequestPasswordReset.tsx create mode 100644 src/components/hooks/auth/useResetPassword.tsx diff --git a/pages/forgot-password.tsx b/pages/forgot-password.tsx new file mode 100644 index 000000000..8d4b1e570 --- /dev/null +++ b/pages/forgot-password.tsx @@ -0,0 +1,10 @@ +import { FormForgot, Layout } from 'src/components/common' + +export default function NotFound() { + return ( +
+ +
+ ) +} +NotFound.Layout = Layout diff --git a/pages/reset-password.tsx b/pages/reset-password.tsx new file mode 100644 index 000000000..bc8905da3 --- /dev/null +++ b/pages/reset-password.tsx @@ -0,0 +1,10 @@ +import { FormResetPassword, Layout } from 'src/components/common' + +export default function NotFound() { + return ( +
+ +
+ ) +} +NotFound.Layout = Layout diff --git a/src/components/common/ForgotPassword/FormForgot/FormForgot.module.scss b/src/components/common/ForgotPassword/FormForgot/FormForgot.module.scss new file mode 100644 index 000000000..57b39c56c --- /dev/null +++ b/src/components/common/ForgotPassword/FormForgot/FormForgot.module.scss @@ -0,0 +1,22 @@ +@import '../../../../styles/utilities'; +.formAuthen{ + width: 50%; + margin: 0 auto; + padding: 4rem 0 ; + .title{ + @apply font-heading heading-3; + padding: 0 1.6rem 0 0.8rem; + margin-bottom: 2rem; + } + .bottom { + @apply flex justify-between items-center; + margin: 4rem auto; + .remembered { + @apply font-bold cursor-pointer; + color: var(--primary); + } + } + .socialAuthen{ + margin-bottom: 3rem; + } +} diff --git a/src/components/common/ForgotPassword/FormForgot/FormForgot.tsx b/src/components/common/ForgotPassword/FormForgot/FormForgot.tsx new file mode 100644 index 000000000..834c65919 --- /dev/null +++ b/src/components/common/ForgotPassword/FormForgot/FormForgot.tsx @@ -0,0 +1,89 @@ +import { Form, Formik } from 'formik'; +import React, { useRef } from 'react'; +import { ButtonCommon, InputFiledInForm } from 'src/components/common'; +import { useModalCommon } from 'src/components/hooks'; +import useRequestPasswordReset from 'src/components/hooks/auth/useRequestPasswordReset'; +import { CustomInputCommon } from 'src/utils/type.utils'; +import * as Yup from 'yup'; +import ModalAuthenticate from '../../ModalAuthenticate/ModalAuthenticate'; +import { default as s, default as styles } from './FormForgot.module.scss'; +import { useMessage } from 'src/components/contexts' +import { LANGUAGE } from 'src/utils/language.utils' + +interface Props { + +} +const DisplayingErrorMessagesSchema = Yup.object().shape({ + email: Yup.string().email('Your email was wrong').required('Required') +}) + +const FormForgot = ({ }: Props) => { + const {requestPassword} = useRequestPasswordReset(); + const { showMessageSuccess, showMessageError } = useMessage(); + + const emailRef = useRef(null); + + const { visible: visibleModalAuthen,closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false }); + + const onForgot = (values: { email: string }) => { + requestPassword({email: values.email},onForgotPasswordCallBack); + } + + const onForgotPasswordCallBack = (isSuccess: boolean, message?: string) => { + if (isSuccess) { + showMessageSuccess("Request forgot password successfully. Please verify your email to login.") + } else { + showMessageError(message || LANGUAGE.MESSAGE.ERROR) + } + } + + return ( +
+
+
+
Forgot Password
+ + {({ errors, touched, isValid, submitForm }) => ( +
+
+ +
+
+
+ I Remembered My Password? +
+ + Reset Password + +
+
+ )} +
+
+ +
+
+ ) + + +} + + +export default FormForgot; \ No newline at end of file diff --git a/src/components/common/ForgotPassword/FormResetPassword/FormResetPassword.module.scss b/src/components/common/ForgotPassword/FormResetPassword/FormResetPassword.module.scss new file mode 100644 index 000000000..faf1b7f06 --- /dev/null +++ b/src/components/common/ForgotPassword/FormResetPassword/FormResetPassword.module.scss @@ -0,0 +1,27 @@ +@import '../../../../styles/utilities'; +.formAuthen{ + width: 50%; + margin: 0 auto; + padding: 4rem 0 ; + .title{ + @apply font-heading heading-3; + padding: 0 1.6rem 0 0.8rem; + margin-bottom: 2rem; + } + .passwordNote { + @apply text-center caption text-label; + margin-top: 0.8rem; + } + .bottom { + @apply flex justify-center items-center; + margin: 4rem auto; + .remembered { + @apply font-bold cursor-pointer; + color: var(--primary); + } + } + .confirmPassword{ + margin-top: 2rem; + } + +} diff --git a/src/components/common/ForgotPassword/FormResetPassword/FormResetPassword.tsx b/src/components/common/ForgotPassword/FormResetPassword/FormResetPassword.tsx new file mode 100644 index 000000000..ad41396ab --- /dev/null +++ b/src/components/common/ForgotPassword/FormResetPassword/FormResetPassword.tsx @@ -0,0 +1,108 @@ +import { Form, Formik } from 'formik'; +import React, { useRef } from 'react'; +import { ButtonCommon, InputPasswordFiledInForm } from 'src/components/common'; +import { useMessage } from 'src/components/contexts'; +import useRequestPasswordReset from 'src/components/hooks/auth/useRequestPasswordReset'; +import { LANGUAGE } from 'src/utils/language.utils'; +import { CustomInputCommon } from 'src/utils/type.utils'; +import * as Yup from 'yup'; +import { useRouter } from 'next/router' +import { default as s, default as styles } from './FormResetPassword.module.scss'; +import { useResetPassword } from 'src/components/hooks/auth'; + +interface Props { + +} +const DisplayingErrorMessagesSchema = Yup.object().shape({ + 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'), + confirmPassword: Yup.string() + .label('Password Confirm') + .required() + .oneOf([Yup.ref('password')], 'Passwords does not match'), +}) + +const FormResetPassword = ({ }: Props) => { + const router = useRouter(); + + const {resetPassword} = useResetPassword(); + + const { showMessageSuccess, showMessageError } = useMessage(); + + const onReset = (values: {password: string }) => { + const { token } = router.query; + resetPassword({token:token,password: values.password},onResetPasswordCallBack); + } + + const onResetPasswordCallBack = (isSuccess: boolean, message?: string) => { + if (isSuccess) { + showMessageSuccess("Reset password successfully. Please to login.") + } else { + showMessageError(message || LANGUAGE.MESSAGE.ERROR) + } + } + + return ( +
+
+
+
Reset Password
+ + {({ errors, touched, isValid, submitForm }) => ( +
+
+ +
+
+ +
+ +
+ Must contain 8 characters with at least 1 uppercase and 1 + lowercase letter and either 1 number or 1 special character. +
+
+ + Change Password + +
+
+ )} +
+
+
+
+ ) +} + + +export default FormResetPassword; \ No newline at end of file diff --git a/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx b/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx index 4f7e5d21a..5922e5017 100644 --- a/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx +++ b/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx @@ -60,6 +60,10 @@ const HeaderMenu = memo( onClick: openModalRegister, name: 'Create account', }, + { + link: '/forgot-password', + name: 'Forgot Password', + }, ], [openModalLogin, openModalRegister] ) diff --git a/src/components/common/index.ts b/src/components/common/index.ts index eaa33176c..42d326690 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -51,5 +51,7 @@ 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' +export { default as FormForgot} from './ForgotPassword/FormForgot/FormForgot' +export { default as FormResetPassword} from './ForgotPassword/FormResetPassword/FormResetPassword' diff --git a/src/components/hooks/auth/index.ts b/src/components/hooks/auth/index.ts index 845617bcd..ffd93b6e6 100644 --- a/src/components/hooks/auth/index.ts +++ b/src/components/hooks/auth/index.ts @@ -3,4 +3,6 @@ export { default as useLogin } from './useLogin' export { default as useLogout } from './useLogout' export { default as useVerifyCustomer } from './useVerifyCustomer' export { default as useActiveCustomer } from './useActiveCustomer' +export { default as useRequestPasswordReset } from './useRequestPasswordReset' +export { default as useResetPassword } from './useResetPassword' diff --git a/src/components/hooks/auth/useRequestPasswordReset.tsx b/src/components/hooks/auth/useRequestPasswordReset.tsx new file mode 100644 index 000000000..f30c1ab44 --- /dev/null +++ b/src/components/hooks/auth/useRequestPasswordReset.tsx @@ -0,0 +1,50 @@ +import { useState } from 'react' +import useActiveCustomer from './useActiveCustomer' +import fetcher from 'src/utils/fetcher' +import { CommonError } from 'src/domains/interfaces/CommonError' +import { requestPasswordReset } from '@framework/utils/mutations/request-password-reset-mutation' +import { RequestPasswordReset } from '@framework/schema' + +interface ForgotPassword { + email: string +} + +const useRequestPasswordReset = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + // const { mutate } = useActiveCustomer() + + const requestPassword = ( + {email}: ForgotPassword, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + fetcher({ + query: requestPasswordReset, + variables: { + emailAddress: email + }, + }) + .then((data) => { + if (data.requestPasswordReset.__typename !== 'Success') { + throw CommonError.create( + data.requestPasswordReset.message, + data.requestPasswordReset.errorCode + ) + } + // mutate() + fCallBack(true) + return data + }) + .catch((error) => { + setError(error) + fCallBack(false, error.message) + }) + .finally(() => setLoading(false)) + } + + return { loading, requestPassword, error } +} + +export default useRequestPasswordReset diff --git a/src/components/hooks/auth/useResetPassword.tsx b/src/components/hooks/auth/useResetPassword.tsx new file mode 100644 index 000000000..788d496df --- /dev/null +++ b/src/components/hooks/auth/useResetPassword.tsx @@ -0,0 +1,52 @@ +import { useState } from 'react' +import useActiveCustomer from './useActiveCustomer' +import fetcher from 'src/utils/fetcher' +import { CommonError } from 'src/domains/interfaces/CommonError' +import { resetPasswordMutation } from '@framework/utils/mutations/reset-password-mutation' +import { ResetPasswordMutation } from '@framework/schema' + +interface Props { + token?: string| string[] , + password:string +} + +const useResetPassword = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) +// const { mutate } = useActiveCustomer() + + const resetPassword = ( + {token,password}: Props, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + fetcher({ + query: resetPasswordMutation, + variables: { + token: token, + password:password + }, + }) + .then((data) => { + if (data.resetPassword.__typename !== 'CurrentUser') { + throw CommonError.create( + data.resetPassword.message, + data.resetPassword.errorCode + ) + } + // mutate() + fCallBack(true) + return data + }) + .catch((error) => { + setError(error) + fCallBack(false, error.message) + }) + .finally(() => setLoading(false)) + } + + return { loading, resetPassword, error } +} + +export default useResetPassword From 2ccccc51deb5f990dd689401f04844b7f63d5315 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Tue, 5 Oct 2021 10:35:53 +0700 Subject: [PATCH 06/28] :recycle: enhan: not get fresh and featured product when don't have facet id :%s --- pages/index.tsx | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/pages/index.tsx b/pages/index.tsx index cac464a43..010094de4 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -62,27 +62,34 @@ export async function getStaticProps({ const freshFacetId = getFreshFacetId(facets) if (freshFacetId) { freshProductvariables.facetValueIds = [freshFacetId] + const freshProductsPromise = commerce.getAllProducts({ + variables: freshProductvariables, + config, + preview, + }) + promisesWithKey.push({ key: 'freshProducts', promise: freshProductsPromise, keyResult: 'products' }) + } else { + props.freshProducts = [] } - const freshProductsPromise = commerce.getAllProducts({ - variables: freshProductvariables, - config, - preview, - }) - promisesWithKey.push({ key: 'freshProducts', promise: freshProductsPromise, keyResult: 'products' }) // featured products const allFeaturedFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_FEATURED) const allDiscountFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_DISCOUNT) const facetValueIdsForFeaturedProducts = [...allFeaturedFacetIds, ...allDiscountFacetIds] - const featuredProductsPromise = commerce.getAllProducts({ - variables: { - facetValueIds: facetValueIdsForFeaturedProducts - }, - config, - preview, - }) - promisesWithKey.push({ key: 'featuredProducts', promise: featuredProductsPromise, keyResult: 'products' }) + + if (facetValueIdsForFeaturedProducts.length > 0) { + const featuredProductsPromise = commerce.getAllProducts({ + variables: { + facetValueIds: facetValueIdsForFeaturedProducts + }, + config, + preview, + }) + promisesWithKey.push({ key: 'featuredProducts', promise: featuredProductsPromise, keyResult: 'products' }) + } else { + props.featuredProducts = [] + } // collection const collectionsPromise = commerce.getAllCollections({ From c154541948a5a1d647b4f7bc4c638618049a331f Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Tue, 5 Oct 2021 18:10:22 +0700 Subject: [PATCH 07/28] :sparkles: feat: (product list) all products, brands, collection, featured :%s --- .../vendure/api/operations/get-all-facets.ts | 9 +- pages/products.tsx | 85 +++++++++++++++++-- .../common/MenuNavigation/MenuNavigation.tsx | 12 +-- .../common/ProductList/ProductList.tsx | 5 +- .../ProductListFilter/ProductListFilter.tsx | 32 ++++--- src/utils/constanst.utils.ts | 1 + src/utils/types.utils.ts | 19 +++-- 7 files changed, 128 insertions(+), 35 deletions(-) diff --git a/framework/vendure/api/operations/get-all-facets.ts b/framework/vendure/api/operations/get-all-facets.ts index c4b002744..0bde04090 100644 --- a/framework/vendure/api/operations/get-all-facets.ts +++ b/framework/vendure/api/operations/get-all-facets.ts @@ -1,10 +1,10 @@ import { OperationContext } from '@commerce/api/operations' import { Facet } from '@commerce/types/facet' import { Provider, VendureConfig } from '../' -import { GetAllFacetsQuery } from '../../schema' +import { FacetFilterParameter, FacetSortParameter, GetAllFacetsQuery } from '../../schema' import { getAllFacetsQuery } from '../../utils/queries/get-all-facets-query' -export type FacetVariables = { first?: number } +export type FacetVariables = { first?: number, filter?: FacetFilterParameter, sort?: FacetSortParameter } export default function getAllFacetsOperation({ commerce, @@ -27,9 +27,10 @@ export default function getAllFacetsOperation({ } = {}): Promise<{ facets: Facet[] | any[] }> { const config = commerce.getConfig(cfg) const variables = { - input: { + options: { take: vars.first, - groupByFacet: true, + filter: vars.filter, + sort: vars.sort, }, } const { data } = await config.fetch(query, { diff --git a/pages/products.tsx b/pages/products.tsx index 4f9c4eb66..93dda5cdb 100644 --- a/pages/products.tsx +++ b/pages/products.tsx @@ -1,19 +1,94 @@ +import { ProductCard } from '@commerce/types/product'; +import { Collection, Facet } from '@framework/schema'; +import commerce from '@lib/api/commerce'; +import { GetStaticPropsContext } from 'next'; import { Layout } from 'src/components/common'; import { ViewedProducts } from 'src/components/modules/product-detail'; import ProductListFilter from 'src/components/modules/product-list/ProductListFilter/ProductListFilter'; -import RecipeListBanner from 'src/components/modules/recipes-list/RecipeListBanner/RecipeListBanner'; -import RecipesList from 'src/components/modules/recipes-list/RecipesList/RecipesList'; +import { CODE_FACET_BRAND, CODE_FACET_FEATURED, DEFAULT_PAGE_SIZE } from 'src/utils/constanst.utils'; +import { getAllPromies } from 'src/utils/funtion.utils'; +import { PromiseWithKey, SortOrder } from 'src/utils/types.utils'; import ProductListBanner from '../src/components/modules/product-list/ProductListBanner/ProductListBanner'; +interface Props { + facets: Facet[], + collections: Collection[], + products: ProductCard[], -export default function Products() { +} + +export default function Products({ facets, collections, products }: Props) { + // console.log("facets: ", products) return ( <> - - + + ) } +export async function getStaticProps({ + preview, + locale, + locales, +}: GetStaticPropsContext) { + const config = { locale, locales } + let promisesWithKey = [] as PromiseWithKey[] + let props = {} as any + + const facetsPromise = commerce.getAllFacets({ + variables: { + sort: { + code: SortOrder.Asc + }, + filter: { + code: { + in: [CODE_FACET_FEATURED, CODE_FACET_BRAND] + } + } + }, + config, + preview, + }) + + promisesWithKey.push({ key: 'facets', promise: facetsPromise, keyResult: 'facets' }) + + // collection + const collectionsPromise = commerce.getAllCollections({ + variables: {}, + config, + preview, + }) + promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' }) + + // products + const productsPromise = commerce.getAllProducts({ + variables: { + first: DEFAULT_PAGE_SIZE, + }, + config, + preview, + }) + promisesWithKey.push({ key: 'products', promise: productsPromise, keyResult: 'products' }) + + + try { + const promises = getAllPromies(promisesWithKey) + const rs = await Promise.all(promises) + + promisesWithKey.map((item, index) => { + props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] + return null + }) + + return { + props, + revalidate: 60, + } + } catch (err) { + + } +} + Products.Layout = Layout diff --git a/src/components/common/MenuNavigation/MenuNavigation.tsx b/src/components/common/MenuNavigation/MenuNavigation.tsx index 4a8943051..a2554451e 100644 --- a/src/components/common/MenuNavigation/MenuNavigation.tsx +++ b/src/components/common/MenuNavigation/MenuNavigation.tsx @@ -1,15 +1,17 @@ import classNames from 'classnames' import Link from 'next/link' import { useRouter } from 'next/router' +import { ROUTE } from 'src/utils/constanst.utils' import s from './MenuNavigation.module.scss' interface Props { children?: any, - heading:string, - categories:{name:string,link:string}[] + heading: string, + linkPrefix: string, + categories: { name: string, slug?: string, code?: string }[] } -const MenuNavigation = ({heading,categories}:Props)=> { +const MenuNavigation = ({ heading, linkPrefix, categories }: Props) => { const router = useRouter() return ( @@ -19,8 +21,8 @@ const MenuNavigation = ({heading,categories}:Props)=> { { categories.map(item =>
  • - - + + {item.name} diff --git a/src/components/common/ProductList/ProductList.tsx b/src/components/common/ProductList/ProductList.tsx index 7428e3a63..0c5deb91e 100644 --- a/src/components/common/ProductList/ProductList.tsx +++ b/src/components/common/ProductList/ProductList.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react' +import { DEFAULT_PAGE_SIZE } from 'src/utils/constanst.utils' import PaginationCommon from '../PaginationCommon/PaginationCommon' import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard' import s from "./ProductList.module.scss" @@ -15,13 +16,13 @@ const ProductList = ({data}: ProductListProps) => {
    { - data.slice(currentPage*20,(currentPage+1)*20).map((product,index)=>{ + data.slice(currentPage*DEFAULT_PAGE_SIZE,(currentPage+1)* DEFAULT_PAGE_SIZE).map((product,index)=>{ return }) }
    - +
    ) diff --git a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx index 5315283dc..736f3d239 100644 --- a/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx +++ b/src/components/modules/product-list/ProductListFilter/ProductListFilter.tsx @@ -1,12 +1,19 @@ +import { ProductCard } from '@commerce/types/product' +import { Collection, Facet } from '@framework/schema' import React from 'react' import { HeadingCommon, ProductList, SelectCommon } from 'src/components/common' import BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbCommon' import MenuNavigation from 'src/components/common/MenuNavigation/MenuNavigation' -import { BRAND, CATEGORY, FEATURED} from 'src/utils/constanst.utils' -import { PRODUCT_DATA_TEST_PAGE } from 'src/utils/demo-data' +import { QUERY_KEY, ROUTE } from 'src/utils/constanst.utils' +import { PRODUCT_DATA_TEST_PAGE } from 'src/utils/demo-data' import s from './ProductListFilter.module.scss' -interface ProductListFilterProps {} +interface ProductListFilterProps { + facets: Facet[] + collections: Collection[] + products: ProductCard[] + +} const BREADCRUMB = [ { @@ -29,11 +36,7 @@ const OPTIONSLECT = [ }, ] -const onModalClose = () => { - -} - -const ProductListFilter = (props: ProductListFilterProps) => { +const ProductListFilter = ({facets, collections, products}: ProductListFilterProps) => { return (
    @@ -41,9 +44,14 @@ const ProductListFilter = (props: ProductListFilterProps) => {
    - - - + + { + facets.map(item => ) + }
    @@ -56,7 +64,7 @@ const ProductListFilter = (props: ProductListFilterProps) => {
    - +
  • diff --git a/src/utils/constanst.utils.ts b/src/utils/constanst.utils.ts index 1db198178..432e9cc78 100644 --- a/src/utils/constanst.utils.ts +++ b/src/utils/constanst.utils.ts @@ -113,6 +113,7 @@ export const BRAND = [ export const CODE_FACET_FEATURED = 'featured' export const CODE_FACET_DISCOUNT = 'discount' +export const CODE_FACET_BRAND = 'brand' export const CODE_FACET_FEATURED_VARIANT = { FRESH: 'fresh', } diff --git a/src/utils/types.utils.ts b/src/utils/types.utils.ts index ca21b605f..59243b80f 100644 --- a/src/utils/types.utils.ts +++ b/src/utils/types.utils.ts @@ -34,18 +34,23 @@ export interface BlogProps { export interface CheckOutForm { name?: string - email?:string + email?: string address?: string - city?:string - state?:string - code?:number - phone?:number - method?:string - shipping_fee?:number + city?: string + state?: string + code?: number + phone?: number + method?: string + shipping_fee?: number } export type MouseAndTouchEvent = MouseEvent | TouchEvent +export enum SortOrder { + Asc = 'ASC', + Desc = 'DESC', +} + export type filterContextType = { visible: boolean; open: () => void; From ef7b490416b19aeace5baa00da68326f116da456 Mon Sep 17 00:00:00 2001 From: lytrankieio123 Date: Wed, 6 Oct 2021 11:14:10 +0700 Subject: [PATCH 08/28] :sparkles: feat: get collections, facets in menu filter :%s --- framework/vendure/schema.d.ts | 5 +- .../HeaderSubMenu/HeaderSubMenu.tsx | 2 +- .../Layout/LayoutContent/LayoutContent.tsx | 9 +- .../common/MenuFilter/MenuFilter.module.scss | 4 +- .../common/MenuFilter/MenuFilter.tsx | 36 +++---- .../MenuFilterItem/MenuFilterItem.module.scss | 16 +++ .../MenuFilterItem/MenuFilterItem.tsx | 28 +++++ .../MenuNavigationProductList.module.scss | 18 +--- .../MenuNavigationProductList.tsx | 100 +++++++++++------- .../MenuSort/MenuSort.module.scss | 7 +- .../hooks/collection/useGetAllCollection.tsx | 4 +- src/components/hooks/facets/useFacets.tsx | 2 +- 12 files changed, 136 insertions(+), 95 deletions(-) create mode 100644 src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.module.scss create mode 100644 src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index ef667d9f3..5faa87b43 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -3228,8 +3228,9 @@ export type GetAllFacetsQuery = { __typename?: 'Query' } & { items: Array< { __typename?: 'Facet' } & Pick< Facet, - 'id' | 'name' | 'code' - > & { + 'id' | 'name' | 'code' | 'values' + > + & { parent?: Maybe<{ __typename?: 'Facet' } & Pick> children?: Maybe< Array<{ __typename?: 'Facet' } & Pick> diff --git a/src/components/common/Header/components/HeaderSubMenu/HeaderSubMenu.tsx b/src/components/common/Header/components/HeaderSubMenu/HeaderSubMenu.tsx index 2cd72e5f2..b119a1c8a 100644 --- a/src/components/common/Header/components/HeaderSubMenu/HeaderSubMenu.tsx +++ b/src/components/common/Header/components/HeaderSubMenu/HeaderSubMenu.tsx @@ -42,7 +42,7 @@ const HeaderSubMenu = memo(() => {
      {/* todo: handle active item */}
    • - Categories + Categories
    • { MENU.map(item =>
    • = ({ children }) => { - const { pathname } = useRouter() - const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false }) const router = useRouter() + const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: true }) const {messages, removeMessage} = useMessage() const toggleFilter = () => { @@ -30,6 +29,7 @@ const LayoutContent: FC = ({ children }) => { return ( <>
      + {router.pathname}
      { router.pathname === ROUTE.ACCOUNT ? @@ -38,10 +38,9 @@ const LayoutContent: FC = ({ children }) => {
    :
    {children}
    } -
    { - FILTER_PAGE.includes(pathname) && (
    ) + FILTER_PAGE.includes(router.pathname) && (
    ) }