mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 12:24:18 +00:00
bug: fix-conflict
This commit is contained in:
@@ -47,6 +47,8 @@ export type Product = {
|
||||
currencyCode: CurrencyCode
|
||||
options: ProductOption[]
|
||||
facetValueIds?: string[]
|
||||
collectionIds?: string[]
|
||||
collection?: string,
|
||||
}
|
||||
|
||||
export type ProductCard = {
|
||||
|
@@ -2,7 +2,7 @@ import { Product } from '@commerce/types/product'
|
||||
import { OperationContext } from '@commerce/api/operations'
|
||||
import { Provider, VendureConfig } from '../'
|
||||
import { GetProductQuery } from '../../schema'
|
||||
import { getProductQuery } from '../../utils/queries/get-product-query'
|
||||
import { getProductQuery, getProductDetailQuery } from '../../utils/queries/get-product-query'
|
||||
|
||||
export default function getProductOperation({
|
||||
commerce,
|
||||
@@ -53,7 +53,8 @@ export default function getProductOperation({
|
||||
displayName: og.name,
|
||||
values: og.options.map((o) => ({ label: o.name })),
|
||||
})),
|
||||
facetValueIds: product.facetValues.map(item=> item.id)
|
||||
facetValueIds: product.facetValues.map(item=> item.id),
|
||||
collectionIds: product.collections.map(item => item.id)
|
||||
} as Product
|
||||
}
|
||||
|
||||
|
57
framework/vendure/schema.d.ts
vendored
57
framework/vendure/schema.d.ts
vendored
@@ -1,3 +1,6 @@
|
||||
import { FacetValue, UpdateAddressInput } from './schema.d';
|
||||
import { ResetPassword } from './schema.d';
|
||||
import { requestPasswordReset } from '@framework/schema';
|
||||
import { FacetValue } from './schema.d';
|
||||
export type Maybe<T> = T | null
|
||||
export type Exact<T extends { [key: string]: unknown }> = {
|
||||
@@ -304,6 +307,11 @@ export type MutationResetPasswordArgs = {
|
||||
}
|
||||
|
||||
export type Address = Node & {
|
||||
updateCustomerAddress:
|
||||
| {
|
||||
__typename?: 'Address'
|
||||
id: Scalars['ID']
|
||||
}
|
||||
__typename?: 'Address'
|
||||
id: Scalars['ID']
|
||||
createdAt: Scalars['DateTime']
|
||||
@@ -322,6 +330,9 @@ export type Address = Node & {
|
||||
customFields?: Maybe<Scalars['JSON']>
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export type Asset = Node & {
|
||||
__typename?: 'Asset'
|
||||
id: Scalars['ID']
|
||||
@@ -1459,6 +1470,11 @@ export type CustomerListOptions = {
|
||||
}
|
||||
|
||||
export type Customer = Node & {
|
||||
updateCustomer:
|
||||
| {
|
||||
__typename?: 'Customer'
|
||||
id: Scalars['ID']
|
||||
}
|
||||
__typename?: 'Customer'
|
||||
id: Scalars['ID']
|
||||
createdAt: Scalars['DateTime']
|
||||
@@ -1466,7 +1482,7 @@ export type Customer = Node & {
|
||||
title?: Maybe<Scalars['String']>
|
||||
firstName: Scalars['String']
|
||||
lastName: Scalars['String']
|
||||
phoneNumber?: Maybe<Scalars['String']>
|
||||
phoneNumber?: Maybe<Scalars['String']>
|
||||
emailAddress: Scalars['String']
|
||||
addresses?: Maybe<Array<Address>>
|
||||
orders: OrderList
|
||||
@@ -3126,6 +3142,36 @@ export type LoginMutation = { __typename?: 'Mutation' } & {
|
||||
>)
|
||||
}
|
||||
|
||||
export type ResetPasswordMutation = { __typename?: 'Mutation' } & {
|
||||
resetPassword:
|
||||
| ({ __typename: 'CurrentUser' } & Pick<CurrentUser, 'id'>)
|
||||
| ({ __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<Success, 'success'>)
|
||||
| ({ __typename: 'MissingPasswordError' } & Pick<
|
||||
MissingPasswordError,
|
||||
'errorCode' | 'message'
|
||||
>)
|
||||
| ({ __typename: 'NativeAuthStrategyError' } & Pick<
|
||||
NativeAuthStrategyError,
|
||||
'errorCode' | 'message'
|
||||
>)
|
||||
}
|
||||
|
||||
export type VerifyCustomerAccountVariables = Exact<{
|
||||
token: Scalars['String']
|
||||
password?: Maybe<Scalars['String']>
|
||||
@@ -3179,8 +3225,9 @@ export type SignupMutationVariables = Exact<{
|
||||
input: RegisterCustomerInput
|
||||
}>
|
||||
|
||||
export type SignupMutation = { __typename?: 'Mutation' } & {
|
||||
registerCustomerAccount:
|
||||
|
||||
export type RequestPasswordReset = { __typename?: 'Mutation' } & {
|
||||
requestPasswordReset:
|
||||
| ({ __typename: 'Success' } & Pick<Success, 'success'>)
|
||||
| ({ __typename: 'MissingPasswordError' } & Pick<
|
||||
MissingPasswordError,
|
||||
@@ -3192,6 +3239,8 @@ export type SignupMutation = { __typename?: 'Mutation' } & {
|
||||
>)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type ActiveCustomerQueryVariables = Exact<{ [key: string]: never }>
|
||||
|
||||
export type ActiveCustomerQuery = { __typename?: 'Query' } & {
|
||||
@@ -3332,7 +3381,7 @@ export type GetProductQuery = { __typename?: 'Query' } & {
|
||||
variants: Array<
|
||||
{ __typename?: 'ProductVariant' } & Pick<
|
||||
ProductVariant,
|
||||
'id' | 'priceWithTax' | 'currencyCode'
|
||||
'id' | 'priceWithTax' | 'currencyCode' | 'price'
|
||||
> & {
|
||||
options: Array<
|
||||
{ __typename?: 'ProductOption' } & Pick<
|
||||
|
@@ -0,0 +1,14 @@
|
||||
export const requestPasswordReset = /* GraphQL */ `
|
||||
mutation RequestPasswordReset($emailAddress: String!) {
|
||||
requestPasswordReset(emailAddress: $emailAddress) {
|
||||
__typename
|
||||
...on Success{
|
||||
success
|
||||
}
|
||||
...on ErrorResult{
|
||||
errorCode
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
15
framework/vendure/utils/mutations/reset-password-mutation.ts
Normal file
15
framework/vendure/utils/mutations/reset-password-mutation.ts
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@@ -0,0 +1,14 @@
|
||||
export const updateCustomerAddress = /* GraphQL */ `
|
||||
mutation updateCustomerAddress($input: UpdateAddressInput!){
|
||||
updateCustomerAddress(input: $input){
|
||||
__typename
|
||||
...on Address{
|
||||
id
|
||||
streetLine1
|
||||
city
|
||||
postalCode
|
||||
province
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@@ -0,0 +1,13 @@
|
||||
export const updateCustomer = /* GraphQL */ `
|
||||
mutation updateCustomer($input: UpdateCustomerInput!){
|
||||
updateCustomer(input:$input){
|
||||
__typename
|
||||
...on Customer{
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
phoneNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@@ -39,6 +39,25 @@ export const getProductQuery = /* GraphQL */ `
|
||||
facetValues {
|
||||
id
|
||||
}
|
||||
collections {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
export const getProductDetailQuery = /* GraphQL */ `
|
||||
query GetProductDetail($slug: String! = "hand-trowel") {
|
||||
product(slug: $slug) {
|
||||
name
|
||||
description
|
||||
variants {
|
||||
price
|
||||
priceWithTax
|
||||
}
|
||||
assets {
|
||||
preview
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
16
framework/vendure/utils/queries/user-info-query.ts
Normal file
16
framework/vendure/utils/queries/user-info-query.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export const userInfoQuery = /* GraphQL */ `
|
||||
query activeCustomer{
|
||||
activeCustomer{
|
||||
lastName
|
||||
firstName
|
||||
emailAddress
|
||||
phoneNumber
|
||||
addresses{
|
||||
streetLine1
|
||||
city
|
||||
province
|
||||
postalCode
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@@ -26,6 +26,7 @@
|
||||
"body-scroll-lock": "^3.1.5",
|
||||
"classnames": "^2.3.1",
|
||||
"cookie": "^0.4.1",
|
||||
"dns": "^0.2.2",
|
||||
"email-validator": "^2.0.4",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-next": "^11.1.2",
|
||||
@@ -35,6 +36,7 @@
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.random": "^3.2.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"net": "^1.0.2",
|
||||
"next": "^11.0.0",
|
||||
"next-seo": "^4.26.0",
|
||||
"next-themes": "^0.0.14",
|
||||
|
10
pages/forgot-password.tsx
Normal file
10
pages/forgot-password.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { FormForgot, Layout } from 'src/components/common'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div>
|
||||
<FormForgot/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
NotFound.Layout = Layout
|
@@ -12,7 +12,7 @@ import { PromiseWithKey } from 'src/utils/types.utils'
|
||||
export default function Slug({ product, relevantProducts, collections }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||
|
||||
return <>
|
||||
<ProductInfoDetail />
|
||||
<ProductInfoDetail productDetail={product} collections={collections}/>
|
||||
<RecipeDetail ingredients={INGREDIENT_DATA_TEST} />
|
||||
<RecommendedRecipes data={RECIPE_DATA_TEST} />
|
||||
<ReleventProducts data={relevantProducts} collections={collections}/>
|
||||
|
10
pages/reset-password.tsx
Normal file
10
pages/reset-password.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { FormResetPassword, Layout } from 'src/components/common'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div>
|
||||
<FormResetPassword/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
NotFound.Layout = Layout
|
@@ -1,11 +1,12 @@
|
||||
import commerce from '@lib/api/commerce';
|
||||
import { GetStaticPropsContext } from 'next';
|
||||
import { ProductCard } from '@commerce/types/product';
|
||||
import { Layout, ListProductCardSkeleton } from 'src/components/common';
|
||||
|
||||
interface Props {
|
||||
products: any
|
||||
productDetail: ProductCard[],
|
||||
}
|
||||
export default function Home({ products }: Props) {
|
||||
export default function Home({ productDetail }: Props) {
|
||||
return (
|
||||
<>
|
||||
{/* <ListProductCardSkeleton /> */}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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<CustomInputCommon>(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 (
|
||||
<section className={s.formAuthen}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<div className={s.title}>Forgot Password</div>
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
}}
|
||||
validationSchema={DisplayingErrorMessagesSchema}
|
||||
onSubmit={onForgot}
|
||||
>
|
||||
{({ 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}
|
||||
onEnter={isValid ? submitForm : undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.bottom}>
|
||||
<div className={styles.remembered} onClick={openModalAuthen}>
|
||||
I Remembered My Password?
|
||||
</div>
|
||||
<ButtonCommon HTMLType='submit' size="large">
|
||||
Reset Password
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
<ModalAuthenticate visible={visibleModalAuthen} closeModal={closeModalAuthen} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default FormForgot;
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -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 (
|
||||
<section className={s.formAuthen}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<div className={s.title}>Reset Password</div>
|
||||
<Formik
|
||||
initialValues={{
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
}}
|
||||
validationSchema={DisplayingErrorMessagesSchema}
|
||||
onSubmit={onReset}
|
||||
>
|
||||
{({ errors, touched, isValid, submitForm }) => (
|
||||
<Form className="u-form">
|
||||
<div>
|
||||
<InputPasswordFiledInForm
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
error={
|
||||
touched.password && errors.password
|
||||
? errors.password.toString()
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={s.confirmPassword}>
|
||||
<InputPasswordFiledInForm
|
||||
name="confirmPassword"
|
||||
placeholder="Password confirm"
|
||||
error={
|
||||
touched.confirmPassword && errors.confirmPassword
|
||||
? errors.confirmPassword.toString()
|
||||
: ''
|
||||
}
|
||||
onEnter={isValid ? submitForm : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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 className={styles.bottom}>
|
||||
<ButtonCommon HTMLType='submit' size="large">
|
||||
Change Password
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default FormResetPassword;
|
@@ -58,6 +58,10 @@ const HeaderMenu = memo(
|
||||
onClick: openModalRegister,
|
||||
name: 'Create account',
|
||||
},
|
||||
{
|
||||
link: '/forgot-password',
|
||||
name: 'Forgot Password',
|
||||
},
|
||||
],
|
||||
[openModalLogin, openModalRegister]
|
||||
)
|
||||
|
@@ -11,7 +11,7 @@
|
||||
width: 20.6rem;
|
||||
.selectTrigger {
|
||||
width: 20.6rem;
|
||||
padding: 1.2rem 1.6rem;
|
||||
padding: 1.6rem;
|
||||
}
|
||||
}
|
||||
&.large {
|
||||
|
@@ -5,6 +5,8 @@ import s from './SelectCommon.module.scss'
|
||||
import SelectOption from './SelectOption/SelectOption'
|
||||
|
||||
interface Props {
|
||||
selected?:string|null,
|
||||
initValue?:string|null,
|
||||
placeholder? : string,
|
||||
value?: string,
|
||||
size?: 'base' | 'large',
|
||||
@@ -13,16 +15,16 @@ interface Props {
|
||||
onChange?: (value: string) => void,
|
||||
}
|
||||
|
||||
const SelectCommon = ({ value, type = 'default', size = 'base', options, placeholder, onChange}: Props) => {
|
||||
const [selectedName, setSelectedName] = useState<string>()
|
||||
const [selectedValue, setSelectedValue] = useState<string>('')
|
||||
const SelectCommon = ({selected,initValue, type = 'default', size = 'base', options, placeholder, onChange}: Props) => {
|
||||
const [selectedName, setSelectedName] = useState(placeholder)
|
||||
const [selectedValue, setSelectedValue] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedValue(value || '')
|
||||
|
||||
const name = options.find(item => item.value === value)?.name
|
||||
setSelectedName(name)
|
||||
}, [value, options])
|
||||
useEffect(()=>{
|
||||
const nameSelect = options.find((val)=>val.value === selected);
|
||||
setSelectedName(nameSelect?.name ?? 'State');
|
||||
setSelectedValue(initValue ?? '');
|
||||
onChange && onChange(initValue ?? '');
|
||||
},[])
|
||||
|
||||
const changeSelectedName = (value: string) => {
|
||||
setSelectedValue(value)
|
||||
|
@@ -51,6 +51,8 @@ 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'
|
||||
export { default as ProductCardSkeleton} from './ProductCardSkeleton/ProductCardSkeleton'
|
||||
export { default as ListProductCardSkeleton} from './ListProductCardSkeleton/ListProductCardSkeleton'
|
||||
|
||||
|
55
src/components/hooks/account/useEditCustomerAddress.tsx
Normal file
55
src/components/hooks/account/useEditCustomerAddress.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { Address } from '@framework/schema'
|
||||
import { updateCustomerAddress } from '@framework/utils/mutations/update-customer-address-mutation'
|
||||
import { useState } from 'react'
|
||||
import fetcher from 'src/utils/fetcher'
|
||||
import { useActiveCustomer } from '../auth'
|
||||
|
||||
interface Props {
|
||||
address?:string,
|
||||
city?:string|null,
|
||||
postalCode?:string|null,
|
||||
state?:string
|
||||
}
|
||||
|
||||
const useEditCustomerAddress = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
const {customer,mutate} = useActiveCustomer();
|
||||
|
||||
const editCustomerAddress = (
|
||||
{ address,city,postalCode,state}: Props,
|
||||
fCallBack: (isSuccess: boolean, message?: string) => void
|
||||
) => {
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
|
||||
fetcher<Address>({
|
||||
query: updateCustomerAddress,
|
||||
variables: {
|
||||
input: {
|
||||
id:customer?.id,
|
||||
streetLine1:address,
|
||||
city,
|
||||
postalCode,
|
||||
province:state
|
||||
},
|
||||
},
|
||||
}) .then((data) => {
|
||||
|
||||
if(data.updateCustomerAddress.__typename == 'Address'){
|
||||
mutate();
|
||||
fCallBack(true)
|
||||
return data
|
||||
}
|
||||
|
||||
}) .catch((error) => {
|
||||
setError(error)
|
||||
fCallBack(false, error.message)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
|
||||
}
|
||||
return { loading, editCustomerAddress, error }
|
||||
}
|
||||
|
||||
export default useEditCustomerAddress
|
51
src/components/hooks/account/useEditUserInfo.tsx
Normal file
51
src/components/hooks/account/useEditUserInfo.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useState } from 'react'
|
||||
import { Customer } from '@framework/schema'
|
||||
import fetcher from 'src/utils/fetcher'
|
||||
import { updateCustomer } from '@framework/utils/mutations/update-customer-mutation'
|
||||
import { useActiveCustomer } from '../auth'
|
||||
|
||||
interface Props {
|
||||
firstName?: string;
|
||||
lastName?: string,
|
||||
phoneNumber?:string,
|
||||
}
|
||||
|
||||
const useEditUserInfo = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
const {mutate} = useActiveCustomer();
|
||||
|
||||
const editUserInfo = (
|
||||
{ firstName,lastName,phoneNumber}: Props,
|
||||
fCallBack: (isSuccess: boolean, message?: string) => void
|
||||
) => {
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
|
||||
fetcher<Customer>({
|
||||
query: updateCustomer,
|
||||
variables: {
|
||||
input: {
|
||||
firstName,
|
||||
lastName,
|
||||
phoneNumber
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.updateCustomer.__typename == 'Customer') {
|
||||
mutate();
|
||||
return data
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error)
|
||||
fCallBack(false, error.message)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}
|
||||
|
||||
return { loading, editUserInfo, error }
|
||||
}
|
||||
|
||||
export default useEditUserInfo
|
@@ -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'
|
||||
|
||||
|
50
src/components/hooks/auth/useRequestPasswordReset.tsx
Normal file
50
src/components/hooks/auth/useRequestPasswordReset.tsx
Normal file
@@ -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<Error | null>(null)
|
||||
// const { mutate } = useActiveCustomer()
|
||||
|
||||
const requestPassword = (
|
||||
{email}: ForgotPassword,
|
||||
fCallBack: (isSuccess: boolean, message?: string) => void
|
||||
) => {
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
fetcher<RequestPasswordReset>({
|
||||
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
|
52
src/components/hooks/auth/useResetPassword.tsx
Normal file
52
src/components/hooks/auth/useResetPassword.tsx
Normal file
@@ -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<Error | null>(null)
|
||||
// const { mutate } = useActiveCustomer()
|
||||
|
||||
const resetPassword = (
|
||||
{token,password}: Props,
|
||||
fCallBack: (isSuccess: boolean, message?: string) => void
|
||||
) => {
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
fetcher<ResetPasswordMutation>({
|
||||
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
|
@@ -4,7 +4,6 @@ import { SignupMutation } from '@framework/schema'
|
||||
import fetcher from 'src/utils/fetcher'
|
||||
import { CommonError } from 'src/domains/interfaces/CommonError'
|
||||
import { signupMutation } from '@framework/utils/mutations/sign-up-mutation'
|
||||
|
||||
interface SignupInput {
|
||||
email: string
|
||||
firstName?: string
|
||||
|
@@ -1 +1,4 @@
|
||||
export { default as useModalCommon } from './useModalCommon'
|
||||
export { default as useEditUserInfo } from './account/useEditUserInfo'
|
||||
export { default as useEditCustomerAddress } from './account/useEditCustomerAddress'
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
export { default as useSearchProducts } from './useSearchProducts'
|
||||
export { default as useToggleProductWishlist } from './useToggleProductWishlist'
|
||||
export { default as useProductDetail } from './useProductDetail'
|
||||
|
||||
|
||||
|
16
src/components/hooks/product/useProductDetail.tsx
Normal file
16
src/components/hooks/product/useProductDetail.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { GetProductQuery } from '@framework/schema'
|
||||
import { getProductDetailQuery } from '@framework/utils/queries/get-product-query';
|
||||
import gglFetcher from 'src/utils/gglFetcher'
|
||||
import useSWR from 'swr'
|
||||
|
||||
|
||||
interface ProductDetail {
|
||||
slug: string
|
||||
}
|
||||
|
||||
const useProductDetail = () => {
|
||||
const { data, ...rest } = useSWR<GetProductQuery>([getProductDetailQuery],gglFetcher)
|
||||
return { productDetail: data?.product, ...rest }
|
||||
}
|
||||
|
||||
export default useProductDetail
|
@@ -15,7 +15,6 @@ import EditInfoModal from './components/EditInfoModal/EditInfoModal'
|
||||
import FavouriteProducts from "./components/FavouriteProducts/FavouriteProducts"
|
||||
import OrderInfomation from './components/OrderInformation/OrderInformation'
|
||||
|
||||
|
||||
const waiting = [
|
||||
{
|
||||
id: "NO 123456",
|
||||
@@ -55,16 +54,6 @@ const delivered = [
|
||||
}
|
||||
]
|
||||
|
||||
let account = {
|
||||
name: "vu duong",
|
||||
email: "vuduong@gmail.com",
|
||||
address: "234 Dien Bien Phu Bis, Dakao ward",
|
||||
state: "District 1",
|
||||
city: "HCMC",
|
||||
postalCode: "700000",
|
||||
phoneNumber: "(+84) 937 937 195"
|
||||
}
|
||||
|
||||
interface AccountPageProps {
|
||||
defaultActiveContent?: "info" | "orders" | "favorites"
|
||||
}
|
||||
@@ -90,6 +79,10 @@ const DEFAULT_FAVORITE_ARGS = {
|
||||
|
||||
const AccountPage = ({ defaultActiveContent="orders" } : AccountPageProps) => {
|
||||
const router = useRouter()
|
||||
|
||||
const {userInfo} = useActiveCustomer();
|
||||
|
||||
|
||||
const [activeTab, setActiveTab] = useState(defaultActiveContent==="info" ? 0 : defaultActiveContent==="orders" ? 1 : 2)
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [optionQueryFavorite, setoptionQueryFavorite] = useState<QueryFavorite>(DEFAULT_FAVORITE_ARGS)
|
||||
@@ -129,7 +122,7 @@ const AccountPage = ({ defaultActiveContent="orders" } : AccountPageProps) => {
|
||||
|
||||
<AccountNavigation defaultActiveIndex={activeTab}>
|
||||
<TabPane tabName="Customer Information">
|
||||
<AccountInfomation account={account} onClick={showModal} />
|
||||
<AccountInfomation account={userInfo} onClick={showModal} />
|
||||
</TabPane>
|
||||
<TabPane tabName="Your Orders">
|
||||
<OrderInfomation waiting={waiting} delivering={delivering} delivered={delivered} />
|
||||
@@ -139,7 +132,7 @@ const AccountPage = ({ defaultActiveContent="orders" } : AccountPageProps) => {
|
||||
</TabPane>
|
||||
</AccountNavigation>
|
||||
</section>
|
||||
<EditInfoModal accountInfo={account} closeModal={closeModal} visible={modalVisible} />
|
||||
<EditInfoModal accountInfo={userInfo} closeModal={closeModal} visible={modalVisible} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@@ -6,17 +6,21 @@ import avatar from '../../assets/avatar.png'
|
||||
|
||||
import { ButtonCommon } from 'src/components/common'
|
||||
import { useActiveCustomer } from 'src/components/hooks/auth'
|
||||
import { Address } from '@framework/schema'
|
||||
|
||||
interface AccountProps {
|
||||
name: string
|
||||
email: string
|
||||
address: string
|
||||
state: string
|
||||
city: string
|
||||
postalCode: string
|
||||
phoneNumber: string
|
||||
export interface AccountProps {
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
email?: string
|
||||
phoneNumber?:string|null
|
||||
address?: Address
|
||||
}
|
||||
|
||||
const states = [
|
||||
{name: "District 1", value: "D1"},
|
||||
{name: "District 2", value: "D2"},
|
||||
{name: "District 3", value: "D3"}
|
||||
]
|
||||
interface AccountInfomationProps {
|
||||
account: AccountProps
|
||||
onClick: () => void
|
||||
@@ -24,11 +28,10 @@ interface AccountInfomationProps {
|
||||
|
||||
const AccountInfomation = ({ account, onClick }: AccountInfomationProps) => {
|
||||
const { customer } = useActiveCustomer()
|
||||
|
||||
// need to handle call back when edit account information
|
||||
|
||||
const showEditForm = () => onClick()
|
||||
|
||||
const state = states.find((val)=>val.value == account.address?.province);
|
||||
return (
|
||||
<section className={s.accountInfomation}>
|
||||
<div className={s.avatar}>
|
||||
@@ -45,8 +48,8 @@ const AccountInfomation = ({ account, onClick }: AccountInfomationProps) => {
|
||||
<div className={s.shippingInfo}>Shipping Infomation</div>
|
||||
|
||||
<div className={s.accountAddress}>
|
||||
{account.address +
|
||||
`, ${account.state}, ${account.city}, ${account.postalCode}`}
|
||||
{account.address?.streetLine1 +
|
||||
`, ${state?.name}, ${account.address?.city}, ${account.address?.postalCode}`}
|
||||
</div>
|
||||
|
||||
<div className={s.accountPhoneNumber}>{account.phoneNumber}</div>
|
||||
|
@@ -1,6 +1,15 @@
|
||||
@import '../../../../../../styles/utilities';
|
||||
|
||||
.editInfoModal {
|
||||
.u-form{
|
||||
width: 60rem;
|
||||
}
|
||||
.inputName{
|
||||
@apply flex justify-between;
|
||||
.input{
|
||||
width: 48.5%;
|
||||
}
|
||||
}
|
||||
.input {
|
||||
@apply bg-white;
|
||||
margin-bottom: 1.6rem;
|
||||
@@ -23,6 +32,7 @@
|
||||
.inputPostalCode {
|
||||
@apply bg-white;
|
||||
margin-left: 0.8rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.inputPhoneNumber {
|
||||
|
@@ -1,19 +1,34 @@
|
||||
import React from "react"
|
||||
import React, { useState } from "react"
|
||||
import s from './EditInfoModal.module.scss'
|
||||
|
||||
import { ModalCommon, Inputcommon, SelectCommon, ButtonCommon } from '../../../../../common'
|
||||
|
||||
import { ModalCommon, SelectCommon, ButtonCommon } from '../../../../../common'
|
||||
import { Address } from "@framework/schema";
|
||||
import {
|
||||
InputFiledInForm,
|
||||
} from 'src/components/common'
|
||||
import * as Yup from 'yup'
|
||||
import { Form, Formik } from 'formik'
|
||||
import { useEditCustomerAddress, useEditUserInfo } from "src/components/hooks";
|
||||
import { LANGUAGE } from 'src/utils/language.utils'
|
||||
import { useMessage } from 'src/components/contexts'
|
||||
interface EditInfoModalProps {
|
||||
accountInfo: {name: string, email: string, address: string, state: string, city: string, postalCode: string, phoneNumber: string};
|
||||
accountInfo: {
|
||||
firstName?: string
|
||||
lastName?: string
|
||||
email?: string
|
||||
phoneNumber?:string|null
|
||||
address?: Address
|
||||
};
|
||||
visible: boolean;
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoModalProps) => {
|
||||
const [stateValue,setStateValue] = useState('');
|
||||
const { loading, editUserInfo } = useEditUserInfo();
|
||||
const {editCustomerAddress} = useEditCustomerAddress();
|
||||
const { showMessageSuccess, showMessageError } = useMessage()
|
||||
|
||||
function saveInfo() {
|
||||
closeModal();
|
||||
}
|
||||
|
||||
const states = [
|
||||
{name: "District 1", value: "D1"},
|
||||
@@ -21,44 +36,165 @@ const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoMod
|
||||
{name: "District 3", value: "D3"}
|
||||
]
|
||||
|
||||
const DisplayingErrorMessagesSchema = Yup.object().shape({
|
||||
firstName: Yup.string().required('Required'),
|
||||
lastName: Yup.string().required('Required'),
|
||||
address: Yup.string().required('Required'),
|
||||
city: Yup.string().required('Required'),
|
||||
postalCode: Yup.string(),
|
||||
phoneNumber: Yup.string(),
|
||||
})
|
||||
|
||||
function onEditUserInfo (
|
||||
values: {
|
||||
firstName: string|undefined;
|
||||
lastName: string|undefined,
|
||||
address:string|undefined,
|
||||
city?:string|null,
|
||||
postalCode?:string|null,
|
||||
phoneNumber?:string|null
|
||||
}) {
|
||||
|
||||
editUserInfo(
|
||||
{
|
||||
firstName: values.firstName,
|
||||
lastName: values.lastName,
|
||||
phoneNumber:values.phoneNumber ?? '',
|
||||
},onChangUserInfoCallBack);
|
||||
|
||||
editCustomerAddress(
|
||||
{
|
||||
address: values.address ,
|
||||
city:values.city,
|
||||
postalCode:values.postalCode,
|
||||
state:stateValue
|
||||
},
|
||||
onChangUserInfoCallBack);
|
||||
}
|
||||
|
||||
function onChangUserInfoCallBack(isSuccess: boolean, message?: string){
|
||||
if (isSuccess) {
|
||||
closeModal();
|
||||
showMessageSuccess("Change Your Information Successfully.", 15000)
|
||||
} else {
|
||||
showMessageError(LANGUAGE.MESSAGE.ERROR)
|
||||
}
|
||||
}
|
||||
function state(state:string){
|
||||
setStateValue(state);
|
||||
}
|
||||
return (
|
||||
<ModalCommon onClose={closeModal} visible={visible} title="Edit Infomation">
|
||||
<section className={s.editInfoModal}>
|
||||
<div className={s.input}>
|
||||
<Inputcommon placeholder="Name" value={accountInfo.name} type="text" />
|
||||
</div>
|
||||
|
||||
<div className={s.inputDisable}>
|
||||
<Inputcommon placeholder="Email" value={accountInfo.email} type="email" />
|
||||
</div>
|
||||
|
||||
<div className={s.input}>
|
||||
<Inputcommon placeholder="Address" value={accountInfo.address} type="text" />
|
||||
</div>
|
||||
|
||||
<div className={s.input}>
|
||||
<Inputcommon placeholder="City" value={accountInfo.city} type="text" />
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex">
|
||||
<div className={s.inputState}>
|
||||
<SelectCommon type="custom" placeholder="State" options={states} />
|
||||
<Formik
|
||||
initialValues={
|
||||
{
|
||||
firstName:accountInfo.firstName,
|
||||
lastName: accountInfo.lastName,
|
||||
address:accountInfo.address?.streetLine1,
|
||||
city: accountInfo.address?.city,
|
||||
postalCode: accountInfo.address?.postalCode,
|
||||
phoneNumber:accountInfo.phoneNumber
|
||||
}}
|
||||
validationSchema={DisplayingErrorMessagesSchema}
|
||||
onSubmit={onEditUserInfo}
|
||||
>
|
||||
{({ errors, touched, isValid, submitForm }) => (
|
||||
<Form className="u-form">
|
||||
<div className={s.inputName}>
|
||||
<div className={s.input}>
|
||||
<InputFiledInForm
|
||||
name="firstName"
|
||||
placeholder="First Name"
|
||||
error={
|
||||
touched.firstName && errors.firstName
|
||||
? errors.firstName.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.firstName && !errors.firstName}
|
||||
/>
|
||||
</div>
|
||||
<div className={s.input}>
|
||||
<InputFiledInForm
|
||||
name="lastName"
|
||||
placeholder="Last Name"
|
||||
error={
|
||||
touched.lastName && errors.lastName
|
||||
? errors.lastName.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.lastName && !errors.lastName}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={s.inputPostalCode}>
|
||||
<Inputcommon placeholder="Postal code" value={accountInfo.postalCode} type="text" />
|
||||
<div className={s.input}>
|
||||
<InputFiledInForm
|
||||
name="address"
|
||||
placeholder="Address"
|
||||
error={
|
||||
touched.address && errors.address
|
||||
? errors.address.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.address && !errors.address}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={s.input}>
|
||||
<InputFiledInForm
|
||||
name="city"
|
||||
placeholder="City"
|
||||
error={
|
||||
touched.city && errors.city
|
||||
? errors.city.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.city && !errors.city}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div className={s.inputPhoneNumber}>
|
||||
<Inputcommon placeholder="Phone number" value={accountInfo.phoneNumber} type="text" />
|
||||
</div>
|
||||
<div className="flex">
|
||||
<div className={s.inputState}>
|
||||
<SelectCommon initValue={accountInfo.address?.province} selected={accountInfo.address?.province} type="custom" onChange={state} placeholder="State" options={states} size="large"/>
|
||||
</div>
|
||||
|
||||
<div className={s.buttons}>
|
||||
<ButtonCommon onClick={closeModal} type="light" size="large" >Cancel</ButtonCommon>
|
||||
<ButtonCommon onClick={saveInfo} size="large" >Save</ButtonCommon>
|
||||
</div>
|
||||
<div className={s.inputPostalCode}>
|
||||
<InputFiledInForm
|
||||
name="postalCode"
|
||||
placeholder="Postal code"
|
||||
error={
|
||||
touched.postalCode && errors.postalCode
|
||||
? errors.postalCode.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.postalCode && !errors.postalCode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={s.inputPhoneNumber}>
|
||||
<InputFiledInForm
|
||||
name="phoneNumber"
|
||||
placeholder="Phone number"
|
||||
error={
|
||||
touched.phoneNumber && errors.phoneNumber
|
||||
? errors.phoneNumber.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.phoneNumber && !errors.phoneNumber}
|
||||
onEnter={isValid ? submitForm : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={s.buttons}>
|
||||
<ButtonCommon onClick={closeModal} type="light" size="large" >Cancel</ButtonCommon>
|
||||
<ButtonCommon HTMLType="submit" loading={loading} size="large" >Save</ButtonCommon>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</section>
|
||||
</ModalCommon>
|
||||
)
|
||||
|
@@ -1,20 +1,28 @@
|
||||
import React from 'react'
|
||||
import React, { useMemo } from 'react';
|
||||
import ProductImgs from './components/ProductImgs/ProductImgs'
|
||||
import ProductInfo from './components/ProductInfo/ProductInfo'
|
||||
import s from './ProductInfoDetail.module.scss'
|
||||
import { Product } from '@commerce/types/product'
|
||||
import { Collection } from '@framework/schema'
|
||||
import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils';
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any
|
||||
productDetail: Product,
|
||||
collections: Collection[]
|
||||
}
|
||||
|
||||
const ProductInfoDetail = ({ }: Props) => {
|
||||
const ProductInfoDetail = ({ productDetail, collections }: Props) => {
|
||||
const dataWithCategoryName = useMemo(() => {
|
||||
return {
|
||||
...productDetail,
|
||||
collection: getCategoryNameFromCollectionId(collections, productDetail.collectionIds ? productDetail.collectionIds[0] : undefined)
|
||||
}
|
||||
}, [productDetail, collections])
|
||||
return (
|
||||
<section className={s.productInfoDetail}>
|
||||
<ProductImgs/>
|
||||
<ProductInfo/>
|
||||
<ProductImgs productImage={productDetail.images}/>
|
||||
<ProductInfo productInfoDetail={dataWithCategoryName}/>
|
||||
</section >
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductInfoDetail
|
||||
|
@@ -3,15 +3,15 @@ import { ImgWithLink } from 'src/components/common'
|
||||
import s from './ProductImgItem.module.scss'
|
||||
|
||||
export interface ProductImgItemProps {
|
||||
src: string
|
||||
url: string
|
||||
alt?: string
|
||||
}
|
||||
|
||||
|
||||
const ProductImgItem = ({ src, alt }: ProductImgItemProps) => {
|
||||
const ProductImgItem = ({ url, alt }: ProductImgItemProps) => {
|
||||
return (
|
||||
<section className={s.productImgItem}>
|
||||
<ImgWithLink src={src} alt={alt} />
|
||||
<ImgWithLink src={url} alt={alt} />
|
||||
</section >
|
||||
)
|
||||
}
|
||||
|
@@ -3,26 +3,12 @@ import { ResponsiveType } from 'react-multi-carousel'
|
||||
import { CarouselCommon } from 'src/components/common'
|
||||
import ProductImgItem, { ProductImgItemProps } from '../ProductImgItem/ProductImgItem'
|
||||
import s from './ProductImgs.module.scss'
|
||||
import { ProductImage } from '@commerce/types/product';
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any,
|
||||
productImage: ProductImage[]
|
||||
}
|
||||
|
||||
const DATA = [
|
||||
{
|
||||
src: 'https://user-images.githubusercontent.com/76729908/133026929-199799fc-bd75-4445-a24d-15c0e41796eb.png',
|
||||
alt: 'Meat',
|
||||
},
|
||||
{
|
||||
src: 'https://user-images.githubusercontent.com/76729908/130574371-3b75fa72-9552-4605-aba9-a4b31cd9dce7.png',
|
||||
alt: 'Broccoli',
|
||||
},
|
||||
{
|
||||
src: 'https://user-images.githubusercontent.com/76729908/130574371-3b75fa72-9552-4605-aba9-a4b31cd9dce7.png',
|
||||
alt: 'Broccoli',
|
||||
}
|
||||
]
|
||||
|
||||
const RESPONSIVE: ResponsiveType = {
|
||||
desktop: {
|
||||
@@ -31,11 +17,11 @@ const RESPONSIVE: ResponsiveType = {
|
||||
slidesToSlide: 1, // optional, default to 1.
|
||||
},
|
||||
}
|
||||
const ProductImgs = ({ }: Props) => {
|
||||
const ProductImgs = ({ productImage }: Props) => {
|
||||
return (
|
||||
<section className={s.productImgs}>
|
||||
<CarouselCommon<ProductImgItemProps>
|
||||
data={DATA}
|
||||
data={productImage}
|
||||
itemKey="product-detail-img"
|
||||
Component={ProductImgItem}
|
||||
responsive={RESPONSIVE}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { Product } from '@commerce/types/product'
|
||||
import React from 'react'
|
||||
import { ButtonCommon, LabelCommon, QuanittyInput } from 'src/components/common'
|
||||
import { IconBuy } from 'src/components/icons'
|
||||
@@ -5,25 +6,25 @@ import { LANGUAGE } from 'src/utils/language.utils'
|
||||
import s from './ProductInfo.module.scss'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children?: any,
|
||||
productInfoDetail: Product
|
||||
}
|
||||
|
||||
const ProductInfo = ({ }: Props) => {
|
||||
const ProductInfo = ({ productInfoDetail }: Props) => {
|
||||
console.log(productInfoDetail)
|
||||
return (
|
||||
<section className={s.productInfo}>
|
||||
<div className={s.info}>
|
||||
<LabelCommon shape='half'>SEAFOOD</LabelCommon>
|
||||
<h2 className={s.heading}>SeaPAk</h2>
|
||||
<LabelCommon shape='half'>{productInfoDetail.collection}</LabelCommon>
|
||||
<h2 className={s.heading}>{productInfoDetail.name}</h2>
|
||||
<div className={s.price}>
|
||||
<div className={s.old}>
|
||||
<span className={s.number}>Rp 32.000</span>
|
||||
<span className={s.number}>Rp {productInfoDetail.price}</span>
|
||||
<LabelCommon type='discount'>-15%</LabelCommon>
|
||||
</div>
|
||||
<div className={s.current}>Rp 27.500</div>
|
||||
<div className={s.current}>Rp {productInfoDetail.price}</div>
|
||||
</div>
|
||||
<div className={s.description}>
|
||||
In a large non-reactive dish, mix together the orange juice, soy sauce, olive oil, lemon juice, parsley
|
||||
{productInfoDetail.description}
|
||||
</div>
|
||||
</div>
|
||||
<div className={s.actions}>
|
||||
|
@@ -23,7 +23,6 @@ const ReleventProducts = ({ data, collections }: Props) => {
|
||||
if (data.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ListProductWithInfo
|
||||
title="Relevant Products"
|
||||
|
Reference in New Issue
Block a user