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 quantity: number
discounts: Discount[] discounts: Discount[]
// A human-friendly unique string automatically generated from the products name // A human-friendly unique string automatically generated from the products name
slug: string
path: string path: string
variant: ProductVariant variant: ProductVariant
options?: SelectedOption[] options?: SelectedOption[]

View File

@@ -30,6 +30,7 @@ export type ProductOptionValues = {
export type ProductVariant = { export type ProductVariant = {
id: string | number id: string | number
name:string,
options: ProductOption[] options: ProductOption[]
availableForSale?: boolean availableForSale?: boolean
} }
@@ -48,7 +49,8 @@ export type Product = {
options: ProductOption[] options: ProductOption[]
facetValueIds?: string[] facetValueIds?: string[]
collectionIds?: string[] collectionIds?: string[]
collection?: string, collection?: string[],
variants?: ProductVariant[]
} }
export type ProductCard = { export type ProductCard = {
@@ -65,6 +67,8 @@ export type ProductCard = {
collectionIds?: string[], collectionIds?: string[],
collection?: string, collection?: string,
isNotSell?: boolean isNotSell?: boolean
productVariantId?:string
productVariantName?:string
} }
export type SearchProductsBody = { export type SearchProductsBody = {

View File

@@ -16,7 +16,7 @@ export default function getAllBlogsOperation({
variables?: BlogVariables variables?: BlogVariables
config?: Partial<VendureConfig> config?: Partial<VendureConfig>
preview?: boolean preview?: boolean
}): Promise<{ blogs: GetAllBlogsQuery,totalItems:number }> }): Promise<{ blogs: GetAllBlogsQuery[],totalItems:number }>
async function getAllBlogs({ async function getAllBlogs({
query = getAllBlogsQuery, query = getAllBlogsQuery,
@@ -27,8 +27,8 @@ export default function getAllBlogsOperation({
variables?: BlogVariables variables?: BlogVariables
config?: Partial<VendureConfig> config?: Partial<VendureConfig>
preview?: boolean preview?: boolean
} = {}): Promise<{ blogs: GetAllBlogsQuery | any[] ,totalItems?:number }> { } = {}): Promise<{ blogs: GetAllBlogsQuery[] | any[] ,totalItems?:number }> {
console.log(vars.excludeBlogIds)
const config = commerce.getConfig(cfg) const config = commerce.getConfig(cfg)
const variables = { const variables = {
excludeBlogIds: vars.excludeBlogIds, excludeBlogIds: vars.excludeBlogIds,

View File

@@ -5,7 +5,7 @@ import { normalizeSearchResult } from '../../utils/normalize'
import { getAllProductsQuery } from '../../utils/queries/get-all-products-query' import { getAllProductsQuery } from '../../utils/queries/get-all-products-query'
import { OperationContext } from '@commerce/api/operations' 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({ export default function getAllProductsOperation({
commerce, commerce,
@@ -31,13 +31,13 @@ export default function getAllProductsOperation({
input: { input: {
take: vars.first, take: vars.first,
facetValueIds: vars.facetValueIds, facetValueIds: vars.facetValueIds,
groupByProduct: true, collectionSlug : vars.collectionSlug,
groupByProduct: vars.groupByProduct??true,
}, },
} }
const { data } = await config.fetch<GetAllProductsQuery>(query, { const { data } = await config.fetch<GetAllProductsQuery>(query, {
variables, variables,
}) })
return { return {
products: data.search.items.map((item) => normalizeSearchResult(item)), products: data.search.items.map((item) => normalizeSearchResult(item)),
totalItems: data.search.totalItems as number, totalItems: data.search.totalItems as number,

View File

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

View File

@@ -36,6 +36,7 @@ export default function getProductOperation({
})), })),
variants: product.variants.map((v) => ({ variants: product.variants.map((v) => ({
id: v.id, id: v.id,
name:v.name,
options: v.options.map((o) => ({ options: v.options.map((o) => ({
// This __typename property is required in order for the correct // This __typename property is required in order for the correct
// variant selection to work, see `components/product/helpers.ts` // 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 })), values: og.options.map((o) => ({ label: o.name })),
})), })),
facetValueIds: product.facetValues.map(item=> item.id), facetValueIds: product.facetValues.map(item=> item.id),
collectionIds: product.collections.map(item => item.id) collectionIds: product.collections.map(item => item.id),
collection:product.collections.map(item => item.name),
} as Product } 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'; import { FacetValue } from './schema.d';
export type Maybe<T> = T | null export type Maybe<T> = T | null
export type Exact<T extends { [key: string]: unknown }> = { export type Exact<T extends { [key: string]: unknown }> = {
@@ -304,6 +307,11 @@ export type MutationResetPasswordArgs = {
} }
export type Address = Node & { export type Address = Node & {
updateCustomerAddress:
| {
__typename?: 'Address'
id: Scalars['ID']
}
__typename?: 'Address' __typename?: 'Address'
id: Scalars['ID'] id: Scalars['ID']
createdAt: Scalars['DateTime'] createdAt: Scalars['DateTime']
@@ -322,6 +330,9 @@ export type Address = Node & {
customFields?: Maybe<Scalars['JSON']> customFields?: Maybe<Scalars['JSON']>
} }
export type Asset = Node & { export type Asset = Node & {
__typename?: 'Asset' __typename?: 'Asset'
id: Scalars['ID'] id: Scalars['ID']
@@ -1459,6 +1470,11 @@ export type CustomerListOptions = {
} }
export type Customer = Node & { export type Customer = Node & {
updateCustomer:
| {
__typename?: 'Customer'
id: Scalars['ID']
}
__typename?: 'Customer' __typename?: 'Customer'
id: Scalars['ID'] id: Scalars['ID']
createdAt: Scalars['DateTime'] createdAt: Scalars['DateTime']
@@ -1466,7 +1482,7 @@ export type Customer = Node & {
title?: Maybe<Scalars['String']> title?: Maybe<Scalars['String']>
firstName: Scalars['String'] firstName: Scalars['String']
lastName: Scalars['String'] lastName: Scalars['String']
phoneNumber?: Maybe<Scalars['String']> phoneNumber?: Maybe<Scalars['String']>
emailAddress: Scalars['String'] emailAddress: Scalars['String']
addresses?: Maybe<Array<Address>> addresses?: Maybe<Array<Address>>
orders: OrderList orders: OrderList
@@ -2344,6 +2360,7 @@ export type GetAllBlogsQuery = PaginatedList & {
} }
} }
export type GetFeaturedBlogQuery = PaginatedList & { export type GetFeaturedBlogQuery = PaginatedList & {
id:string,
featuredBlogs: { __typename?: 'BlogList' } & { featuredBlogs: { __typename?: 'BlogList' } & {
items: Array<{ __typename?: 'Blog' } & BlogList!>, items: Array<{ __typename?: 'Blog' } & BlogList!>,
'totalItems' 'totalItems'
@@ -3087,7 +3104,7 @@ export type SearchResultFragment = { __typename?: 'SearchResult' } & Pick<
SearchResult, SearchResult,
'productId' | 'sku' | 'productName' | 'description' | 'slug' | 'sku' | 'currencyCode' 'productId' | 'sku' | 'productName' | 'description' | 'slug' | 'sku' | 'currencyCode'
| 'productAsset' | 'price' | 'priceWithTax' | 'currencyCode' | 'productAsset' | 'price' | 'priceWithTax' | 'currencyCode'
| 'collectionIds' | 'facetValueIds' | 'collectionIds' | 'collectionIds' | 'productVariantId' | 'facetValueIds' | "productVariantName"
> & { > & {
productAsset?: Maybe< productAsset?: Maybe<
{ __typename?: 'SearchResultAsset' } & Pick< { __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<{ export type VerifyCustomerAccountVariables = Exact<{
token: Scalars['String'] token: Scalars['String']
password?: Maybe<Scalars['String']> password?: Maybe<Scalars['String']>
@@ -3227,8 +3274,9 @@ export type SignupMutationVariables = Exact<{
input: RegisterCustomerInput input: RegisterCustomerInput
}> }>
export type SignupMutation = { __typename?: 'Mutation' } & {
registerCustomerAccount: export type RequestPasswordReset = { __typename?: 'Mutation' } & {
requestPasswordReset:
| ({ __typename: 'Success' } & Pick<Success, 'success'>) | ({ __typename: 'Success' } & Pick<Success, 'success'>)
| ({ __typename: 'MissingPasswordError' } & Pick< | ({ __typename: 'MissingPasswordError' } & Pick<
MissingPasswordError, MissingPasswordError,
@@ -3240,17 +3288,48 @@ export type SignupMutation = { __typename?: 'Mutation' } & {
>) >)
} }
export type ActiveCustomerQueryVariables = Exact<{ [key: string]: never }> export type ActiveCustomerQueryVariables = Exact<{ [key: string]: never }>
export type ActiveCustomerQuery = { __typename?: 'Query' } & { export type ActiveCustomerQuery = { __typename?: 'Query' } & {
activeCustomer?: Maybe< activeCustomer?: Maybe<
{ __typename?: 'Customer' } & Pick< { __typename?: 'Customer' } & Pick<
Customer, 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<{ export type GetAllProductPathsQueryVariables = Exact<{
first?: Maybe<Scalars['Int']> first?: Maybe<Scalars['Int']>
}> }>
@@ -3351,7 +3430,7 @@ export type GetProductQuery = { __typename?: 'Query' } & {
variants: Array< variants: Array<
{ __typename?: 'ProductVariant' } & Pick< { __typename?: 'ProductVariant' } & Pick<
ProductVariant, ProductVariant,
'id' | 'priceWithTax' | 'currencyCode' | 'price' 'id' | 'priceWithTax' | 'currencyCode' | 'price' | "name"
> & { > & {
options: Array< options: Array<
{ __typename?: 'ProductOption' } & Pick< { __typename?: 'ProductOption' } & Pick<
@@ -3395,7 +3474,7 @@ export type GetProductQuery = { __typename?: 'Query' } & {
collections: Array< collections: Array<
{ __typename?: 'Collection' } & Pick< { __typename?: 'Collection' } & Pick<
Collection, Collection,
'id' 'id'|"name"
> >
> >
} }

View File

@@ -7,6 +7,8 @@ export const searchResultFragment = /* GraphQL */ `
slug slug
sku sku
currencyCode currencyCode
productVariantId
productVariantName
productAsset { productAsset {
id id
preview 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 { Cart } from '@commerce/types/cart'
import { ProductCard } from '@commerce/types/product' import { ProductCard, Product } from '@commerce/types/product'
import { CartFragment, SearchResultFragment } from '../schema' import { CartFragment, SearchResultFragment,Favorite } from '../schema'
export function normalizeSearchResult(item: SearchResultFragment): ProductCard { export function normalizeSearchResult(item: SearchResultFragment): ProductCard {
return { return {
@@ -10,6 +10,8 @@ export function normalizeSearchResult(item: SearchResultFragment): ProductCard {
imageSrc: item.productAsset?.preview ? item.productAsset?.preview + '?w=800&mode=crop' : '', imageSrc: item.productAsset?.preview ? item.productAsset?.preview + '?w=800&mode=crop' : '',
price: (item.priceWithTax as any).min / 100, price: (item.priceWithTax as any).min / 100,
currencyCode: item.currencyCode, currencyCode: item.currencyCode,
productVariantId: item.productVariantId,
productVariantName:item.productVariantName,
facetValueIds: item.facetValueIds, facetValueIds: item.facetValueIds,
collectionIds: item.collectionIds, 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 { export function normalizeCart(order: CartFragment): Cart {
return { return {
id: order.id.toString(), id: order.id.toString(),
@@ -35,7 +49,7 @@ export function normalizeCart(order: CartFragment): Cart {
id: l.id, id: l.id,
name: l.productVariant.name, name: l.productVariant.name,
quantity: l.quantity, quantity: l.quantity,
url: l.productVariant.product.slug, slug: l.productVariant.product.slug,
variantId: l.productVariant.id, variantId: l.productVariant.id,
productId: l.productVariant.productId, productId: l.productVariant.productId,
images: [{ url: l.featuredAsset?.preview + '?preset=thumb' || '' }], 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 */ ` export const activeCustomerQuery = /* GraphQL */ `
query activeCustomer { query activeCustomer {
activeCustomer { activeCustomer {
id id
firstName firstName
lastName lastName
emailAddress 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 { variants {
id id
name
priceWithTax priceWithTax
currencyCode currencyCode
options { options {
@@ -41,6 +42,7 @@ export const getProductQuery = /* GraphQL */ `
} }
collections { collections {
id 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 { Layout } from 'src/components/common';
import { BlogCardProps } from 'src/components/common/CardBlog/CardBlog'; import { BlogCardProps } from 'src/components/common/CardBlog/CardBlog';
import { BlogBreadCrumb, BlogHeading, BlogsList, FeaturedCardBlog } from 'src/components/modules/blogs'; 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 { getAllPromies } from 'src/utils/funtion.utils';
import { PromiseWithKey } from 'src/utils/types.utils'; import { PromiseWithKey } from 'src/utils/types.utils';
import { getIdFeaturedBlog } from 'src/utils/funtion.utils';
interface Props { interface Props {
blogsResult: { blogs?: BlogCardProps[],featuredBlogs?: BlogCardProps[] }, blogsResult: { blogs?: BlogCardProps[],featuredBlog?: BlogCardProps[] },
totalItems: number totalItems: number
} }
export default function BlogsPage({blogsResult,totalItems}:Props) { export default function BlogsPage({blogsResult,totalItems}:Props) {
let date = new Date(blogsResult.featuredBlog?.[0]?.createdAt ?? '' );
// let date = new Date(blogsResult?.featuredBlogs[0]?.createdAt ?? '' ); let fullDate = date.toLocaleString('en-us', { month: 'long' }) + " " + date.getDate()+","+date.getFullYear();
// let fullDate = date.toLocaleString('en-us', { month: 'long' }) + " " + date.getDate()+","+date.getFullYear();
return( return(
<> <>
<BlogBreadCrumb /> <BlogBreadCrumb />
<BlogHeading /> <BlogHeading />
{/* <FeaturedCardBlog <FeaturedCardBlog
title={blogsResult?.featuredBlogs[0]?.title} title={blogsResult.featuredBlog?.[0]?.title}
imgSrc={blogsResult?.featuredBlogs[0]?.imageSrc ?? ''} slug={blogsResult.featuredBlog?.[0]?.slug}
content={blogsResult?.featuredBlogs[0]?.description} imgSrc={blogsResult.featuredBlog?.[0]?.imageSrc ?? ''}
imgAuthor={blogsResult?.featuredBlogs[0]?.authorAvatarAsset} content={blogsResult.featuredBlog?.[0]?.description}
authorName={blogsResult?.featuredBlogs[0]?.authorName} imgAuthor={blogsResult.featuredBlog?.[0]?.authorAvatarAsset}
authorName={blogsResult.featuredBlog?.[0]?.authorName}
date={fullDate} 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, config,
preview, preview,
}) })
// console.log(featuredBlogs[0].id);
// promisesWithKey.push({ key: 'blogsResult', promise: FeaturedBlogPromise })
// Blogs // Blogs
const idBlog = featuredBlogs[0].id; const idFeaturedBlog = featuredBlogs[0].id;
const blogsPromise = commerce.getAllBlogs({ const blogsPromise = commerce.getAllBlogs({
variables: { variables: {
excludeBlogIds: [idBlog], excludeBlogIds: [idFeaturedBlog],
take: DEFAULT_BLOG_PAGE_SIZE take: DEFAULT_BLOG_PAGE_SIZE
}, },
config, config,
@@ -71,7 +64,6 @@ export async function getStaticProps({
}) })
promisesWithKey.push({ key: 'blogsResult', promise: blogsPromise }) promisesWithKey.push({ key: 'blogsResult', promise: blogsPromise })
try { try {
const promises = getAllPromies(promisesWithKey) const promises = getAllPromies(promisesWithKey)
@@ -81,8 +73,9 @@ export async function getStaticProps({
props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index] props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index]
return null return null
}) })
props['blogsResult']['featuredBlog'] = featuredBlogs;
props['blogsResult']['featuredBlog'] = featuredBlogs;
return { return {
props, props,
revalidate: 60 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 { Layout } from 'src/components/common';
import { FeaturedProductsCarousel, FreshProducts, HomeBanner, HomeCategories, HomeCollection, HomeCTA, HomeFeature, HomeRecipe, HomeSubscribe, HomeVideo } from 'src/components/modules/home'; 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 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 { getAllFacetValueIdsByParentCode, getAllFacetValuesForFeatuedProducts, getAllPromies, getFreshFacetId } from 'src/utils/funtion.utils';
import { PromiseWithKey } from 'src/utils/types.utils'; import { PromiseWithKey } from 'src/utils/types.utils';
@@ -16,19 +18,23 @@ interface Props {
freshProducts: ProductCard[], freshProducts: ProductCard[],
featuredProducts: ProductCard[], featuredProducts: ProductCard[],
collections: Collection[] collections: Collection[]
spiceProducts:ProductCard[]
veggie: ProductCard[],
} }
export default function Home({ featuredAndDiscountFacetsValue, export default function Home({ featuredAndDiscountFacetsValue, veggie,
freshProducts, featuredProducts, freshProducts, featuredProducts,
collections }: Props) { collections,spiceProducts }: Props) {
return ( return (
<> <>
<HomeBanner /> <HomeBanner />
<HomeFeature /> <HomeFeature />
<HomeCategories /> <HomeCategories />
<HomeCollection data = {veggie}/>
<FreshProducts data={freshProducts} collections={collections} /> <FreshProducts data={freshProducts} collections={collections} />
<HomeCollection />
<HomeVideo /> <HomeVideo />
<HomeSpice /> {spiceProducts.length>0 && <HomeSpice data={spiceProducts}/>}
<FeaturedProductsCarousel data={featuredProducts} featuredFacetsValue={featuredAndDiscountFacetsValue} /> <FeaturedProductsCarousel data={featuredProducts} featuredFacetsValue={featuredAndDiscountFacetsValue} />
<HomeCTA /> <HomeCTA />
<HomeRecipe /> <HomeRecipe />
@@ -55,6 +61,8 @@ export async function getStaticProps({
config, config,
preview, preview,
}) })
props.featuredAndDiscountFacetsValue = getAllFacetValuesForFeatuedProducts(facets) props.featuredAndDiscountFacetsValue = getAllFacetValuesForFeatuedProducts(facets)
// fresh products // fresh products
@@ -72,7 +80,20 @@ export async function getStaticProps({
props.freshProducts = [] 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 // featured products
const allFeaturedFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_FEATURED) const allFeaturedFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_FEATURED)
const allDiscountFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_DISCOUNT) const allDiscountFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_DISCOUNT)
@@ -99,15 +120,24 @@ export async function getStaticProps({
}) })
promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' }) 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 { try {
const promises = getAllPromies(promisesWithKey) const promises = getAllPromies(promisesWithKey)
const rs = await Promise.all(promises) const rs = await Promise.all(promises)
promisesWithKey.map((item, index) => { 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 null
}) })
return { return {
props, props,
revalidate: 60, 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 commerce from '@lib/api/commerce'
import { GetStaticPathsContext, GetStaticPropsContext, InferGetStaticPropsType } from 'next' import { GetStaticPathsContext, GetStaticPropsContext, InferGetStaticPropsType } from 'next'
import { useEffect, useState } from 'react'
import { Layout, RecipeDetail, RecommendedRecipes, RelevantBlogPosts } from 'src/components/common' 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 { 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 { BLOGS_DATA_TEST, INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data'
import { getAllPromies } from 'src/utils/funtion.utils' import { getAllPromies } from 'src/utils/funtion.utils'
import { normalizeProductCard } from '@framework/utils/normalize';
import { PromiseWithKey } from 'src/utils/types.utils' import { PromiseWithKey } from 'src/utils/types.utils'
interface Props {
export default function Slug({ product, relevantProducts, collections }: InferGetStaticPropsType<typeof getStaticProps>) { 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 <> return <>
<ProductInfoDetail productDetail={product} collections={collections}/> <ProductInfoDetail productDetail={product}/>
<RecipeDetail ingredients={INGREDIENT_DATA_TEST} /> <RecipeDetail ingredients={INGREDIENT_DATA_TEST} />
<RecommendedRecipes data={RECIPE_DATA_TEST} /> <RecommendedRecipes data={RECIPE_DATA_TEST} />
<ReleventProducts data={relevantProducts} collections={collections}/> <ReleventProducts data={relevantProducts} collections={collections}/>
<ViewedProducts /> <ViewedProducts data={viewed}/>
<RelevantBlogPosts data={BLOGS_DATA_TEST} title="relevent blog posts" /> <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' }) promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' })
try { try {
const promises = getAllPromies(promisesWithKey) const promises = getAllPromies(promisesWithKey)
const rs = await Promise.all(promises) 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 React from 'react';
import { useCartDrawer } from 'src/components/contexts'; 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 { PRODUCT_CART_DATA_TEST } from 'src/utils/demo-data';
import { DrawerCommon } from '..'; import { DrawerCommon } from '..';
import s from './CartDrawer.module.scss'; import s from './CartDrawer.module.scss';
@@ -14,14 +16,15 @@ interface Props {
const CartDrawer = ({ }: Props) => { const CartDrawer = ({ }: Props) => {
const { cartVisible, closeCartDrawer } = useCartDrawer() const { cartVisible, closeCartDrawer } = useCartDrawer()
const {order} = useGetActiveOrder()
return ( return (
<DrawerCommon <DrawerCommon
title={`Your cart (${PRODUCT_CART_DATA_TEST.length})`} title={`Your cart (${order?.lineItems.length})`}
visible={cartVisible} visible={cartVisible}
onClose={closeCartDrawer}> onClose={closeCartDrawer}>
<div className={s.cartDrawer}> <div className={s.cartDrawer}>
<div className={s.body}> <div className={s.body}>
<ProductsInCart data={PRODUCT_CART_DATA_TEST} /> <ProductsInCart data={order?.lineItems||[]} currency={order?.currency||{code:"USA"}}/>
<CartRecommendation /> <CartRecommendation />
</div> </div>
<div> <div>

View File

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

View File

@@ -1,25 +1,21 @@
import { LineItem } from '@commerce/types/cart';
import React from 'react'; import React from 'react';
import ProductCartItem, { ProductCartItempProps } from '../ProductCartItem/ProductCartItem'; import ProductCartItem, { ProductCartItempProps } from '../ProductCartItem/ProductCartItem';
import s from './ProductsInCart.module.scss'; import s from './ProductsInCart.module.scss';
interface Props { interface Props {
data: ProductCartItempProps[] data: LineItem[]
currency: { code: string }
} }
const ProductsInCart = ({ data }: Props) => { const ProductsInCart = ({ data, currency }: Props) => {
return ( return (
<ul className={s.productsInCart}> <ul className={s.productsInCart}>
{ {
data.map(item => <li key={item.name}> data.map(item => <li key={item.name}>
<ProductCartItem <ProductCartItem
name={item.name} currency = {currency}
slug={item.slug} {...item}
weight={item.weight}
price={item.price}
oldPrice={item.oldPrice}
discount={item.discount}
imageSrc={item.imageSrc}
quantity={item.quantity}
/> />
</li>) </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, onClick: openModalRegister,
name: 'Create account', name: 'Create account',
}, },
{
link: '/forgot-password',
name: 'Forgot Password',
},
], ],
[openModalLogin, openModalRegister] [openModalLogin, openModalRegister]
) )

View File

@@ -2,19 +2,38 @@ import classNames from 'classnames'
import IconHeart from 'src/components/icons/IconHeart' import IconHeart from 'src/components/icons/IconHeart'
import React, { memo } from 'react' import React, { memo } from 'react'
import s from './ItemWishList.module.scss' 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 { interface Props {
id:string,
isActive?: boolean, 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( return(
<div className={classNames({ <div className={classNames({
[s.heartToggle]: true, [s.heartToggle]: true,
[s.isToggleOn]: isActive [s.isToggleOn]: isActive
})} })}
onChange={onChange} onChange={onChange}
onClick={toggleWishlist}
> >
<IconHeart /> <IconHeart />
</div> </div>

View File

@@ -1,5 +1,4 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { route } from 'next/dist/server/router';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import s from './MenuFilterItem.module.scss'; import s from './MenuFilterItem.module.scss';

View File

@@ -5,6 +5,7 @@ import s from './ModalConfirm.module.scss'
interface ModalConfirmProps extends ModalCommonProps { interface ModalConfirmProps extends ModalCommonProps {
okText?: String okText?: String
cancelText?: String cancelText?: String
loading?:boolean
onOk?: () => void onOk?: () => void
onCancel?: () => void onCancel?: () => void
} }
@@ -16,6 +17,7 @@ const ModalConfirm = ({
onCancel, onCancel,
children, children,
title = 'Confirm', title = 'Confirm',
loading,
...props ...props
}: ModalConfirmProps) => { }: ModalConfirmProps) => {
return ( return (
@@ -25,7 +27,7 @@ const ModalConfirm = ({
<div className="mr-4"> <div className="mr-4">
<ButtonCommon onClick={onCancel} type="light"> {cancelText}</ButtonCommon> <ButtonCommon onClick={onCancel} type="light"> {cancelText}</ButtonCommon>
</div> </div>
<ButtonCommon onClick={onOk}>{okText}</ButtonCommon> <ButtonCommon onClick={onOk} loading={loading}>{okText}</ButtonCommon>
</div> </div>
</ModalCommon> </ModalCommon>
) )

View File

@@ -10,13 +10,17 @@ import ItemWishList from '../ItemWishList/ItemWishList'
import LabelCommon from '../LabelCommon/LabelCommon' import LabelCommon from '../LabelCommon/LabelCommon'
import s from './ProductCard.module.scss' import s from './ProductCard.module.scss'
import ProductNotSell from './ProductNotSell/ProductNotSell' 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 { export interface ProductCardProps extends ProductCard {
buttonText?: string buttonText?: string
isSingleButton?: boolean, isSingleButton?: boolean,
activeWishlist?:boolean
} }
const ProductCardComponent = ({ const ProductCardComponent = ({
id,
collection, collection,
name, name,
slug, slug,
@@ -27,13 +31,42 @@ const ProductCardComponent = ({
imageSrc, imageSrc,
isNotSell, isNotSell,
isSingleButton, isSingleButton,
productVariantId,
productVariantName,
activeWishlist
}: ProductCardProps) => { }: 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) { if (isNotSell) {
return <div className={`${s.productCardWarpper} ${s.notSell}`}> return <div className={`${s.productCardWarpper} ${s.notSell}`}>
<ProductNotSell name={name} imageSrc={imageSrc} /> <ProductNotSell name={name} imageSrc={imageSrc} />
</div> </div>
} }
return ( return (
<div className={s.productCardWarpper}> <div className={s.productCardWarpper}>
<div className={s.cardTop}> <div className={s.cardTop}>
@@ -55,7 +88,7 @@ const ProductCardComponent = ({
<div className={s.cardMidTop}> <div className={s.cardMidTop}>
<Link href={`${ROUTE.PRODUCT_DETAIL}/${slug}`}> <Link href={`${ROUTE.PRODUCT_DETAIL}/${slug}`}>
<a> <a>
<div className={s.productname}>{name} </div> <div className={s.productname}>{productVariantName} </div>
</a> </a>
</Link> </Link>
<div className={s.productWeight}>{weight}</div> <div className={s.productWeight}>{weight}</div>
@@ -63,7 +96,7 @@ const ProductCardComponent = ({
<div className={s.cardMidBot}> <div className={s.cardMidBot}>
<div className={s.productPrice}>{price} {currencyCode}</div> <div className={s.productPrice}>{price} {currencyCode}</div>
<div className={s.wishList}> <div className={s.wishList}>
<ItemWishList /> <ItemWishList isActive={activeWishlist} id={id}/>
</div> </div>
</div> </div>
</div> </div>
@@ -71,15 +104,15 @@ const ProductCardComponent = ({
{ {
isSingleButton ? isSingleButton ?
<div className={s.cardButton}> <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>
: :
<> <>
<div className={s.cardIcon}> <div className={s.cardIcon} >
<ButtonIconBuy/> <ButtonIconBuy onClick={handleAddToCart} loading={loading}/>
</div> </div>
<div className={s.cardButton}> <div className={s.cardButton}>
<ButtonCommon type="light" size='small'>{buttonText}</ButtonCommon> <ButtonCommon type="light" size='small' onClick={handleBuyNow}>{buttonText}</ButtonCommon>
</div> </div>
</> </>
} }

View File

@@ -1,12 +1,12 @@
import classNames from 'classnames' import classNames from 'classnames'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import React from 'react' import React from 'react'
import { useActiveCustomer } from 'src/components/hooks/auth'
import { DEFAULT_PAGE_SIZE, ROUTE } from 'src/utils/constanst.utils' import { DEFAULT_PAGE_SIZE, ROUTE } from 'src/utils/constanst.utils'
import { ButtonCommon, EmptyCommon } from '..' import { ButtonCommon, EmptyCommon } from '..'
import PaginationCommon from '../PaginationCommon/PaginationCommon' import PaginationCommon from '../PaginationCommon/PaginationCommon'
import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard' import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard'
import s from "./ProductList.module.scss" import s from "./ProductList.module.scss"
interface ProductListProps { interface ProductListProps {
data: ProductCardProps[], data: ProductCardProps[],
total?: number, total?: number,
@@ -14,8 +14,10 @@ interface ProductListProps {
onPageChange?: (page: number) => void 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 router = useRouter()
const {wishlistId } = useActiveCustomer();
const handlePageChange = (page: number) => { const handlePageChange = (page: number) => {
onPageChange && onPageChange(page) onPageChange && onPageChange(page)
} }
@@ -32,19 +34,20 @@ const ProductList = ({ data, total = data.length, defaultCurrentPage, onPageChan
<div className={s.wrapper}> <div className={s.wrapper}>
<div className={s.list}> <div className={s.list}>
{ {
data.map((product, index) => { data?.map((product, index) => {
return <ProductCard {...product} key={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 /> <EmptyCommon />
<ButtonCommon onClick={handleShowAllProduct}>Show all products</ButtonCommon> <ButtonCommon onClick={handleShowAllProduct}>Show all products</ButtonCommon>
</div> </div>
} }
</div> </div>
<div className={classNames(s.pagination, { [s.hide]: data.length === 0 })}> <div className={classNames(s.pagination, { [s.hide]: data?.length === 0 })}>
<PaginationCommon defaultCurrent={defaultCurrentPage} total={total} pageSize={DEFAULT_PAGE_SIZE} onChange={handlePageChange} /> <PaginationCommon defaultCurrent={defaultCurrentPage} total={total ?? 0} pageSize={DEFAULT_PAGE_SIZE} onChange={handlePageChange} />
</div> </div>
</div> </div>
) )

View File

@@ -26,10 +26,6 @@ const QuanittyInput = ({
}: QuanittyInputProps) => { }: QuanittyInputProps) => {
const [value, setValue] = useState<number>(0) const [value, setValue] = useState<number>(0)
useEffect(() => {
onChange && onChange(value)
}, [value])
useEffect(() => { useEffect(() => {
initValue && setValue(initValue) initValue && setValue(initValue)
}, [initValue]) }, [initValue])
@@ -37,16 +33,20 @@ const QuanittyInput = ({
const onPlusClick = () => { const onPlusClick = () => {
if (max && value + step > max) { if (max && value + step > max) {
setValue(max) setValue(max)
onChange && onChange(max)
} else { } else {
setValue(value + step) setValue(value + step)
onChange && onChange(value + step)
} }
} }
const onMinusClick = () => { const onMinusClick = () => {
if (min && value - step < min) { if (min && value - step < min) {
setValue(min) setValue(min)
onChange && onChange(min)
} else { } else {
setValue(value - step) setValue(value - step)
onChange && onChange(value - step)
} }
} }
@@ -54,10 +54,13 @@ const QuanittyInput = ({
let value = Number(e.target.value) || 0 let value = Number(e.target.value) || 0
if (min && value < min) { if (min && value < min) {
setValue(min) setValue(min)
onChange && onChange(min)
} else if (max && value > max) { } else if (max && value > max) {
setValue(max) setValue(max)
onChange && onChange(max)
} else { } else {
setValue(value) setValue(value)
onChange && onChange(value)
} }
} }

View File

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

View File

@@ -5,6 +5,8 @@ import s from './SelectCommon.module.scss'
import SelectOption from './SelectOption/SelectOption' import SelectOption from './SelectOption/SelectOption'
interface Props { interface Props {
selected?:string|null,
initValue?:string|null,
placeholder? : string, placeholder? : string,
value?: string, value?: string,
size?: 'base' | 'large', size?: 'base' | 'large',
@@ -13,16 +15,16 @@ interface Props {
onChange?: (value: string) => void, onChange?: (value: string) => void,
} }
const SelectCommon = ({ value, type = 'default', size = 'base', options, placeholder, onChange}: Props) => { const SelectCommon = ({selected,initValue, type = 'default', size = 'base', options, placeholder, onChange}: Props) => {
const [selectedName, setSelectedName] = useState<string>() const [selectedName, setSelectedName] = useState(placeholder)
const [selectedValue, setSelectedValue] = useState<string>('') const [selectedValue, setSelectedValue] = useState('')
useEffect(() => { useEffect(()=>{
setSelectedValue(value || '') const nameSelect = options.find((val)=>val.value === selected);
setSelectedName(nameSelect?.name ?? 'State');
const name = options.find(item => item.value === value)?.name setSelectedValue(initValue ?? '');
setSelectedName(name) onChange && onChange(initValue ?? '');
}, [value, options]) },[])
const changeSelectedName = (value: string) => { const changeSelectedName = (value: string) => {
setSelectedValue(value) 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 InputPasswordFiledInForm} from './InputPasswordFiledInForm/InputPasswordFiledInForm'
export { default as InputFiledInForm} from './InputFiledInForm/InputFiledInForm' export { default as InputFiledInForm} from './InputFiledInForm/InputFiledInForm'
export { default as MessageCommon} from './MessageCommon/MessageCommon' 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 ProductCardSkeleton} from './ProductCardSkeleton/ProductCardSkeleton'
export { default as ListProductCardSkeleton} from './ListProductCardSkeleton/ListProductCardSkeleton' 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 useLogout } from './useLogout'
export { default as useVerifyCustomer } from './useVerifyCustomer' export { default as useVerifyCustomer } from './useVerifyCustomer'
export { default as useActiveCustomer } from './useActiveCustomer' 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 { activeCustomerQuery } from '@framework/utils/queries/active-customer-query'
import gglFetcher from 'src/utils/gglFetcher' import gglFetcher from 'src/utils/gglFetcher'
import useSWR from 'swr' import useSWR from 'swr'
const useActiveCustomer = () => { const useActiveCustomer = () => {
const { data, ...rest } = useSWR<ActiveCustomerQuery>([activeCustomerQuery], gglFetcher) 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 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 fetcher from 'src/utils/fetcher'
import { CommonError } from 'src/domains/interfaces/CommonError' import { CommonError } from 'src/domains/interfaces/CommonError'
import { signupMutation } from '@framework/utils/mutations/sign-up-mutation' import { signupMutation } from '@framework/utils/mutations/sign-up-mutation'
interface SignupInput { interface SignupInput {
email: string email: string
firstName?: 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' export { default as useModalCommon } from './useModalCommon'

View File

@@ -1,3 +1,5 @@
export { default as useSearchProducts } from './useSearchProducts' 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; @apply bg-background-gray;
padding: 3.2rem 2rem; padding: 3.2rem 2rem;
min-height: 70rem; min-height: 70rem;
@screen xl {
section{
div{
div{
grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
}
}
}
}
@screen md { @screen md {
padding-left: 2.8rem; padding-left: 2.8rem;
padding-right: 2.8rem; padding-right: 2.8rem;
@@ -28,4 +38,5 @@
margin-bottom: 3.8rem; 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 React, { useEffect, useState } from "react"
import s from './AccountPage.module.scss'
import { HeadingCommon, TabPane } from "src/components/common" 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 AccountNavigation from '../AccountNavigation/AccountNavigation'
import s from './AccountPage.module.scss'
import AccountInfomation from "./components/AccountInfomation/AccountInfomation" import AccountInfomation from "./components/AccountInfomation/AccountInfomation"
import EditInfoModal from './components/EditInfoModal/EditInfoModal'
import FavouriteProducts from "./components/FavouriteProducts/FavouriteProducts" import FavouriteProducts from "./components/FavouriteProducts/FavouriteProducts"
import OrderInfomation from './components/OrderInformation/OrderInformation' 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 = [ const waiting = [
{ {
@@ -26,6 +26,8 @@ const waiting = [
} }
] ]
const delivering = [ const delivering = [
{ {
id: "NO 123456", 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 { interface AccountPageProps {
defaultActiveContent?: "info" | "orders" | "favorites" 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 AccountPage = ({ defaultActiveContent="orders" } : AccountPageProps) => {
const router = useRouter() const router = useRouter()
const {userInfo} = useActiveCustomer();
const {addingItem,arrangingPayment,cancelled} = useGetUserOrder();
const [activeTab, setActiveTab] = useState(defaultActiveContent==="info" ? 0 : defaultActiveContent==="orders" ? 1 : 2) const [activeTab, setActiveTab] = useState(defaultActiveContent==="info" ? 0 : defaultActiveContent==="orders" ? 1 : 2)
const [modalVisible, setModalVisible] = useState(false); 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(() => { useEffect(() => {
const query = router.query[QUERY_KEY.TAB] as string const query = router.query[QUERY_KEY.TAB] as string
const index = getTabIndex(query) const index = getTabIndex(query)
@@ -106,19 +124,20 @@ const AccountPage = ({ defaultActiveContent="orders" } : AccountPageProps) => {
<AccountNavigation defaultActiveIndex={activeTab}> <AccountNavigation defaultActiveIndex={activeTab}>
<TabPane tabName="Customer Information"> <TabPane tabName="Customer Information">
<AccountInfomation account={account} onClick={showModal} /> <AccountInfomation account={userInfo} onClick={showModal} />
</TabPane> </TabPane>
<TabPane tabName="Your Orders"> <TabPane tabName="Your Orders">
<OrderInfomation waiting={waiting} delivering={delivering} delivered={delivered} /> <OrderInfomation addingItem={addingItem} arrangingPayment={arrangingPayment} cancelled={cancelled} />
</TabPane> </TabPane>
<TabPane tabName="Favourite"> <TabPane tabName="Favourite">
<FavouriteProducts products={PRODUCT_CART_DATA_TEST} /> <FavouriteProducts products={itemWishlist} totalItems={totalItems} />
</TabPane> </TabPane>
</AccountNavigation> </AccountNavigation>
</section> </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 { ButtonCommon } from 'src/components/common'
import { useActiveCustomer } from 'src/components/hooks/auth' import { useActiveCustomer } from 'src/components/hooks/auth'
import { Address } from '@framework/schema'
interface AccountProps { export interface AccountProps {
name: string firstName?: string
email: string lastName?: string
address: string email?: string
state: string phoneNumber?:string|null
city: string address?: Address
postalCode: string
phoneNumber: string
} }
const states = [
{name: "District 1", value: "D1"},
{name: "District 2", value: "D2"},
{name: "District 3", value: "D3"}
]
interface AccountInfomationProps { interface AccountInfomationProps {
account: AccountProps account: AccountProps
onClick: () => void onClick: () => void
@@ -24,11 +28,10 @@ interface AccountInfomationProps {
const AccountInfomation = ({ account, onClick }: AccountInfomationProps) => { const AccountInfomation = ({ account, onClick }: AccountInfomationProps) => {
const { customer } = useActiveCustomer() const { customer } = useActiveCustomer()
// need to handle call back when edit account information // need to handle call back when edit account information
const showEditForm = () => onClick() const showEditForm = () => onClick()
const state = states.find((val)=>val.value == account.address?.province);
return ( return (
<section className={s.accountInfomation}> <section className={s.accountInfomation}>
<div className={s.avatar}> <div className={s.avatar}>
@@ -45,8 +48,8 @@ const AccountInfomation = ({ account, onClick }: AccountInfomationProps) => {
<div className={s.shippingInfo}>Shipping Infomation</div> <div className={s.shippingInfo}>Shipping Infomation</div>
<div className={s.accountAddress}> <div className={s.accountAddress}>
{account.address + {account.address?.streetLine1 +
`, ${account.state}, ${account.city}, ${account.postalCode}`} `, ${state?.name}, ${account.address?.city}, ${account.address?.postalCode}`}
</div> </div>
<div className={s.accountPhoneNumber}>{account.phoneNumber}</div> <div className={s.accountPhoneNumber}>{account.phoneNumber}</div>

View File

@@ -1,6 +1,15 @@
@import '../../../../../../styles/utilities'; @import '../../../../../../styles/utilities';
.editInfoModal { .editInfoModal {
.u-form{
width: 60rem;
}
.inputName{
@apply flex justify-between;
.input{
width: 48.5%;
}
}
.input { .input {
@apply bg-white; @apply bg-white;
margin-bottom: 1.6rem; margin-bottom: 1.6rem;
@@ -23,6 +32,7 @@
.inputPostalCode { .inputPostalCode {
@apply bg-white; @apply bg-white;
margin-left: 0.8rem; margin-left: 0.8rem;
width: 100%;
} }
.inputPhoneNumber { .inputPhoneNumber {

View File

@@ -1,19 +1,34 @@
import React from "react" import React, { useState } from "react"
import s from './EditInfoModal.module.scss' 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 { 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; visible: boolean;
closeModal: () => void; closeModal: () => void;
} }
const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoModalProps) => { 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 = [ const states = [
{name: "District 1", value: "D1"}, {name: "District 1", value: "D1"},
@@ -21,44 +36,165 @@ const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoMod
{name: "District 3", value: "D3"} {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 ( return (
<ModalCommon onClose={closeModal} visible={visible} title="Edit Infomation"> <ModalCommon onClose={closeModal} visible={visible} title="Edit Infomation">
<section className={s.editInfoModal}> <section className={s.editInfoModal}>
<div className={s.input}> <Formik
<Inputcommon placeholder="Name" value={accountInfo.name} type="text" /> initialValues={
</div> {
firstName:accountInfo.firstName,
<div className={s.inputDisable}> lastName: accountInfo.lastName,
<Inputcommon placeholder="Email" value={accountInfo.email} type="email" /> address:accountInfo.address?.streetLine1,
</div> city: accountInfo.address?.city,
postalCode: accountInfo.address?.postalCode,
<div className={s.input}> phoneNumber:accountInfo.phoneNumber
<Inputcommon placeholder="Address" value={accountInfo.address} type="text" /> }}
</div> validationSchema={DisplayingErrorMessagesSchema}
onSubmit={onEditUserInfo}
<div className={s.input}> >
<Inputcommon placeholder="City" value={accountInfo.city} type="text" /> {({ errors, touched, isValid, submitForm }) => (
</div> <Form className="u-form">
<div className={s.inputName}>
<div className={s.input}>
<div className="flex"> <InputFiledInForm
<div className={s.inputState}> name="firstName"
<SelectCommon type="custom" placeholder="State" options={states} /> 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>
<div className={s.inputPostalCode}> <div className={s.input}>
<Inputcommon placeholder="Postal code" value={accountInfo.postalCode} type="text" /> <InputFiledInForm
name="address"
placeholder="Address"
error={
touched.address && errors.address
? errors.address.toString()
: ''
}
isShowIconSuccess={touched.address && !errors.address}
/>
</div> </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}> <div className="flex">
<Inputcommon placeholder="Phone number" value={accountInfo.phoneNumber} type="text" /> <div className={s.inputState}>
</div> <SelectCommon initValue={accountInfo.address?.province} selected={accountInfo.address?.province} type="custom" onChange={state} placeholder="State" options={states} size="large"/>
</div>
<div className={s.buttons}> <div className={s.inputPostalCode}>
<ButtonCommon onClick={closeModal} type="light" size="large" >Cancel</ButtonCommon> <InputFiledInForm
<ButtonCommon onClick={saveInfo} size="large" >Save</ButtonCommon> name="postalCode"
</div> 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> </section>
</ModalCommon> </ModalCommon>
) )

View File

@@ -1,18 +1,34 @@
import React from "react" import { useRouter } from 'next/router'
import s from './FavouriteProducts.module.scss' import React, { useState } from "react"
import {ProductList} from '../../../../../common' import { QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
import { ProductList } from '../../../../../common'
import { ProductCardProps } from '../../../../../common/ProductCard/ProductCard' import { ProductCardProps } from '../../../../../common/ProductCard/ProductCard'
import s from './FavouriteProducts.module.scss'
interface FavouriteProductsProps { 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 ( return (
<section className={s.favouriteProducts}> <section className={s.favouriteProducts}>
<ProductList data={products} /> <ProductList data={products} total={totalItems} onPageChange={onPageChange}/>
</section> </section>
) )
} }

View File

@@ -4,50 +4,50 @@ import s from './OrderInformation.module.scss'
import { TabCommon } from '../../../../../common' import { TabCommon } from '../../../../../common'
import TabPane from 'src/components/common/TabCommon/components/TabPane/TabPane' import TabPane from 'src/components/common/TabCommon/components/TabPane/TabPane'
import DeliveryItem from '../../../DeliveryItem/DeliveryItem' import DeliveryItem from '../../../DeliveryItem/DeliveryItem'
import { Order } from "@framework/schema"
interface OrderInformationProps { interface OrderInformationProps {
waiting: {id: string, products: string[], totalPrice: number}[], addingItem?: Order[],
delivering: {id: string, products: string[], totalPrice: number}[], arrangingPayment?: Order[],
delivered: {id: string, products: string[], totalPrice: number}[], cancelled?: Order[],
} }
const OrderInformation = ({ waiting, delivering, delivered} : OrderInformationProps) => { const OrderInformation = ({ addingItem, arrangingPayment, cancelled} : OrderInformationProps) => {
return ( return (
<section className={s.orderInformation}> <section className={s.orderInformation}>
<div className={s.title}>Order Information</div> <div className={s.title}>Order Information</div>
<div className={s.tabs}> <div className={s.tabs}>
<TabCommon> <TabCommon>
<TabPane tabName={"Wait for Comfirmation"} > <TabPane tabName={"Adding Item"} >
<div className={s.blank}></div> <div className={s.blank}></div>
{ {
waiting.map((order, i) => { addingItem?.map((order, i) => {
return ( 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>
<TabPane tabName={"Delivering"}> <TabPane tabName={"Arranging Payment"}>
<div className={s.blank}></div> <div className={s.blank}></div>
{ {
delivering.map((order, i) => { arrangingPayment?.map((order, i) => {
return ( 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>
<TabPane tabName={"Delivered"}> <TabPane tabName={"Cancelled"}>
<div className={s.blank}></div> <div className={s.blank}></div>
{ {
delivered.map((order, i) => { cancelled?.map((order, i) => {
return ( 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 Products from './components/Products/Products'
import TotalPrice from './components/TotalPrice/TotalPrice' import TotalPrice from './components/TotalPrice/TotalPrice'
import ReOrder from './components/ReOrder/ReOrder' import ReOrder from './components/ReOrder/ReOrder'
import { OrderLine } from "@framework/schema"
interface DeliveryItemProps { interface DeliveryItemProps {
id: string; id: string;
status: "waiting" | "delivering" | "delivered"; status: "waiting" | "delivering" | "delivered";
products: string[]; products?: OrderLine[];
totalPrice: number; totalPrice: number;
} }

View File

@@ -1,19 +1,19 @@
import { OrderLine } from "@framework/schema";
import React from "react" import React from "react"
import s from './Products.module.scss' import s from './Products.module.scss'
interface ProductsProps { interface ProductsProps {
products: string[]; products?: OrderLine[];
} }
const Products = ({ products } : ProductsProps) => { const Products = ({ products } : ProductsProps) => {
function toString(products?:OrderLine[]): string {
function toString(products:string[]): string {
let strProducts = ""; let strProducts = "";
products.map((prod, i) => { products?.map((prod, i) => {
if (i === 0) { if (i === 0) {
strProducts += prod; strProducts += prod.productVariant?.name;
} else { } else {
strProducts += `, ${prod}` strProducts += `, ${prod.productVariant?.name}`
} }
}); });
return strProducts; return strProducts;

View File

@@ -1,5 +1,5 @@
import { useRouter } from 'next/router' 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 CardBlog, { BlogCardProps } from 'src/components/common/CardBlog/CardBlog'
import PaginationCommon from 'src/components/common/PaginationCommon/PaginationCommon' import PaginationCommon from 'src/components/common/PaginationCommon/PaginationCommon'
import { DEFAULT_BLOG_PAGE_SIZE, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils' 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 { interface BlogsListProps {
blogList?: BlogCardProps[], blogList?: BlogCardProps[],
total?: number total?: number,
idFeatured?:string
} }
const DEFAULT_BLOGS_ARGS = {
excludeBlogIds: ["28"],
options:{ const BlogsList = ({ blogList,total,idFeatured }:BlogsListProps) => {
skip: 1, take: DEFAULT_BLOG_PAGE_SIZE
const DEFAULT_BLOGS_ARGS = {
excludeBlogIds: [idFeatured],
options:{
skip: 1, take: DEFAULT_BLOG_PAGE_SIZE
}
} }
}
const BlogsList = ({ blogList,total }:BlogsListProps) => {
const router = useRouter(); const router = useRouter();
const [initialQueryFlag, setInitialQueryFlag] = useState<boolean>(true) const [initialQueryFlag, setInitialQueryFlag] = useState<boolean>(true)
@@ -44,13 +47,14 @@ const BlogsList = ({ blogList,total }:BlogsListProps) => {
} }
// skip // skip
const firstRender = useRef(true);
useEffect(() => { useEffect(() => {
firstRender.current = false;
const query = { ...DEFAULT_BLOGS_ARGS } as QueryBlogs; const query = { ...DEFAULT_BLOGS_ARGS } as QueryBlogs;
const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string); const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string);
query.options.skip = page * DEFAULT_BLOG_PAGE_SIZE; query.options.skip = page * DEFAULT_BLOG_PAGE_SIZE;
setOptionQueryBlog(query); setOptionQueryBlog(query);
setInitialQueryFlag(false); setInitialQueryFlag(false);
},[router.query]) },[router.query])
@@ -60,7 +64,7 @@ const BlogsList = ({ blogList,total }:BlogsListProps) => {
}else{ }else{
data = blogs data = blogs
} }
console.log(blogList);
return ( return (
<section> <section>

View File

@@ -1,8 +1,10 @@
import s from './FeaturedCardBlog.module.scss' import s from './FeaturedCardBlog.module.scss'
import { Author, DateTime, ImgWithLink } from 'src/components/common' import { Author, DateTime, ImgWithLink } from 'src/components/common'
import Link from 'next/link'
import { ROUTE } from 'src/utils/constanst.utils'
interface FeaturedCardBlogProps{ interface FeaturedCardBlogProps{
title?: string, title?: string,
slug?:string,
content?: string, content?: string,
imgSrc?: string, imgSrc?: string,
imgAuthor?: string, imgAuthor?: string,
@@ -10,33 +12,31 @@ interface FeaturedCardBlogProps{
authorName?: string, 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 = ({ const FeaturedCardBlog = ({
title = FEATURED_DATA.title, title,
content = FEATURED_DATA.content, slug,
imgSrc = FEATURED_DATA.imgSrc, content,
imgAuthor = FEATURED_DATA.imgAuthor, imgSrc = '',
date = FEATURED_DATA.date, imgAuthor = '',
authorName = FEATURED_DATA.author date = '',
authorName = ''
}: FeaturedCardBlogProps) => { }: FeaturedCardBlogProps) => {
return ( return (
<section className={s.featuredCard}> <section className={s.featuredCard}>
<div className={s.featuredCardWrapper}> <div className={s.featuredCardWrapper}>
<div className={s.left}> <Link href={`${ROUTE.BLOG_DETAIL}/${slug}`}>
<ImgWithLink src={imgSrc} alt="image feature card"/> <a>
</div> <div className={s.left}>
<ImgWithLink src={imgSrc} alt="image feature card"/>
</div>
</a>
</Link>
<div className={s.right}> <div className={s.right}>
<div className={s.titleWrapper}> <div className={s.titleWrapper}>
<DateTime date={date}/> <DateTime date={date }/>
<a className={s.title}>{title}</a> <Link href={`${ROUTE.BLOG_DETAIL}/${slug}`}>
<a className={s.title}>{title}</a>
</Link>
</div> </div>
<Author name={authorName} image={imgAuthor}/> <Author name={authorName} image={imgAuthor}/>
<div className={s.content}>{content}</div> <div className={s.content}>{content}</div>

View File

@@ -10,6 +10,7 @@ interface FreshProductsProps {
} }
const FreshProducts = ({ data, collections }: FreshProductsProps) => { const FreshProducts = ({ data, collections }: FreshProductsProps) => {
const dataWithCategory = useMemo(() => { const dataWithCategory = useMemo(() => {
return data.map(item => { return data.map(item => {
return { 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 { Banner, StaticImage } from 'src/components/common'
import useGetProductListByCollection from 'src/components/hooks/useGetProductListByCollection'
import { ROUTE } from 'src/utils/constanst.utils' import { ROUTE } from 'src/utils/constanst.utils'
import BannerImgRight from './assets/banner_full.png' import BannerImgRight from './assets/banner_full.png'
import HomeBannerImg from './assets/home_banner.png' import HomeBannerImg from './assets/home_banner.png'
@@ -11,6 +13,10 @@ interface Props {
} }
const HomeBanner = ({ }: Props) => { const HomeBanner = ({ }: Props) => {
// const variables = useMemo(() => {
// return {option: {filter: {name: {eq: "Computers" }}}}
// }, [])
// const {collections} = useGetProductListByCollection(variables)
return ( return (
<div className={s.homeBanner}> <div className={s.homeBanner}>
<section className={s.left}> <section className={s.left}>

View File

@@ -1,10 +1,13 @@
import React from 'react' import React from 'react'
import { ProductCard } from '@commerce/types/product'
import { CollectionCarcousel } from '..' import { CollectionCarcousel } from '..'
import image5 from '../../../../../public/assets/images/image5.png' import image5 from '../../../../../public/assets/images/image5.png'
import image6 from '../../../../../public/assets/images/image6.png' import image6 from '../../../../../public/assets/images/image6.png'
import image7 from '../../../../../public/assets/images/image7.png' import image7 from '../../../../../public/assets/images/image7.png'
import image8 from '../../../../../public/assets/images/image8.png' import image8 from '../../../../../public/assets/images/image8.png'
interface HomeCollectionProps {} interface HomeCollectionProps {
data: ProductCard[]
}
const dataTest = [ const dataTest = [
{ {
name: 'Tomato', name: 'Tomato',
@@ -92,39 +95,39 @@ const dataTest = [
}, },
] ]
const HomeCollection = (props: HomeCollectionProps) => { const HomeCollection = ({data}: HomeCollectionProps) => {
return ( return (
<div className="w-full"> <div className="w-full">
<CollectionCarcousel <CollectionCarcousel
data={dataTest} data={data}
itemKey="product-2" itemKey="product-2"
title="VEGGIE" title="VEGGIE"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can." subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
category={"veggie"} category={"veggie"}
/> />
<CollectionCarcousel <CollectionCarcousel
data={dataTest} data={data}
itemKey="product-3" itemKey="product-3"
title="VEGGIE" title="VEGGIE"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can." subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
category={"veggie"} category={"veggie"}
/> />
<CollectionCarcousel <CollectionCarcousel
data={dataTest} data={data}
itemKey="product-4" itemKey="product-4"
title="VEGGIE" title="VEGGIE"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can." subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
category={"veggie"} category={"veggie"}
/> />
<CollectionCarcousel <CollectionCarcousel
data={dataTest} data={data}
itemKey="product-5" itemKey="product-5"
title="VEGGIE" title="VEGGIE"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can." subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
category={"veggie"} category={"veggie"}
/> />
<CollectionCarcousel <CollectionCarcousel
data={dataTest} data={data}
itemKey="product-6" itemKey="product-6"
title="VEGGIE" title="VEGGIE"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can." 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 { ProductCarousel } from 'src/components/common'
import { SPICE_DATA_TEST } from "../../../../utils/demo-data" import { SPICE_DATA_TEST } from "../../../../utils/demo-data"
import s from './HomeSpice.module.scss' import s from './HomeSpice.module.scss'
import { ProductCard } from '@commerce/types/product'
interface HomeSpice { interface HomeSpice {
data: ProductCard[]
} }
const HomeSpice = ({}: HomeSpice) => { const HomeSpice = ({data}: HomeSpice) => {
return ( return (
<div className={s.homeSpiceWarpper}> <div className={s.homeSpiceWarpper}>
<ProductCarousel data={SPICE_DATA_TEST} itemKey="product-7"/> <ProductCarousel data={data} itemKey="product-7"/>
</div> </div>
) )
} }

View File

@@ -1,27 +1,18 @@
import React, { useMemo } from 'react'; import React from 'react';
import ProductImgs from './components/ProductImgs/ProductImgs' import ProductImgs from './components/ProductImgs/ProductImgs'
import ProductInfo from './components/ProductInfo/ProductInfo' import ProductInfo from './components/ProductInfo/ProductInfo'
import s from './ProductInfoDetail.module.scss' import s from './ProductInfoDetail.module.scss'
import { Product } from '@commerce/types/product' import { Product } from '@commerce/types/product'
import { Collection } from '@framework/schema'
import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils';
interface Props { interface Props {
productDetail: Product, productDetail: Product,
collections: Collection[]
} }
const ProductInfoDetail = ({ productDetail, collections }: Props) => { const ProductInfoDetail = ({ productDetail }: Props) => {
const dataWithCategoryName = useMemo(() => {
return {
...productDetail,
collection: getCategoryNameFromCollectionId(collections, productDetail.collectionIds ? productDetail.collectionIds[0] : undefined)
}
}, [productDetail, collections])
return ( return (
<section className={s.productInfoDetail}> <section className={s.productInfoDetail}>
<ProductImgs productImage={productDetail.images}/> <ProductImgs productImage={productDetail.images}/>
<ProductInfo productInfoDetail={dataWithCategoryName}/> <ProductInfo productInfoDetail={productDetail}/>
</section > </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 { 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 { 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 { 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 { LANGUAGE } from 'src/utils/language.utils'
import { SelectedOptions } from 'src/utils/types.utils'
import ProductDetailOption from '../ProductDetailOption/ProductDetailOption'
import s from './ProductInfo.module.scss' import s from './ProductInfo.module.scss'
interface Props { interface Props {
@@ -10,11 +17,73 @@ interface Props {
} }
const ProductInfo = ({ productInfoDetail }: 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 ( return (
<section className={s.productInfo}> <section className={s.productInfo}>
<div className={s.info}> <div className={s.info}>
<LabelCommon shape='half'>{productInfoDetail.collection}</LabelCommon> <LabelCommon shape='half'>{productInfoDetail.collection?.[0]}</LabelCommon>
<h2 className={s.heading}>{productInfoDetail.name}</h2> <h2 className={s.heading}>{productInfoDetail.name}</h2>
<div className={s.price}> <div className={s.price}>
<div className={s.old}> <div className={s.old}>
@@ -26,14 +95,22 @@ const ProductInfo = ({ productInfoDetail }: Props) => {
<div className={s.description}> <div className={s.description}>
{productInfoDetail.description} {productInfoDetail.description}
</div> </div>
<div className={s.options}>
{
productInfoDetail.options.map((option)=>{
return <ProductDetailOption option={option} onChane={onSelectOption} key={option.displayName}/>
})
}
</div>
</div> </div>
<div className={s.actions}> <div className={s.actions}>
<QuanittyInput /> <QuanittyInput value={quanitty} onChange={handleQuanittyChange}/>
<div className={s.bottom}> <div className={s.bottom}>
{/* <ButtonCommon size='large'>{LANGUAGE.BUTTON_LABEL.PREORDER}</ButtonCommon> */} {/* <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}> <span className={s.buttonWithIcon}>
<IconBuy /><span className={s.label}>{LANGUAGE.BUTTON_LABEL.ADD_TO_CARD}</span> <IconBuy /><span className={s.label}>{LANGUAGE.BUTTON_LABEL.ADD_TO_CARD}</span>
</span> </span>

View File

@@ -1,5 +1,5 @@
import { Collection } from '@commerce/types/collection';
import { ProductCard } from '@commerce/types/product'; import { ProductCard } from '@commerce/types/product';
import { Collection } from '@framework/schema';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo'; import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo';
import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils'; 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 ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo';
import { PRODUCT_DATA_TEST } from 'src/utils/demo-data'; import { ProductCardProps } from 'src/components/common/ProductCard/ProductCard';
import { LOCAL_STORAGE_KEY } from 'src/utils/constanst.utils'
const ViewedProducts = () => { 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 ( return (
<ListProductWithInfo <ListProductWithInfo
title="viewed Products" title="viewed Products"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can." subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
data={PRODUCT_DATA_TEST} data={data}
hasBorderBottomMobile={true} hasBorderBottomMobile={true}
/> />
); );

View File

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

View File

@@ -44,7 +44,8 @@ export const ACCOUNT_TAB = {
} }
export const LOCAL_STORAGE_KEY = { export const LOCAL_STORAGE_KEY = {
TOKEN: 'token' TOKEN: 'token',
VIEWEDPRODUCT: "viewed-product"
} }
export const QUERY_SPLIT_SEPERATOR = ',' export const QUERY_SPLIT_SEPERATOR = ','
@@ -82,46 +83,58 @@ export const DEFAULT_PAGE_SIZE = 20;
export const CATEGORY = [ export const CATEGORY = [
{ {
name: 'All', name: 'All',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=${OPTION_ALL}`, link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=${OPTION_ALL}`,
}, },
{ {
name: 'Veggie', name: 'Veggie',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=veggie`, link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=veggie`,
}, },
{ {
name: 'Seafood', name: 'Seafood',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=seafood`, link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=seafood`,
}, },
{ {
name: 'Frozen', name: 'Frozen',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=frozen`, link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=frozen`,
}, },
{ {
name: 'Coffee Bean', name: 'Coffee Bean',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=coffee_bean`, link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=coffee_bean`,
}, },
{ {
name: 'Sauce', name: 'Sauce',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.CATEGORY}=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 = [ export const FACET = {
{ FEATURE: {
name: 'Maggi', PARENT_NAME: 'Featured',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=maggi`, FRESH: 'Fresh',
BEST_SELLERS: 'Best seller'
}, },
{ CATEGORY: {
name: 'Chomilex', PARENT_NAME:"category",
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chomilex`, VEGGIE:"veggie"
}, }
{ }
name: 'Chinsu',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.BRAND}=chinsu`,
},
]
export const CODE_FACET_FEATURED = 'featured' export const CODE_FACET_FEATURED = 'featured'
export const CODE_FACET_DISCOUNT = 'discount' 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 { Facet } from "@commerce/types/facet";
import { Collection, FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d'; import { Product, ProductCard, ProductOption, ProductOptionValues } from "@commerce/types/product";
import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT, PRODUCT_SORT_OPTION_VALUE } from "./constanst.utils"; import { FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d';
import { PromiseWithKey, SortOrder } from "./types.utils"; 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() { export function isMobile() {
return window.innerWidth < 768 return window.innerWidth < 768
@@ -80,6 +81,18 @@ export function getFreshFacetId(facets: Facet[]) {
return freshFacetValue?.id 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) { export function getAllFacetValueIdsByParentCode(facets: Facet[], code: string) {
const featuredFacet = facets.find((item: Facet) => item.code === code) const featuredFacet = facets.find((item: Facet) => item.code === code)
const rs = featuredFacet?.values.map((item: FacetValue) => item.id) const rs = featuredFacet?.values.map((item: FacetValue) => item.id)
@@ -134,3 +147,34 @@ export function getIdFeaturedBlog(blog: BlogList) {
return blog?.id 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 { export interface ProductProps {
category?: string category?: string
name: string name: string
@@ -57,8 +58,18 @@ export type filterContextType = {
close: () => void; 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 = { export type PromiseWithKey = {
key: string key: string
promise: PromiseLike<any> promise: PromiseLike<any>
keyResult?: string, keyResult?: string,
} }
export type SelectedOptions = Record<string, string | null>