feat: get blog list

This commit is contained in:
Quangnhankie
2021-10-19 10:32:09 +07:00
88 changed files with 2019 additions and 356 deletions

View File

@@ -17,6 +17,7 @@ export type LineItem = {
quantity: number
discounts: Discount[]
// A human-friendly unique string automatically generated from the products name
slug: string
path: string
variant: ProductVariant
options?: SelectedOption[]

View File

@@ -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 = {

View File

@@ -16,7 +16,7 @@ export default function getAllBlogsOperation({
variables?: BlogVariables
config?: Partial<VendureConfig>
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<VendureConfig>
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,

View File

@@ -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<GetAllProductsQuery>(query, {
variables,
})
return {
products: data.search.items.map((item) => normalizeSearchResult(item)),
totalItems: data.search.totalItems as number,

View File

@@ -15,7 +15,7 @@ export default function getFeaturedBlogOperation({
variables?: BlogVariables
config?: Partial<VendureConfig>
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<VendureConfig>
preview?: boolean
} = {}): Promise<{ featuredBlogs: GetFeaturedBlogQuery | any[] ,totalItems?:number }> {
} = {}): Promise<{ featuredBlogs: GetFeaturedBlogQuery[] | any[] ,totalItems?:number }> {
const config = commerce.getConfig(cfg)
const variables = {
options: {

View File

@@ -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
}

View File

@@ -1,3 +1,6 @@
import { FacetValue, UpdateAddressInput } from './schema.d';
import { ResetPassword } from './schema.d';
import { requestPasswordReset } from '@framework/schema';
import { FacetValue } from './schema.d';
export type Maybe<T> = T | null
export type Exact<T extends { [key: string]: unknown }> = {
@@ -304,6 +307,11 @@ export type MutationResetPasswordArgs = {
}
export type Address = Node & {
updateCustomerAddress:
| {
__typename?: 'Address'
id: Scalars['ID']
}
__typename?: 'Address'
id: Scalars['ID']
createdAt: Scalars['DateTime']
@@ -322,6 +330,9 @@ export type Address = Node & {
customFields?: Maybe<Scalars['JSON']>
}
export type Asset = Node & {
__typename?: 'Asset'
id: Scalars['ID']
@@ -1459,6 +1470,11 @@ export type CustomerListOptions = {
}
export type Customer = Node & {
updateCustomer:
| {
__typename?: 'Customer'
id: Scalars['ID']
}
__typename?: 'Customer'
id: Scalars['ID']
createdAt: Scalars['DateTime']
@@ -1466,7 +1482,7 @@ export type Customer = Node & {
title?: Maybe<Scalars['String']>
firstName: Scalars['String']
lastName: Scalars['String']
phoneNumber?: Maybe<Scalars['String']>
phoneNumber?: Maybe<Scalars['String']>
emailAddress: Scalars['String']
addresses?: Maybe<Array<Address>>
orders: OrderList
@@ -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<CurrentUser, 'id'>)
| ({ __typename: 'PasswordResetTokenInvalidError' } & Pick<
PasswordResetTokenInvalidError,
'errorCode' | 'message'
>)
| ({ __typename: 'PasswordResetTokenExpiredError' } & Pick<
PasswordResetTokenExpiredError,
'errorCode' | 'message'
>)
| ({ __typename: 'NativeAuthStrategyError' } & Pick<
NativeAuthStrategyError,
'errorCode' | 'message'
>)
}
export type SignupMutation = { __typename?: 'Mutation' } & {
registerCustomerAccount:
| ({ __typename: 'Success' } & Pick<Success, 'success'>)
| ({ __typename: 'MissingPasswordError' } & Pick<
MissingPasswordError,
'errorCode' | 'message'
>)
| ({ __typename: 'NativeAuthStrategyError' } & Pick<
NativeAuthStrategyError,
'errorCode' | 'message'
>)
}
export type VerifyCustomerAccountVariables = Exact<{
token: Scalars['String']
password?: Maybe<Scalars['String']>
@@ -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<Success, 'success'>)
| ({ __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<Scalars['Int']>
take?: Maybe<Scalars['Int']>
}
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<Scalars['Int']>
}>
@@ -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"
>
>
}

View File

@@ -7,6 +7,8 @@ export const searchResultFragment = /* GraphQL */ `
slug
sku
currencyCode
productVariantId
productVariantName
productAsset {
id
preview

View File

@@ -0,0 +1,14 @@
export const requestPasswordReset = /* GraphQL */ `
mutation RequestPasswordReset($emailAddress: String!) {
requestPasswordReset(emailAddress: $emailAddress) {
__typename
...on Success{
success
}
...on ErrorResult{
errorCode
message
}
}
}
`

View File

@@ -0,0 +1,15 @@
export const resetPasswordMutation = /* GraphQL */ `
mutation resetPassword($token: String!,$password: String!){
resetPassword(token: $token,password: $password){
__typename
...on CurrentUser{
id
identifier
}
...on ErrorResult{
errorCode
message
}
}
}
`

View File

@@ -0,0 +1,9 @@
export const toggleWishlistMutation = /* GraphQL */ `
mutation toggleFavorite($productId:ID!){
toggleFavorite(productId:$productId){
items{
id
}
}
}
`

View File

@@ -0,0 +1,14 @@
export const updateCustomerAddress = /* GraphQL */ `
mutation updateCustomerAddress($input: UpdateAddressInput!){
updateCustomerAddress(input: $input){
__typename
...on Address{
id
streetLine1
city
postalCode
province
}
}
}
`

View File

@@ -0,0 +1,13 @@
export const updateCustomer = /* GraphQL */ `
mutation updateCustomer($input: UpdateCustomerInput!){
updateCustomer(input:$input){
__typename
...on Customer{
id
firstName
lastName
phoneNumber
}
}
}
`

View File

@@ -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,
}
}

View File

@@ -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
}
}
}
`

View File

@@ -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
}
}
}
`

View File

@@ -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
}
}
}

View File

@@ -0,0 +1,19 @@
export const getUserOrderQuery = /* GraphQL */ `
query activeCustomer {
activeCustomer {
orders{
items{
lines{
productVariant{
name
}
quantity
}
total
state
code
}
}
}
}
`

View File

@@ -0,0 +1,16 @@
export const userInfoQuery = /* GraphQL */ `
query activeCustomer{
activeCustomer{
lastName
firstName
emailAddress
phoneNumber
addresses{
streetLine1
city
province
postalCode
}
}
}
`

View File

@@ -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(
<>
<BlogBreadCrumb />
<BlogHeading />
{/* <FeaturedCardBlog
title={blogsResult?.featuredBlogs[0]?.title}
imgSrc={blogsResult?.featuredBlogs[0]?.imageSrc ?? ''}
content={blogsResult?.featuredBlogs[0]?.description}
imgAuthor={blogsResult?.featuredBlogs[0]?.authorAvatarAsset}
authorName={blogsResult?.featuredBlogs[0]?.authorName}
<FeaturedCardBlog
title={blogsResult.featuredBlog?.[0]?.title}
slug={blogsResult.featuredBlog?.[0]?.slug}
imgSrc={blogsResult.featuredBlog?.[0]?.imageSrc ?? ''}
content={blogsResult.featuredBlog?.[0]?.description}
imgAuthor={blogsResult.featuredBlog?.[0]?.authorAvatarAsset}
authorName={blogsResult.featuredBlog?.[0]?.authorName}
date={fullDate}
/> */}
<BlogsList blogList={blogsResult.blogs} total={totalItems} />
/>
<BlogsList blogList={blogsResult.blogs} total={totalItems} idFeatured={blogsResult.featuredBlog?.[0]?.id} />
</>
)
}
@@ -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

10
pages/forgot-password.tsx Normal file
View File

@@ -0,0 +1,10 @@
import { FormForgot, Layout } from 'src/components/common'
export default function NotFound() {
return (
<div>
<FormForgot/>
</div>
)
}
NotFound.Layout = Layout

View File

@@ -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 (
<>
<HomeBanner />
<HomeFeature />
<HomeCategories />
<HomeCollection data = {veggie}/>
<FreshProducts data={freshProducts} collections={collections} />
<HomeCollection />
<HomeVideo />
<HomeSpice />
{spiceProducts.length>0 && <HomeSpice data={spiceProducts}/>}
<FeaturedProductsCarousel data={featuredProducts} featuredFacetsValue={featuredAndDiscountFacetsValue} />
<HomeCTA />
<HomeRecipe />
@@ -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,

View File

@@ -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<typeof getStaticProps>) {
interface Props {
relevantProducts: ProductCard[],
product: Product,
collections: Collection[]
}
export default function Slug({ product, relevantProducts, collections }: Props) {
const [local,setLocal] = useLocalStorage<Product[]>(LOCAL_STORAGE_KEY.VIEWEDPRODUCT, []);
const [viewed, setViewed] = useState<ProductCard[]>([])
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 <>
<ProductInfoDetail productDetail={product} collections={collections}/>
<ProductInfoDetail productDetail={product}/>
<RecipeDetail ingredients={INGREDIENT_DATA_TEST} />
<RecommendedRecipes data={RECIPE_DATA_TEST} />
<ReleventProducts data={relevantProducts} collections={collections}/>
<ViewedProducts />
<ViewedProducts data={viewed}/>
<RelevantBlogPosts data={BLOGS_DATA_TEST} title="relevent blog posts" />
</>
}
@@ -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)

10
pages/reset-password.tsx Normal file
View File

@@ -0,0 +1,10 @@
import { FormResetPassword, Layout } from 'src/components/common'
export default function NotFound() {
return (
<div>
<FormResetPassword/>
</div>
)
}
NotFound.Layout = Layout

View File

@@ -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 (
<DrawerCommon
title={`Your cart (${PRODUCT_CART_DATA_TEST.length})`}
title={`Your cart (${order?.lineItems.length})`}
visible={cartVisible}
onClose={closeCartDrawer}>
<div className={s.cartDrawer}>
<div className={s.body}>
<ProductsInCart data={PRODUCT_CART_DATA_TEST} />
<ProductsInCart data={order?.lineItems||[]} currency={order?.currency||{code:"USA"}}/>
<CartRecommendation />
</div>
<div>

View File

@@ -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 (
<div className={s.productCartItem}>
<div className={s.info}>
<Link href={`${ROUTE.PRODUCT_DETAIL}/${slug}`}>
<a href="">
<div className={s.imgWrap}>
<ImgWithLink src={imageSrc} alt={name} />
<ImgWithLink src={variant?.image?.url ?? ''} alt={name} />
</div>
</a>
</Link>
@@ -27,30 +66,32 @@ const ProductCartItem = ({ name, slug, weight, price, oldPrice, discount, imageS
<Link href={`${ROUTE.PRODUCT_DETAIL}/${slug}`}>
<a>
<div className={s.name}>
{name} {weight ? `(${weight})` : ''}
{name} {variant?.weight ? `(${variant.weight})` : ''}
</div>
</a>
</Link>
<div className={s.price}>
{
oldPrice &&
<div className={s.old}>
<span className={s.number}>{oldPrice}</span>
<LabelCommon type='discount'>{discount}</LabelCommon>
</div>
}
<div className={s.current}>{price}</div>
{discounts.length > 0 && (
<div className={s.old}>
{/* <span className={s.number}>{oldPrice}</span> */}
<LabelCommon type="discount">{discounts[0]}</LabelCommon>
</div>
)}
<div className={s.current}>{variant?.price} {currency?.code}</div>
</div>
</div>
</div>
<div className={s.actions}>
<div className={s.iconDelete}>
<div className={s.iconDelete} onClick={handleOpen}>
<IconDelete />
</div>
<QuanittyInput size='small' initValue={quantity} />
<QuanittyInput size="small" initValue={quantity} onChange={debounceFn}/>
</div>
<ModalConfirm visible={visible} onClose={handleCancel} onCancel={handleCancel} onOk={handleConfirm} loading={loading}>
Are you sure want to remove {name} form your cart
</ModalConfirm>
</div>
)
}
export default ProductCartItem;
export default ProductCartItem

View File

@@ -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 (
<ul className={s.productsInCart}>
{
data.map(item => <li key={item.name}>
<ProductCartItem
name={item.name}
slug={item.slug}
weight={item.weight}
price={item.price}
oldPrice={item.oldPrice}
discount={item.discount}
imageSrc={item.imageSrc}
quantity={item.quantity}
currency = {currency}
{...item}
/>
</li>)
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,89 @@
import { Form, Formik } from 'formik';
import React, { useRef } from 'react';
import { ButtonCommon, InputFiledInForm } from 'src/components/common';
import { useModalCommon } from 'src/components/hooks';
import useRequestPasswordReset from 'src/components/hooks/auth/useRequestPasswordReset';
import { CustomInputCommon } from 'src/utils/type.utils';
import * as Yup from 'yup';
import ModalAuthenticate from '../../ModalAuthenticate/ModalAuthenticate';
import { default as s, default as styles } from './FormForgot.module.scss';
import { useMessage } from 'src/components/contexts'
import { LANGUAGE } from 'src/utils/language.utils'
interface Props {
}
const DisplayingErrorMessagesSchema = Yup.object().shape({
email: Yup.string().email('Your email was wrong').required('Required')
})
const FormForgot = ({ }: Props) => {
const {requestPassword} = useRequestPasswordReset();
const { showMessageSuccess, showMessageError } = useMessage();
const emailRef = useRef<CustomInputCommon>(null);
const { visible: visibleModalAuthen,closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false });
const onForgot = (values: { email: string }) => {
requestPassword({email: values.email},onForgotPasswordCallBack);
}
const onForgotPasswordCallBack = (isSuccess: boolean, message?: string) => {
if (isSuccess) {
showMessageSuccess("Request forgot password successfully. Please verify your email to login.")
} else {
showMessageError(message || LANGUAGE.MESSAGE.ERROR)
}
}
return (
<section className={s.formAuthen}>
<div className={s.inner}>
<div className={s.body}>
<div className={s.title}>Forgot Password</div>
<Formik
initialValues={{
email: '',
}}
validationSchema={DisplayingErrorMessagesSchema}
onSubmit={onForgot}
>
{({ errors, touched, isValid, submitForm }) => (
<Form className="u-form">
<div className="body">
<InputFiledInForm
name="email"
placeholder="Email Address"
ref={emailRef}
error={
touched.email && errors.email
? errors.email.toString()
: ''
}
isShowIconSuccess={touched.email && !errors.email}
onEnter={isValid ? submitForm : undefined}
/>
</div>
<div className={styles.bottom}>
<div className={styles.remembered} onClick={openModalAuthen}>
I Remembered My Password?
</div>
<ButtonCommon HTMLType='submit' size="large">
Reset Password
</ButtonCommon>
</div>
</Form>
)}
</Formik>
</div>
<ModalAuthenticate visible={visibleModalAuthen} closeModal={closeModalAuthen} />
</div>
</section>
)
}
export default FormForgot;

View File

@@ -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;
}
}

View File

@@ -0,0 +1,108 @@
import { Form, Formik } from 'formik';
import React, { useRef } from 'react';
import { ButtonCommon, InputPasswordFiledInForm } from 'src/components/common';
import { useMessage } from 'src/components/contexts';
import useRequestPasswordReset from 'src/components/hooks/auth/useRequestPasswordReset';
import { LANGUAGE } from 'src/utils/language.utils';
import { CustomInputCommon } from 'src/utils/type.utils';
import * as Yup from 'yup';
import { useRouter } from 'next/router'
import { default as s, default as styles } from './FormResetPassword.module.scss';
import { useResetPassword } from 'src/components/hooks/auth';
interface Props {
}
const DisplayingErrorMessagesSchema = Yup.object().shape({
password: Yup.string()
.matches(
/^(?=.{8,})(?=.*[a-z])(?=.*[A-Z])((?=.*[0-9!@#$%^&*()\-_=+{};:,<.>]){1}).*$/,
'Must contain 8 characters with at least 1 uppercase and 1 lowercase letter and either 1 number or 1 special character.'
)
.max(30, 'Password is too long')
.required('Required'),
confirmPassword: Yup.string()
.label('Password Confirm')
.required()
.oneOf([Yup.ref('password')], 'Passwords does not match'),
})
const FormResetPassword = ({ }: Props) => {
const router = useRouter();
const {resetPassword} = useResetPassword();
const { showMessageSuccess, showMessageError } = useMessage();
const onReset = (values: {password: string }) => {
const { token } = router.query;
resetPassword({token:token,password: values.password},onResetPasswordCallBack);
}
const onResetPasswordCallBack = (isSuccess: boolean, message?: string) => {
if (isSuccess) {
showMessageSuccess("Reset password successfully. Please to login.")
} else {
showMessageError(message || LANGUAGE.MESSAGE.ERROR)
}
}
return (
<section className={s.formAuthen}>
<div className={s.inner}>
<div className={s.body}>
<div className={s.title}>Reset Password</div>
<Formik
initialValues={{
password: '',
confirmPassword: '',
}}
validationSchema={DisplayingErrorMessagesSchema}
onSubmit={onReset}
>
{({ errors, touched, isValid, submitForm }) => (
<Form className="u-form">
<div>
<InputPasswordFiledInForm
name="password"
placeholder="Password"
error={
touched.password && errors.password
? errors.password.toString()
: ''
}
/>
</div>
<div className={s.confirmPassword}>
<InputPasswordFiledInForm
name="confirmPassword"
placeholder="Password confirm"
error={
touched.confirmPassword && errors.confirmPassword
? errors.confirmPassword.toString()
: ''
}
onEnter={isValid ? submitForm : undefined}
/>
</div>
<div className={styles.passwordNote}>
Must contain 8 characters with at least 1 uppercase and 1
lowercase letter and either 1 number or 1 special character.
</div>
<div className={styles.bottom}>
<ButtonCommon HTMLType='submit' size="large">
Change Password
</ButtonCommon>
</div>
</Form>
)}
</Formik>
</div>
</div>
</section>
)
}
export default FormResetPassword;

View File

@@ -58,6 +58,10 @@ const HeaderMenu = memo(
onClick: openModalRegister,
name: 'Create account',
},
{
link: '/forgot-password',
name: 'Forgot Password',
},
],
[openModalLogin, openModalRegister]
)

View File

@@ -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(
<div className={classNames({
[s.heartToggle]: true,
[s.isToggleOn]: isActive
})}
onChange={onChange}
onClick={toggleWishlist}
>
<IconHeart />
</div>

View File

@@ -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';

View File

@@ -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 = ({
<div className="mr-4">
<ButtonCommon onClick={onCancel} type="light"> {cancelText}</ButtonCommon>
</div>
<ButtonCommon onClick={onOk}>{okText}</ButtonCommon>
<ButtonCommon onClick={onOk} loading={loading}>{okText}</ButtonCommon>
</div>
</ModalCommon>
)

View File

@@ -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 <div className={`${s.productCardWarpper} ${s.notSell}`}>
<ProductNotSell name={name} imageSrc={imageSrc} />
</div>
}
return (
<div className={s.productCardWarpper}>
<div className={s.cardTop}>
@@ -55,7 +88,7 @@ const ProductCardComponent = ({
<div className={s.cardMidTop}>
<Link href={`${ROUTE.PRODUCT_DETAIL}/${slug}`}>
<a>
<div className={s.productname}>{name} </div>
<div className={s.productname}>{productVariantName} </div>
</a>
</Link>
<div className={s.productWeight}>{weight}</div>
@@ -63,7 +96,7 @@ const ProductCardComponent = ({
<div className={s.cardMidBot}>
<div className={s.productPrice}>{price} {currencyCode}</div>
<div className={s.wishList}>
<ItemWishList />
<ItemWishList isActive={activeWishlist} id={id}/>
</div>
</div>
</div>
@@ -71,15 +104,15 @@ const ProductCardComponent = ({
{
isSingleButton ?
<div className={s.cardButton}>
<ButtonCommon type="light" icon={<IconBuy />} size='small'>Add to cart</ButtonCommon>
<ButtonCommon type="light" icon={<IconBuy />} size='small' onClick={handleAddToCart}>Add to cart</ButtonCommon>
</div>
:
<>
<div className={s.cardIcon}>
<ButtonIconBuy/>
<div className={s.cardIcon} >
<ButtonIconBuy onClick={handleAddToCart} loading={loading}/>
</div>
<div className={s.cardButton}>
<ButtonCommon type="light" size='small'>{buttonText}</ButtonCommon>
<ButtonCommon type="light" size='small' onClick={handleBuyNow}>{buttonText}</ButtonCommon>
</div>
</>
}

View File

@@ -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
<div className={s.wrapper}>
<div className={s.list}>
{
data.map((product, index) => {
return <ProductCard {...product} key={index} />
data?.map((product, index) => {
let activeWishlist = wishlistId?.findIndex((val:string) => val == product.id) !== -1;
return <ProductCard activeWishlist={activeWishlist} {...product} key={index} />
})
}
{
data.length === 0 && <div className={s.empty}>
data?.length === 0 && <div className={s.empty}>
<EmptyCommon />
<ButtonCommon onClick={handleShowAllProduct}>Show all products</ButtonCommon>
</div>
}
</div>
<div className={classNames(s.pagination, { [s.hide]: data.length === 0 })}>
<PaginationCommon defaultCurrent={defaultCurrentPage} total={total} pageSize={DEFAULT_PAGE_SIZE} onChange={handlePageChange} />
<div className={classNames(s.pagination, { [s.hide]: data?.length === 0 })}>
<PaginationCommon defaultCurrent={defaultCurrentPage} total={total ?? 0} pageSize={DEFAULT_PAGE_SIZE} onChange={handlePageChange} />
</div>
</div>
)

View File

@@ -26,10 +26,6 @@ const QuanittyInput = ({
}: QuanittyInputProps) => {
const [value, setValue] = useState<number>(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)
}
}

View File

@@ -11,7 +11,7 @@
width: 20.6rem;
.selectTrigger {
width: 20.6rem;
padding: 1.2rem 1.6rem;
padding: 1.6rem;
}
}
&.large {

View File

@@ -5,6 +5,8 @@ import s from './SelectCommon.module.scss'
import SelectOption from './SelectOption/SelectOption'
interface Props {
selected?:string|null,
initValue?:string|null,
placeholder? : string,
value?: string,
size?: 'base' | 'large',
@@ -13,16 +15,16 @@ interface Props {
onChange?: (value: string) => void,
}
const SelectCommon = ({ value, type = 'default', size = 'base', options, placeholder, onChange}: Props) => {
const [selectedName, setSelectedName] = useState<string>()
const [selectedValue, setSelectedValue] = useState<string>('')
const SelectCommon = ({selected,initValue, type = 'default', size = 'base', options, placeholder, onChange}: Props) => {
const [selectedName, setSelectedName] = useState(placeholder)
const [selectedValue, setSelectedValue] = useState('')
useEffect(() => {
setSelectedValue(value || '')
const name = options.find(item => item.value === value)?.name
setSelectedName(name)
}, [value, options])
useEffect(()=>{
const nameSelect = options.find((val)=>val.value === selected);
setSelectedName(nameSelect?.name ?? 'State');
setSelectedValue(initValue ?? '');
onChange && onChange(initValue ?? '');
},[])
const changeSelectedName = (value: string) => {
setSelectedValue(value)

View File

@@ -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'

View File

@@ -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'

View File

@@ -0,0 +1,55 @@
import { Address } from '@framework/schema'
import { updateCustomerAddress } from '@framework/utils/mutations/update-customer-address-mutation'
import { useState } from 'react'
import fetcher from 'src/utils/fetcher'
import { useActiveCustomer } from '../auth'
interface Props {
address?:string,
city?:string|null,
postalCode?:string|null,
state?:string
}
const useEditCustomerAddress = () => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const {customer,mutate} = useActiveCustomer();
const editCustomerAddress = (
{ address,city,postalCode,state}: Props,
fCallBack: (isSuccess: boolean, message?: string) => void
) => {
setError(null)
setLoading(true)
fetcher<Address>({
query: updateCustomerAddress,
variables: {
input: {
id:customer?.id,
streetLine1:address,
city,
postalCode,
province:state
},
},
}) .then((data) => {
if(data.updateCustomerAddress.__typename == 'Address'){
mutate();
fCallBack(true)
return data
}
}) .catch((error) => {
setError(error)
fCallBack(false, error.message)
})
.finally(() => setLoading(false))
}
return { loading, editCustomerAddress, error }
}
export default useEditCustomerAddress

View File

@@ -0,0 +1,51 @@
import { useState } from 'react'
import { Customer } from '@framework/schema'
import fetcher from 'src/utils/fetcher'
import { updateCustomer } from '@framework/utils/mutations/update-customer-mutation'
import { useActiveCustomer } from '../auth'
interface Props {
firstName?: string;
lastName?: string,
phoneNumber?:string,
}
const useEditUserInfo = () => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const {mutate} = useActiveCustomer();
const editUserInfo = (
{ firstName,lastName,phoneNumber}: Props,
fCallBack: (isSuccess: boolean, message?: string) => void
) => {
setError(null)
setLoading(true)
fetcher<Customer>({
query: updateCustomer,
variables: {
input: {
firstName,
lastName,
phoneNumber
},
},
})
.then((data) => {
if (data.updateCustomer.__typename == 'Customer') {
mutate();
return data
}
})
.catch((error) => {
setError(error)
fCallBack(false, error.message)
})
.finally(() => setLoading(false))
}
return { loading, editUserInfo, error }
}
export default useEditUserInfo

View File

@@ -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<ActiveCustomerQuery>([getFavoriteProductQuery, options], gglFetcher)
return {
itemWishlist: data?.activeCustomer?.favorites?.items?.map((item:Favorite) => normalizeFavoriteProductResult(item)),
totalItems: data?.activeCustomer?.favorites?.totalItems,
...rest
}
}
export default useGetFavoriteProduct

View File

@@ -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<ActiveCustomerQuery>([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

View File

@@ -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'

View File

@@ -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>([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

View File

@@ -0,0 +1,50 @@
import { useState } from 'react'
import useActiveCustomer from './useActiveCustomer'
import fetcher from 'src/utils/fetcher'
import { CommonError } from 'src/domains/interfaces/CommonError'
import { requestPasswordReset } from '@framework/utils/mutations/request-password-reset-mutation'
import { RequestPasswordReset } from '@framework/schema'
interface ForgotPassword {
email: string
}
const useRequestPasswordReset = () => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
// const { mutate } = useActiveCustomer()
const requestPassword = (
{email}: ForgotPassword,
fCallBack: (isSuccess: boolean, message?: string) => void
) => {
setError(null)
setLoading(true)
fetcher<RequestPasswordReset>({
query: requestPasswordReset,
variables: {
emailAddress: email
},
})
.then((data) => {
if (data.requestPasswordReset.__typename !== 'Success') {
throw CommonError.create(
data.requestPasswordReset.message,
data.requestPasswordReset.errorCode
)
}
// mutate()
fCallBack(true)
return data
})
.catch((error) => {
setError(error)
fCallBack(false, error.message)
})
.finally(() => setLoading(false))
}
return { loading, requestPassword, error }
}
export default useRequestPasswordReset

View File

@@ -0,0 +1,52 @@
import { useState } from 'react'
import useActiveCustomer from './useActiveCustomer'
import fetcher from 'src/utils/fetcher'
import { CommonError } from 'src/domains/interfaces/CommonError'
import { resetPasswordMutation } from '@framework/utils/mutations/reset-password-mutation'
import { ResetPasswordMutation } from '@framework/schema'
interface Props {
token?: string| string[] ,
password:string
}
const useResetPassword = () => {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
// const { mutate } = useActiveCustomer()
const resetPassword = (
{token,password}: Props,
fCallBack: (isSuccess: boolean, message?: string) => void
) => {
setError(null)
setLoading(true)
fetcher<ResetPasswordMutation>({
query: resetPasswordMutation,
variables: {
token: token,
password:password
},
})
.then((data) => {
if (data.resetPassword.__typename !== 'CurrentUser') {
throw CommonError.create(
data.resetPassword.message,
data.resetPassword.errorCode
)
}
// mutate()
fCallBack(true)
return data
})
.catch((error) => {
setError(error)
fCallBack(false, error.message)
})
.finally(() => setLoading(false))
}
return { loading, resetPassword, error }
}
export default useResetPassword

View File

@@ -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

View File

@@ -0,0 +1,3 @@
export { default as useAddProductToCart } from './useAddProductToCart'
export { default as useUpdateProductInCart } from './useUpdateProductInCart'
export { default as useGetActiveOrder } from './useGetActiveOrder'

View File

@@ -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<CommonError | null>(null)
const { mutate } = useGetActiveOrder()
const addProduct = (options:AddItemToOrderMutationVariables,
fCallBack: (isSuccess: boolean, message?: string) => void
) => {
setError(null)
setLoading(true)
rawFetcher<AddItemToOrderMutation>({
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

View File

@@ -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<ActiveOrderQuery>([query], gglFetcher)
return { order: data?.activeOrder ? normalizeCart(data!.activeOrder) : null, ...rest }
}
export default useGetActiveOrder

View File

@@ -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<CommonError | null>(null)
const { mutate } = useGetActiveOrder()
const removeProduct = (options:RemoveOrderLineMutationVariables,
fCallBack: (isSuccess: boolean, message?: string) => void
) => {
setError(null)
setLoading(true)
rawFetcher<RemoveOrderLineMutation>({
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

View File

@@ -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<CommonError | null>(null)
const { mutate } = useGetActiveOrder()
const updateProduct = (options:AdjustOrderLineMutationVariables,
fCallBack: (isSuccess: boolean, message?: string) => void
) => {
setError(null)
setLoading(true)
rawFetcher<AdjustOrderLineMutation>({
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

View File

@@ -1 +1,2 @@
export { default as useModalCommon } from './useModalCommon'

View File

@@ -1,3 +1,5 @@
export { default as useSearchProducts } from './useSearchProducts'
export { default as useToggleProductWishlist } from './useToggleProductWishlist'
export { default as useProductDetail } from './useProductDetail'

View File

@@ -1 +0,0 @@
export { default as useProductDetail } from './useProductDetail'

View File

@@ -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<Error | null>(null)
const { mutate } = useGetFavoriteProduct();
const onToggleProductWishlist = (
{ productId }:Props ,
fCallBack: (isSuccess: boolean, message?: string) => void
) => {
setError(null)
setLoading(true)
fetcher<FavoriteList>({
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

View File

@@ -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<GetCollectionsQuery>([query, options], gglFetcher)
return { collections: data?.collections, ...rest }
}
export default useGetProductListByCollection

View File

@@ -0,0 +1,36 @@
import { useState } from "react";
// Hook
export function useLocalStorage<T>(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<T>(() => {
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;
}

View File

@@ -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;
}
}
}

View File

@@ -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<QueryFavorite>(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) => {
<AccountNavigation defaultActiveIndex={activeTab}>
<TabPane tabName="Customer Information">
<AccountInfomation account={account} onClick={showModal} />
<AccountInfomation account={userInfo} onClick={showModal} />
</TabPane>
<TabPane tabName="Your Orders">
<OrderInfomation waiting={waiting} delivering={delivering} delivered={delivered} />
<OrderInfomation addingItem={addingItem} arrangingPayment={arrangingPayment} cancelled={cancelled} />
</TabPane>
<TabPane tabName="Favourite">
<FavouriteProducts products={PRODUCT_CART_DATA_TEST} />
<FavouriteProducts products={itemWishlist} totalItems={totalItems} />
</TabPane>
</AccountNavigation>
</section>
<EditInfoModal accountInfo={account} closeModal={closeModal} visible={modalVisible} />
<EditInfoModal accountInfo={userInfo} closeModal={closeModal} visible={modalVisible} />
</>
)
}
export default AccountPage
export default AccountPage

View File

@@ -6,17 +6,21 @@ import avatar from '../../assets/avatar.png'
import { ButtonCommon } from 'src/components/common'
import { useActiveCustomer } from 'src/components/hooks/auth'
import { Address } from '@framework/schema'
interface AccountProps {
name: string
email: string
address: string
state: string
city: string
postalCode: string
phoneNumber: string
export interface AccountProps {
firstName?: string
lastName?: string
email?: string
phoneNumber?:string|null
address?: Address
}
const states = [
{name: "District 1", value: "D1"},
{name: "District 2", value: "D2"},
{name: "District 3", value: "D3"}
]
interface AccountInfomationProps {
account: AccountProps
onClick: () => void
@@ -24,11 +28,10 @@ interface AccountInfomationProps {
const AccountInfomation = ({ account, onClick }: AccountInfomationProps) => {
const { customer } = useActiveCustomer()
// need to handle call back when edit account information
const showEditForm = () => onClick()
const state = states.find((val)=>val.value == account.address?.province);
return (
<section className={s.accountInfomation}>
<div className={s.avatar}>
@@ -45,8 +48,8 @@ const AccountInfomation = ({ account, onClick }: AccountInfomationProps) => {
<div className={s.shippingInfo}>Shipping Infomation</div>
<div className={s.accountAddress}>
{account.address +
`, ${account.state}, ${account.city}, ${account.postalCode}`}
{account.address?.streetLine1 +
`, ${state?.name}, ${account.address?.city}, ${account.address?.postalCode}`}
</div>
<div className={s.accountPhoneNumber}>{account.phoneNumber}</div>

View File

@@ -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 {

View File

@@ -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 (
<ModalCommon onClose={closeModal} visible={visible} title="Edit Infomation">
<section className={s.editInfoModal}>
<div className={s.input}>
<Inputcommon placeholder="Name" value={accountInfo.name} type="text" />
</div>
<div className={s.inputDisable}>
<Inputcommon placeholder="Email" value={accountInfo.email} type="email" />
</div>
<div className={s.input}>
<Inputcommon placeholder="Address" value={accountInfo.address} type="text" />
</div>
<div className={s.input}>
<Inputcommon placeholder="City" value={accountInfo.city} type="text" />
</div>
<div className="flex">
<div className={s.inputState}>
<SelectCommon type="custom" placeholder="State" options={states} />
<Formik
initialValues={
{
firstName:accountInfo.firstName,
lastName: accountInfo.lastName,
address:accountInfo.address?.streetLine1,
city: accountInfo.address?.city,
postalCode: accountInfo.address?.postalCode,
phoneNumber:accountInfo.phoneNumber
}}
validationSchema={DisplayingErrorMessagesSchema}
onSubmit={onEditUserInfo}
>
{({ errors, touched, isValid, submitForm }) => (
<Form className="u-form">
<div className={s.inputName}>
<div className={s.input}>
<InputFiledInForm
name="firstName"
placeholder="First Name"
error={
touched.firstName && errors.firstName
? errors.firstName.toString()
: ''
}
isShowIconSuccess={touched.firstName && !errors.firstName}
/>
</div>
<div className={s.input}>
<InputFiledInForm
name="lastName"
placeholder="Last Name"
error={
touched.lastName && errors.lastName
? errors.lastName.toString()
: ''
}
isShowIconSuccess={touched.lastName && !errors.lastName}
/>
</div>
</div>
<div className={s.inputPostalCode}>
<Inputcommon placeholder="Postal code" value={accountInfo.postalCode} type="text" />
<div className={s.input}>
<InputFiledInForm
name="address"
placeholder="Address"
error={
touched.address && errors.address
? errors.address.toString()
: ''
}
isShowIconSuccess={touched.address && !errors.address}
/>
</div>
</div>
<div className={s.input}>
<InputFiledInForm
name="city"
placeholder="City"
error={
touched.city && errors.city
? errors.city.toString()
: ''
}
isShowIconSuccess={touched.city && !errors.city}
/>
</div>
<div className={s.inputPhoneNumber}>
<Inputcommon placeholder="Phone number" value={accountInfo.phoneNumber} type="text" />
</div>
<div className="flex">
<div className={s.inputState}>
<SelectCommon initValue={accountInfo.address?.province} selected={accountInfo.address?.province} type="custom" onChange={state} placeholder="State" options={states} size="large"/>
</div>
<div className={s.buttons}>
<ButtonCommon onClick={closeModal} type="light" size="large" >Cancel</ButtonCommon>
<ButtonCommon onClick={saveInfo} size="large" >Save</ButtonCommon>
</div>
<div className={s.inputPostalCode}>
<InputFiledInForm
name="postalCode"
placeholder="Postal code"
error={
touched.postalCode && errors.postalCode
? errors.postalCode.toString()
: ''
}
isShowIconSuccess={touched.postalCode && !errors.postalCode}
/>
</div>
</div>
<div className={s.inputPhoneNumber}>
<InputFiledInForm
name="phoneNumber"
placeholder="Phone number"
error={
touched.phoneNumber && errors.phoneNumber
? errors.phoneNumber.toString()
: ''
}
isShowIconSuccess={touched.phoneNumber && !errors.phoneNumber}
onEnter={isValid ? submitForm : undefined}
/>
</div>
<div className={s.buttons}>
<ButtonCommon onClick={closeModal} type="light" size="large" >Cancel</ButtonCommon>
<ButtonCommon HTMLType="submit" loading={loading} size="large" >Save</ButtonCommon>
</div>
</Form>
)}
</Formik>
</section>
</ModalCommon>
)

View File

@@ -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 (
<section className={s.favouriteProducts}>
<ProductList data={products} />
<ProductList data={products} total={totalItems} onPageChange={onPageChange}/>
</section>
)
}

View File

@@ -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 (
<section className={s.orderInformation}>
<div className={s.title}>Order Information</div>
<div className={s.tabs}>
<TabCommon>
<TabPane tabName={"Wait for Comfirmation"} >
<TabPane tabName={"Adding Item"} >
<div className={s.blank}></div>
{
waiting.map((order, i) => {
addingItem?.map((order, i) => {
return (
<DeliveryItem key={order.id} id={order.id} status="waiting" products={order.products} totalPrice={order.totalPrice} />
<DeliveryItem key={order.code} id={order.code} status="waiting" products={order.lines} totalPrice={order.total} />
)
})
}
</TabPane>
<TabPane tabName={"Delivering"}>
<TabPane tabName={"Arranging Payment"}>
<div className={s.blank}></div>
{
delivering.map((order, i) => {
arrangingPayment?.map((order, i) => {
return (
<DeliveryItem key={order.id} id={order.id} status="delivering" products={order.products} totalPrice={order.totalPrice} />
<DeliveryItem key={order.id} id={order.id} status="delivering" products={order.lines} totalPrice={order.total} />
)
})
}
</TabPane>
<TabPane tabName={"Delivered"}>
<TabPane tabName={"Cancelled"}>
<div className={s.blank}></div>
{
delivered.map((order, i) => {
cancelled?.map((order, i) => {
return (
<DeliveryItem key={order.id} id={order.id} status="delivered" products={order.products} totalPrice={order.totalPrice} />
<DeliveryItem key={order.id} id={order.id} status="delivered" products={order.lines} totalPrice={order.total} />
)
})
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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<boolean>(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 (
<section>

View File

@@ -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 (
<section className={s.featuredCard}>
<div className={s.featuredCardWrapper}>
<div className={s.left}>
<ImgWithLink src={imgSrc} alt="image feature card"/>
</div>
<Link href={`${ROUTE.BLOG_DETAIL}/${slug}`}>
<a>
<div className={s.left}>
<ImgWithLink src={imgSrc} alt="image feature card"/>
</div>
</a>
</Link>
<div className={s.right}>
<div className={s.titleWrapper}>
<DateTime date={date}/>
<a className={s.title}>{title}</a>
<DateTime date={date }/>
<Link href={`${ROUTE.BLOG_DETAIL}/${slug}`}>
<a className={s.title}>{title}</a>
</Link>
</div>
<Author name={authorName} image={imgAuthor}/>
<div className={s.content}>{content}</div>

View File

@@ -10,6 +10,7 @@ interface FreshProductsProps {
}
const FreshProducts = ({ data, collections }: FreshProductsProps) => {
const dataWithCategory = useMemo(() => {
return data.map(item => {
return {

View File

@@ -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 (
<div className={s.homeBanner}>
<section className={s.left}>

View File

@@ -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 (
<div className="w-full">
<CollectionCarcousel
data={dataTest}
data={data}
itemKey="product-2"
title="VEGGIE"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
category={"veggie"}
/>
<CollectionCarcousel
data={dataTest}
data={data}
itemKey="product-3"
title="VEGGIE"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
category={"veggie"}
/>
<CollectionCarcousel
data={dataTest}
data={data}
itemKey="product-4"
title="VEGGIE"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
category={"veggie"}
/>
<CollectionCarcousel
data={dataTest}
data={data}
itemKey="product-5"
title="VEGGIE"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
category={"veggie"}
/>
<CollectionCarcousel
data={dataTest}
data={data}
itemKey="product-6"
title="VEGGIE"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."

View File

@@ -2,16 +2,16 @@ import React from 'react'
import { ProductCarousel } from 'src/components/common'
import { SPICE_DATA_TEST } from "../../../../utils/demo-data"
import s from './HomeSpice.module.scss'
import { ProductCard } from '@commerce/types/product'
interface HomeSpice {
data: ProductCard[]
}
const HomeSpice = ({}: HomeSpice) => {
const HomeSpice = ({data}: HomeSpice) => {
return (
<div className={s.homeSpiceWarpper}>
<ProductCarousel data={SPICE_DATA_TEST} itemKey="product-7"/>
<ProductCarousel data={data} itemKey="product-7"/>
</div>
)
}

View File

@@ -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 (
<section className={s.productInfoDetail}>
<ProductImgs productImage={productDetail.images}/>
<ProductInfo productInfoDetail={dataWithCategoryName}/>
<ProductInfo productInfoDetail={productDetail}/>
</section >
)
}

View File

@@ -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);
}
}
}
}

View File

@@ -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<string>('')
useEffect(() => {
if (option) {
setSelected(option.values[0].label)
}
}, [option])
const handleClick = (value:string) => {
setSelected(value)
onChane && onChane({[option.displayName]:value})
}
return (
<div className={s.warpper}>
<div className={s.name}>{option.displayName}:</div>
<div className={s.option}>
{option.values.map((value) => {
return <ProductDetailOptionButton value={value} selected={selected} onClick={handleClick} key={value.label}/>
})}
</div>
</div>
)
})
interface ProductDetailOptionButtonProps {
value: ProductOptionValues
selected: string
onClick: (value: string) => void
}
const ProductDetailOptionButton = ({
value,
selected,
onClick,
}: ProductDetailOptionButtonProps) => {
const handleClick = () => {
onClick && onClick(value.label)
}
return (
<div className={s.button}>
<div onClick={handleClick} className={classNames({ [s.active]: selected === value.label })}>
{value.label}
</div>
</div>
)
}
export default ProductDetailOption

View File

@@ -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 (
<section className={s.productInfo}>
<div className={s.info}>
<LabelCommon shape='half'>{productInfoDetail.collection}</LabelCommon>
<LabelCommon shape='half'>{productInfoDetail.collection?.[0]}</LabelCommon>
<h2 className={s.heading}>{productInfoDetail.name}</h2>
<div className={s.price}>
<div className={s.old}>
@@ -26,14 +95,22 @@ const ProductInfo = ({ productInfoDetail }: Props) => {
<div className={s.description}>
{productInfoDetail.description}
</div>
<div className={s.options}>
{
productInfoDetail.options.map((option)=>{
return <ProductDetailOption option={option} onChane={onSelectOption} key={option.displayName}/>
})
}
</div>
</div>
<div className={s.actions}>
<QuanittyInput />
<QuanittyInput value={quanitty} onChange={handleQuanittyChange}/>
<div className={s.bottom}>
{/* <ButtonCommon size='large'>{LANGUAGE.BUTTON_LABEL.PREORDER}</ButtonCommon> */}
<ButtonCommon size='large'>{LANGUAGE.BUTTON_LABEL.BUY_NOW}</ButtonCommon>
<ButtonCommon size='large' onClick={handleBuyNow} loading={buyNowLoading} disabled={addToCartLoading}>{LANGUAGE.BUTTON_LABEL.BUY_NOW}</ButtonCommon>
<ButtonCommon size='large' type='light'>
<ButtonCommon size='large' type='light' onClick={handleAddToCart} loading={addToCartLoading} disabled={buyNowLoading}>
<span className={s.buttonWithIcon}>
<IconBuy /><span className={s.label}>{LANGUAGE.BUTTON_LABEL.ADD_TO_CARD}</span>
</span>

View File

@@ -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';

View File

@@ -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 <div></div>
}
return (
<ListProductWithInfo
title="viewed Products"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
data={PRODUCT_DATA_TEST}
data={data}
hasBorderBottomMobile={true}
/>
);

View File

@@ -0,0 +1,2 @@

View File

@@ -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";

0
src/utils/enum.ts Normal file
View File

View File

@@ -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
}

View File

@@ -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<any>
keyResult?: string,
}
export type SelectedOptions = Record<string, string | null>