bug: fix conflict

This commit is contained in:
Quangnhankie
2021-10-15 17:18:55 +07:00
94 changed files with 1915 additions and 594 deletions

View File

@@ -43,8 +43,12 @@ export type Product = {
slug?: string
path?: string
images: ProductImage[]
price: ProductPrice
price: number
currencyCode: CurrencyCode
options: ProductOption[]
facetValueIds?: string[]
collectionIds?: string[]
collection?: string,
}
export type ProductCard = {

View File

@@ -1,10 +1,10 @@
import { OperationContext } from '@commerce/api/operations'
import { Facet } from '@commerce/types/facet'
import { Provider, VendureConfig } from '../'
import { GetAllFacetsQuery } from '../../schema'
import { FacetFilterParameter, FacetSortParameter, GetAllFacetsQuery } from '../../schema'
import { getAllFacetsQuery } from '../../utils/queries/get-all-facets-query'
export type FacetVariables = { first?: number }
export type FacetVariables = { first?: number, filter?: FacetFilterParameter, sort?: FacetSortParameter }
export default function getAllFacetsOperation({
commerce,
@@ -27,9 +27,10 @@ export default function getAllFacetsOperation({
} = {}): Promise<{ facets: Facet[] | any[] }> {
const config = commerce.getConfig(cfg)
const variables = {
input: {
options: {
take: vars.first,
groupByFacet: true,
filter: vars.filter,
sort: vars.sort,
},
}
const { data } = await config.fetch<GetAllFacetsQuery>(query, {

View File

@@ -14,7 +14,7 @@ export default function getAllProductsOperation({
variables?: ProductVariables
config?: Partial<VendureConfig>
preview?: boolean
}): Promise<{ products: Product[] }>
}): Promise<{ products: Product[], totalItems: number }>
async function getAllProducts({
query = getAllProductsQuery,
@@ -25,7 +25,7 @@ export default function getAllProductsOperation({
variables?: ProductVariables
config?: Partial<VendureConfig>
preview?: boolean
} = {}): Promise<{ products: Product[] | any[] }> {
} = {}): Promise<{ products: Product[] | any[], totalItems: number }> {
const config = commerce.getConfig(cfg)
const variables = {
input: {
@@ -40,6 +40,7 @@ export default function getAllProductsOperation({
return {
products: data.search.items.map((item) => normalizeSearchResult(item)),
totalItems: data.search.totalItems as number,
}
}

View File

@@ -2,7 +2,7 @@ import { Product } from '@commerce/types/product'
import { OperationContext } from '@commerce/api/operations'
import { Provider, VendureConfig } from '../'
import { GetProductQuery } from '../../schema'
import { getProductQuery } from '../../utils/queries/get-product-query'
import { getProductQuery, getProductDetailQuery } from '../../utils/queries/get-product-query'
export default function getProductOperation({
commerce,
@@ -16,10 +16,8 @@ export default function getProductOperation({
variables: { slug: string }
config?: Partial<VendureConfig>
preview?: boolean
}): Promise<Product | {} | any> {
}): Promise<Product | null> {
const config = commerce.getConfig(cfg)
const locale = config.locale
const { data } = await config.fetch<GetProductQuery>(query, { variables })
const product = data.product
@@ -28,7 +26,6 @@ export default function getProductOperation({
return product.optionGroups.find((og) => og.id === id)!.name
}
return {
product: {
id: product.id,
name: product.name,
description: product.description,
@@ -49,20 +46,19 @@ export default function getProductOperation({
values: [{ label: o.name }],
})),
})),
price: {
value: product.variants[0].priceWithTax / 100,
currencyCode: product.variants[0].currencyCode,
},
price: product.variants[0].priceWithTax / 100,
currencyCode: product.variants[0].currencyCode,
options: product.optionGroups.map((og) => ({
id: og.id,
displayName: og.name,
values: og.options.map((o) => ({ label: o.name })),
})),
} as Product,
}
facetValueIds: product.facetValues.map(item=> item.id),
collectionIds: product.collections.map(item => item.id)
} as Product
}
return {}
return null
}
return getProduct

View File

@@ -1,4 +1,7 @@
import { FacetValue, UpdateAddressInput } from './schema.d';
import { ResetPassword } from './schema.d';
import { requestPasswordReset } from '@framework/schema';
import { FacetValue } from './schema.d';
export type Maybe<T> = T | null
export type Exact<T extends { [key: string]: unknown }> = {
[K in keyof T]: T[K]
@@ -3139,6 +3142,36 @@ export type LoginMutation = { __typename?: 'Mutation' } & {
>)
}
export type ResetPasswordMutation = { __typename?: 'Mutation' } & {
resetPassword:
| ({ __typename: 'CurrentUser' } & Pick<CurrentUser, 'id'>)
| ({ __typename: 'PasswordResetTokenInvalidError' } & Pick<
PasswordResetTokenInvalidError,
'errorCode' | 'message'
>)
| ({ __typename: 'PasswordResetTokenExpiredError' } & Pick<
PasswordResetTokenExpiredError,
'errorCode' | 'message'
>)
| ({ __typename: 'NativeAuthStrategyError' } & Pick<
NativeAuthStrategyError,
'errorCode' | 'message'
>)
}
export type SignupMutation = { __typename?: 'Mutation' } & {
registerCustomerAccount:
| ({ __typename: 'Success' } & Pick<Success, 'success'>)
| ({ __typename: 'MissingPasswordError' } & Pick<
MissingPasswordError,
'errorCode' | 'message'
>)
| ({ __typename: 'NativeAuthStrategyError' } & Pick<
NativeAuthStrategyError,
'errorCode' | 'message'
>)
}
export type VerifyCustomerAccountVariables = Exact<{
token: Scalars['String']
password?: Maybe<Scalars['String']>
@@ -3192,8 +3225,9 @@ export type SignupMutationVariables = Exact<{
input: RegisterCustomerInput
}>
export type SignupMutation = { __typename?: 'Mutation' } & {
registerCustomerAccount:
export type RequestPasswordReset = { __typename?: 'Mutation' } & {
requestPasswordReset:
| ({ __typename: 'Success' } & Pick<Success, 'success'>)
| ({ __typename: 'MissingPasswordError' } & Pick<
MissingPasswordError,
@@ -3205,16 +3239,30 @@ export type SignupMutation = { __typename?: 'Mutation' } & {
>)
}
export type ActiveCustomerQueryVariables = Exact<{ [key: string]: never }>
export type ActiveCustomerQuery = { __typename?: 'Query' } & {
activeCustomer?: Maybe<
{ __typename?: 'Customer' } & Pick<
Customer,
'id' | 'firstName' | 'lastName' | 'emailAddress' | 'addresses' | 'phoneNumber' | 'orders'
'id' | 'firstName' | 'lastName' | 'emailAddress' | 'addresses' | 'phoneNumber'| 'favorites' | 'orders'
>
>
}
export type FavoriteList = PaginatedList & {
items: [Favorite!]!
totalItems: Int!
}
type Favorite = Node & {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
product: Product
customer: Customer!
}
export type GetAllProductPathsQueryVariables = Exact<{
first?: Maybe<Scalars['Int']>
@@ -3232,7 +3280,8 @@ export type GetAllProductsQueryVariables = Exact<{
export type GetAllProductsQuery = { __typename?: 'Query' } & {
search: { __typename?: 'SearchResponse' } & {
items: Array<{ __typename?: 'SearchResult' } & SearchResultFragment>
items: Array<{ __typename?: 'SearchResult' } & SearchResultFragment>,
'totalItems'
}
}
@@ -3241,8 +3290,9 @@ export type GetAllFacetsQuery = { __typename?: 'Query' } & {
items: Array<
{ __typename?: 'Facet' } & Pick<
Facet,
'id' | 'name' | 'code'
> & {
'id' | 'name' | 'code' | 'values'
>
& {
parent?: Maybe<{ __typename?: 'Facet' } & Pick<Facet, 'id'>>
children?: Maybe<
Array<{ __typename?: 'Facet' } & Pick<Facet, 'id'>>
@@ -3314,7 +3364,7 @@ export type GetProductQuery = { __typename?: 'Query' } & {
variants: Array<
{ __typename?: 'ProductVariant' } & Pick<
ProductVariant,
'id' | 'priceWithTax' | 'currencyCode'
'id' | 'priceWithTax' | 'currencyCode' | 'price'
> & {
options: Array<
{ __typename?: 'ProductOption' } & Pick<
@@ -3349,6 +3399,18 @@ export type GetProductQuery = { __typename?: 'Query' } & {
>
}
>
facetValues: Array<
{ __typename?: 'FacetValue' } & Pick<
FacetValue,
'id'
>
>
collections: Array<
{ __typename?: 'Collection' } & Pick<
Collection,
'id'
>
>
}
>
}

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

@@ -1,17 +1,35 @@
import { searchResultFragment } from '../fragments/search-result-fragment'
export const activeCustomerQuery = /* GraphQL */ `
query activeCustomer {
activeCustomer {
id
firstName
lastName
emailAddress
phoneNumber
addresses{
streetLine1
city
province
postalCode
query activeCustomer {
activeCustomer {
id
firstName
lastName
emailAddress
favorites{
items{
product{
id
name
slug
assets{
source
preview
}
variants{
price
}
}
}
}
phoneNumber
addresses{
streetLine1
city
province
postalCode
}
}
}
`

View File

@@ -3,6 +3,7 @@ import { searchResultFragment } from '../fragments/search-result-fragment'
export const getAllProductsQuery = /* GraphQL */ `
query getAllProducts($input: SearchInput!) {
search(input: $input) {
totalItems
items {
...SearchResult
}

View File

@@ -24,7 +24,7 @@ export const getCollectionsNameQuery = /* GraphQL */ `
collections{
items{
name
link:slug
slug
}
}
}

View File

@@ -36,6 +36,28 @@ export const getProductQuery = /* GraphQL */ `
name
}
}
facetValues {
id
}
collections {
id
}
}
}
`
export const getProductDetailQuery = /* GraphQL */ `
query GetProductDetail($slug: String! = "hand-trowel") {
product(slug: $slug) {
name
description
variants {
price
priceWithTax
}
assets {
preview
name
}
}
}
`

View File

@@ -26,6 +26,7 @@
"body-scroll-lock": "^3.1.5",
"classnames": "^2.3.1",
"cookie": "^0.4.1",
"dns": "^0.2.2",
"email-validator": "^2.0.4",
"eslint": "^7.32.0",
"eslint-config-next": "^11.1.2",
@@ -35,6 +36,7 @@
"lodash.debounce": "^4.0.8",
"lodash.random": "^3.2.0",
"lodash.throttle": "^4.1.1",
"net": "^1.0.2",
"next": "^11.0.0",
"next-seo": "^4.26.0",
"next-themes": "^0.0.14",

10
pages/forgot-password.tsx Normal file
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

@@ -62,27 +62,34 @@ export async function getStaticProps({
const freshFacetId = getFreshFacetId(facets)
if (freshFacetId) {
freshProductvariables.facetValueIds = [freshFacetId]
const freshProductsPromise = commerce.getAllProducts({
variables: freshProductvariables,
config,
preview,
})
promisesWithKey.push({ key: 'freshProducts', promise: freshProductsPromise, keyResult: 'products' })
} else {
props.freshProducts = []
}
const freshProductsPromise = commerce.getAllProducts({
variables: freshProductvariables,
config,
preview,
})
promisesWithKey.push({ key: 'freshProducts', promise: freshProductsPromise, keyResult: 'products' })
// featured products
const allFeaturedFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_FEATURED)
const allDiscountFacetIds = getAllFacetValueIdsByParentCode(facets, CODE_FACET_DISCOUNT)
const facetValueIdsForFeaturedProducts = [...allFeaturedFacetIds, ...allDiscountFacetIds]
const featuredProductsPromise = commerce.getAllProducts({
variables: {
facetValueIds: facetValueIdsForFeaturedProducts
},
config,
preview,
})
promisesWithKey.push({ key: 'featuredProducts', promise: featuredProductsPromise, keyResult: 'products' })
if (facetValueIdsForFeaturedProducts.length > 0) {
const featuredProductsPromise = commerce.getAllProducts({
variables: {
facetValueIds: facetValueIdsForFeaturedProducts
},
config,
preview,
})
promisesWithKey.push({ key: 'featuredProducts', promise: featuredProductsPromise, keyResult: 'products' })
} else {
props.featuredProducts = []
}
// collection
const collectionsPromise = commerce.getAllCollections({

View File

@@ -1,18 +1,114 @@
import { Product } from '@framework/schema'
import commerce from '@lib/api/commerce'
import { GetStaticPathsContext, GetStaticPropsContext, InferGetStaticPropsType } from 'next'
import { Layout, RecipeDetail, RecommendedRecipes, RelevantBlogPosts } from 'src/components/common'
import { ProductInfoDetail, ReleventProducts, ViewedProducts } from 'src/components/modules/product-detail'
import { MAX_PRODUCT_CAROUSEL, REVALIDATE_TIME } from 'src/utils/constanst.utils'
import { BLOGS_DATA_TEST, INGREDIENT_DATA_TEST, RECIPE_DATA_TEST } from 'src/utils/demo-data'
import { getAllPromies } from 'src/utils/funtion.utils'
import { PromiseWithKey } from 'src/utils/types.utils'
export default function Slug() {
export default function Slug({ product, relevantProducts, collections }: InferGetStaticPropsType<typeof getStaticProps>) {
return <>
<ProductInfoDetail />
<ProductInfoDetail productDetail={product} collections={collections}/>
<RecipeDetail ingredients={INGREDIENT_DATA_TEST} />
<RecommendedRecipes data={RECIPE_DATA_TEST} />
<ReleventProducts />
<ReleventProducts data={relevantProducts} collections={collections}/>
<ViewedProducts />
<RelevantBlogPosts data={BLOGS_DATA_TEST} title="relevent blog posts"/>
<RelevantBlogPosts data={BLOGS_DATA_TEST} title="relevent blog posts" />
</>
}
export async function getStaticProps({
params,
locale,
locales,
preview,
}: GetStaticPropsContext<{ slug: string }>) {
const config = { locale, locales }
let promisesWithKey = [] as PromiseWithKey[]
let props = {} as any
const product = await commerce.getProduct({
variables: { slug: params!.slug },
config,
preview,
})
props.product = product
if (!product) {
throw new Error(`Product with slug '${params!.slug}' not found`)
}
// relevant product (filter by product detail's facetIds)
const relevantFacetIds = product.facetValueIds
if (relevantFacetIds && relevantFacetIds.length > 0) {
const relevantProductsPromise = commerce.getAllProducts({
variables: {
first: MAX_PRODUCT_CAROUSEL,
facetValueIds: relevantFacetIds,
},
config,
preview,
})
promisesWithKey.push({ key: 'relevantProducts', promise: relevantProductsPromise, keyResult: 'products' })
} else {
props.relevantProducts = []
}
// collection
const collectionsPromise = commerce.getAllCollections({
variables: {},
config,
preview,
})
promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' })
try {
const promises = getAllPromies(promisesWithKey)
const rs = await Promise.all(promises)
promisesWithKey.map((item, index) => {
props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index]
return null
})
if (props.relevantProducts.length > 0) {
const relevantProducts = props.relevantProducts.filter((item: Product) => item.id !== product.id)
props.relevantProducts = relevantProducts
}
return {
props,
revalidate: REVALIDATE_TIME,
}
} catch (err) {
console.log('err: ', err)
}
}
export async function getStaticPaths({ locales }: GetStaticPathsContext) {
const { products } = await commerce.getAllProductPaths()
return {
paths: locales
? locales.reduce<string[]>((arr, locale) => {
// Add a product path for every locale
products.forEach((product: any) => {
arr.push(`/${locale}/product${product.path}`)
})
return arr
}, [])
: products.map((product: any) => `/product${product.path}`),
fallback: 'blocking',
}
}
Slug.Layout = Layout

View File

@@ -1,19 +1,97 @@
import { ProductCard } from '@commerce/types/product';
import { Collection, Facet } from '@framework/schema';
import commerce from '@lib/api/commerce';
import { GetStaticPropsContext } from 'next';
import { Layout } from 'src/components/common';
import { ViewedProducts } from 'src/components/modules/product-detail';
import ProductListFilter from 'src/components/modules/product-list/ProductListFilter/ProductListFilter';
import RecipeListBanner from 'src/components/modules/recipes-list/RecipeListBanner/RecipeListBanner';
import RecipesList from 'src/components/modules/recipes-list/RecipesList/RecipesList';
import { CODE_FACET_BRAND, CODE_FACET_FEATURED, DEFAULT_PAGE_SIZE, REVALIDATE_TIME } from 'src/utils/constanst.utils';
import { getAllPromies } from 'src/utils/funtion.utils';
import { PromiseWithKey, SortOrder } from 'src/utils/types.utils';
import ProductListBanner from '../src/components/modules/product-list/ProductListBanner/ProductListBanner';
interface Props {
facets: Facet[],
collections: Collection[],
productsResult: { products: ProductCard[], totalItems: number },
export default function Products() {
}
export default function Products({ facets, collections, productsResult }: Props) {
return (
<>
<ProductListBanner />
<ProductListFilter/>
<ViewedProducts/>
<ProductListFilter
collections={collections}
facets={facets}
products={productsResult.products}
total={productsResult.totalItems} />
<ViewedProducts />
</>
)
}
export async function getStaticProps({
preview,
locale,
locales,
}: GetStaticPropsContext) {
const config = { locale, locales }
let promisesWithKey = [] as PromiseWithKey[]
let props = {} as any
const facetsPromise = commerce.getAllFacets({
variables: {
sort: {
code: SortOrder.Asc
},
filter: {
code: {
in: [CODE_FACET_FEATURED, CODE_FACET_BRAND]
}
}
},
config,
preview,
})
promisesWithKey.push({ key: 'facets', promise: facetsPromise, keyResult: 'facets' })
// collection
const collectionsPromise = commerce.getAllCollections({
variables: {},
config,
preview,
})
promisesWithKey.push({ key: 'collections', promise: collectionsPromise, keyResult: 'collections' })
// products
const productsPromise = commerce.getAllProducts({
variables: {
first: DEFAULT_PAGE_SIZE,
},
config,
preview,
})
promisesWithKey.push({ key: 'productsResult', promise: productsPromise })
try {
const promises = getAllPromies(promisesWithKey)
const rs = await Promise.all(promises)
promisesWithKey.map((item, index) => {
props[item.key] = item.keyResult ? rs[index][item.keyResult] : rs[index]
return null
})
return {
props,
revalidate: REVALIDATE_TIME,
}
} catch (err) {
}
}
Products.Layout = Layout

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,17 +1,19 @@
import commerce from '@lib/api/commerce';
import { GetStaticPropsContext } from 'next';
import { Layout } from 'src/components/common';
import { ProductCard } from '@commerce/types/product';
import { Layout, ListProductCardSkeleton } from 'src/components/common';
interface Props {
products: any
productDetail: ProductCard[],
}
export default function Home({ products }: Props) {
export default function Home({ productDetail }: Props) {
return (
<>
<p>
TOTAL: {products?.length}
</p>
{JSON.stringify(products[0])}
{/* <ListProductCardSkeleton /> */}
{/* <ListProductCardSkeleton count={1} /> */}
<ListProductCardSkeleton count={10} />
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Ab qui magnam debitis ex laborum laboriosam suscipit! Totam excepturi eum libero.
<ListProductCardSkeleton count={10} isWrap/>
</>
)
}
@@ -22,28 +24,9 @@ export async function getServerSideProps({
locale,
locales,
}: GetStaticPropsContext) {
const config = { locale, locales }
const productsPromise = commerce.getAllProducts({
// const productsPromise = commerce.getAllFacets({
variables: {
first: 70,
// filter: {
// name: {
// contains: 'ca'
// }
// }
},
config,
preview,
// Saleor provider only
...({ featured: true } as any),
})
const { products } = await productsPromise
return {
props: { products },
props: {},
}
}

View File

@@ -4,13 +4,16 @@
padding: 1.6rem;
margin: auto;
.imgWrap {
min-width: 10rem;
min-height: 10rem;
text-align: center;
img {
min-height: 10rem;
}
}
.description {
color: var(--disabled);
text-align: center;
margin-top: .8rem;
margin-top: 0.8rem;
}
}

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

@@ -1,5 +1,6 @@
import classNames from 'classnames'
import React, { memo, useEffect, useRef, useState } from 'react'
import { useProductFilter } from 'src/components/contexts'
import { useModalCommon } from 'src/components/hooks'
import ModalAuthenticate from '../ModalAuthenticate/ModalAuthenticate'
import ModalCreateUserInfo from '../ModalCreateUserInfo/ModalCreateUserInfo'
@@ -9,12 +10,12 @@ import HeaderSubMenu from './components/HeaderSubMenu/HeaderSubMenu'
import HeaderSubMenuMobile from './components/HeaderSubMenuMobile/HeaderSubMenuMobile'
import s from './Header.module.scss'
interface props {
toggleFilter: () => void,
visibleFilter: boolean
}
const Header = memo(({ toggleFilter, visibleFilter }: props) => {
const Header = memo(({ }: props) => {
const headeFullRef = useRef<HTMLDivElement>(null)
const { toggleProductFilter: toggleFilter } = useProductFilter()
const [isFullHeader, setIsFullHeader] = useState<boolean>(true)
const [isModeAuthenRegister, setIsModeAuthenRegister] = useState<boolean>(false)
const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false })
@@ -63,7 +64,6 @@ const Header = memo(({ toggleFilter, visibleFilter }: props) => {
<div className={s.menu}>
<HeaderMenu
isFull={isFullHeader}
visibleFilter={visibleFilter}
toggleFilter={toggleFilter}
openModalLogin={openModalLogin}
openModalRegister = {openModalRegister}

View File

@@ -50,10 +50,6 @@
width: 1.2rem;
height: 1.2rem;
border-radius: 1.2rem;
@apply hidden;
&.isShow {
@apply block;
}
}
@screen md {
display: none;

View File

@@ -27,7 +27,6 @@ interface Props {
children?: any
isFull?: boolean
isStickyHeader?: boolean
visibleFilter?: boolean
openModalLogin: () => void
openModalRegister: () => void
openModalInfo: () => void
@@ -38,7 +37,6 @@ const HeaderMenu = memo(
({
isFull,
isStickyHeader,
visibleFilter,
openModalLogin,
openModalRegister,
openModalInfo,
@@ -60,6 +58,10 @@ const HeaderMenu = memo(
onClick: openModalRegister,
name: 'Create account',
},
{
link: '/forgot-password',
name: 'Forgot Password',
},
],
[openModalLogin, openModalRegister]
)
@@ -90,6 +92,7 @@ const HeaderMenu = memo(
],
[logout]
)
return (
<section
className={classNames({
@@ -105,12 +108,7 @@ const HeaderMenu = memo(
{FILTER_PAGE.includes(router.pathname) && (
<button className={s.iconFilter} onClick={toggleFilter}>
<IconFilter />
<div
className={classNames({
[s.dot]: true,
[s.isShow]: visibleFilter,
})}
></div>
<div className={s.dot}></div>
</button>
)}
<button

View File

@@ -42,7 +42,7 @@ const HeaderSubMenu = memo(() => {
<ul className={s.menu}>
{/* todo: handle active item */}
<li>
<MenuDropdown options={collections?.items ?? []} align="left">Categories</MenuDropdown>
<MenuDropdown options={collections || []} align="left">Categories</MenuDropdown>
</li>
{
MENU.map(item => <li key={item.name}

View File

@@ -68,4 +68,5 @@ const HeaderSubMenuMobile = memo(({ }: Props) => {
)
})
HeaderSubMenuMobile.displayName = 'HeaderSubMenuMobile'
export default HeaderSubMenuMobile

View File

@@ -2,19 +2,38 @@ import classNames from 'classnames'
import IconHeart from 'src/components/icons/IconHeart'
import React, { memo } from 'react'
import s from './ItemWishList.module.scss'
import { useToggleProductWishlist } from '../../../../src/components/hooks/product'
import { useMessage } from 'src/components/contexts'
import { LANGUAGE } from 'src/utils/language.utils'
interface Props {
id:string,
isActive?: boolean,
onChange?: () => void
onChange?: () => string
}
const ItemWishList = memo(({isActive=false, onChange}:Props) => {
const ItemWishList = memo(({id,isActive=false, onChange}:Props) => {
const {onToggleProductWishlist} = useToggleProductWishlist();
const { showMessageSuccess, showMessageError } = useMessage();
function toggleWishlist(){
onToggleProductWishlist({productId:id},onSignupCallBack)
}
const onSignupCallBack = (isSuccess: boolean, message?: string) => {
if (isSuccess) {
// showMessageSuccess("Create account successfully. Please verify your email to login.", 15000)
} else {
showMessageError(message || LANGUAGE.MESSAGE.ERROR)
}
}
return(
<div className={classNames({
[s.heartToggle]: true,
[s.isToggleOn]: isActive
})}
onChange={onChange}
onClick={toggleWishlist}
>
<IconHeart />
</div>

View File

@@ -1,7 +1,7 @@
import { CommerceProvider } from '@framework'
import { useRouter } from 'next/router'
import { FC } from 'react'
import { CartDrawerProvider, MessageProvider } from 'src/components/contexts'
import { CartDrawerProvider, MessageProvider, ProductFilterProvider } from 'src/components/contexts'
import LayoutContent from './LayoutContent/LayoutContent'
interface Props {
className?: string
@@ -13,9 +13,11 @@ const Layout: FC<Props> = ({ children }) => {
return (
<CommerceProvider locale={locale}>
<CartDrawerProvider>
<MessageProvider>
<LayoutContent>{children}</LayoutContent>
</MessageProvider>
<ProductFilterProvider>
<MessageProvider>
<LayoutContent>{children}</LayoutContent>
</MessageProvider>
</ProductFilterProvider>
</CartDrawerProvider>
</CommerceProvider>
)

View File

@@ -17,7 +17,7 @@
}
}
.filter {
@screen xl {
@screen md {
display: none;
}
}

View File

@@ -2,7 +2,7 @@ import { useRouter } from 'next/router'
import { FC } from 'react'
import { useMessage } from 'src/components/contexts'
import { useModalCommon } from 'src/components/hooks'
import { BRAND, CATEGORY, FEATURED, FILTER_PAGE, ROUTE } from 'src/utils/constanst.utils'
import { FILTER_PAGE, ROUTE } from 'src/utils/constanst.utils'
import { CartDrawer, Footer, MessageCommon, ScrollToTop } from '../..'
import Header from '../../Header/Header'
import MenuNavigationProductList from '../../MenuNavigationProductList/MenuNavigationProductList'
@@ -14,23 +14,13 @@ interface Props {
}
const LayoutContent: FC<Props> = ({ children }) => {
const { pathname } = useRouter()
const { visible: visibleFilter, openModal: openFilter, closeModal: closeFilter } = useModalCommon({ initialValue: false })
const router = useRouter()
const {messages, removeMessage} = useMessage()
const toggleFilter = () => {
if (visibleFilter) {
closeFilter()
} else {
openFilter()
}
}
return (
<>
<div className={s.mainLayout}>
<Header toggleFilter={toggleFilter} visibleFilter={visibleFilter} />
<Header/>
{
router.pathname === ROUTE.ACCOUNT ?
<section className={s.wrapperWithBg}>
@@ -38,10 +28,9 @@ const LayoutContent: FC<Props> = ({ children }) => {
</section> :
<main>{children}</main>
}
<div className={s.filter}><MenuNavigationProductList categories={CATEGORY} brands={BRAND} featured={FEATURED} visible={visibleFilter} onClose={closeFilter} /> </div>
<ScrollToTop visibilityHeight={1500} />
{
FILTER_PAGE.includes(pathname) && (<div className={s.filter}><MenuNavigationProductList categories={CATEGORY} brands={BRAND} featured={FEATURED} visible={visibleFilter} onClose={closeFilter} /> </div>)
FILTER_PAGE.includes(router.pathname) && (<div className={s.filter}><MenuNavigationProductList /> </div>)
}
<Footer />
</div>

View File

@@ -0,0 +1,12 @@
.listProductCardSkeleton {
display: flex;
overflow: hidden;
&.wrap {
flex-wrap: wrap;
overflow: unset;
> div {
margin-bottom: 1.6rem;
}
}
}

View File

@@ -0,0 +1,21 @@
import classNames from 'classnames'
import { ProductCardSkeleton } from '..'
import s from './ListProductCardSkeleton.module.scss'
type Props = {
count?: number
isWrap?: boolean
}
const ListProductCardSkeleton = ({ count = 5, isWrap }: Props) => {
return (
<div className={classNames(s.listProductCardSkeleton, { [s.wrap]: isWrap })}>
{
Array.from(Array(count).keys()).map(item => <ProductCardSkeleton key={item} />)
}
</div>
)
}
export default ListProductCardSkeleton

View File

@@ -1,9 +1,8 @@
@import "../../../styles/utilities";
.menuFilterWrapper{
@apply spacing-horizontal;
.menuFilterWrapper{
.menuFilterHeading{
@apply sub-headline font-bold ;
@apply sub-headline font-bold;
color: var(--text-active);
font-feature-settings: 'salt' on;
margin: 0.8rem 0;
@@ -19,21 +18,5 @@
width: 100%;
border-bottom: 1px solid var(--border-line);
}
li{
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:white;
background-color: var(--primary);
}
}
}
}
}

View File

@@ -1,44 +1,34 @@
import classNames from 'classnames'
import { useEffect, useState } from 'react';
import s from './MenuFilter.module.scss';
import MenuFilterItem from './MenuFilterItem/MenuFilterItem';
import s from './MenuFilter.module.scss'
interface Props {
children?: any,
heading?:string,
categories:{name:string,link:string}[],
type:string,
onChangeValue?: (value: Object) => void
heading?: string,
categories: { name: string, slug?: string, code?: string }[],
type: string,
onChange: (value: string, type: string, isSellect?: boolean) => void
isSingleSelect?: boolean
singleSelectedValue?: string
}
const MenuFilter = ({heading,categories,type,onChangeValue}:Props)=> {
const [active, setActive] = useState<string>('');
function handleClick(link:string){
setActive(link);
if(active === link){
setActive('');
}
const MenuFilter = ({ heading, categories, type, onChange, singleSelectedValue, isSingleSelect }: Props) => {
function handleChange(value: string, isSellect: boolean) {
onChange(value, type, isSellect)
}
useEffect(()=>{
let href = active?.split("=");
const linkValue = href[1];
onChangeValue && onChangeValue({[type]:linkValue});
},[active])
return (
<section className={s.menuFilterWrapper}>
<h2 className={s.menuFilterHeading}>{heading}</h2>
<ul className={s.menuFilterList}>
{
categories.map(item => <li key={item.name}>
<div onClick={()=> handleClick(item.link)} className={classNames({ [s.active]: item.link === active? true: false })}>
{item.name}
</div>
</li>)
categories.map(item => <MenuFilterItem
key={item.slug || item.code}
name={item.name}
type={type}
value={item.slug || item.code || ''}
onChange={handleChange}
isActive={isSingleSelect && (item.slug || item.code) === singleSelectedValue}
/>)
}
</ul>
</section>

View File

@@ -0,0 +1,15 @@
.menuFilterItem {
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,42 @@
import classNames from 'classnames';
import { route } from 'next/dist/server/router';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import s from './MenuFilterItem.module.scss';
interface Props {
name: string,
value: string,
type: string,
isActive?: boolean
onChange: (value: string, isSellect: boolean) => void
}
const MenuFilterItem = ({ name, value, type, isActive, onChange }: Props) => {
const router = useRouter()
const [isSelected, setIsSelected] = useState<boolean>()
useEffect(() => {
setIsSelected(isActive)
}, [isActive])
useEffect(() => {
const rs = (router.query[type] || []).includes(value)
setIsSelected(rs)
}, [type, router.query, value])
function handleClick() {
onChange(value, !isSelected)
setIsSelected(!isSelected)
}
return (
<li className={s.menuFilterItem}>
<div onClick={handleClick} className={classNames({ [s.active]: isSelected })}>
{name}
</div>
</li>
)
}
export default MenuFilterItem

View File

@@ -3,31 +3,13 @@
@apply hidden;
@screen md {
@apply block;
}
.menuNavigationHeading{
@screen md {
@apply sub-headline font-bold ;
color: var(--text-active);
font-feature-settings: 'salt' on;
margin: 1.6rem 0;
}
}
.menuNavigationList{
@screen md {
li{
margin: 0.8rem 0;
a{
display:block;
width:100%;
color:var(--text-base);
&:hover {
@apply text-primary;
}
&.active {
@apply text-primary;
}
}
.menuNavigationHeading{
@screen md {
@apply sub-headline font-bold ;
color: var(--text-active);
font-feature-settings: 'salt' on;
margin: 1.6rem 0;
}
}
}
}
}

View File

@@ -1,30 +1,27 @@
import classNames from 'classnames'
import Link from 'next/link'
import { useRouter } from 'next/router'
import s from './MenuNavigation.module.scss'
import MenuNavigationItem from './MenuNavigationItem/MenuNavigationItem'
interface Props {
children?: any,
heading:string,
categories:{name:string,link:string}[]
heading: string,
queryKey: string,
categories: { name: string, slug?: string, code?: string }[]
isSingleSelect?: boolean
}
const MenuNavigation = ({heading,categories}:Props)=> {
const router = useRouter()
const MenuNavigation = ({ heading, queryKey, categories, isSingleSelect }: Props) => {
return (
<section className={s.menuNavigationWrapper}>
<h2 className={s.menuNavigationHeading}>{heading}({categories.length})</h2>
<ul className={s.menuNavigationList}>
{
categories.map(item => <li key={item.name}
>
<Link href={item.link}>
<a className={classNames({ [s.active]: router.asPath === item.link})}>
{item.name}
</a>
</Link>
</li>)
categories.map(item => <MenuNavigationItem
key={item.name}
name={item.name}
value={item.slug || item.code || ''}
queryKey={queryKey}
isSingleSelect={isSingleSelect}
/>)
}
</ul>
</section>

View File

@@ -0,0 +1,15 @@
.menuNavigationItem {
@screen md {
display: block;
width: 100%;
margin: 0.8rem 0;
color: var(--text-base);
cursor: pointer;
&:hover {
@apply text-active;
}
&.active {
@apply text-primary;
}
}
}

View File

@@ -0,0 +1,66 @@
import classNames from 'classnames'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { QUERY_KEY, QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils'
import s from './MenuNavigationItem.module.scss'
interface Props {
name: string
value: string
queryKey: string
isSingleSelect?: boolean
}
const MenuNavigationItem = ({ name, value, queryKey, isSingleSelect }: Props) => {
const router = useRouter()
const [isActive, setIsActive] = useState<boolean>()
useEffect(() => {
if (!value) {
setIsActive(false)
}
const queryString = router.query[queryKey] as string || ''
setIsActive(queryString.split(QUERY_SPLIT_SEPERATOR).includes(value))
}, [router.query, queryKey, value])
const handleClick = () => {
const queryString = router.query[queryKey] as string || ''
const prevQuery = queryString.split(QUERY_SPLIT_SEPERATOR)
let newQuery = ''
if (isSingleSelect) {
newQuery = isActive ? '' : value
} else {
if (isActive) {
newQuery = prevQuery.filter(item => item !== value).join(QUERY_SPLIT_SEPERATOR)
} else {
newQuery = [...prevQuery, value].join(QUERY_SPLIT_SEPERATOR)
}
}
const query = {
...router.query,
[queryKey]: newQuery
}
if (queryKey === QUERY_KEY.CATEGORY) {
query[QUERY_KEY.PAGE] = "0"
}
router.push({
pathname: ROUTE.PRODUCTS,
query
},
undefined, { shallow: true }
)
}
return (<li className={classNames(s.menuNavigationItem, { [s.active]: isActive })}
onClick={handleClick}>
{name}
</li>)
}
export default MenuNavigationItem

View File

@@ -1,13 +1,5 @@
@import "../../../styles/utilities";
.menuNavigationProductListDesktop{
@screen sm {
@apply hidden;
}
@screen xl {
@apply block;
}
}
.menuNavigationProductListMobile{
@apply relative transition-all duration-100;
&.isShow{
@@ -37,7 +29,7 @@
transform: translateY(0%)
}
.content{
@apply absolute w-full h-full;
@apply absolute w-full h-full spacing-horizontal custom-scroll;
margin-top: 3rem;
padding-top: 10rem ;
padding-bottom: 10rem;
@@ -46,6 +38,7 @@
height: 96%;
bottom: 0;
border-radius: 2.4rem 2.4rem 0 0;
.head{
@apply flex justify-between fixed;
top:0;
@@ -57,12 +50,11 @@
background-color: white;
z-index: 10000;
h3{
@apply heading-3 font-bold;
color:var(--text-base);
@apply heading-3 font-heading;
}
}
.foot{
@apply fixed;
@apply fixed text-center;
bottom: 0;
left:0;
width: 100%;
@@ -70,7 +62,7 @@
padding: 0 1rem 3rem 1rem;
}
button{
button {
margin-top: 2rem;
width: 100%;
}

View File

@@ -1,56 +1,155 @@
import React, { useState } from 'react';
import {ButtonCommon} from 'src/components/common';
import { QueryFacetsArgs } from '@framework/schema';
import classNames from 'classnames';
import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react';
import { ButtonCommon } from 'src/components/common';
import { useProductFilter } from 'src/components/contexts';
import { useGetAllCollection } from 'src/components/hooks/collection';
import { useFacets } from 'src/components/hooks/facets';
import IconHide from 'src/components/icons/IconHide';
import { CODE_FACET_BRAND, CODE_FACET_FEATURED, OPTIONS_SORT_PRODUCT, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils';
import { LANGUAGE } from 'src/utils/language.utils';
import { SortOrder } from 'src/utils/types.utils';
import MenuFilter from '../MenuFilter/MenuFilter';
import SkeletonParagraph from '../SkeletonCommon/SkeletonParagraph/SkeletonParagraph';
import s from './MenuNavigationProductList.module.scss';
import MenuSort from './MenuSort/MenuSort';
import {LANGUAGE} from 'src/utils/language.utils';
import classNames from 'classnames'
import MenuFilter from '../MenuFilter/MenuFilter';
import MenuNavigation from '../MenuNavigation/MenuNavigation';
import IconHide from 'src/components/icons/IconHide';
interface Props{
categories:{name:string,link:string}[],
brands:{name:string,link:string}[],
featured:{name:string,link:string}[],
visible: boolean,
onClose: () => void
interface Props {
}
const MenuNavigationProductList = ({categories,brands,featured,visible,onClose}:Props)=>{
const [dataSort,setDataSort] = useState({});
function handleValue(value:Object){
setDataSort({...dataSort,...value});
const FACET_QUERY = {
options: {
sort: {
code: SortOrder.Asc
},
filter: {
code: {
in: [CODE_FACET_FEATURED, CODE_FACET_BRAND]
}
}
}
function filter(){
// console.log(dataSort)
} as QueryFacetsArgs
const MenuNavigationProductList = ({}: Props) => {
const router = useRouter()
const { productFilterVisible: visible, closeProductFilter: onClose } = useProductFilter()
const { facets, loading: facetsLoading } = useFacets(FACET_QUERY)
const { collections, loading: collectionLoading } = useGetAllCollection()
const [brandQuery, setBrandQuery] = useState<string[]>([])
const [featuredQuery, setFeaturedQuery] = useState<string[]>([])
const [categoryQuery, setCategoryQuery] = useState<string>()
const [sortValue, setSortValue] = useState<string>();
useEffect(() => {
const rs = router.query[QUERY_KEY.SORTBY] as string
if (rs) {
setSortValue(rs)
}
}, [router.query])
useEffect(() => {
const rs = router.query[QUERY_KEY.CATEGORY] as string
if (rs) {
setCategoryQuery(rs)
}
}, [router.query])
function onSubmit() {
let newURL = `${ROUTE.PRODUCTS}?`
if (categoryQuery) {
newURL += `&${QUERY_KEY.CATEGORY}=${categoryQuery}`
}
if (brandQuery.length > 0) {
newURL += `&${QUERY_KEY.BRAND}=${brandQuery.join(",")}`
}
if (featuredQuery.length > 0) {
newURL += `&${QUERY_KEY.FEATURED}=${featuredQuery.join(",")}`
}
if (sortValue) {
newURL += `&${QUERY_KEY.SORTBY}=${sortValue}`
}
router.push(newURL)
onClose()
}
return(
<>
<div className={s.menuNavigationProductListDesktop}>
<MenuNavigation categories={categories} heading="Categories"/>
<MenuNavigation categories={brands} heading="Brands"/>
<MenuNavigation categories={featured} heading="Featured"/>
</div>
<div className={classNames({ [s.menuNavigationProductListMobile] :true,[s.isShow]: visible})}>
<div className={classNames({ [s.menuNavigationProductModal] :true,[s.animation]: visible})}>
<div className={s.content}>
<div className={s.head}>
<h3>FILTER</h3>
<div onClick={onClose}><IconHide/></div>
</div>
<MenuFilter categories={categories} heading="Categories" type="category" onChangeValue={handleValue}/>
<MenuFilter categories={brands} heading="Brand" type="brand" onChangeValue={handleValue}/>
<MenuFilter categories={featured} heading="Featured" type="featured" onChangeValue={handleValue}/>
<MenuSort heading="SORT BY" type="sort" onChangeValue={handleValue}/>
<div className={s.foot}>
<ButtonCommon size="large" onClick={filter}>{LANGUAGE.BUTTON_LABEL.CONFIRM}</ButtonCommon>
</div>
const onSortChange = (value: string) => {
setSortValue(value)
}
const onCategoryChange = (value: string, isSelect: boolean) => {
if (isSelect) {
setCategoryQuery(value)
} else {
setCategoryQuery('')
}
}
const onFilterOptionChange = (value: string, type: string, isSelect: boolean = true) => {
if (type === QUERY_KEY.CATEGORY) {
onCategoryChange(value, isSelect)
} else {
let rs = [...featuredQuery]
let setDataFunction = setFeaturedQuery
if (type === CODE_FACET_BRAND) {
rs = [...brandQuery]
setDataFunction = setBrandQuery
}
if (isSelect) {
rs.push(value)
} else {
rs = rs.filter(item => item !== value)
}
setDataFunction(rs)
}
}
return (
<div className={classNames({ [s.menuNavigationProductListMobile]: true, [s.isShow]: visible })}>
<div className={classNames({ [s.menuNavigationProductModal]: true, [s.animation]: visible })}>
<div className={s.content}>
<div className={s.head}>
<h3>FILTER</h3>
<div onClick={onClose}><IconHide /></div>
</div>
{collectionLoading && <SkeletonParagraph rows={5} />}
<MenuFilter categories={collections}
heading="Categories"
type={QUERY_KEY.CATEGORY}
onChange={onFilterOptionChange}
singleSelectedValue={categoryQuery}
isSingleSelect={true}
/>
{facetsLoading && <>
<SkeletonParagraph rows={5} />
<SkeletonParagraph rows={5} />
</>}
{
facets?.map(item => <MenuFilter
key={item.id}
type={item.code}
categories={item.values}
heading={item.name}
onChange={onFilterOptionChange}
/>)
}
<MenuSort heading="SORT BY" onChange={onSortChange} value={sortValue} options={OPTIONS_SORT_PRODUCT} />
<div className={s.foot}>
<ButtonCommon size="large" onClick={onSubmit}>{LANGUAGE.BUTTON_LABEL.CONFIRM}</ButtonCommon>
</div>
</div>
</div>
</>
</div>
)
}

View File

@@ -1,40 +1,12 @@
@import "../../../../styles/utilities";
.menuSortWrapper{
@apply spacing-horizontal;
.menuSortWrapper{
.menuSortHeading{
@apply sub-headline font-bold ;
color: var(--text-active);
font-feature-settings: 'salt' on;
@apply heading-3 font-heading;
margin: 0.8rem 0;
}
.menuSortList{
padding-bottom: 1rem;
box-sizing: border-box;
li{
div{
height: 4.8rem;
line-height: 4.8rem;
padding: 0 1.6rem;
margin-right: 0.8rem;
border-radius: 0.8rem;
cursor: pointer;
&.active {
@apply font-bold relative;
color:var(--text-active);
background-color: var(--gray);
&::after{
@apply absolute;
content:"";
background-image: url('/assets/svg/check.svg');
right: 1.6rem;
top: calc(50% - 24px/2);
width: 2.4rem;
height: 2.4rem;
}
}
}
}
}
}

View File

@@ -1,63 +1,30 @@
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { QUERY_KEY, ROUTE } from 'src/utils/constanst.utils';
import { PRODUCT_SORT_OPTION_VALUE, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils';
import s from './MenuSort.module.scss';
import MenuSortItem from './MenuSortItem/MenuSortItem';
interface Props {
children?: any,
heading:string,
type:string,
onChangeValue?: (value: Object) => void
heading: string,
options: {name: string, value: string}[]
value?: string,
onChange: (value: string) => void
}
const SORT = [
{
name: 'By Name',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.SORTBY}=by-name`,
},
{
name: 'Price(High to Low)',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.SORTBY}=high-to-low`,
},
{
name: 'Price (Low to High)',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.SORTBY}=low-to-high`,
},
{
name: 'On Sale',
link: `${ROUTE.PRODUCTS}/?${QUERY_KEY.SORTBY}=on-sale`,
},
];
const MenuSort = ({heading,type,onChangeValue}:Props)=> {
const [active, setActive] = useState<string>('');
function handleClick(link:string){
setActive(link);
if(active === link){
setActive('');
}
}
useEffect(()=>{
let href = active?.split("=");
const linkValue = href[1];
onChangeValue && onChangeValue({[type]:linkValue});
},[active])
const MenuSort = ({ heading, value, onChange, options }: Props) => {
return (
<section className={classNames(s.menuSortWrapper)}>
<h2 className={classNames(s.menuSortHeading)}>{heading}</h2>
<ul className={s.menuSortList}>
{
SORT.map(item => <li key={item.name}>
<div onClick={()=> handleClick(item.link)} className={classNames({ [s.active]: item.link === active? true: false })}>
{item.name}
</div>
</li>)
options.map(item => <MenuSortItem
key={item.value}
name={item.name}
value={item.value}
currentValue={value}
onChange={onChange}
/>)
}
</ul>
</section>

View File

@@ -0,0 +1,26 @@
.menuSortItem {
div {
height: 4.8rem;
line-height: 4.8rem;
padding: 0 1.6rem;
margin-right: 0.8rem;
border-radius: 0.8rem;
cursor: pointer;
&.active {
@apply font-bold relative;
color: var(--text-active);
background-color: var(--primary-lightest);
&::after {
@apply absolute;
content: "";
background-image: url("./check.svg");
background-repeat: no-repeat;
background-position: center;
right: 1.6rem;
top: calc(50% - 24px / 2);
width: 2.4rem;
height: 2.4rem;
}
}
}
}

View File

@@ -0,0 +1,24 @@
import classNames from 'classnames';
import s from './MenuSortItem.module.scss';
interface Props {
name: string,
value: string,
currentValue?: string,
onChange: (value: string) => void
}
const MenuSortItem = ({ onChange, name, value, currentValue }: Props) => {
const handleChange = () => {
onChange(value)
}
return (
<li className={s.menuSortItem}>
<div onClick={handleChange} className={classNames({ [s.active]: value === currentValue })}>
{name}
</div>
</li>
)
}
export default MenuSortItem

View File

@@ -0,0 +1,3 @@
<svg width="16" height="12" viewBox="0 0 16 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.7104 1.20986C14.6175 1.11613 14.5069 1.04174 14.385 0.990969C14.2632 0.940201 14.1324 0.914062 14.0004 0.914062C13.8684 0.914062 13.7377 0.940201 13.6159 0.990969C13.494 1.04174 13.3834 1.11613 13.2904 1.20986L5.84044 8.66986L2.71044 5.52986C2.61392 5.43662 2.49998 5.36331 2.37512 5.3141C2.25026 5.2649 2.11694 5.24077 1.98276 5.24309C1.84858 5.24541 1.71617 5.27414 1.59309 5.32763C1.47001 5.38113 1.35868 5.45834 1.26544 5.55486C1.1722 5.65138 1.09889 5.76532 1.04968 5.89018C1.00048 6.01503 0.976347 6.14836 0.978669 6.28254C0.98099 6.41672 1.00972 6.54913 1.06321 6.67221C1.1167 6.79529 1.19392 6.90662 1.29044 6.99986L5.13044 10.8399C5.2234 10.9336 5.334 11.008 5.45586 11.0588C5.57772 11.1095 5.70843 11.1357 5.84044 11.1357C5.97245 11.1357 6.10316 11.1095 6.22502 11.0588C6.34687 11.008 6.45748 10.9336 6.55044 10.8399L14.7104 2.67986C14.8119 2.58622 14.893 2.47257 14.9484 2.34607C15.0038 2.21957 15.0324 2.08296 15.0324 1.94486C15.0324 1.80676 15.0038 1.67015 14.9484 1.54365C14.893 1.41715 14.8119 1.3035 14.7104 1.20986Z" fill="#5b9a74" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -25,7 +25,7 @@ const ModalCreateUserInfo = ({ demoVisible: visible, demoCloseModal: closeModal
<Inputcommon placeholder='Street Address' ref={firstInputRef} />
<Inputcommon placeholder='City' />
<div className={s.line}>
<SelectCommon option={STATE_OPTIONS} type="custom" size="large" placeholder='State'/>
<SelectCommon options={STATE_OPTIONS} type="custom" size="large" placeholder='State'/>
<Inputcommon placeholder='Zip code' />
</div>
<Inputcommon placeholder='Phone (delivery contact)' />

View File

@@ -14,9 +14,11 @@ import ProductNotSell from './ProductNotSell/ProductNotSell'
export interface ProductCardProps extends ProductCard {
buttonText?: string
isSingleButton?: boolean,
activeWishlist?:boolean
}
const ProductCardComponent = ({
id,
collection,
name,
slug,
@@ -27,6 +29,7 @@ const ProductCardComponent = ({
imageSrc,
isNotSell,
isSingleButton,
activeWishlist
}: ProductCardProps) => {
if (isNotSell) {
return <div className={`${s.productCardWarpper} ${s.notSell}`}>
@@ -34,6 +37,7 @@ const ProductCardComponent = ({
</div>
}
return (
<div className={s.productCardWarpper}>
<div className={s.cardTop}>
@@ -63,7 +67,7 @@ const ProductCardComponent = ({
<div className={s.cardMidBot}>
<div className={s.productPrice}>{price} {currencyCode}</div>
<div className={s.wishList}>
<ItemWishList />
<ItemWishList isActive={activeWishlist} id={id}/>
</div>
</div>
</div>

View File

@@ -0,0 +1,15 @@
.productCardSkeleton {
max-width: 22.4rem;
min-width: 20rem;
margin: 0 0.8rem;
> div {
width: 100% !important;
}
.content {
margin-top: 1.6rem;
> div {
margin: 0;
width: 100% !important;
}
}
}

View File

@@ -0,0 +1,17 @@
import SkeletonImage from "../SkeletonCommon/SkeletonImage/SkeletonImage"
import SkeletonParagraph from "../SkeletonCommon/SkeletonParagraph/SkeletonParagraph"
import s from './ProductCardSkeleton.module.scss'
const ProductCardSkeleton = ({ }) => {
return (
<div className={s.productCardSkeleton}>
<SkeletonImage />
<div className={s.content}>
<SkeletonParagraph rows={3} />
</div>
</div>
)
}
export default ProductCardSkeleton

View File

@@ -1,11 +1,28 @@
.wrapper{
.list{
// max-width: 109.4rem;
@apply flex flex-wrap justify-around;
.wrapper {
margin: 4rem auto;
.list {
@apply grid grid-cols-2;
@screen md {
@apply grid-cols-3;
}
@screen lg {
@apply grid-cols-4;
}
@screen xl {
@apply grid-cols-6;
}
.empty {
button {
margin-top: 1.6rem;
width: 100%;
}
}
}
.pagination{
.pagination {
padding-top: 4.8rem;
// max-width: 109.4rem;
@apply flex justify-center items-center ;
@apply flex justify-center items-center;
&.hide {
@apply hidden;
}
}
}
}

View File

@@ -1,27 +1,53 @@
import React, { useState } from 'react'
import classNames from 'classnames'
import { useRouter } from 'next/router'
import React from 'react'
import { useActiveCustomer } from 'src/components/hooks/auth'
import { DEFAULT_PAGE_SIZE, ROUTE } from 'src/utils/constanst.utils'
import { ButtonCommon, EmptyCommon } from '..'
import PaginationCommon from '../PaginationCommon/PaginationCommon'
import ProductCard, { ProductCardProps } from '../ProductCard/ProductCard'
import s from "./ProductList.module.scss"
interface ProductListProps {
data: ProductCardProps[]
data: ProductCardProps[],
total?: number,
defaultCurrentPage?: number
onPageChange?: (page: number) => void
}
const ProductList = ({data}: ProductListProps) => {
const [currentPage, setCurrentPage] = useState(0)
const onPageChange = (page:number) => {
setCurrentPage(page)
const ProductList = ({ data, total = data.length, defaultCurrentPage, onPageChange }: ProductListProps) => {
const router = useRouter()
const {wishlistId } = useActiveCustomer();
const handlePageChange = (page: number) => {
onPageChange && onPageChange(page)
}
const handleShowAllProduct = () => {
router.push({
pathname: ROUTE.PRODUCTS,
},
undefined, { shallow: true }
)
}
return (
<div className={s.wrapper}>
<div className={s.list}>
{
data.slice(currentPage*20,(currentPage+1)*20).map((product,index)=>{
return <ProductCard {...product} key={index}/>
data.map((product, index) => {
let activeWishlist = wishlistId?.findIndex((val:string) => val == product.id) !== -1;
return <ProductCard activeWishlist={activeWishlist} {...product} key={index} />
})
}
{
data.length === 0 && <div className={s.empty}>
<EmptyCommon />
<ButtonCommon onClick={handleShowAllProduct}>Show all products</ButtonCommon>
</div>
}
</div>
<div className={s.pagination}>
<PaginationCommon total={data.length} pageSize={20} onChange={onPageChange}/>
<div className={classNames(s.pagination, { [s.hide]: data.length === 0 })}>
<PaginationCommon defaultCurrent={defaultCurrentPage} total={total} pageSize={DEFAULT_PAGE_SIZE} onChange={handlePageChange} />
</div>
</div>
)

View File

@@ -1,33 +1,35 @@
import s from './SelectCommon.module.scss'
import classNames from 'classnames'
import { useEffect, useState } from 'react'
import { IconVectorDown } from 'src/components/icons'
import s from './SelectCommon.module.scss'
import SelectOption from './SelectOption/SelectOption'
interface Props {
selected?:string|null,
initValue?:string|null,
placeholder? : string,
value?: string,
size?: 'base' | 'large',
type?: 'default' | 'custom',
option: {name: string, value: string}[],
options: {name: string, value: string}[],
onChange?: (value: string) => void,
}
const SelectCommon = ({selected,initValue, type = 'default', size = 'base', option, placeholder, onChange}: Props) => {
const SelectCommon = ({selected,initValue, type = 'default', size = 'base', options, placeholder, onChange}: Props) => {
const [selectedName, setSelectedName] = useState(placeholder)
const [selectedValue, setSelectedValue] = useState('')
useEffect(()=>{
const nameSelect = option.find((val)=>val.value === selected);
const nameSelect = options.find((val)=>val.value === selected);
setSelectedName(nameSelect?.name ?? 'State');
setSelectedValue(initValue ?? '');
onChange && onChange(initValue ?? '');
},[])
const changeSelectedName = (item:string, value: string) => {
const changeSelectedName = (value: string) => {
setSelectedValue(value)
setSelectedName(item)
const name = options.find(item => item.value === value)?.name
setSelectedName(name)
onChange && onChange(value)
}
return(
@@ -42,7 +44,7 @@ const SelectCommon = ({selected,initValue, type = 'default', size = 'base', opti
[s.selectTrigger] : true,
})}
>{selectedName}<IconVectorDown /></div>
>{selectedName || placeholder}<IconVectorDown /></div>
<div className={s.hoverWrapper}>
<div className={classNames({
@@ -52,8 +54,12 @@ const SelectCommon = ({selected,initValue, type = 'default', size = 'base', opti
})}
>
{
option.map(item =>
<SelectOption key={item.value} itemName={item.name} value={item.value} onClick={changeSelectedName} size={size} selected={(selectedValue === item.value)} />
options.map(item =>
<SelectOption key={item.value}
itemName={item.name}
value={item.value}
onChange={changeSelectedName}
size={size} selected={(selectedValue === item.value)} />
)
}
</div>

View File

@@ -2,16 +2,16 @@ import s from './SelectOption.module.scss'
import classNames from 'classnames'
interface Props{
onClick: (name: string, value: string) => void,
onChange: (value: string) => void,
itemName: string,
size: 'base' | 'large',
value: string,
selected?: boolean,
}
const SelectOption = ({onClick, itemName, size, value, selected} : Props) => {
const changeName = () => {
onClick(itemName, value)
const SelectOption = ({onChange, itemName, size, value, selected} : Props) => {
const handleChange = () => {
onChange(value)
}
return(
<div className={classNames({
@@ -19,7 +19,7 @@ const SelectOption = ({onClick, itemName, size, value, selected} : Props) => {
[s[size]] : !!size,
[s.isChoose] : selected ,
})}
onClick = {changeName}
onClick = {handleChange}
>{itemName}</div>
)
}

View File

@@ -1,7 +1,7 @@
@import '../../../../styles/utilities';
.skeletonImage {
@apply relative;
@apply relative overflow-hidden;
background: #DDDBDD;
&.small {

View File

@@ -1,6 +1,7 @@
@import '../../../../styles/utilities';
.skeletonParagraph {
@apply overflow-hidden;
margin: 0 1.6rem;
.row {

View File

@@ -51,5 +51,8 @@ export { default as LayoutCheckout} from './LayoutCheckout/LayoutCheckout'
export { default as InputPasswordFiledInForm} from './InputPasswordFiledInForm/InputPasswordFiledInForm'
export { default as InputFiledInForm} from './InputFiledInForm/InputFiledInForm'
export { default as MessageCommon} from './MessageCommon/MessageCommon'
export { default as FormForgot} from './ForgotPassword/FormForgot/FormForgot'
export { default as FormResetPassword} from './ForgotPassword/FormResetPassword/FormResetPassword'
export { default as ProductCardSkeleton} from './ProductCardSkeleton/ProductCardSkeleton'
export { default as ListProductCardSkeleton} from './ListProductCardSkeleton/ListProductCardSkeleton'

View File

@@ -1,43 +0,0 @@
import { createContext, ReactNode, useContext, useState } from "react";
import { filterContextType } from "src/utils/types.utils";
const contextDefaultValues: filterContextType = {
visible: false,
open: () => {},
close: () => {},
};
const FilterContext = createContext<filterContextType>(contextDefaultValues);
export function useAuth() {
return useContext(FilterContext);
}
type FilterProviderProps = {
children: ReactNode;
};
export function FilterProvider({ children }: FilterProviderProps) {
const [visible, setVisible] = useState<boolean>(false);
const open = () => {
setVisible(true);
};
const close = () => {
setVisible(false);
};
const value = {
visible,
open,
close,
};
return (
<>
<FilterContext.Provider value={value}>
{children}
</FilterContext.Provider>
</>
);
}

View File

@@ -0,0 +1,20 @@
import { createContext, useContext } from 'react';
export type ProductFilterContextType = {
productFilterVisible: boolean;
toggleProductFilter: (visible?: boolean) => void;
openProductFilter: () => void;
closeProductFilter: () => void;
};
const DEFAULT_VALUE: ProductFilterContextType = {
productFilterVisible: false,
toggleProductFilter: () => { },
openProductFilter: () => { },
closeProductFilter: () => { },
};
export const ProductFilterContext = createContext<ProductFilterContextType>(DEFAULT_VALUE)
export function useProductFilter() {
return useContext(ProductFilterContext);
}

View File

@@ -0,0 +1,30 @@
import { ReactNode, useState } from "react";
import { ProductFilterContext } from "./ProductFilterContext";
type Props = {
children: ReactNode;
};
export function ProductFilterProvider({ children }: Props) {
const [visible, setVisible] = useState<boolean>(false);
const closeProductFilter = () => {
setVisible(false);
};
const openProductFilter = () => {
setVisible(true);
};
const toggleProductFilter = () => {
setVisible(!visible);
};
return (
<>
<ProductFilterContext.Provider value={{productFilterVisible: visible, closeProductFilter, openProductFilter, toggleProductFilter}}>
{children}
</ProductFilterContext.Provider>
</>
);
}

View File

@@ -3,3 +3,6 @@ export * from './CartDrawer/CartDrawerProvider'
export * from './Message/MessageContext'
export * from './Message/MessageProvider'
export * from './ProductFilter/ProductFilterContext'
export * from './ProductFilter/ProductFilterProvider'

View File

@@ -3,4 +3,6 @@ export { default as useLogin } from './useLogin'
export { default as useLogout } from './useLogout'
export { default as useVerifyCustomer } from './useVerifyCustomer'
export { default as useActiveCustomer } from './useActiveCustomer'
export { default as useRequestPasswordReset } from './useRequestPasswordReset'
export { default as useResetPassword } from './useResetPassword'

View File

@@ -1,20 +1,24 @@
import { ActiveCustomerQuery } from '@framework/schema'
import { ActiveCustomerQuery,Favorite } from '@framework/schema'
import { activeCustomerQuery } from '@framework/utils/queries/active-customer-query'
import gglFetcher from 'src/utils/gglFetcher'
import useSWR from 'swr'
const useActiveCustomer = () => {
const { data, ...rest } = useSWR<ActiveCustomerQuery>([activeCustomerQuery], gglFetcher)
return {
customer: data?.activeCustomer,
userInfo:{
firstName: data?.activeCustomer?.firstName,
lastName:data?.activeCustomer?.lastName,
email:data?.activeCustomer?.emailAddress,
phoneNumber: data?.activeCustomer?.phoneNumber,
address: data?.activeCustomer?.addresses?.[0]
},
...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]
},
itemWishlist:data?.activeCustomer?.favorites?.items,
wishlistId: data?.activeCustomer?.favorites?.items.map((val:Favorite)=>val.product.id),
...rest
}
}
export default useActiveCustomer

View File

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

View File

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

View File

@@ -5,8 +5,8 @@ import useSWR from 'swr';
const useGetAllCollection = () => {
const { data, ...rest } = useSWR<GetCollectionsQuery>([getCollectionsNameQuery], gglFetcher)
return { collections: data?.collections, ...rest }
const { data, isValidating, ...rest } = useSWR<GetCollectionsQuery>([getCollectionsNameQuery], gglFetcher)
return { collections: data?.collections.items || [], loading: isValidating, ...rest }
}
export default useGetAllCollection;

View File

@@ -5,7 +5,7 @@ import useSWR from 'swr'
const useFacets = (options?: QueryFacetsArgs) => {
const { data, isValidating, ...rest } = useSWR<GetAllFacetsQuery>([getAllFacetsQuery, options], gglFetcher)
return { items: data?.facets.items, totalItems: data?.facets.totalItems, loading: isValidating, ...rest }
return { facets: data?.facets.items, totalItems: data?.facets.totalItems, loading: isValidating, ...rest }
}
export default useFacets

View File

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

View File

@@ -0,0 +1,16 @@
import { GetProductQuery } from '@framework/schema'
import { getProductDetailQuery } from '@framework/utils/queries/get-product-query';
import gglFetcher from 'src/utils/gglFetcher'
import useSWR from 'swr'
interface ProductDetail {
slug: string
}
const useProductDetail = () => {
const { data, ...rest } = useSWR<GetProductQuery>([getProductDetailQuery],gglFetcher)
return { productDetail: data?.product, ...rest }
}
export default useProductDetail

View File

@@ -0,0 +1,13 @@
import { GetAllProductsQuery, QuerySearchArgs } from '@framework/schema'
import { normalizeSearchResult } from '@framework/utils/normalize'
import { getAllProductsQuery } from '@framework/utils/queries/get-all-products-query'
import gglFetcher from 'src/utils/gglFetcher'
import useSWR from 'swr'
const useSearchProducts = (options?: QuerySearchArgs) => {
const { data, isValidating, ...rest } = useSWR<GetAllProductsQuery>([getAllProductsQuery, options], gglFetcher)
return { products: data?.search.items.map((item) => normalizeSearchResult(item)), totalItems: data?.search.totalItems, loading: isValidating, ...rest }
}
export default useSearchProducts

View File

@@ -0,0 +1,44 @@
import { useState } from 'react'
import useActiveCustomer from '../auth/useActiveCustomer'
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 } = useActiveCustomer()
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

@@ -69,6 +69,8 @@ const AccountPage = ({ defaultActiveContent="orders" } : AccountPageProps) => {
const [activeTab, setActiveTab] = useState(defaultActiveContent==="info" ? 0 : defaultActiveContent==="orders" ? 1 : 2)
const [modalVisible, setModalVisible] = useState(false);
// const { itemWishlist } = useActiveCustomer();
// console.log(itemWishlist)
useEffect(() => {
const query = router.query[QUERY_KEY.TAB] as string

View File

@@ -157,7 +157,7 @@ const EditInfoModal = ({ accountInfo, visible = false, closeModal }: EditInfoMod
<div className="flex">
<div className={s.inputState}>
<SelectCommon initValue={accountInfo.address?.province} selected={accountInfo.address?.province} type="custom" onChange={state} placeholder="State" option={states} size="large"/>
<SelectCommon initValue={accountInfo.address?.province} selected={accountInfo.address?.province} type="custom" onChange={state} placeholder="State" options={states} size="large"/>
</div>
<div className={s.inputPostalCode}>

View File

@@ -46,7 +46,7 @@ const ShippingInfoForm = ({onConfirm,id}: ShippingInfoFormProps) => {
/>
<Inputcommon type="text" placeholder="City" ref={cityRef} />
<div className={s.line}>
<SelectCommon option={option} type="custom" size="large">State</SelectCommon>
<SelectCommon options={option} type="custom" size="large">State</SelectCommon>
<Inputcommon type="text" placeholder="Zip Code" ref={codeRef} />
</div>
<Inputcommon

View File

@@ -2,21 +2,13 @@ import { ProductCard } from '@commerce/types/product'
import { Collection } from '@framework/schema'
import React, { useMemo } from 'react'
import { OPTION_ALL, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils'
import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils'
import { CollectionCarcousel } from '..'
interface FreshProductsProps {
data: ProductCard[]
collections: Collection[]
}
const getCategoryNameFromCollectionId = (colelctions: Collection[], collectionId?: string ) => {
if (!collectionId) {
return ''
}
const collection = colelctions.find(item => item.id === collectionId)
return collection?.name || ''
}
const FreshProducts = ({ data, collections }: FreshProductsProps) => {
const dataWithCategory = useMemo(() => {
return data.map(item => {

View File

@@ -1,20 +1,28 @@
import React from 'react'
import React, { useMemo } from 'react';
import ProductImgs from './components/ProductImgs/ProductImgs'
import ProductInfo from './components/ProductInfo/ProductInfo'
import s from './ProductInfoDetail.module.scss'
import { Product } from '@commerce/types/product'
import { Collection } from '@framework/schema'
import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils';
interface Props {
className?: string
children?: any
productDetail: Product,
collections: Collection[]
}
const ProductInfoDetail = ({ }: Props) => {
const ProductInfoDetail = ({ productDetail, collections }: Props) => {
const dataWithCategoryName = useMemo(() => {
return {
...productDetail,
collection: getCategoryNameFromCollectionId(collections, productDetail.collectionIds ? productDetail.collectionIds[0] : undefined)
}
}, [productDetail, collections])
return (
<section className={s.productInfoDetail}>
<ProductImgs/>
<ProductInfo/>
<ProductImgs productImage={productDetail.images}/>
<ProductInfo productInfoDetail={dataWithCategoryName}/>
</section >
)
}
export default ProductInfoDetail

View File

@@ -3,15 +3,15 @@ import { ImgWithLink } from 'src/components/common'
import s from './ProductImgItem.module.scss'
export interface ProductImgItemProps {
src: string
url: string
alt?: string
}
const ProductImgItem = ({ src, alt }: ProductImgItemProps) => {
const ProductImgItem = ({ url, alt }: ProductImgItemProps) => {
return (
<section className={s.productImgItem}>
<ImgWithLink src={src} alt={alt} />
<ImgWithLink src={url} alt={alt} />
</section >
)
}

View File

@@ -3,26 +3,12 @@ import { ResponsiveType } from 'react-multi-carousel'
import { CarouselCommon } from 'src/components/common'
import ProductImgItem, { ProductImgItemProps } from '../ProductImgItem/ProductImgItem'
import s from './ProductImgs.module.scss'
import { ProductImage } from '@commerce/types/product';
interface Props {
className?: string
children?: any,
productImage: ProductImage[]
}
const DATA = [
{
src: 'https://user-images.githubusercontent.com/76729908/133026929-199799fc-bd75-4445-a24d-15c0e41796eb.png',
alt: 'Meat',
},
{
src: 'https://user-images.githubusercontent.com/76729908/130574371-3b75fa72-9552-4605-aba9-a4b31cd9dce7.png',
alt: 'Broccoli',
},
{
src: 'https://user-images.githubusercontent.com/76729908/130574371-3b75fa72-9552-4605-aba9-a4b31cd9dce7.png',
alt: 'Broccoli',
}
]
const RESPONSIVE: ResponsiveType = {
desktop: {
@@ -31,11 +17,11 @@ const RESPONSIVE: ResponsiveType = {
slidesToSlide: 1, // optional, default to 1.
},
}
const ProductImgs = ({ }: Props) => {
const ProductImgs = ({ productImage }: Props) => {
return (
<section className={s.productImgs}>
<CarouselCommon<ProductImgItemProps>
data={DATA}
data={productImage}
itemKey="product-detail-img"
Component={ProductImgItem}
responsive={RESPONSIVE}

View File

@@ -1,3 +1,4 @@
import { Product } from '@commerce/types/product'
import React from 'react'
import { ButtonCommon, LabelCommon, QuanittyInput } from 'src/components/common'
import { IconBuy } from 'src/components/icons'
@@ -5,25 +6,25 @@ import { LANGUAGE } from 'src/utils/language.utils'
import s from './ProductInfo.module.scss'
interface Props {
className?: string
children?: any,
productInfoDetail: Product
}
const ProductInfo = ({ }: Props) => {
const ProductInfo = ({ productInfoDetail }: Props) => {
console.log(productInfoDetail)
return (
<section className={s.productInfo}>
<div className={s.info}>
<LabelCommon shape='half'>SEAFOOD</LabelCommon>
<h2 className={s.heading}>SeaPAk</h2>
<LabelCommon shape='half'>{productInfoDetail.collection}</LabelCommon>
<h2 className={s.heading}>{productInfoDetail.name}</h2>
<div className={s.price}>
<div className={s.old}>
<span className={s.number}>Rp 32.000</span>
<span className={s.number}>Rp {productInfoDetail.price}</span>
<LabelCommon type='discount'>-15%</LabelCommon>
</div>
<div className={s.current}>Rp 27.500</div>
<div className={s.current}>Rp {productInfoDetail.price}</div>
</div>
<div className={s.description}>
In a large non-reactive dish, mix together the orange juice, soy sauce, olive oil, lemon juice, parsley
{productInfoDetail.description}
</div>
</div>
<div className={s.actions}>

View File

@@ -1,13 +1,33 @@
import React from 'react';
import { ProductCard } from '@commerce/types/product';
import { Collection } from '@framework/schema';
import React, { useMemo } from 'react';
import ListProductWithInfo from 'src/components/common/ListProductWithInfo/ListProductWithInfo';
import { PRODUCT_DATA_TEST } from 'src/utils/demo-data';
import { getCategoryNameFromCollectionId } from 'src/utils/funtion.utils';
const ReleventProducts = () => {
interface Props {
data: ProductCard[]
collections: Collection[]
}
const ReleventProducts = ({ data, collections }: Props) => {
const dataWithCategoryName = useMemo(() => {
return data.map(item => {
return {
...item,
collection: getCategoryNameFromCollectionId(collections, item.collectionIds ? item.collectionIds[0] : undefined)
}
})
}, [data, collections])
if (data.length === 0) {
return null
}
return (
<ListProductWithInfo
title="Relevant Products"
subtitle="Last call! Shop deep deals on 100+ bulk picks while you can."
data={PRODUCT_DATA_TEST}
data={dataWithCategoryName}
/>
);
};

View File

@@ -13,72 +13,59 @@
@screen md {
@apply flex;
}
.categories{
padding-right: 2.4rem;
@apply hidden;
@screen md {
@apply block;
}
@screen xl{
@apply block;
width:25%;
}
}
.list{
@screen md {
@apply flex justify-between flex-wrap w-full;
margin: 1rem 0;
}
@screen xl {
width:75%;
}
.inner{
@screen md {
@apply flex flex-col items-center justify-center;
@apply w-full;
.top {
.left {
@apply flex justify-between items-center;
}
.boxItem {
@screen md {
@apply flex justify-between flex-wrap;
margin: 1rem 0;
.iconFilter {
@apply relative;
&:focus {
outline: none;
filter: brightness(1.05);
}
.item {
@screen md {
width: calc(97% / 2);
margin-top:1rem;
}
@screen lg{
width: calc(97% / 3);
margin-top:1rem;
}
&:focus-visible {
outline: 2px solid var(--text-active);
}
.dot {
@apply absolute;
top: -0.08rem;
right: -0.2rem;
background-color: var(--negative);
width: 1.2rem;
height: 1.2rem;
border-radius: 1.2rem;
}
}
@screen md {
@apply flex justify-between flex-wrap w-full;
margin: 1rem 0;
.iconFilter {
@apply hidden;
}
}
}
.boxSelect{
@apply w-auto;
// padding: 2.5rem 0;
display: none;
@screen xl {
@screen md {
@apply block;
width: auto;
padding:0;
margin-top: 1rem;
}
.categorySelectCate{
@screen xl {
@apply hidden;
}
}
label{
label {
@apply font-bold topline ;
color:var(--text-active);
@screen xl {
@apply hidden;
}
}
.select{
margin-top: 1rem;
}
}
}

View File

@@ -1,12 +1,25 @@
import React from 'react'
import { HeadingCommon, ProductList, SelectCommon } from 'src/components/common'
import { ProductCard } from '@commerce/types/product'
import { Collection, Facet, FacetValue, QuerySearchArgs } from '@framework/schema'
import { useRouter } from 'next/router'
import React, { useEffect, useState } from 'react'
import { HeadingCommon, ListProductCardSkeleton, ProductList } from 'src/components/common'
import BreadcrumbCommon from 'src/components/common/BreadcrumbCommon/BreadcrumbCommon'
import MenuNavigation from 'src/components/common/MenuNavigation/MenuNavigation'
import { BRAND, CATEGORY, FEATURED} from 'src/utils/constanst.utils'
import { PRODUCT_DATA_TEST_PAGE } from 'src/utils/demo-data'
import { useProductFilter } from 'src/components/contexts'
import { useSearchProducts } from 'src/components/hooks/product'
import { IconFilter } from 'src/components/icons'
import { DEFAULT_PAGE_SIZE, QUERY_KEY, QUERY_SPLIT_SEPERATOR, ROUTE } from 'src/utils/constanst.utils'
import { getFacetIdsFromCodes, getPageFromQuery, getProductSortParamFromQuery } from 'src/utils/funtion.utils'
import s from './ProductListFilter.module.scss'
import ProductsMenuNavigationTablet from './ProductsMenuNavigationTablet/ProductsMenuNavigationTablet'
import ProductSort from './ProductSort/ProductSort'
interface ProductListFilterProps {}
interface ProductListFilterProps {
facets: Facet[]
collections: Collection[]
products: ProductCard[]
total: number
}
const BREADCRUMB = [
{
@@ -14,49 +27,98 @@ const BREADCRUMB = [
link: `#`,
},
]
const OPTIONSLECT = [
{
name: 'Most Viewed',
value: 'most-viewed',
},
{
name: 'Lastest Products',
value: 'lastest-products',
},
{
name: 'Recent Products',
value: 'recent-products',
},
]
const onModalClose = () => {
const DEFAULT_SEARCH_ARGS = {
groupByProduct: true, take: DEFAULT_PAGE_SIZE
}
const ProductListFilter = (props: ProductListFilterProps) => {
const ProductListFilter = ({ facets, collections, products, total }: ProductListFilterProps) => {
const router = useRouter()
const { openProductFilter } = useProductFilter()
const [initialQueryFlag, setInitialQueryFlag] = useState<boolean>(true)
const [optionQueryProduct, setOptionQueryProduct] = useState<QuerySearchArgs>({ input: DEFAULT_SEARCH_ARGS })
const { products: productSearchResult, totalItems, loading } = useSearchProducts(optionQueryProduct)
const [currentPage, setCurrentPage] = useState(0)
useEffect(() => {
const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string)
setCurrentPage(page)
}, [router.query])
const onPageChange = (page: number) => {
setCurrentPage(page)
router.push({
pathname: ROUTE.PRODUCTS,
query: {
...router.query,
[QUERY_KEY.PAGE]: page
}
},
undefined, { shallow: true }
)
}
useEffect(() => {
const query = { input: { ...DEFAULT_SEARCH_ARGS } } as QuerySearchArgs
const page = getPageFromQuery(router.query[QUERY_KEY.PAGE] as string)
query.input.skip = page * DEFAULT_PAGE_SIZE
const sortQuery = router.query[QUERY_KEY.SORTBY] as string
if (sortQuery) {
query.input.sort = getProductSortParamFromQuery(sortQuery)
}
// collections
const categoryQuery = router.query[QUERY_KEY.CATEGORY] as string
if (categoryQuery) {
query.input.collectionSlug = categoryQuery
}
// facets
const facetsQuery = [router.query[QUERY_KEY.FEATURED] as string, router.query[QUERY_KEY.BRAND] as string].join(QUERY_SPLIT_SEPERATOR)
if (facetsQuery) {
const facetsValue = [] as FacetValue[]
facets.map((item: Facet) => {
facetsValue.push(...item.values)
return null
})
query.input.facetValueIds = getFacetIdsFromCodes(facetsValue, facetsQuery.split(QUERY_SPLIT_SEPERATOR))
}
setOptionQueryProduct(query)
setInitialQueryFlag(false)
}, [router.query, facets])
return (
<div className={s.warpper}>
<div className={s.breadcrumb}>
<BreadcrumbCommon crumbs={BREADCRUMB} />
</div>
<div className={s.main}>
<div className={s.categories}>
<MenuNavigation categories={CATEGORY} heading="Categories" />
<MenuNavigation categories={BRAND} heading="Brands" />
<MenuNavigation categories={FEATURED} heading="featured" />
</div>
<ProductsMenuNavigationTablet facets={facets} collections={collections} />
<div className={s.list}>
<HeadingCommon align="left">SPECIAL RECIPES</HeadingCommon>
<div className={s.top}>
<div className={s.left}>
<HeadingCommon align="left">SPECIAL RECIPES</HeadingCommon>
<button className={s.iconFilter} onClick={openProductFilter}>
<IconFilter />
<div className={s.dot}></div>
</button>
</div>
<div className={s.boxSelect}>
<div className={s.categorySelectSort}>
<div className={s.select}>
<SelectCommon option={OPTIONSLECT} placeholder="Sort By" />
</div>
<div className={s.boxSelect}>
<ProductSort />
</div>
</div>
<ProductList data={PRODUCT_DATA_TEST_PAGE} />
{
(!initialQueryFlag && loading && !productSearchResult) && <ListProductCardSkeleton count={DEFAULT_PAGE_SIZE} isWrap />
}
<ProductList data={initialQueryFlag ? products : (productSearchResult || [])} total={totalItems !== undefined ? totalItems : total} onPageChange={onPageChange} defaultCurrentPage={currentPage} />
</div>
</div>
</div>

View File

@@ -0,0 +1,40 @@
import { useRouter } from 'next/router';
import React, { useEffect, useState } from 'react';
import { SelectCommon } from 'src/components/common';
import { OPTIONS_SORT_PRODUCT, QUERY_KEY, ROUTE } from 'src/utils/constanst.utils';
const ProductSort = () => {
const router = useRouter()
const [sortValue, setSortValue] = useState<string>();
useEffect(() => {
const rs = router.query[QUERY_KEY.SORTBY] as string
if (rs) {
setSortValue(rs)
}
}, [router.query])
const onSortChange = (value: string) => {
setSortValue(value)
router.push({
pathname: ROUTE.PRODUCTS,
query: {
...router.query,
[QUERY_KEY.SORTBY]: value
}
},
undefined, { shallow: true }
)
}
return (
<SelectCommon
options={OPTIONS_SORT_PRODUCT}
placeholder="Sort By"
value={sortValue}
onChange={onSortChange}
/>
);
};
export default ProductSort;

View File

@@ -0,0 +1,12 @@
.productsMenuNavigationTablet {
@apply hidden;
@screen md {
@apply block;
padding-right: 2.4rem;
}
@screen xl {
@apply block;
width: 25%;
}
}

View File

@@ -0,0 +1,32 @@
import { Collection, Facet } from '@framework/schema'
import React from 'react'
import MenuNavigation from 'src/components/common/MenuNavigation/MenuNavigation'
import { QUERY_KEY } from 'src/utils/constanst.utils'
import s from './ProductsMenuNavigationTablet.module.scss'
interface Props {
facets: Facet[]
collections: Collection[]
}
const ProductsMenuNavigationTablet = ({ facets, collections }: Props) => {
return (
<div className={s.productsMenuNavigationTablet}>
<MenuNavigation
heading="Categories"
categories={collections}
queryKey={QUERY_KEY.CATEGORY}
isSingleSelect={true} />
{
facets.map(item => <MenuNavigation
key={item.id}
queryKey={item.code}
categories={item.values}
heading={item.name} />)
}
</div>
)
}
export default ProductsMenuNavigationTablet

View File

@@ -189,13 +189,13 @@ const RecipesList = ({ data =recipe}:Props) => {
<div className={s.categorySelectCate}>
<label htmlFor="">Categories</label>
<div className={s.select}>
<SelectCommon option={CATEGORYSELECT} placeholder="Categories"/>
<SelectCommon options={CATEGORYSELECT} placeholder="Categories"/>
</div>
</div>
<div className={s.categorySelectSort}>
<label htmlFor="" >Sort By</label>
<div className={s.select}>
<SelectCommon option={OPTIONSLECT} placeholder="Sort By" />
<SelectCommon options={OPTIONSLECT} placeholder="Sort By" />
</div>
</div>
</div>

View File

@@ -4,7 +4,7 @@
:root {
--primary: #5b9a74;
--primary-light: #e3f2e9;
--primary-lightest: #effaf4;
--primary-lightest: #F1F8F4;
--info-dark: #00317a;
--info: #3468B7;

View File

@@ -1,5 +1,7 @@
import DefaultImg from '../../public/assets/images/default_img.jpg'
export const REVALIDATE_TIME = 60
export const MAX_PRODUCT_CAROUSEL = 20
export const BLUR_DATA_IMG = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mN8fBIAApUBruKYvzsAAAAASUVORK5CYII='
export const DEFAULT_IMG = DefaultImg
@@ -45,13 +47,23 @@ export const LOCAL_STORAGE_KEY = {
TOKEN: 'token'
}
export const QUERY_SPLIT_SEPERATOR = ','
export const QUERY_KEY = {
TAB: 'tab',
CATEGORY: 'category',
BRAND: 'brand',
FEATURED: 'feature',
FEATURED: 'featured',
SORTBY: 'sortby',
RECIPES: 'recipes'
RECIPES: 'recipes',
PAGE: 'page',
}
export const PRODUCT_SORT_OPTION_VALUE = {
NAME_ASC: 'name_asc',
NAME_DESC: 'name_desc',
PRICE_ASC: 'price_asc',
PRICE_DESC: 'price_desc',
}
export enum ProductFeature {
@@ -113,10 +125,31 @@ export const BRAND = [
export const CODE_FACET_FEATURED = 'featured'
export const CODE_FACET_DISCOUNT = 'discount'
export const CODE_FACET_BRAND = 'brand'
export const CODE_FACET_FEATURED_VARIANT = {
FRESH: 'fresh',
}
export const OPTIONS_SORT_PRODUCT = [
{
name: 'By Name (A-Z)',
value: PRODUCT_SORT_OPTION_VALUE.NAME_ASC,
},
{
name: 'By Name (Z-A)',
value: PRODUCT_SORT_OPTION_VALUE.NAME_DESC,
},
{
name: 'Price (Low to High)',
value: PRODUCT_SORT_OPTION_VALUE.PRICE_ASC,
},
{
name: 'Price (High to Low)',
value: PRODUCT_SORT_OPTION_VALUE.PRICE_DESC,
},
];
export const FEATURED = [
{
name: 'Best Sellers',

View File

@@ -1,12 +1,60 @@
import { Facet } from "@commerce/types/facet";
import { FacetValue } from './../../framework/vendure/schema.d';
import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT } from "./constanst.utils";
import { PromiseWithKey } from "./types.utils";
import { Collection, FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d';
import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT, PRODUCT_SORT_OPTION_VALUE } from "./constanst.utils";
import { PromiseWithKey, SortOrder } from "./types.utils";
export function isMobile() {
return window.innerWidth < 768
}
export function getPageFromQuery(pageQuery: string) {
let page = 0
try {
page = +pageQuery
if (isNaN(page)) {
page = 0
}
} catch (err) {
page = 0
}
return page
}
export function getProductSortParamFromQuery(query: string) {
let rs = {} as SearchResultSortParameter
switch (query) {
case PRODUCT_SORT_OPTION_VALUE.NAME_ASC:
rs = {
name: SortOrder.Asc
}
break;
case PRODUCT_SORT_OPTION_VALUE.NAME_DESC:
rs = {
name: SortOrder.Desc
}
break;
case PRODUCT_SORT_OPTION_VALUE.PRICE_ASC:
rs = {
price: SortOrder.Asc
}
break;
case PRODUCT_SORT_OPTION_VALUE.PRICE_DESC:
rs = {
price: SortOrder.Desc
}
break;
default:
break;
}
return rs
}
export function removeItem<T>(arr: Array<T>, value: T): Array<T> {
const index = arr.indexOf(value);
if (index > -1) {
@@ -58,6 +106,25 @@ export function getFacetNamesFromIds(facets: FacetValue[], ids?: string[]): stri
return names.join(", ")
}
export function getAllPromies (promies: PromiseWithKey[]) {
return promies.map(item => item.promise)
export function getFacetIdsFromCodes(facets: FacetValue[], codes?: string[]): string[] {
if (!codes || codes?.length === 0) {
return []
}
const facetItems = facets.filter((item: FacetValue) => codes.includes(item.code))
const ids = facetItems.map((item: FacetValue) => item.id)
return ids
}
export const getCategoryNameFromCollectionId = (colelctions: Collection[], collectionId?: string ) => {
if (!collectionId) {
return ''
}
const collection = colelctions.find(item => item.id === collectionId)
return collection?.name || ''
}
export function getAllPromies(promies: PromiseWithKey[]) {
return promies.map(item => item.promise)
}

View File

@@ -34,18 +34,23 @@ export interface BlogProps {
export interface CheckOutForm {
name?: string
email?:string
email?: string
address?: string
city?:string
state?:string
code?:number
phone?:number
method?:string
shipping_fee?:number
city?: string
state?: string
code?: number
phone?: number
method?: string
shipping_fee?: number
}
export type MouseAndTouchEvent = MouseEvent | TouchEvent
export enum SortOrder {
Asc = 'ASC',
Desc = 'DESC',
}
export type filterContextType = {
visible: boolean;
open: () => void;