diff --git a/framework/commerce/types/cart.ts b/framework/commerce/types/cart.ts index e4af878de..b3d3b1e59 100644 --- a/framework/commerce/types/cart.ts +++ b/framework/commerce/types/cart.ts @@ -17,6 +17,7 @@ export type LineItem = { quantity: number discounts: Discount[] // A human-friendly unique string automatically generated from the product’s name + slug: string path: string variant: ProductVariant options?: SelectedOption[] diff --git a/framework/commerce/types/product.ts b/framework/commerce/types/product.ts index ddf51c518..e31f34df9 100644 --- a/framework/commerce/types/product.ts +++ b/framework/commerce/types/product.ts @@ -30,6 +30,7 @@ export type ProductOptionValues = { export type ProductVariant = { id: string | number + name:string, options: ProductOption[] availableForSale?: boolean } @@ -48,7 +49,8 @@ export type Product = { options: ProductOption[] facetValueIds?: string[] collectionIds?: string[] - collection?: string, + collection?: string[], + variants?: ProductVariant[] } export type ProductCard = { @@ -65,6 +67,8 @@ export type ProductCard = { collectionIds?: string[], collection?: string, isNotSell?: boolean + productVariantId?:string + productVariantName?:string } export type SearchProductsBody = { diff --git a/framework/vendure/api/operations/get-all-blogs.ts b/framework/vendure/api/operations/get-all-blogs.ts index 3a1d01399..d3998aa59 100644 --- a/framework/vendure/api/operations/get-all-blogs.ts +++ b/framework/vendure/api/operations/get-all-blogs.ts @@ -16,7 +16,7 @@ export default function getAllBlogsOperation({ variables?: BlogVariables config?: Partial preview?: boolean - }): Promise<{ blogs: GetAllBlogsQuery,totalItems:number }> + }): Promise<{ blogs: GetAllBlogsQuery[],totalItems:number }> async function getAllBlogs({ query = getAllBlogsQuery, @@ -27,8 +27,8 @@ export default function getAllBlogsOperation({ variables?: BlogVariables config?: Partial preview?: boolean - } = {}): Promise<{ blogs: GetAllBlogsQuery | any[] ,totalItems?:number }> { - console.log(vars.excludeBlogIds) + } = {}): Promise<{ blogs: GetAllBlogsQuery[] | any[] ,totalItems?:number }> { + const config = commerce.getConfig(cfg) const variables = { excludeBlogIds: vars.excludeBlogIds, diff --git a/framework/vendure/api/operations/get-all-products.ts b/framework/vendure/api/operations/get-all-products.ts index 5fb458f67..1ff2675bc 100644 --- a/framework/vendure/api/operations/get-all-products.ts +++ b/framework/vendure/api/operations/get-all-products.ts @@ -5,7 +5,7 @@ import { normalizeSearchResult } from '../../utils/normalize' import { getAllProductsQuery } from '../../utils/queries/get-all-products-query' import { OperationContext } from '@commerce/api/operations' -export type ProductVariables = { first?: number, facetValueIds?: string[] } +export type ProductVariables = { first?: number, facetValueIds?: string[], collectionSlug?:string, groupByProduct?:boolean } export default function getAllProductsOperation({ commerce, @@ -31,13 +31,13 @@ export default function getAllProductsOperation({ input: { take: vars.first, facetValueIds: vars.facetValueIds, - groupByProduct: true, + collectionSlug : vars.collectionSlug, + groupByProduct: vars.groupByProduct??true, }, } const { data } = await config.fetch(query, { variables, }) - return { products: data.search.items.map((item) => normalizeSearchResult(item)), totalItems: data.search.totalItems as number, diff --git a/framework/vendure/api/operations/get-featured-blog.ts b/framework/vendure/api/operations/get-featured-blog.ts index 6dc05c1be..8f26e4288 100644 --- a/framework/vendure/api/operations/get-featured-blog.ts +++ b/framework/vendure/api/operations/get-featured-blog.ts @@ -15,7 +15,7 @@ export default function getFeaturedBlogOperation({ variables?: BlogVariables config?: Partial preview?: boolean - }): Promise<{ featuredBlogs: GetFeaturedBlogQuery,totalItems:number }> + }): Promise<{ featuredBlogs: GetFeaturedBlogQuery[],totalItems:number }> async function getFeaturedBlog({ query = getFeatuedBlogQuery, @@ -26,7 +26,7 @@ export default function getFeaturedBlogOperation({ variables?: BlogVariables config?: Partial preview?: boolean - } = {}): Promise<{ featuredBlogs: GetFeaturedBlogQuery | any[] ,totalItems?:number }> { + } = {}): Promise<{ featuredBlogs: GetFeaturedBlogQuery[] | any[] ,totalItems?:number }> { const config = commerce.getConfig(cfg) const variables = { options: { diff --git a/framework/vendure/api/operations/get-product.ts b/framework/vendure/api/operations/get-product.ts index 0aa761ab0..66f798ee1 100644 --- a/framework/vendure/api/operations/get-product.ts +++ b/framework/vendure/api/operations/get-product.ts @@ -36,6 +36,7 @@ export default function getProductOperation({ })), variants: product.variants.map((v) => ({ id: v.id, + name:v.name, options: v.options.map((o) => ({ // This __typename property is required in order for the correct // variant selection to work, see `components/product/helpers.ts` @@ -54,7 +55,8 @@ export default function getProductOperation({ values: og.options.map((o) => ({ label: o.name })), })), facetValueIds: product.facetValues.map(item=> item.id), - collectionIds: product.collections.map(item => item.id) + collectionIds: product.collections.map(item => item.id), + collection:product.collections.map(item => item.name), } as Product } diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 5b4b58757..542af51fb 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -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 | null export type Exact = { @@ -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 } + + + 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 firstName: Scalars['String'] lastName: Scalars['String'] - phoneNumber?: Maybe + phoneNumber?: Maybe emailAddress: Scalars['String'] addresses?: Maybe> orders: OrderList @@ -2344,6 +2360,7 @@ export type GetAllBlogsQuery = PaginatedList & { } } export type GetFeaturedBlogQuery = PaginatedList & { + id:string, featuredBlogs: { __typename?: 'BlogList' } & { items: Array<{ __typename?: 'Blog' } & BlogList!>, 'totalItems' @@ -3087,7 +3104,7 @@ export type SearchResultFragment = { __typename?: 'SearchResult' } & Pick< SearchResult, 'productId' | 'sku' | 'productName' | 'description' | 'slug' | 'sku' | 'currencyCode' | 'productAsset' | 'price' | 'priceWithTax' | 'currencyCode' - | 'collectionIds' | 'facetValueIds' | 'collectionIds' + | 'collectionIds' | 'productVariantId' | 'facetValueIds' | "productVariantName" > & { productAsset?: Maybe< { __typename?: 'SearchResultAsset' } & Pick< @@ -3174,6 +3191,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 @@ -3227,8 +3274,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, @@ -3240,17 +3288,48 @@ export type SignupMutation = { __typename?: 'Mutation' } & { >) } + + export type ActiveCustomerQueryVariables = Exact<{ [key: string]: never }> export type ActiveCustomerQuery = { __typename?: 'Query' } & { activeCustomer?: Maybe< { __typename?: 'Customer' } & Pick< Customer, - 'id' | 'firstName' | 'lastName' | 'emailAddress' + Favorite, + 'id' | 'firstName' | 'lastName' | 'emailAddress' | 'addresses' | 'phoneNumber'| 'orders' > > } +export type QueryFavorite = { + options: FavoriteListOptions +} + +export type FavoriteListOptions = { + skip?: Maybe + take?: Maybe +} + +export type FavoriteList = PaginatedList & { + items: [Favorite!]! + totalItems: Int! +} + +type Favorite = Node & { + id: ID! + createdAt: DateTime! + updatedAt: DateTime! + product: Product + customer: Customer! +} + + + +type FavouriteOption = Customer & { + favorites(options: FavoriteListOptions): FavoriteList! +} + export type GetAllProductPathsQueryVariables = Exact<{ first?: Maybe }> @@ -3351,7 +3430,7 @@ export type GetProductQuery = { __typename?: 'Query' } & { variants: Array< { __typename?: 'ProductVariant' } & Pick< ProductVariant, - 'id' | 'priceWithTax' | 'currencyCode' | 'price' + 'id' | 'priceWithTax' | 'currencyCode' | 'price' | "name" > & { options: Array< { __typename?: 'ProductOption' } & Pick< @@ -3395,7 +3474,7 @@ export type GetProductQuery = { __typename?: 'Query' } & { collections: Array< { __typename?: 'Collection' } & Pick< Collection, - 'id' + 'id'|"name" > > } diff --git a/framework/vendure/utils/fragments/search-result-fragment.ts b/framework/vendure/utils/fragments/search-result-fragment.ts index d2d82f42e..5a5998dca 100644 --- a/framework/vendure/utils/fragments/search-result-fragment.ts +++ b/framework/vendure/utils/fragments/search-result-fragment.ts @@ -7,6 +7,8 @@ export const searchResultFragment = /* GraphQL */ ` slug sku currencyCode + productVariantId + productVariantName productAsset { id preview 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 diff --git a/framework/vendure/utils/mutations/toggle-wishlist-mutation.tsx b/framework/vendure/utils/mutations/toggle-wishlist-mutation.tsx new file mode 100644 index 000000000..d3dcb7c18 --- /dev/null +++ b/framework/vendure/utils/mutations/toggle-wishlist-mutation.tsx @@ -0,0 +1,9 @@ +export const toggleWishlistMutation = /* GraphQL */ ` + mutation toggleFavorite($productId:ID!){ + toggleFavorite(productId:$productId){ + items{ + id + } + } + } +` diff --git a/framework/vendure/utils/mutations/update-customer-address-mutation.ts b/framework/vendure/utils/mutations/update-customer-address-mutation.ts new file mode 100644 index 000000000..4cac594ed --- /dev/null +++ b/framework/vendure/utils/mutations/update-customer-address-mutation.ts @@ -0,0 +1,14 @@ +export const updateCustomerAddress = /* GraphQL */ ` + mutation updateCustomerAddress($input: UpdateAddressInput!){ + updateCustomerAddress(input: $input){ + __typename + ...on Address{ + id + streetLine1 + city + postalCode + province + } + } + } +` diff --git a/framework/vendure/utils/mutations/update-customer-mutation.ts b/framework/vendure/utils/mutations/update-customer-mutation.ts new file mode 100644 index 000000000..535f80eee --- /dev/null +++ b/framework/vendure/utils/mutations/update-customer-mutation.ts @@ -0,0 +1,13 @@ +export const updateCustomer = /* GraphQL */ ` + mutation updateCustomer($input: UpdateCustomerInput!){ + updateCustomer(input:$input){ + __typename + ...on Customer{ + id + firstName + lastName + phoneNumber + } + } + } +` diff --git a/framework/vendure/utils/normalize.ts b/framework/vendure/utils/normalize.ts index cc76e5f97..871352070 100644 --- a/framework/vendure/utils/normalize.ts +++ b/framework/vendure/utils/normalize.ts @@ -1,6 +1,6 @@ import { Cart } from '@commerce/types/cart' -import { ProductCard } from '@commerce/types/product' -import { CartFragment, SearchResultFragment } from '../schema' +import { ProductCard, Product } from '@commerce/types/product' +import { CartFragment, SearchResultFragment,Favorite } from '../schema' export function normalizeSearchResult(item: SearchResultFragment): ProductCard { return { @@ -10,6 +10,8 @@ export function normalizeSearchResult(item: SearchResultFragment): ProductCard { imageSrc: item.productAsset?.preview ? item.productAsset?.preview + '?w=800&mode=crop' : '', price: (item.priceWithTax as any).min / 100, currencyCode: item.currencyCode, + productVariantId: item.productVariantId, + productVariantName:item.productVariantName, facetValueIds: item.facetValueIds, collectionIds: item.collectionIds, @@ -21,6 +23,18 @@ export function normalizeSearchResult(item: SearchResultFragment): ProductCard { } } +export function normalizeFavoriteProductResult(item: Favorite) { + return { + id: item.product.id, + name: item.product.name, + slug: item.product.slug, + imageSrc: item.product.assets[0].preview ? item.product.assets[0].preview + '?w=800&mode=crop' : '', + price: item.product.variants[0].priceWithTax as number / 100, + currencyCode: item.product.variants[0].currencyCode, + } +} + + export function normalizeCart(order: CartFragment): Cart { return { id: order.id.toString(), @@ -35,7 +49,7 @@ export function normalizeCart(order: CartFragment): Cart { id: l.id, name: l.productVariant.name, quantity: l.quantity, - url: l.productVariant.product.slug, + slug: l.productVariant.product.slug, variantId: l.productVariant.id, productId: l.productVariant.productId, images: [{ url: l.featuredAsset?.preview + '?preset=thumb' || '' }], @@ -55,3 +69,18 @@ export function normalizeCart(order: CartFragment): Cart { })), } } + +export function normalizeProductCard(product: Product): ProductCard { + return { + id: product.id, + name: product.name, + slug: product.slug, + imageSrc: product.images[0].url, + price: product.price, + currencyCode: product.currencyCode, + productVariantId: product.variants?.[0].id.toString(), + productVariantName:product.variants?.[0].name, + facetValueIds: product.facetValueIds, + collectionIds: product.collectionIds, + } +} \ No newline at end of file diff --git a/framework/vendure/utils/queries/active-customer-query.ts b/framework/vendure/utils/queries/active-customer-query.ts index 65b280743..3c0664102 100644 --- a/framework/vendure/utils/queries/active-customer-query.ts +++ b/framework/vendure/utils/queries/active-customer-query.ts @@ -1,10 +1,24 @@ export const activeCustomerQuery = /* GraphQL */ ` - query activeCustomer { - activeCustomer { - id - firstName - lastName - emailAddress +query activeCustomer { + activeCustomer { + id + firstName + lastName + emailAddress + favorites{ + items{ + product{ + id + } + } } + phoneNumber + addresses{ + streetLine1 + city + province + postalCode + } } +} ` diff --git a/framework/vendure/utils/queries/get-favorite-product-query.ts b/framework/vendure/utils/queries/get-favorite-product-query.ts new file mode 100644 index 000000000..f73e0fb26 --- /dev/null +++ b/framework/vendure/utils/queries/get-favorite-product-query.ts @@ -0,0 +1,28 @@ +export const getFavoriteProductQuery = /* GraphQL */ ` +query activeCustomer($options: FavoriteListOptions) { + activeCustomer { + id + firstName + lastName + emailAddress + favorites(options: $options){ + items{ + product{ + id + name + slug + assets{ + source + preview + } + variants{ + priceWithTax + currencyCode + } + } + } + totalItems + } + } +} +` diff --git a/framework/vendure/utils/queries/get-product-query.ts b/framework/vendure/utils/queries/get-product-query.ts index 6db960a96..1ff057e61 100644 --- a/framework/vendure/utils/queries/get-product-query.ts +++ b/framework/vendure/utils/queries/get-product-query.ts @@ -12,6 +12,7 @@ export const getProductQuery = /* GraphQL */ ` } variants { id + name priceWithTax currencyCode options { @@ -41,6 +42,7 @@ export const getProductQuery = /* GraphQL */ ` } collections { id + name } } } diff --git a/framework/vendure/utils/queries/get-user-order-query.ts b/framework/vendure/utils/queries/get-user-order-query.ts new file mode 100644 index 000000000..adf75365c --- /dev/null +++ b/framework/vendure/utils/queries/get-user-order-query.ts @@ -0,0 +1,19 @@ +export const getUserOrderQuery = /* GraphQL */ ` + query activeCustomer { + activeCustomer { + orders{ + items{ + lines{ + productVariant{ + name + } + quantity + } + total + state + code + } + } + } + } +` diff --git a/framework/vendure/utils/queries/user-info-query.ts b/framework/vendure/utils/queries/user-info-query.ts new file mode 100644 index 000000000..5e5fa24cb --- /dev/null +++ b/framework/vendure/utils/queries/user-info-query.ts @@ -0,0 +1,16 @@ +export const userInfoQuery = /* GraphQL */ ` + query activeCustomer{ + activeCustomer{ + lastName + firstName + emailAddress + phoneNumber + addresses{ + streetLine1 + city + province + postalCode + } + } + } +` diff --git a/pages/blogs.tsx b/pages/blogs.tsx index 7e8a2b64a..7c3483a94 100644 --- a/pages/blogs.tsx +++ b/pages/blogs.tsx @@ -3,34 +3,32 @@ import { GetStaticPropsContext } from 'next'; import { Layout } from 'src/components/common'; import { BlogCardProps } from 'src/components/common/CardBlog/CardBlog'; import { BlogBreadCrumb, BlogHeading, BlogsList, FeaturedCardBlog } from 'src/components/modules/blogs'; -import { DEFAULT_BLOG_PAGE_SIZE,DEFAULT_FEATURED_BLOG_PAGE_SIZE } from "src/utils/constanst.utils"; +import { DEFAULT_BLOG_PAGE_SIZE } from "src/utils/constanst.utils"; import { getAllPromies } from 'src/utils/funtion.utils'; import { PromiseWithKey } from 'src/utils/types.utils'; -import { getIdFeaturedBlog } from 'src/utils/funtion.utils'; - interface Props { - blogsResult: { blogs?: BlogCardProps[],featuredBlogs?: BlogCardProps[] }, + blogsResult: { blogs?: BlogCardProps[],featuredBlog?: BlogCardProps[] }, totalItems: number } export default function BlogsPage({blogsResult,totalItems}:Props) { - - // let date = new Date(blogsResult?.featuredBlogs[0]?.createdAt ?? '' ); - // let fullDate = date.toLocaleString('en-us', { month: 'long' }) + " " + date.getDate()+","+date.getFullYear(); - + let date = new Date(blogsResult.featuredBlog?.[0]?.createdAt ?? '' ); + let fullDate = date.toLocaleString('en-us', { month: 'long' }) + " " + date.getDate()+","+date.getFullYear(); + return( <> - {/* */} - + /> + ) } @@ -53,17 +51,12 @@ export async function getStaticProps({ config, preview, }) - - - // console.log(featuredBlogs[0].id); - // promisesWithKey.push({ key: 'blogsResult', promise: FeaturedBlogPromise }) - // Blogs - const idBlog = featuredBlogs[0].id; - const blogsPromise = commerce.getAllBlogs({ + const idFeaturedBlog = featuredBlogs[0].id; + const blogsPromise = commerce.getAllBlogs({ variables: { - excludeBlogIds: [idBlog], + excludeBlogIds: [idFeaturedBlog], take: DEFAULT_BLOG_PAGE_SIZE }, config, @@ -71,7 +64,6 @@ export async function getStaticProps({ }) promisesWithKey.push({ key: 'blogsResult', promise: blogsPromise }) - try { const promises = getAllPromies(promisesWithKey) @@ -81,8 +73,9 @@ export async function getStaticProps({ props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] return null }) - props['blogsResult']['featuredBlog'] = featuredBlogs; + props['blogsResult']['featuredBlog'] = featuredBlogs; + return { props, revalidate: 60 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/index.tsx b/pages/index.tsx index d1c5c39f8..62aeca0ab 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -6,7 +6,9 @@ import { GetStaticPropsContext } from 'next'; import { Layout } from 'src/components/common'; import { FeaturedProductsCarousel, FreshProducts, HomeBanner, HomeCategories, HomeCollection, HomeCTA, HomeFeature, HomeRecipe, HomeSubscribe, HomeVideo } from 'src/components/modules/home'; import HomeSpice from 'src/components/modules/home/HomeSpice/HomeSpice'; -import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED } from 'src/utils/constanst.utils'; +import { FACET } from 'src/utils/constanst.utils'; +import { FilterOneVatiant, getFacetIdByName } from 'src/utils/funtion.utils'; +import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED,COLLECTION_SLUG_SPICE } from 'src/utils/constanst.utils'; import { getAllFacetValueIdsByParentCode, getAllFacetValuesForFeatuedProducts, getAllPromies, getFreshFacetId } from 'src/utils/funtion.utils'; import { PromiseWithKey } from 'src/utils/types.utils'; @@ -16,19 +18,23 @@ interface Props { freshProducts: ProductCard[], featuredProducts: ProductCard[], collections: Collection[] + spiceProducts:ProductCard[] + veggie: ProductCard[], + } -export default function Home({ featuredAndDiscountFacetsValue, +export default function Home({ featuredAndDiscountFacetsValue, veggie, freshProducts, featuredProducts, - collections }: Props) { + collections,spiceProducts }: Props) { + return ( <> + - - + {spiceProducts.length>0 && } @@ -55,6 +61,8 @@ export async function getStaticProps({ config, preview, }) + + props.featuredAndDiscountFacetsValue = getAllFacetValuesForFeatuedProducts(facets) // fresh products @@ -72,7 +80,20 @@ export async function getStaticProps({ props.freshProducts = [] } - + //veggie + const veggieProductvariables: ProductVariables = { + groupByProduct:false + } + const veggieId = getFacetIdByName(facets,FACET.CATEGORY.PARENT_NAME,FACET.CATEGORY.VEGGIE) + if (veggieId) { + veggieProductvariables.facetValueIds = [veggieId] + } + const veggieProductsPromise = commerce.getAllProducts({ + variables: veggieProductvariables, + config, + preview, + }) + promisesWithKey.push({ key: 'veggie', promise: veggieProductsPromise, keyResult: 'products' }) // featured products const allFeaturedFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_FEATURED) const allDiscountFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_DISCOUNT) @@ -99,15 +120,24 @@ export async function getStaticProps({ }) promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' }) + // spiceProducts + const spiceProducts = commerce.getAllProducts({ + variables: { + collectionSlug: COLLECTION_SLUG_SPICE, + }, + config, + preview, + }) + promisesWithKey.push({ key: 'spiceProducts', promise: spiceProducts, 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] + props[item.key] = item.keyResult ? FilterOneVatiant(rs[index][item.keyResult]) : rs[index] return null }) - return { props, revalidate: 60, diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx index 2da14a995..b107dc36d 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -1,22 +1,43 @@ -import { Product } from '@framework/schema' +import { Collection } from '@commerce/types/collection' +import { Product, ProductCard } from '@commerce/types/product' import commerce from '@lib/api/commerce' import { GetStaticPathsContext, GetStaticPropsContext, InferGetStaticPropsType } from 'next' +import { useEffect, useState } from 'react' import { Layout, RecipeDetail, RecommendedRecipes, RelevantBlogPosts } from 'src/components/common' +import { useLocalStorage } from 'src/components/hooks/useLocalStorage' import { ProductInfoDetail, ReleventProducts, ViewedProducts } from 'src/components/modules/product-detail' -import { MAX_PRODUCT_CAROUSEL, REVALIDATE_TIME } from 'src/utils/constanst.utils' +import { LOCAL_STORAGE_KEY, MAX_PRODUCT_CAROUSEL, REVALIDATE_TIME } from 'src/utils/constanst.utils' import { BLOGS_DATA_TEST, INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data' import { getAllPromies } from 'src/utils/funtion.utils' +import { normalizeProductCard } from '@framework/utils/normalize'; import { PromiseWithKey } from 'src/utils/types.utils' - -export default function Slug({ product, relevantProducts, collections }: InferGetStaticPropsType) { - +interface Props { + relevantProducts: ProductCard[], + product: Product, + collections: Collection[] +} +export default function Slug({ product, relevantProducts, collections }: Props) { + const [local,setLocal] = useLocalStorage(LOCAL_STORAGE_KEY.VIEWEDPRODUCT, []); + const [viewed, setViewed] = useState([]) + useEffect(() => { + if(local){ + if(!local.find(p => p.id === product.id)){ + setLocal([...local, product]) + }else{ + setViewed(local.filter((p)=>p.id !== product.id).map((p)=>normalizeProductCard(p))) + } + }else{ + setLocal([product]) + } + }, [product]) + return <> - + - + } @@ -68,7 +89,6 @@ export async function getStaticProps({ }) promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' }) - try { const promises = getAllPromies(promisesWithKey) const rs = await Promise.all(promises) 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/CartDrawer/CartDrawer.tsx b/src/components/common/CartDrawer/CartDrawer.tsx index 03cb966ad..089e8b620 100644 --- a/src/components/common/CartDrawer/CartDrawer.tsx +++ b/src/components/common/CartDrawer/CartDrawer.tsx @@ -1,5 +1,7 @@ +import { normalizeCart } from '@framework/utils/normalize'; import React from 'react'; import { useCartDrawer } from 'src/components/contexts'; +import useGetActiveOrder from 'src/components/hooks/cart/useGetActiveOrder'; import { PRODUCT_CART_DATA_TEST } from 'src/utils/demo-data'; import { DrawerCommon } from '..'; import s from './CartDrawer.module.scss'; @@ -14,14 +16,15 @@ interface Props { const CartDrawer = ({ }: Props) => { const { cartVisible, closeCartDrawer } = useCartDrawer() + const {order} = useGetActiveOrder() return (
- +
diff --git a/src/components/common/CartDrawer/components/ProductCartItem/ProductCartItem.tsx b/src/components/common/CartDrawer/components/ProductCartItem/ProductCartItem.tsx index 7ec3ecbdb..ef219d31d 100644 --- a/src/components/common/CartDrawer/components/ProductCartItem/ProductCartItem.tsx +++ b/src/components/common/CartDrawer/components/ProductCartItem/ProductCartItem.tsx @@ -1,25 +1,64 @@ -import React from 'react'; +import React, { useCallback, useState } from 'react' import Link from 'next/link' -import { QuanittyInput } from 'src/components/common'; -import { IconDelete } from 'src/components/icons'; -import { ROUTE } from 'src/utils/constanst.utils'; -import { ProductProps } from 'src/utils/types.utils'; -import ImgWithLink from '../../../ImgWithLink/ImgWithLink'; -import LabelCommon from '../../../LabelCommon/LabelCommon'; -import s from './ProductCartItem.module.scss'; +import { ModalConfirm, QuanittyInput } from 'src/components/common' +import { IconDelete } from 'src/components/icons' +import { ROUTE } from 'src/utils/constanst.utils' +import ImgWithLink from '../../../ImgWithLink/ImgWithLink' +import LabelCommon from '../../../LabelCommon/LabelCommon' +import s from './ProductCartItem.module.scss' +import { LineItem } from '@commerce/types/cart' +import { useUpdateProductInCart } from 'src/components/hooks/cart' +import { debounce } from 'lodash' +import useRemoveProductInCart from 'src/components/hooks/cart/useRemoveProductInCart' -export interface ProductCartItempProps extends ProductProps { - quantity: number, +export interface ProductCartItempProps extends LineItem { + currency: { code: string } } -const ProductCartItem = ({ name, slug, weight, price, oldPrice, discount, imageSrc, quantity }: ProductCartItempProps) => { +const ProductCartItem = ({ + slug, + discounts, + quantity, + variant, + name, + currency, + id +}: ProductCartItempProps) => { + const [visible, setVisible] = useState(false) + const {updateProduct} = useUpdateProductInCart() + const {removeProduct, loading} = useRemoveProductInCart() + const handleQuantityChangeCallback = (isSuccess:boolean,mess?:string) => { + if(!isSuccess){ + console.log(mess) + } + } + const handleRemoveCallback = (isSuccess:boolean,mess?:string) => { + if(!isSuccess){ + console.log(mess) + }else{ + setVisible(false) + } + } + const handleQuantityChange = (value:number) => { + updateProduct({orderLineId:id,quantity:value},handleQuantityChangeCallback) + } + const debounceFn = useCallback(debounce(handleQuantityChange, 500), []); + const handleCancel = () => { + setVisible(false) + } + const handleOpen = () => { + setVisible(true) + } + const handleConfirm = () => { + removeProduct({orderLineId:id},handleRemoveCallback) + } return (
- +
@@ -27,30 +66,32 @@ const ProductCartItem = ({ name, slug, weight, price, oldPrice, discount, imageS
- {name} {weight ? `(${weight})` : ''} + {name} {variant?.weight ? `(${variant.weight})` : ''}
- { - oldPrice && -
- {oldPrice} - {discount} -
- } -
{price}
+ {discounts.length > 0 && ( +
+ {/* {oldPrice} */} + {discounts[0]} +
+ )} +
{variant?.price} {currency?.code}
-
+
- +
+ + Are you sure want to remove {name} form your cart +
) } -export default ProductCartItem; \ No newline at end of file +export default ProductCartItem diff --git a/src/components/common/CartDrawer/components/ProductsInCart/ProductsInCart.tsx b/src/components/common/CartDrawer/components/ProductsInCart/ProductsInCart.tsx index b455d4d73..7d6432a9d 100644 --- a/src/components/common/CartDrawer/components/ProductsInCart/ProductsInCart.tsx +++ b/src/components/common/CartDrawer/components/ProductsInCart/ProductsInCart.tsx @@ -1,25 +1,21 @@ +import { LineItem } from '@commerce/types/cart'; import React from 'react'; import ProductCartItem, { ProductCartItempProps } from '../ProductCartItem/ProductCartItem'; import s from './ProductsInCart.module.scss'; interface Props { - data: ProductCartItempProps[] + data: LineItem[] + currency: { code: string } } -const ProductsInCart = ({ data }: Props) => { +const ProductsInCart = ({ data, currency }: Props) => { return (
    { data.map(item =>
  • ) } 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 588edbb0e..c5df16e12 100644 --- a/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx +++ b/src/components/common/Header/components/HeaderMenu/HeaderMenu.tsx @@ -58,6 +58,10 @@ const HeaderMenu = memo( onClick: openModalRegister, name: 'Create account', }, + { + link: '/forgot-password', + name: 'Forgot Password', + }, ], [openModalLogin, openModalRegister] ) diff --git a/src/components/common/ItemWishList/ItemWishList.tsx b/src/components/common/ItemWishList/ItemWishList.tsx index 74d0b3b04..ac72c6879 100644 --- a/src/components/common/ItemWishList/ItemWishList.tsx +++ b/src/components/common/ItemWishList/ItemWishList.tsx @@ -2,19 +2,38 @@ import classNames from 'classnames' import IconHeart from 'src/components/icons/IconHeart' import React, { memo } from 'react' import s from './ItemWishList.module.scss' - +import { useToggleProductWishlist } from '../../../../src/components/hooks/product' +import { useMessage } from 'src/components/contexts' +import { LANGUAGE } from 'src/utils/language.utils' interface Props { + id:string, isActive?: boolean, - onChange?: () => void + onChange?: () => string } -const ItemWishList = memo(({isActive=false, onChange}:Props) => { +const ItemWishList = memo(({id,isActive=false, onChange}:Props) => { + const {onToggleProductWishlist} = useToggleProductWishlist(); + const { showMessageSuccess, showMessageError } = useMessage(); + + function toggleWishlist(){ + onToggleProductWishlist({productId:id},onSignupCallBack) + } + + const onSignupCallBack = (isSuccess: boolean, message?: string) => { + if (isSuccess) { + // showMessageSuccess("Create account successfully. Please verify your email to login.", 15000) + } else { + showMessageError(message || LANGUAGE.MESSAGE.ERROR) + } + } + return(
    diff --git a/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx index eeb96fae1..b694e7da8 100644 --- a/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx +++ b/src/components/common/MenuFilter/MenuFilterItem/MenuFilterItem.tsx @@ -1,5 +1,4 @@ import classNames from 'classnames'; -import { route } from 'next/dist/server/router'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; import s from './MenuFilterItem.module.scss'; diff --git a/src/components/common/ModalConfirm/ModalConfirm.tsx b/src/components/common/ModalConfirm/ModalConfirm.tsx index 1e425482f..a41dd41d7 100644 --- a/src/components/common/ModalConfirm/ModalConfirm.tsx +++ b/src/components/common/ModalConfirm/ModalConfirm.tsx @@ -5,6 +5,7 @@ import s from './ModalConfirm.module.scss' interface ModalConfirmProps extends ModalCommonProps { okText?: String cancelText?: String + loading?:boolean onOk?: () => void onCancel?: () => void } @@ -16,6 +17,7 @@ const ModalConfirm = ({ onCancel, children, title = 'Confirm', + loading, ...props }: ModalConfirmProps) => { return ( @@ -25,7 +27,7 @@ const ModalConfirm = ({
    {cancelText}
    - {okText} + {okText}
) diff --git a/src/components/common/ProductCard/ProductCard.tsx b/src/components/common/ProductCard/ProductCard.tsx index 4761aac92..4f14e5b1f 100644 --- a/src/components/common/ProductCard/ProductCard.tsx +++ b/src/components/common/ProductCard/ProductCard.tsx @@ -10,13 +10,17 @@ import ItemWishList from '../ItemWishList/ItemWishList' import LabelCommon from '../LabelCommon/LabelCommon' import s from './ProductCard.module.scss' import ProductNotSell from './ProductNotSell/ProductNotSell' - +import {useAddProductToCart} from "../../hooks/cart" +import { useCartDrawer } from 'src/components/contexts' +import Router from 'next/router' export interface ProductCardProps extends ProductCard { buttonText?: string isSingleButton?: boolean, + activeWishlist?:boolean } const ProductCardComponent = ({ + id, collection, name, slug, @@ -27,13 +31,42 @@ const ProductCardComponent = ({ imageSrc, isNotSell, isSingleButton, + productVariantId, + productVariantName, + activeWishlist }: ProductCardProps) => { + + const {addProduct,loading} = useAddProductToCart() + const { openCartDrawer } = useCartDrawer() + + const handleAddToCart = () => { + if(productVariantId){ + addProduct({variantId:productVariantId,quantity:1},handleAddToCartCallback) + } + } + const handleAddToCartCallback = () => { + openCartDrawer && openCartDrawer() + } + + const handleBuyNowCallback = (success:boolean) => { + if(success){ + Router.push(ROUTE.CHECKOUT) + } + } + + const handleBuyNow = () => { + if(productVariantId){ + addProduct({variantId:productVariantId,quantity:1},handleBuyNowCallback) + } + } + if (isNotSell) { return
- } + + return (
@@ -55,7 +88,7 @@ const ProductCardComponent = ({
-
{name}
+
{productVariantName}
{weight}
@@ -63,7 +96,7 @@ const ProductCardComponent = ({
{price} {currencyCode}
- +
@@ -71,15 +104,15 @@ const ProductCardComponent = ({ { isSingleButton ?
- } size='small'>Add to cart + } size='small' onClick={handleAddToCart}>Add to cart
: <> -
- +
+
- {buttonText} + {buttonText}
} diff --git a/src/components/common/ProductList/ProductList.tsx b/src/components/common/ProductList/ProductList.tsx index c901b4d46..111173fa9 100644 --- a/src/components/common/ProductList/ProductList.tsx +++ b/src/components/common/ProductList/ProductList.tsx @@ -1,12 +1,12 @@ import classNames from 'classnames' import { useRouter } from 'next/router' import React from 'react' +import { useActiveCustomer } from 'src/components/hooks/auth' import { DEFAULT_PAGE_SIZE, ROUTE } from 'src/utils/constanst.utils' import { ButtonCommon, EmptyCommon } from '..' import PaginationCommon from '../PaginationCommon/PaginationCommon' import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard' import s from "./ProductList.module.scss" - interface ProductListProps { data: ProductCardProps[], total?: number, @@ -14,8 +14,10 @@ interface ProductListProps { onPageChange?: (page: number) => void } -const ProductList = ({ data, total = data.length, defaultCurrentPage, onPageChange }: ProductListProps) => { +const ProductList = ({ data, total = data?.length, defaultCurrentPage, onPageChange }: ProductListProps) => { const router = useRouter() + const {wishlistId } = useActiveCustomer(); + const handlePageChange = (page: number) => { onPageChange && onPageChange(page) } @@ -32,19 +34,20 @@ const ProductList = ({ data, total = data.length, defaultCurrentPage, onPageChan
{ - data.map((product, index) => { - return + data?.map((product, index) => { + let activeWishlist = wishlistId?.findIndex((val:string) => val == product.id) !== -1; + return }) } { - data.length === 0 &&
+ data?.length === 0 &&
Show all products
}
-
- +
+
) diff --git a/src/components/common/QuanittyInput/QuanittyInput.tsx b/src/components/common/QuanittyInput/QuanittyInput.tsx index e31abb880..e2229830f 100644 --- a/src/components/common/QuanittyInput/QuanittyInput.tsx +++ b/src/components/common/QuanittyInput/QuanittyInput.tsx @@ -26,10 +26,6 @@ const QuanittyInput = ({ }: QuanittyInputProps) => { const [value, setValue] = useState(0) - useEffect(() => { - onChange && onChange(value) - }, [value]) - useEffect(() => { initValue && setValue(initValue) }, [initValue]) @@ -37,16 +33,20 @@ const QuanittyInput = ({ const onPlusClick = () => { if (max && value + step > max) { setValue(max) + onChange && onChange(max) } else { setValue(value + step) + onChange && onChange(value + step) } } const onMinusClick = () => { if (min && value - step < min) { setValue(min) + onChange && onChange(min) } else { setValue(value - step) + onChange && onChange(value - step) } } @@ -54,10 +54,13 @@ const QuanittyInput = ({ let value = Number(e.target.value) || 0 if (min && value < min) { setValue(min) + onChange && onChange(min) } else if (max && value > max) { setValue(max) + onChange && onChange(max) } else { setValue(value) + onChange && onChange(value) } } diff --git a/src/components/common/SelectCommon/SelectCommon.module.scss b/src/components/common/SelectCommon/SelectCommon.module.scss index 82ce46f5b..3f213b567 100644 --- a/src/components/common/SelectCommon/SelectCommon.module.scss +++ b/src/components/common/SelectCommon/SelectCommon.module.scss @@ -11,7 +11,7 @@ width: 20.6rem; .selectTrigger { width: 20.6rem; - padding: 1.2rem 1.6rem; + padding: 1.6rem; } } &.large { diff --git a/src/components/common/SelectCommon/SelectCommon.tsx b/src/components/common/SelectCommon/SelectCommon.tsx index bf34ed3f9..88514ac64 100644 --- a/src/components/common/SelectCommon/SelectCommon.tsx +++ b/src/components/common/SelectCommon/SelectCommon.tsx @@ -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() - const [selectedValue, setSelectedValue] = useState('') +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) diff --git a/src/components/common/index.ts b/src/components/common/index.ts index 2b986130b..2b7724b73 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -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' diff --git a/src/components/hooks/account/index.ts b/src/components/hooks/account/index.ts new file mode 100644 index 000000000..d58cc6b3d --- /dev/null +++ b/src/components/hooks/account/index.ts @@ -0,0 +1,4 @@ +export { default as useGetFavoriteProduct } from './useGetFavoriteProduct' +export { default as useGetUserOrder } from './useGetUserOrder'; +export { default as useEditUserInfo } from './useEditUserInfo' +export { default as useEditCustomerAddress } from './useEditCustomerAddress' diff --git a/src/components/hooks/account/useEditCustomerAddress.tsx b/src/components/hooks/account/useEditCustomerAddress.tsx new file mode 100644 index 000000000..5caae3eb1 --- /dev/null +++ b/src/components/hooks/account/useEditCustomerAddress.tsx @@ -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(null) + const {customer,mutate} = useActiveCustomer(); + + const editCustomerAddress = ( + { address,city,postalCode,state}: Props, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + + fetcher
({ + 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 diff --git a/src/components/hooks/account/useEditUserInfo.tsx b/src/components/hooks/account/useEditUserInfo.tsx new file mode 100644 index 000000000..c2c8b1a83 --- /dev/null +++ b/src/components/hooks/account/useEditUserInfo.tsx @@ -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(null) + const {mutate} = useActiveCustomer(); + + const editUserInfo = ( + { firstName,lastName,phoneNumber}: Props, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + + fetcher({ + 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 diff --git a/src/components/hooks/account/useGetFavoriteProduct.tsx b/src/components/hooks/account/useGetFavoriteProduct.tsx new file mode 100644 index 000000000..3aeab0f57 --- /dev/null +++ b/src/components/hooks/account/useGetFavoriteProduct.tsx @@ -0,0 +1,16 @@ +import { ActiveCustomerQuery,QueryFavorite,Favorite } from '@framework/schema' +import { normalizeFavoriteProductResult } from '@framework/utils/normalize' +import { getFavoriteProductQuery } from '@framework/utils/queries/get-favorite-product-query' +import gglFetcher from 'src/utils/gglFetcher' +import useSWR from 'swr' + +const useGetFavoriteProduct = (options?:QueryFavorite) => { + const { data, ...rest } = useSWR([getFavoriteProductQuery, options], gglFetcher) + return { + itemWishlist: data?.activeCustomer?.favorites?.items?.map((item:Favorite) => normalizeFavoriteProductResult(item)), + totalItems: data?.activeCustomer?.favorites?.totalItems, + ...rest + } +} + +export default useGetFavoriteProduct diff --git a/src/components/hooks/account/useGetUserOrder.tsx b/src/components/hooks/account/useGetUserOrder.tsx new file mode 100644 index 000000000..26e945abf --- /dev/null +++ b/src/components/hooks/account/useGetUserOrder.tsx @@ -0,0 +1,20 @@ +import { ActiveCustomerQuery, Order } from '@framework/schema' +import { getUserOrderQuery } from '@framework/utils/queries/get-user-order-query' +import gglFetcher from 'src/utils/gglFetcher' +import useSWR from 'swr' + +const useGetUserOrder = () => { + const { data, ...rest } = useSWR([getUserOrderQuery], gglFetcher) + + const addingItem = data?.activeCustomer?.orders.items.filter((val:Order) =>val.state == 'AddingItems'); + const arrangingPayment = data?.activeCustomer?.orders.items.filter((val:Order) =>val.state == 'ArrangingPayment'); + const cancelled = data?.activeCustomer?.orders.items.filter((val:Order) =>val.state == "Cancelled"); + return { + addingItem: addingItem, + arrangingPayment: arrangingPayment, + cancelled: cancelled, + ...rest + } +} + +export default useGetUserOrder 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/useActiveCustomer.tsx b/src/components/hooks/auth/useActiveCustomer.tsx index f0f4f6fef..30649895a 100644 --- a/src/components/hooks/auth/useActiveCustomer.tsx +++ b/src/components/hooks/auth/useActiveCustomer.tsx @@ -1,11 +1,22 @@ -import { ActiveCustomerQuery } from '@framework/schema' +import { ActiveCustomerQuery,Favorite } 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], gglFetcher) - return { customer: data?.activeCustomer, ...rest } + return { + customer: data?.activeCustomer, + userInfo:{ + firstName: data?.activeCustomer?.firstName, + lastName:data?.activeCustomer?.lastName, + email:data?.activeCustomer?.emailAddress, + phoneNumber: data?.activeCustomer?.phoneNumber, + address: data?.activeCustomer?.addresses?.[0] + }, + wishlistId: data?.activeCustomer?.favorites?.items.map((val:Favorite)=>val.product.id), + ...rest + } } export default useActiveCustomer 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 diff --git a/src/components/hooks/auth/useSignup.tsx b/src/components/hooks/auth/useSignup.tsx index 922460c77..d9b085b0e 100644 --- a/src/components/hooks/auth/useSignup.tsx +++ b/src/components/hooks/auth/useSignup.tsx @@ -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 diff --git a/src/components/hooks/cart/index.ts b/src/components/hooks/cart/index.ts new file mode 100644 index 000000000..950b32e7d --- /dev/null +++ b/src/components/hooks/cart/index.ts @@ -0,0 +1,3 @@ +export { default as useAddProductToCart } from './useAddProductToCart' +export { default as useUpdateProductInCart } from './useUpdateProductInCart' +export { default as useGetActiveOrder } from './useGetActiveOrder' \ No newline at end of file diff --git a/src/components/hooks/cart/useAddProductToCart.tsx b/src/components/hooks/cart/useAddProductToCart.tsx new file mode 100644 index 000000000..2e6c538e2 --- /dev/null +++ b/src/components/hooks/cart/useAddProductToCart.tsx @@ -0,0 +1,40 @@ +import { useState } from 'react' +import { CommonError } from 'src/domains/interfaces/CommonError' +import rawFetcher from 'src/utils/rawFetcher' +import { AddItemToOrderMutation, AddItemToOrderMutationVariables } from '@framework/schema' +import { errorMapping } from 'src/utils/errrorMapping' +import { useGetActiveOrder } from '.' +import { addItemToOrderMutation } from '@framework/utils/mutations/add-item-to-order-mutation' + +const useAddProductToCart = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { mutate } = useGetActiveOrder() + + const addProduct = (options:AddItemToOrderMutationVariables, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + rawFetcher({ + query: addItemToOrderMutation , + variables: options, + }) + .then(({ data }) => { + if (data.addItemToOrder.__typename !== "Order") { + throw CommonError.create(errorMapping(data.addItemToOrder.message), data.addItemToOrder.errorCode) + } + mutate() + fCallBack(true) + }) + .catch((error) => { + setError(error) + fCallBack(false, error.message) + }) + .finally(() => setLoading(false)) + } + + return { loading, addProduct, error } +} + +export default useAddProductToCart diff --git a/src/components/hooks/cart/useGetActiveOrder.tsx b/src/components/hooks/cart/useGetActiveOrder.tsx new file mode 100644 index 000000000..4c26a1786 --- /dev/null +++ b/src/components/hooks/cart/useGetActiveOrder.tsx @@ -0,0 +1,23 @@ +import { Cart } from '@commerce/types/cart' +import { ActiveOrderQuery } from '@framework/schema' +import { cartFragment } from '@framework/utils/fragments/cart-fragment' +import { normalizeCart } from '@framework/utils/normalize' +import { gql } from 'graphql-request' +import gglFetcher from 'src/utils/gglFetcher' +import useSWR from 'swr' +const query = gql` + query activeOrder { + activeOrder { + ...Cart + } + } + ${ cartFragment } +` + +const useGetActiveOrder = () => { + const { data, ...rest } = useSWR([query], gglFetcher) + return { order: data?.activeOrder ? normalizeCart(data!.activeOrder) : null, ...rest } + +} + +export default useGetActiveOrder diff --git a/src/components/hooks/cart/useRemoveProductInCart.tsx b/src/components/hooks/cart/useRemoveProductInCart.tsx new file mode 100644 index 000000000..d66fd4306 --- /dev/null +++ b/src/components/hooks/cart/useRemoveProductInCart.tsx @@ -0,0 +1,41 @@ +import { useState } from 'react' +import { CommonError } from 'src/domains/interfaces/CommonError' +import rawFetcher from 'src/utils/rawFetcher' +import { AdjustOrderLineMutationVariables,AdjustOrderLineMutation, RemoveOrderLineMutation, RemoveOrderLineMutationVariables } from '@framework/schema' +import { errorMapping } from 'src/utils/errrorMapping' +import { useGetActiveOrder } from '.' +import { adjustOrderLineMutation } from '@framework/utils/mutations/adjust-order-line-mutation' +import { removeOrderLineMutation } from '@framework/utils/mutations/remove-order-line-mutation' + +const useRemoveProductInCart = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { mutate } = useGetActiveOrder() + + const removeProduct = (options:RemoveOrderLineMutationVariables, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + rawFetcher({ + query: removeOrderLineMutation , + variables: options, + }) + .then(({ data }) => { + if (data.removeOrderLine.__typename !== "Order") { + throw CommonError.create(errorMapping(data.removeOrderLine.message), data.removeOrderLine.errorCode) + } + mutate() + fCallBack(true) + }) + .catch((error) => { + setError(error) + fCallBack(false, error.message) + }) + .finally(() => setLoading(false)) + } + + return { loading, removeProduct, error } +} + +export default useRemoveProductInCart diff --git a/src/components/hooks/cart/useUpdateProductInCart.tsx b/src/components/hooks/cart/useUpdateProductInCart.tsx new file mode 100644 index 000000000..b2a0423bb --- /dev/null +++ b/src/components/hooks/cart/useUpdateProductInCart.tsx @@ -0,0 +1,40 @@ +import { useState } from 'react' +import { CommonError } from 'src/domains/interfaces/CommonError' +import rawFetcher from 'src/utils/rawFetcher' +import { AdjustOrderLineMutationVariables,AdjustOrderLineMutation } from '@framework/schema' +import { errorMapping } from 'src/utils/errrorMapping' +import { useGetActiveOrder } from '.' +import { adjustOrderLineMutation } from '@framework/utils/mutations/adjust-order-line-mutation' + +const useUpdateProductInCart = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { mutate } = useGetActiveOrder() + + const updateProduct = (options:AdjustOrderLineMutationVariables, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + rawFetcher({ + query: adjustOrderLineMutation , + variables: options, + }) + .then(({ data }) => { + if (data.adjustOrderLine.__typename !== "Order") { + throw CommonError.create(errorMapping(data.adjustOrderLine.message), data.adjustOrderLine.errorCode) + } + mutate() + fCallBack(true) + }) + .catch((error) => { + setError(error) + fCallBack(false, error.message) + }) + .finally(() => setLoading(false)) + } + + return { loading, updateProduct, error } +} + +export default useUpdateProductInCart diff --git a/src/components/hooks/index.ts b/src/components/hooks/index.ts index cf83feb42..2d4c2da24 100644 --- a/src/components/hooks/index.ts +++ b/src/components/hooks/index.ts @@ -1 +1,2 @@ export { default as useModalCommon } from './useModalCommon' + diff --git a/src/components/hooks/product/index.ts b/src/components/hooks/product/index.ts index ea2afe03a..58dc37f27 100644 --- a/src/components/hooks/product/index.ts +++ b/src/components/hooks/product/index.ts @@ -1,3 +1,5 @@ export { default as useSearchProducts } from './useSearchProducts' +export { default as useToggleProductWishlist } from './useToggleProductWishlist' +export { default as useProductDetail } from './useProductDetail' diff --git a/src/components/hooks/product/index.tsx b/src/components/hooks/product/index.tsx deleted file mode 100644 index bfe1abac2..000000000 --- a/src/components/hooks/product/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default as useProductDetail } from './useProductDetail' \ No newline at end of file diff --git a/src/components/hooks/product/useToggleProductWishlist.tsx b/src/components/hooks/product/useToggleProductWishlist.tsx new file mode 100644 index 000000000..eda975272 --- /dev/null +++ b/src/components/hooks/product/useToggleProductWishlist.tsx @@ -0,0 +1,44 @@ +import { useState } from 'react' +import useGetFavoriteProduct from '../account/useGetFavoriteProduct' +import { FavoriteList } from '@framework/schema' +import fetcher from 'src/utils/fetcher' +import { CommonError } from 'src/domains/interfaces/CommonError' +import { toggleWishlistMutation } from '@framework/utils/mutations/toggle-wishlist-mutation' + +interface Props { + productId?:string +} + +const useToggleProductWishlist = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { mutate } = useGetFavoriteProduct(); + + const onToggleProductWishlist = ( + { productId }:Props , + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + fetcher({ + query: toggleWishlistMutation, + variables: { + productId + }, + }) + .then((data) => { + mutate() + fCallBack(true) + return data + }) + .catch((error) => { + setError(error) + fCallBack(false, error.message) + }) + .finally(() => setLoading(false)) + } + + return { loading, onToggleProductWishlist, error } +} + +export default useToggleProductWishlist diff --git a/src/components/hooks/useGetProductListByCollection.tsx b/src/components/hooks/useGetProductListByCollection.tsx new file mode 100644 index 000000000..49ec4c81b --- /dev/null +++ b/src/components/hooks/useGetProductListByCollection.tsx @@ -0,0 +1,47 @@ +// import { gql } from 'graphql-request' +import { useMemo, useState } from 'react' +// import useActiveCustomer from './useActiveCustomer' +import { CommonError } from 'src/domains/interfaces/CommonError' +import rawFetcher from 'src/utils/rawFetcher' +import { + CollectionList, + CollectionListOptions, + GetCollectionsQuery, + GetCollectionsQueryVariables, + LoginMutation, +} from '@framework/schema' +import { gql } from 'graphql-request' + +import { getCollectionsQuery } from '@framework/utils/queries/get-collections-query' +import useSWR from 'swr' +import gglFetcher from 'src/utils/gglFetcher' + +const query = gql` + query getCollections($option: CollectionListOptions) { + collections(options:$option) { + items { + id + name + description + slug + productVariants { + totalItems + } + parent { + id + } + children { + id + } + } + } + } +` + +const useGetProductListByCollection = (options: any) => { + + const { data, ...rest } = useSWR([query, options], gglFetcher) + return { collections: data?.collections, ...rest } +} + +export default useGetProductListByCollection diff --git a/src/components/hooks/useLocalStorage.tsx b/src/components/hooks/useLocalStorage.tsx new file mode 100644 index 000000000..b265ded9a --- /dev/null +++ b/src/components/hooks/useLocalStorage.tsx @@ -0,0 +1,36 @@ +import { useState } from "react"; + +// Hook +export function useLocalStorage(key: string, initialValue: T) { + // State to store our value + // Pass initial state function to useState so logic is only executed once + const [storedValue, setStoredValue] = useState(() => { + try { + // Get from local storage by key + const item = localStorage.getItem(key); + // Parse stored json or if none return initialValue + return item ? JSON.parse(item) : initialValue; + } catch (error) { + // If error also return initialValue + // console.log(error); + return initialValue; + } + }); + // Return a wrapped version of useState's setter function that ... + // ... persists the new value to localStorage. + const setValue = (value: T | ((val: T) => T)) => { + try { + // Allow value to be a function so we have same API as useState + const valueToStore = + value instanceof Function ? value(storedValue) : value; + // Save state + setStoredValue(valueToStore); + // Save to local storage + localStorage.setItem(key, JSON.stringify(valueToStore)); + } catch (error) { + // A more advanced implementation would handle the error case + // console.log(error); + } + }; + return [storedValue, setValue] as const; +} \ No newline at end of file diff --git a/src/components/modules/account/AccountPage/AccountPage.module.scss b/src/components/modules/account/AccountPage/AccountPage.module.scss index 89beebd86..240c369a8 100644 --- a/src/components/modules/account/AccountPage/AccountPage.module.scss +++ b/src/components/modules/account/AccountPage/AccountPage.module.scss @@ -4,7 +4,17 @@ @apply bg-background-gray; padding: 3.2rem 2rem; min-height: 70rem; - + @screen xl { + section{ + div{ + div{ + grid-template-columns: repeat(4, minmax(0, 1fr)) !important; + } + } + } + + } + @screen md { padding-left: 2.8rem; padding-right: 2.8rem; @@ -28,4 +38,5 @@ margin-bottom: 3.8rem; } } + } \ No newline at end of file diff --git a/src/components/modules/account/AccountPage/AccountPage.tsx b/src/components/modules/account/AccountPage/AccountPage.tsx index db5801235..08ed858ea 100644 --- a/src/components/modules/account/AccountPage/AccountPage.tsx +++ b/src/components/modules/account/AccountPage/AccountPage.tsx @@ -1,17 +1,17 @@ +import { QueryFavorite } from "@framework/schema" +import { useRouter } from "next/router" import React, { useEffect, useState } from "react" -import s from './AccountPage.module.scss' - import { HeadingCommon, TabPane } from "src/components/common" - +import { useGetFavoriteProduct, useGetUserOrder } from 'src/components/hooks/account' +import { useActiveCustomer } from 'src/components/hooks/auth' +import { ACCOUNT_TAB, DEFAULT_PAGE_SIZE, QUERY_KEY } from "src/utils/constanst.utils" +import { getPageFromQuery } from 'src/utils/funtion.utils' import AccountNavigation from '../AccountNavigation/AccountNavigation' +import s from './AccountPage.module.scss' import AccountInfomation from "./components/AccountInfomation/AccountInfomation" +import EditInfoModal from './components/EditInfoModal/EditInfoModal' import FavouriteProducts from "./components/FavouriteProducts/FavouriteProducts" import OrderInfomation from './components/OrderInformation/OrderInformation' -import EditInfoModal from './components/EditInfoModal/EditInfoModal' - -import { PRODUCT_CART_DATA_TEST } from 'src/utils/demo-data'; -import { ACCOUNT_TAB, QUERY_KEY } from "src/utils/constanst.utils" -import { useRouter } from "next/router" const waiting = [ { @@ -26,6 +26,8 @@ const waiting = [ } ] + + const delivering = [ { id: "NO 123456", @@ -52,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" } @@ -78,11 +70,37 @@ const getTabIndex = (tab?: string): number => { } } + +const DEFAULT_FAVORITE_ARGS = { + options:{ + skip:1, take:DEFAULT_PAGE_SIZE + } +} + const AccountPage = ({ defaultActiveContent="orders" } : AccountPageProps) => { const router = useRouter() + + const {userInfo} = useActiveCustomer(); + + const {addingItem,arrangingPayment,cancelled} = useGetUserOrder(); + + const [activeTab, setActiveTab] = useState(defaultActiveContent==="info" ? 0 : defaultActiveContent==="orders" ? 1 : 2) const [modalVisible, setModalVisible] = useState(false); + const [optionQueryFavorite, setoptionQueryFavorite] = useState(DEFAULT_FAVORITE_ARGS) + + const { itemWishlist,totalItems }= useGetFavoriteProduct(optionQueryFavorite); + + // skip + useEffect(() => { + const query = { ...DEFAULT_FAVORITE_ARGS } as QueryFavorite; + const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string); + query.options.skip = page * DEFAULT_PAGE_SIZE; + setoptionQueryFavorite(query); + },[router.query]) + + useEffect(() => { const query = router.query[QUERY_KEY.TAB] as string const index = getTabIndex(query) @@ -106,19 +124,20 @@ const AccountPage = ({ defaultActiveContent="orders" } : AccountPageProps) => { - + - + - + - + ) } -export default AccountPage \ No newline at end of file +export default AccountPage + diff --git a/src/components/modules/account/AccountPage/components/AccountInfomation/AccountInfomation.tsx b/src/components/modules/account/AccountPage/components/AccountInfomation/AccountInfomation.tsx index b025d5744..362d449ac 100644 --- a/src/components/modules/account/AccountPage/components/AccountInfomation/AccountInfomation.tsx +++ b/src/components/modules/account/AccountPage/components/AccountInfomation/AccountInfomation.tsx @@ -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 (
@@ -45,8 +48,8 @@ const AccountInfomation = ({ account, onClick }: AccountInfomationProps) => {
Shipping Infomation
- {account.address + - `, ${account.state}, ${account.city}, ${account.postalCode}`} + {account.address?.streetLine1 + + `, ${state?.name}, ${account.address?.city}, ${account.address?.postalCode}`}
{account.phoneNumber}
diff --git a/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.module.scss b/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.module.scss index 20acd257b..e098b9af7 100644 --- a/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.module.scss +++ b/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.module.scss @@ -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 { diff --git a/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx b/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx index 8289a3a93..1fea67bdc 100644 --- a/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx +++ b/src/components/modules/account/AccountPage/components/EditInfoModal/EditInfoModal.tsx @@ -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/account"; +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 (
-
- -
- -
- -
- -
- -
- -
- -
- - -
-
- + + {({ errors, touched, isValid, submitForm }) => ( +
+
+
+ +
+
+ +
-
- +
+
-
+ +
+ +
+ -
- -
+
+
+ +
-
- Cancel - Save -
+
+ +
+
+ +
+ +
+ +
+ Cancel + Save +
+
+ )} +
) diff --git a/src/components/modules/account/AccountPage/components/FavouriteProducts/FavouriteProducts.tsx b/src/components/modules/account/AccountPage/components/FavouriteProducts/FavouriteProducts.tsx index f88605242..f82266c81 100644 --- a/src/components/modules/account/AccountPage/components/FavouriteProducts/FavouriteProducts.tsx +++ b/src/components/modules/account/AccountPage/components/FavouriteProducts/FavouriteProducts.tsx @@ -1,18 +1,34 @@ -import React from "react" -import s from './FavouriteProducts.module.scss' -import {ProductList} from '../../../../../common' +import { useRouter } from 'next/router' +import React, { useState } from "react" +import { QUERY_KEY, ROUTE } from 'src/utils/constanst.utils' +import { ProductList } from '../../../../../common' import { ProductCardProps } from '../../../../../common/ProductCard/ProductCard' - - +import s from './FavouriteProducts.module.scss' interface FavouriteProductsProps { - products: ProductCardProps[]; + products: ProductCardProps[], + totalItems:number } -const FavouriteProducts = ({ products } : FavouriteProductsProps) => { +const FavouriteProducts = ({ products,totalItems } : FavouriteProductsProps) => { + const router = useRouter() + const [currentPage, setCurrentPage] = useState(0); + + function onPageChange(page:number){ + setCurrentPage(page) + router.push({ + pathname: ROUTE.ACCOUNT, + query: { + ...router.query, + [QUERY_KEY.PAGE]: page + } + }, + undefined, { shallow: true } + ) + } return (
- +
) } diff --git a/src/components/modules/account/AccountPage/components/OrderInformation/OrderInformation.tsx b/src/components/modules/account/AccountPage/components/OrderInformation/OrderInformation.tsx index 211cebe3b..9e825696e 100644 --- a/src/components/modules/account/AccountPage/components/OrderInformation/OrderInformation.tsx +++ b/src/components/modules/account/AccountPage/components/OrderInformation/OrderInformation.tsx @@ -4,50 +4,50 @@ import s from './OrderInformation.module.scss' import { TabCommon } from '../../../../../common' import TabPane from 'src/components/common/TabCommon/components/TabPane/TabPane' import DeliveryItem from '../../../DeliveryItem/DeliveryItem' +import { Order } from "@framework/schema" interface OrderInformationProps { - waiting: {id: string, products: string[], totalPrice: number}[], - delivering: {id: string, products: string[], totalPrice: number}[], - delivered: {id: string, products: string[], totalPrice: number}[], + addingItem?: Order[], + arrangingPayment?: Order[], + cancelled?: Order[], } -const OrderInformation = ({ waiting, delivering, delivered} : OrderInformationProps) => { - +const OrderInformation = ({ addingItem, arrangingPayment, cancelled} : OrderInformationProps) => { return (
Order Information
- +
{ - waiting.map((order, i) => { + addingItem?.map((order, i) => { return ( - + ) }) }
- +
{ - delivering.map((order, i) => { + arrangingPayment?.map((order, i) => { return ( - + ) }) }
- +
{ - delivered.map((order, i) => { + cancelled?.map((order, i) => { return ( - + ) }) } diff --git a/src/components/modules/account/DeliveryItem/DeliveryItem.tsx b/src/components/modules/account/DeliveryItem/DeliveryItem.tsx index b42a0f91e..71432264a 100644 --- a/src/components/modules/account/DeliveryItem/DeliveryItem.tsx +++ b/src/components/modules/account/DeliveryItem/DeliveryItem.tsx @@ -5,12 +5,13 @@ import IdAndStatus from './components/IdAndStatus/IdAndStatus' import Products from './components/Products/Products' import TotalPrice from './components/TotalPrice/TotalPrice' import ReOrder from './components/ReOrder/ReOrder' +import { OrderLine } from "@framework/schema" interface DeliveryItemProps { id: string; status: "waiting" | "delivering" | "delivered"; - products: string[]; + products?: OrderLine[]; totalPrice: number; } diff --git a/src/components/modules/account/DeliveryItem/components/Products/Products.tsx b/src/components/modules/account/DeliveryItem/components/Products/Products.tsx index fdbba2c73..0e054a171 100644 --- a/src/components/modules/account/DeliveryItem/components/Products/Products.tsx +++ b/src/components/modules/account/DeliveryItem/components/Products/Products.tsx @@ -1,19 +1,19 @@ +import { OrderLine } from "@framework/schema"; import React from "react" import s from './Products.module.scss' interface ProductsProps { - products: string[]; + products?: OrderLine[]; } const Products = ({ products } : ProductsProps) => { - - function toString(products:string[]): string { + function toString(products?:OrderLine[]): string { let strProducts = ""; - products.map((prod, i) => { + products?.map((prod, i) => { if (i === 0) { - strProducts += prod; + strProducts += prod.productVariant?.name; } else { - strProducts += `, ${prod}` + strProducts += `, ${prod.productVariant?.name}` } }); return strProducts; diff --git a/src/components/modules/blogs/BlogsList/BlogsList.tsx b/src/components/modules/blogs/BlogsList/BlogsList.tsx index 28c2ee729..dd88d073b 100644 --- a/src/components/modules/blogs/BlogsList/BlogsList.tsx +++ b/src/components/modules/blogs/BlogsList/BlogsList.tsx @@ -1,5 +1,5 @@ import { useRouter } from 'next/router' -import React, { useEffect, useState } from 'react' +import React, { useEffect, useState,useRef } from 'react' import CardBlog, { BlogCardProps } from 'src/components/common/CardBlog/CardBlog' import PaginationCommon from 'src/components/common/PaginationCommon/PaginationCommon' import { DEFAULT_BLOG_PAGE_SIZE, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils' @@ -11,18 +11,21 @@ import { ListProductCardSkeleton } from 'src/components/common' interface BlogsListProps { blogList?: BlogCardProps[], - total?: number + total?: number, + idFeatured?:string } -const DEFAULT_BLOGS_ARGS = { - excludeBlogIds: ["28"], - options:{ - skip: 1, take: DEFAULT_BLOG_PAGE_SIZE + + +const BlogsList = ({ blogList,total,idFeatured }:BlogsListProps) => { + + const DEFAULT_BLOGS_ARGS = { + excludeBlogIds: [idFeatured], + options:{ + skip: 1, take: DEFAULT_BLOG_PAGE_SIZE + } } -} -const BlogsList = ({ blogList,total }:BlogsListProps) => { - const router = useRouter(); const [initialQueryFlag, setInitialQueryFlag] = useState(true) @@ -44,13 +47,14 @@ const BlogsList = ({ blogList,total }:BlogsListProps) => { } // skip + const firstRender = useRef(true); useEffect(() => { + firstRender.current = false; const query = { ...DEFAULT_BLOGS_ARGS } as QueryBlogs; const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string); query.options.skip = page * DEFAULT_BLOG_PAGE_SIZE; setOptionQueryBlog(query); setInitialQueryFlag(false); - },[router.query]) @@ -60,7 +64,7 @@ const BlogsList = ({ blogList,total }:BlogsListProps) => { }else{ data = blogs } - console.log(blogList); + return (
diff --git a/src/components/modules/blogs/FeaturedCardBlog/FeaturedCardBlog.tsx b/src/components/modules/blogs/FeaturedCardBlog/FeaturedCardBlog.tsx index ec90633d8..a7c7708f1 100644 --- a/src/components/modules/blogs/FeaturedCardBlog/FeaturedCardBlog.tsx +++ b/src/components/modules/blogs/FeaturedCardBlog/FeaturedCardBlog.tsx @@ -1,8 +1,10 @@ import s from './FeaturedCardBlog.module.scss' import { Author, DateTime, ImgWithLink } from 'src/components/common' - +import Link from 'next/link' +import { ROUTE } from 'src/utils/constanst.utils' interface FeaturedCardBlogProps{ title?: string, + slug?:string, content?: string, imgSrc?: string, imgAuthor?: string, @@ -10,33 +12,31 @@ interface FeaturedCardBlogProps{ authorName?: string, } -const FEATURED_DATA = { - title: "Flammekueche with green asparagus", - content: "Traditionally, the Flammekueche is made with rapeseed oil, which, contrary to popular belief, is indeed an oil that can be cooked hot and is not limited to seasoning. It is important to vary the oils in the kitchen to take advantage of the benefits of each. Rapeseed oil is an oil rich in omega 3 which participate in the proper functioning of the cardiovascular system as well as in vitamins E which contributes to the protection of cells against oxidative stress. In short, oils are your friends 😉", - imgSrc: "https://user-images.githubusercontent.com/46085455/133186666-1ea8081f-4319-4617-8644-d20ed14b1825.png", - imgAuthor: "https://user-images.githubusercontent.com/46085455/133186783-d0c71d43-b7bc-44b6-b560-818c71bd162f.png", - date: "APRIL 30, 2021", - author: "Alessandro Del Piero" -} - const FeaturedCardBlog = ({ - title = FEATURED_DATA.title, - content = FEATURED_DATA.content, - imgSrc = FEATURED_DATA.imgSrc, - imgAuthor = FEATURED_DATA.imgAuthor, - date = FEATURED_DATA.date, - authorName = FEATURED_DATA.author + title, + slug, + content, + imgSrc = '', + imgAuthor = '', + date = '', + authorName = '' }: FeaturedCardBlogProps) => { return (
-
- -
+ + +
+ +
+
+
- - {title} + + + {title} +
{content}
diff --git a/src/components/modules/home/FreshProducts/FreshProducts.tsx b/src/components/modules/home/FreshProducts/FreshProducts.tsx index 6d30459f3..8a628fe2f 100644 --- a/src/components/modules/home/FreshProducts/FreshProducts.tsx +++ b/src/components/modules/home/FreshProducts/FreshProducts.tsx @@ -10,6 +10,7 @@ interface FreshProductsProps { } const FreshProducts = ({ data, collections }: FreshProductsProps) => { + const dataWithCategory = useMemo(() => { return data.map(item => { return { diff --git a/src/components/modules/home/HomeBanner/HomeBanner.tsx b/src/components/modules/home/HomeBanner/HomeBanner.tsx index ebb9af6bf..e5e678893 100644 --- a/src/components/modules/home/HomeBanner/HomeBanner.tsx +++ b/src/components/modules/home/HomeBanner/HomeBanner.tsx @@ -1,5 +1,7 @@ -import React from 'react' +import { CollectionListOptions, GetCollectionsQuery } from '@framework/schema' +import React, { useEffect, useMemo, useState } from 'react' import { Banner, StaticImage } from 'src/components/common' +import useGetProductListByCollection from 'src/components/hooks/useGetProductListByCollection' import { ROUTE } from 'src/utils/constanst.utils' import BannerImgRight from './assets/banner_full.png' import HomeBannerImg from './assets/home_banner.png' @@ -11,6 +13,10 @@ interface Props { } const HomeBanner = ({ }: Props) => { + // const variables = useMemo(() => { + // return {option: {filter: {name: {eq: "Computers" }}}} + // }, []) + // const {collections} = useGetProductListByCollection(variables) return (
diff --git a/src/components/modules/home/HomeCollection/HomeCollection.tsx b/src/components/modules/home/HomeCollection/HomeCollection.tsx index 98ab5624a..4753f9ebe 100644 --- a/src/components/modules/home/HomeCollection/HomeCollection.tsx +++ b/src/components/modules/home/HomeCollection/HomeCollection.tsx @@ -1,10 +1,13 @@ import React from 'react' +import { ProductCard } from '@commerce/types/product' import { CollectionCarcousel } from '..' import image5 from '../../../../../public/assets/images/image5.png' import image6 from '../../../../../public/assets/images/image6.png' import image7 from '../../../../../public/assets/images/image7.png' import image8 from '../../../../../public/assets/images/image8.png' -interface HomeCollectionProps {} +interface HomeCollectionProps { + data: ProductCard[] +} const dataTest = [ { name: 'Tomato', @@ -92,39 +95,39 @@ const dataTest = [ }, ] -const HomeCollection = (props: HomeCollectionProps) => { +const HomeCollection = ({data}: HomeCollectionProps) => { return (
{ +const HomeSpice = ({data}: HomeSpice) => { return (
- +
) } diff --git a/src/components/modules/product-detail/ProductInfoDetail/ProductInfoDetail.tsx b/src/components/modules/product-detail/ProductInfoDetail/ProductInfoDetail.tsx index 072c1fd56..5e51bd090 100644 --- a/src/components/modules/product-detail/ProductInfoDetail/ProductInfoDetail.tsx +++ b/src/components/modules/product-detail/ProductInfoDetail/ProductInfoDetail.tsx @@ -1,27 +1,18 @@ -import React, { useMemo } from 'react'; +import React 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 { productDetail: Product, - collections: Collection[] } -const ProductInfoDetail = ({ productDetail, collections }: Props) => { - const dataWithCategoryName = useMemo(() => { - return { - ...productDetail, - collection: getCategoryNameFromCollectionId(collections, productDetail.collectionIds ? productDetail.collectionIds[0] : undefined) - } - }, [productDetail, collections]) +const ProductInfoDetail = ({ productDetail }: Props) => { return (
- +
) } diff --git a/src/components/modules/product-detail/ProductInfoDetail/components/ProductDetailOption/ProductDetailOption.module.scss b/src/components/modules/product-detail/ProductInfoDetail/components/ProductDetailOption/ProductDetailOption.module.scss new file mode 100644 index 000000000..d22660a4a --- /dev/null +++ b/src/components/modules/product-detail/ProductInfoDetail/components/ProductDetailOption/ProductDetailOption.module.scss @@ -0,0 +1,37 @@ +.warpper{ + display: flex; + justify-content: flex-start; + flex-direction: column; + align-items: flex-start; + .name{ + margin-bottom: 0.5rem; + margin-top: 0.5rem; + font-size: 2rem; + font-weight: 700; + text-transform: capitalize; + } + .option{ + display: flex; + justify-content: flex-start; + align-items: flex-start; + // > button { + // margin-right: 1rem; + // } + } + .button { + margin: 1rem 0; + padding: 0; + div { + padding: 0.8rem 1.6rem; + margin-right: 0.8rem; + background-color: var(--gray); + border-radius: 0.8rem; + cursor: pointer; + &.active { + color: var(--white); + background-color: var(--primary); + } + } + } + +} \ No newline at end of file diff --git a/src/components/modules/product-detail/ProductInfoDetail/components/ProductDetailOption/ProductDetailOption.tsx b/src/components/modules/product-detail/ProductInfoDetail/components/ProductDetailOption/ProductDetailOption.tsx new file mode 100644 index 000000000..0d7e13c84 --- /dev/null +++ b/src/components/modules/product-detail/ProductInfoDetail/components/ProductDetailOption/ProductDetailOption.tsx @@ -0,0 +1,55 @@ +import { ProductOption, ProductOptionValues } from '@commerce/types/product' +import classNames from 'classnames' +import React, { useEffect, useState } from 'react' +import { SelectedOptions } from 'src/utils/types.utils' +import s from './ProductDetailOption.module.scss' +interface Props { + option: ProductOption + onChane: (values: SelectedOptions) => void +} + +const ProductDetailOption = React.memo(({ option, onChane }: Props) => { + const [selected, setSelected] = useState('') + useEffect(() => { + if (option) { + setSelected(option.values[0].label) + } + }, [option]) + const handleClick = (value:string) => { + setSelected(value) + onChane && onChane({[option.displayName]:value}) + } + return ( +
+
{option.displayName}:
+
+ {option.values.map((value) => { + return + })} +
+
+ ) +}) +interface ProductDetailOptionButtonProps { + value: ProductOptionValues + selected: string + onClick: (value: string) => void +} +const ProductDetailOptionButton = ({ + value, + selected, + onClick, +}: ProductDetailOptionButtonProps) => { + const handleClick = () => { + onClick && onClick(value.label) + } + return ( +
+
+ {value.label} +
+
+ ) +} + +export default ProductDetailOption 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 c3e51d46d..961d8e29b 100644 --- a/src/components/modules/product-detail/ProductInfoDetail/components/ProductInfo/ProductInfo.tsx +++ b/src/components/modules/product-detail/ProductInfoDetail/components/ProductInfo/ProductInfo.tsx @@ -1,8 +1,15 @@ import { Product } from '@commerce/types/product' -import React from 'react' +import Router from 'next/router' +import React, { useEffect, useState } from 'react' import { ButtonCommon, LabelCommon, QuanittyInput } from 'src/components/common' +import { useCartDrawer, useMessage } from 'src/components/contexts' +import { useAddProductToCart } from 'src/components/hooks/cart' import { IconBuy } from 'src/components/icons' +import { ROUTE } from 'src/utils/constanst.utils' +import { getProductVariant } from 'src/utils/funtion.utils' import { LANGUAGE } from 'src/utils/language.utils' +import { SelectedOptions } from 'src/utils/types.utils' +import ProductDetailOption from '../ProductDetailOption/ProductDetailOption' import s from './ProductInfo.module.scss' interface Props { @@ -10,11 +17,73 @@ interface Props { } const ProductInfo = ({ productInfoDetail }: Props) => { - console.log(productInfoDetail) + const [option, setOption] = useState({}) + const [quanitty, setQuanitty] = useState(0) + const [addToCartLoading, setAddToCartLoading] = useState(false) + const [buyNowLoading, setBuyNowLoading] = useState(false) + const { showMessageSuccess, showMessageError } = useMessage() + useEffect(() => { + let defaultOption:SelectedOptions = {} + productInfoDetail.options.map((option)=>{ + defaultOption[option.displayName] = option.values[0].label + return null + }) + }, [productInfoDetail]) + + const {addProduct} = useAddProductToCart() + const { openCartDrawer } = useCartDrawer() + + function handleAddToCart() { + setAddToCartLoading(true) + const variant = getProductVariant(productInfoDetail, option) + if (variant) { + addProduct({ variantId: variant.id.toString(), quantity: quanitty }, handleAddToCartCallback) + }else{ + setAddToCartLoading(false) + } + } + const handleAddToCartCallback = (isSuccess:boolean,message?:string) => { + setAddToCartLoading(false) + if(isSuccess){ + showMessageSuccess("Add to cart successfully!", 4000) + openCartDrawer && openCartDrawer() + }else{ + showMessageError(message||"Error") + } + + } + + const handleBuyNowCallback = (success:boolean,message?:string) => { + setBuyNowLoading(false) + if(success){ + Router.push(ROUTE.CHECKOUT) + }else{ + showMessageError(message||"Error") + } + } + + const handleBuyNow = () => { + setBuyNowLoading(true) + const variant = getProductVariant(productInfoDetail, option) + if (variant) { + addProduct({ variantId: variant.id.toString(), quantity: quanitty }, handleBuyNowCallback) + }else{ + setBuyNowLoading(false) + } + } + + const handleQuanittyChange = (value:number) => { + setQuanitty(value) + } + const onSelectOption = (value:SelectedOptions) => { + setOption({...option,...value}) + // let variant = getProductVariant(productInfoDetail,value) + // console.log(variant) + } return (
- {productInfoDetail.collection} + {productInfoDetail.collection?.[0]}

{productInfoDetail.name}

@@ -26,14 +95,22 @@ const ProductInfo = ({ productInfoDetail }: Props) => {
{productInfoDetail.description}
+
+ { + productInfoDetail.options.map((option)=>{ + return + }) + } + +
- +
{/* {LANGUAGE.BUTTON_LABEL.PREORDER} */} - {LANGUAGE.BUTTON_LABEL.BUY_NOW} + {LANGUAGE.BUTTON_LABEL.BUY_NOW} - + {LANGUAGE.BUTTON_LABEL.ADD_TO_CARD} diff --git a/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx b/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx index d1afde538..9e2581833 100644 --- a/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx +++ b/src/components/modules/product-detail/ReleventProducts/ReleventProducts.tsx @@ -1,5 +1,5 @@ +import { Collection } from '@commerce/types/collection'; import { ProductCard } from '@commerce/types/product'; -import { Collection } from '@framework/schema'; import React, { useMemo } from 'react'; import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo'; import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils'; diff --git a/src/components/modules/product-detail/ViewedProducts/ViewedProducts.tsx b/src/components/modules/product-detail/ViewedProducts/ViewedProducts.tsx index 87e600416..22190b49a 100644 --- a/src/components/modules/product-detail/ViewedProducts/ViewedProducts.tsx +++ b/src/components/modules/product-detail/ViewedProducts/ViewedProducts.tsx @@ -1,13 +1,22 @@ -import React from 'react'; +import { Product } from '@commerce/types/product'; +import React, { useEffect, useState } from 'react'; import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo'; -import { PRODUCT_DATA_TEST } from 'src/utils/demo-data'; - -const ViewedProducts = () => { +import { ProductCardProps } from 'src/components/common/ProductCard/ProductCard'; +import { LOCAL_STORAGE_KEY } from 'src/utils/constanst.utils' +import { normalizeProductCard } from '@framework/utils/normalize'; +import { useLocalStorage } from 'src/components/hooks/useLocalStorage'; +interface Props { + data: ProductCardProps[] +} +const ViewedProducts = ({data}:Props) => { + if (data.length===0){ + return
+ } return ( ); diff --git a/src/domains/enums/Message.ts b/src/domains/enums/Message.ts new file mode 100644 index 000000000..3d8b4e288 --- /dev/null +++ b/src/domains/enums/Message.ts @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/utils/constanst.utils.ts b/src/utils/constanst.utils.ts index 9d1fb6b66..b3ec1cd50 100644 --- a/src/utils/constanst.utils.ts +++ b/src/utils/constanst.utils.ts @@ -44,7 +44,8 @@ export const ACCOUNT_TAB = { } export const LOCAL_STORAGE_KEY = { - TOKEN: 'token' + TOKEN: 'token', + VIEWEDPRODUCT: "viewed-product" } export const QUERY_SPLIT_SEPERATOR = ',' @@ -82,46 +83,58 @@ export const DEFAULT_PAGE_SIZE = 20; export const CATEGORY = [ - { - name: 'All', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=${OPTION_ALL}`, - }, - { - name: 'Veggie', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=veggie`, - }, - { - name: 'Seafood', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=seafood`, - }, - { - name: 'Frozen', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=frozen`, - }, - { - name: 'Coffee Bean', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=coffee_bean`, - }, - { - name: 'Sauce', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=sauce`, - }, -] + { + name: 'All', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=${OPTION_ALL}`, + }, + { + name: 'Veggie', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=veggie`, + }, + { + name: 'Seafood', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=seafood`, + }, + { + name: 'Frozen', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=frozen`, + }, + { + name: 'Coffee Bean', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=coffee_bean`, + }, + { + name: 'Sauce', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=sauce`, + }, + ] + + export const BRAND = [ + { + name: 'Maggi', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=maggi`, + }, + { + name: 'Chomilex', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chomilex`, + }, + { + name: 'Chinsu', + link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chinsu`, + }, + ] -export const BRAND = [ - { - name: 'Maggi', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=maggi`, +export const FACET = { + FEATURE: { + PARENT_NAME: 'Featured', + FRESH: 'Fresh', + BEST_SELLERS: 'Best seller' }, - { - name: 'Chomilex', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chomilex`, - }, - { - name: 'Chinsu', - link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chinsu`, - }, -] + CATEGORY: { + PARENT_NAME:"category", + VEGGIE:"veggie" + } +} export const CODE_FACET_FEATURED = 'featured' export const CODE_FACET_DISCOUNT = 'discount' @@ -186,3 +199,4 @@ export const STATE_OPTIONS = [ }, ] +export const COLLECTION_SLUG_SPICE ="spice"; diff --git a/src/utils/enum.ts b/src/utils/enum.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/utils/funtion.utils.ts b/src/utils/funtion.utils.ts index 2b473c9e2..25c394487 100644 --- a/src/utils/funtion.utils.ts +++ b/src/utils/funtion.utils.ts @@ -1,8 +1,9 @@ -import { BlogList } from '@framework/schema'; +import { Collection } from '@commerce/types/collection'; import { Facet } from "@commerce/types/facet"; -import { Collection, FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d'; -import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT, PRODUCT_SORT_OPTION_VALUE } from "./constanst.utils"; -import { PromiseWithKey, SortOrder } from "./types.utils"; +import { Product, ProductCard, ProductOption, ProductOptionValues } from "@commerce/types/product"; +import { FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d'; +import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT, FACET, PRODUCT_SORT_OPTION_VALUE } from "./constanst.utils"; +import { PromiseWithKey, SelectedOptions, SortOrder } from "./types.utils"; export function isMobile() { return window.innerWidth < 768 @@ -80,6 +81,18 @@ export function getFreshFacetId(facets: Facet[]) { return freshFacetValue?.id } +export function getFacetIdByName(facets: Facet[], facetName: string, valueName:string) { + const featuredFacet = facets.find((item: Facet) => item.name === facetName) + const freshFacetValue = featuredFacet?.values.find((item: FacetValue) => item.name === valueName) + return freshFacetValue?.id +} + + +export function getAllFeaturedFacetId(facets: Facet[]) { + const featuredFacet = facets.find((item: Facet) => item.name === FACET.FEATURE.PARENT_NAME) + const rs = featuredFacet?.values.map((item: FacetValue) => item.id) + return rs || [] +} export function getAllFacetValueIdsByParentCode(facets: Facet[], code: string) { const featuredFacet = facets.find((item: Facet) => item.code === code) const rs = featuredFacet?.values.map((item: FacetValue) => item.id) @@ -134,3 +147,34 @@ export function getIdFeaturedBlog(blog: BlogList) { return blog?.id } +export const FilterOneVatiant = (products:ProductCard[]) => { + let idList:string[] = [] + let filtedProduct: ProductCard[]=[] + products.map((product:ProductCard)=>{ + if(!idList.includes(product.id)){ + filtedProduct.push(product) + idList.push(product.id) + } + }) + return filtedProduct +} + +export const convertOption = (values :ProductOptionValues[]) => { + return values.map((value)=>{ return {name:value.label,value:value.label}}) +} + +export function getProductVariant(product: Product, opts: SelectedOptions) { + const variant = product.variants?.find((variant) => { + return Object.entries(opts).every(([key, value]) => + variant.options.find((option) => { + if ( + option.__typename === 'MultipleChoiceOption' && + option.displayName.toLowerCase() === key.toLowerCase() + ) { + return option.values.find((v) => v.label.toLowerCase() === value) + } + }) + ) + }) + return variant +} diff --git a/src/utils/types.utils.ts b/src/utils/types.utils.ts index a07f23aab..52b890ee8 100644 --- a/src/utils/types.utils.ts +++ b/src/utils/types.utils.ts @@ -1,3 +1,4 @@ + export interface ProductProps { category?: string name: string @@ -57,8 +58,18 @@ export type filterContextType = { close: () => void; }; +export interface StringMap { [key: string]: string; } + +export interface FacetMap extends StringMap{ + PARENT_NAME: string +} +export interface FacetConstant{ + [key: string]: FacetMap; +} export type PromiseWithKey = { key: string promise: PromiseLike keyResult?: string, } + +export type SelectedOptions = Record \ No newline at end of file