@evenicanpm/storefront-core 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/api-manager/datasources/d365/d365-address.datasource.ts +1 -1
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +0 -19
- package/src/api-manager/datasources/e4/e4-cart.datasource.ts +2 -2
- package/src/api-manager/datasources/e4/e4-product.datasource.ts +19 -4
- package/src/api-manager/datasources/e4/e4.datasource.ts +1 -2
- package/src/api-manager/datasources/e4/e4.remaps.ts +24 -0
- package/src/api-manager/datasources/e4/e4.translator.ts +17 -0
- package/src/api-manager/datasources/e4/{e4-user.datasource.ts → user/e4-user.datasource.ts} +5 -7
- package/src/api-manager/datasources/e4/user/e4-user.remaps.ts +4 -0
- package/src/api-manager/datasources/e4/user/e4-user.translator.ts +10 -0
- package/src/api-manager/index.ts +21 -6
- package/src/api-manager/schemas/product.schema.ts +1 -1
- package/src/api-manager/services/address/queries/get-states.ts +1 -1
- package/src/api-manager/services/create-query.ts +10 -3
- package/src/api-manager/types/Datasource.ts +9 -9
- package/src/components/Blocks/Components/ProductCarousel/index.tsx +6 -6
- package/src/components/_components/navbar/category-based-menu/components/categories.tsx +1 -1
- package/src/components/_components/navbar/mega-menu/mega-menu.tsx +1 -1
- package/src/components/_components/navbar/nav-list/nav-item-child.tsx +2 -2
- package/src/components/_components/product-dimensions/product-dimensions.tsx +114 -0
- package/src/components/_components/product-quantity-buttons/product-quantity-buttons.tsx +88 -0
- package/src/components/_components/section-header/section-header.tsx +1 -1
- package/src/components/_components/settings/setting.tsx +1 -1
- package/src/components/_components/wishlist-modal/TODO.md +10 -0
- package/src/components/_components/wishlist-modal/add-to-wishlist-button.tsx +68 -0
- package/src/components/_components/wishlist-modal/add-to-wishlist-dialog.tsx +161 -0
- package/src/components/_components/wishlist-modal/remove-from-wishlist-dialog.tsx +174 -0
- package/src/components/_components/wishlist-modal/wishlist-modal-item.tsx +65 -0
- package/src/components/countries-input.tsx +1 -0
- package/src/hooks/use-cart.tsx +82 -0
- package/src/hooks/use-variants.tsx +85 -0
- package/src/pages/account/addresses/address-form.tsx +289 -0
- package/src/pages/account/addresses/address-item.tsx +104 -0
- package/src/pages/account/dashboard-header.tsx +123 -0
- package/src/pages/account/navigation.tsx +84 -0
- package/src/pages/account/no-records.tsx +20 -0
- package/src/pages/account/orders/icons/delivery.tsx +14 -0
- package/src/pages/account/orders/icons/package-box.tsx +13 -0
- package/src/pages/account/orders/icons/truck-filled.tsx +14 -0
- package/src/pages/account/orders/order-progress.tsx +111 -0
- package/src/pages/account/orders/order-row.tsx +61 -0
- package/src/pages/account/orders/order-summary.tsx +83 -0
- package/src/pages/account/orders/ordered-products.tsx +114 -0
- package/src/pages/account/profile/profile-button.test.tsx +59 -0
- package/src/pages/account/profile/profile-button.tsx +50 -0
- package/src/pages/account/profile/user-info.tsx +59 -0
- package/src/pages/account/styles.ts +32 -0
- package/src/pages/account/table-row.tsx +19 -0
- package/src/pages/account/wishlist/create-new-list.tsx +167 -0
- package/src/pages/account/wishlist/create-wishlist-button.tsx +40 -0
- package/src/pages/account/wishlist/wishlist-item.tsx +82 -0
- package/src/pages/blog/pagination.tsx +38 -0
- package/src/pages/cart/cart-item.tsx +312 -0
- package/src/pages/cart/checkout-form.tsx +122 -0
- package/src/pages/cart/coupon-entry.tsx +90 -0
- package/src/pages/cart/estimate-shipping.tsx +183 -0
- package/src/pages/cart/wrapper.tsx +30 -0
- package/src/pages/checkout/checkout-alt-form/address-card.tsx +73 -0
- package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +133 -0
- package/src/pages/checkout/checkout-alt-form/checkout-step.tsx +120 -0
- package/src/pages/checkout/checkout-alt-form/customer-information.tsx +94 -0
- package/src/pages/checkout/checkout-alt-form/delivery-address.tsx +170 -0
- package/src/pages/checkout/checkout-alt-form/delivery-date.tsx +114 -0
- package/src/pages/checkout/checkout-alt-form/delivery-options.tsx +186 -0
- package/src/pages/checkout/checkout-alt-form/edit-address-form.tsx +130 -0
- package/src/pages/checkout/checkout-alt-form/heading.tsx +50 -0
- package/src/pages/checkout/checkout-alt-form/index.ts +1 -0
- package/src/pages/checkout/checkout-alt-form/new-address-form.tsx +294 -0
- package/src/pages/checkout/checkout-alt-form/node.tsx +9 -0
- package/src/pages/checkout/checkout-alt-form/payment-details.tsx +344 -0
- package/src/pages/checkout/checkout-alt-form/safe-card-preview-data.tsx +36 -0
- package/src/pages/checkout/checkout-alt-form/types.ts +20 -0
- package/src/pages/checkout/checkout-alt-summary/cart-item.tsx +39 -0
- package/src/pages/checkout/checkout-alt-summary/checkout-alt-summary.tsx +40 -0
- package/src/pages/checkout/checkout-alt-summary/index.ts +1 -0
- package/src/pages/checkout/checkout-alt-summary/list-item.tsx +31 -0
- package/src/pages/confirmation/address.tsx +22 -0
- package/src/pages/confirmation/confirmation-summary.tsx +52 -0
- package/src/pages/confirmation/ordered-products.tsx +108 -0
- package/src/pages/product-details/available-shops.tsx +48 -0
- package/src/pages/product-details/bopis/find-in-store-button.tsx +63 -0
- package/src/pages/product-details/bopis/find-in-store-modal.tsx +257 -0
- package/src/pages/product-details/bopis/pickup-option-select.tsx +127 -0
- package/src/pages/product-details/bopis/search-header.tsx +69 -0
- package/src/pages/product-details/checkbox-label.tsx +20 -0
- package/src/pages/product-details/currency.ts +64 -0
- package/src/pages/product-details/frequently-bought.tsx +90 -0
- package/src/pages/product-details/frequently-product-card.tsx +60 -0
- package/src/pages/product-details/product-comment.tsx +44 -0
- package/src/pages/product-details/product-description.tsx +22 -0
- package/src/pages/product-details/product-filter-card.tsx +257 -0
- package/src/pages/product-details/product-intro/product-intro-images.tsx +87 -0
- package/src/pages/product-details/product-intro/product-intro.tsx +250 -0
- package/src/pages/product-details/product-review.tsx +131 -0
- package/src/pages/product-details/product-tabs.tsx +51 -0
- package/src/pages/product-details/related-products.tsx +42 -0
- package/src/pages/product-details/types.ts +11 -0
- package/src/pages/product-list/breadcrumbs.tsx +39 -0
- package/src/pages/product-list/checkbox-label.tsx +20 -0
- package/src/pages/product-list/facet-group.tsx +125 -0
- package/src/pages/product-list/facet.tsx +36 -0
- package/src/pages/product-list/list-filter.tsx +40 -0
- package/src/pages/product-list/pagination.tsx +80 -0
- package/src/pages/product-list/product-list-context.tsx +302 -0
- package/src/pages/product-list/product-list-state.ts +187 -0
- package/src/pages/product-list/product-rating.tsx +16 -0
- package/src/pages/product-list/quick-view-dialog-content.tsx +205 -0
- package/src/pages/product-list/quick-view-dialog.tsx +54 -0
- package/src/pages/product-list/range-filter.tsx +125 -0
- package/src/pages/product-list/scrollbar.tsx +38 -0
- package/src/pages/product-list/search-bar.tsx +162 -0
- package/src/pages/product-list/selected-facets.tsx +80 -0
- package/src/pages/product-list/side-nav.tsx +49 -0
- package/src/pages/product-list/swatch.tsx +80 -0
- package/src/pages/product-list/types.ts +18 -0
- package/src/pages/product-list/use-product-list.ts +12 -0
- package/src/pages/product-list/useCategoryFilter.tsx +26 -0
- package/src/pages/product-list/utils/facet-helpers.ts +5 -0
- package/src/pages/product-list/utils/generate-breadcrumbs.ts +30 -0
- package/src/pages/product-list/utils/getCategoryFilterFromSlug.ts +153 -0
- package/src/pages/product-list/utils/product-list-helper.ts +111 -0
- package/src/pages/product-list/utils/product-list-types.ts +41 -0
- package/src/pages/product-list/utils/search-for-category.ts +76 -0
- package/src/pages/product-list/utils/sort-options.ts +44 -0
- package/src/pages/product-list/utils/use-previous-refiners.ts +14 -0
- package/src/pages/quickorder/order-upload.tsx +150 -0
- package/src/pages/quickorder/quick-order-form.tsx +343 -0
- package/src/pages/quickorder/quick-order-row.tsx +144 -0
- package/tsconfig.json +1 -0
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +0 -10464
- /package/src/{components/_components/hooks/useOverflowDetect.ts → hooks/use-overflow-detect.ts} +0 -0
- /package/src/{components/_components/hooks/useSettings.ts → hooks/use-settings.ts} +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import currencyJs from "currency.js";
|
|
2
|
+
import { formatDistanceStrict } from "date-fns";
|
|
3
|
+
/**
|
|
4
|
+
* NOTE: We can move this to a more appropriate location if needed.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* GET THE DIFFERENCE DATE FORMAT
|
|
9
|
+
* @param DATE | NUMBER | STRING
|
|
10
|
+
* @returns FORMATTED DATE STRING
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function getDateDifference(date: string | number | Date) {
|
|
14
|
+
const distance = formatDistanceStrict(new Date(), new Date(date));
|
|
15
|
+
return distance + " ago";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* RENDER THE PRODUCT PAGINATION INFO
|
|
20
|
+
* @param page - CURRENT PAGE NUMBER
|
|
21
|
+
* @param perPageProduct - PER PAGE PRODUCT LIST
|
|
22
|
+
* @param totalProduct - TOTAL PRODUCT NUMBER
|
|
23
|
+
* @returns
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
function renderProductCount(
|
|
27
|
+
page: number,
|
|
28
|
+
perPageProduct: number,
|
|
29
|
+
totalProduct: number,
|
|
30
|
+
) {
|
|
31
|
+
const startNumber = (page - 1) * perPageProduct;
|
|
32
|
+
let endNumber = page * perPageProduct;
|
|
33
|
+
|
|
34
|
+
if (endNumber > totalProduct) {
|
|
35
|
+
endNumber = totalProduct;
|
|
36
|
+
}
|
|
37
|
+
return `Showing ${startNumber - 1}-${endNumber} of ${totalProduct} products`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* CALCULATE PRICE WITH PRODUCT DISCOUNT THEN RETURN NEW PRODUCT PRICES
|
|
42
|
+
* @param price - PRODUCT PRICE
|
|
43
|
+
* @param discount - DISCOUNT PERCENT
|
|
44
|
+
* @returns - RETURN NEW PRICE
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
function calculateDiscount(price: number, discount: number) {
|
|
48
|
+
const afterDiscount = Number((price - price * (discount / 100)).toFixed(2));
|
|
49
|
+
return currency(afterDiscount);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* CHANGE THE CURRENCY FORMAT
|
|
54
|
+
* @param price - PRODUCT PRICE
|
|
55
|
+
* @param fraction - HOW MANY FRACTION WANT TO SHOW
|
|
56
|
+
* @returns - RETURN PRICE WITH CURRENCY
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
function currency(price: number, fraction = 2) {
|
|
60
|
+
const formatCurrency = currencyJs(price).format({ precision: fraction });
|
|
61
|
+
return formatCurrency;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { currency, getDateDifference, calculateDiscount, renderProductCount };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Fragment } from "react";
|
|
4
|
+
import Box from "@mui/material/Box";
|
|
5
|
+
import Button from "@mui/material/Button";
|
|
6
|
+
import styled from "@mui/material/styles/styled";
|
|
7
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
8
|
+
import { H2, H3, Span } from "@/components/_components/Typography";
|
|
9
|
+
import { FlexBox, FlexRowCenter } from "@/components/_components/flex-box";
|
|
10
|
+
// LOCAL CUSTOM COMPONENT
|
|
11
|
+
import FrequentlyProductCard from "./frequently-product-card";
|
|
12
|
+
// CUSTOM UTILS LIBRARY FUNCTION
|
|
13
|
+
import { currency } from "@/lib/lib";
|
|
14
|
+
// CUSTOM DATA MODEL
|
|
15
|
+
import { ProductSearchResult as Product } from "@msdyn365-commerce/retail-proxy";
|
|
16
|
+
|
|
17
|
+
// STYLED COMPONENTS
|
|
18
|
+
const WrapperBox = styled(Box)(({ theme }) => ({
|
|
19
|
+
[theme.breakpoints.down("sm")]: {
|
|
20
|
+
"& .card-holder": { flexDirection: "column" },
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
// ============================================================
|
|
25
|
+
type Props = { products: Product[] };
|
|
26
|
+
// ============================================================
|
|
27
|
+
|
|
28
|
+
export default function FrequentlyBought({ products }: Props) {
|
|
29
|
+
return (
|
|
30
|
+
<WrapperBox mb={7.5}>
|
|
31
|
+
<H3 mb={3}>Frequently Bought Together</H3>
|
|
32
|
+
|
|
33
|
+
<FlexBox className="card-holder" flexWrap="wrap" m={-1}>
|
|
34
|
+
{products.map((item, ind) => (
|
|
35
|
+
<Fragment key={item.RecordId}>
|
|
36
|
+
<FrequentlyProductCard
|
|
37
|
+
id={item.RecordId}
|
|
38
|
+
key={item.RecordId}
|
|
39
|
+
slug={item.ItemId ?? ""}
|
|
40
|
+
price={item.Price}
|
|
41
|
+
title={item.Name ?? ""}
|
|
42
|
+
imgUrl={item.PrimaryImageUrl ?? ""}
|
|
43
|
+
/>
|
|
44
|
+
|
|
45
|
+
{ind < products.length - 1 && (
|
|
46
|
+
<FlexRowCenter>
|
|
47
|
+
<H2 color="grey.600" mx={1}>
|
|
48
|
+
+
|
|
49
|
+
</H2>
|
|
50
|
+
</FlexRowCenter>
|
|
51
|
+
)}
|
|
52
|
+
</Fragment>
|
|
53
|
+
))}
|
|
54
|
+
|
|
55
|
+
<FlexRowCenter>
|
|
56
|
+
<H2 color="grey.600" mx={3}>
|
|
57
|
+
=
|
|
58
|
+
</H2>
|
|
59
|
+
</FlexRowCenter>
|
|
60
|
+
|
|
61
|
+
<FlexRowCenter
|
|
62
|
+
m={1}
|
|
63
|
+
minWidth={300}
|
|
64
|
+
minHeight={200}
|
|
65
|
+
border="1px solid"
|
|
66
|
+
borderRadius="8px"
|
|
67
|
+
className="gray-box"
|
|
68
|
+
borderColor="grey.400"
|
|
69
|
+
flexDirection="column"
|
|
70
|
+
>
|
|
71
|
+
<H3 color="primary.main">{currency(2500)}</H3>
|
|
72
|
+
|
|
73
|
+
<Span mb={2} fontWeight="600" color="grey.600">
|
|
74
|
+
Save {currency(500)}
|
|
75
|
+
</Span>
|
|
76
|
+
|
|
77
|
+
<FlexBox gap={1.5}>
|
|
78
|
+
<Button variant="contained" color="primary">
|
|
79
|
+
Add to Cart
|
|
80
|
+
</Button>
|
|
81
|
+
|
|
82
|
+
<Button variant="outlined" color="primary">
|
|
83
|
+
Add to List
|
|
84
|
+
</Button>
|
|
85
|
+
</FlexBox>
|
|
86
|
+
</FlexRowCenter>
|
|
87
|
+
</FlexBox>
|
|
88
|
+
</WrapperBox>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import Link from "next/link";
|
|
2
|
+
import Box from "@mui/material/Box";
|
|
3
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
4
|
+
import HoverBox from "@/components/_components/HoverBox";
|
|
5
|
+
import LazyImage from "@/components/_components/LazyImage";
|
|
6
|
+
import BazaarCard from "@/components/_components/BazaarCard";
|
|
7
|
+
import { H6, Span } from "@/components/_components/Typography";
|
|
8
|
+
import FlexBox from "@/components/_components/flex-box/flex-box";
|
|
9
|
+
// CUSTOM UTILS LIBRARY FUNCTIONS
|
|
10
|
+
import { calculateDiscount, currency } from "@/lib/lib";
|
|
11
|
+
|
|
12
|
+
// =======================================================
|
|
13
|
+
interface Props {
|
|
14
|
+
slug: string;
|
|
15
|
+
price: number;
|
|
16
|
+
title: string;
|
|
17
|
+
imgUrl: string;
|
|
18
|
+
id: string | number;
|
|
19
|
+
}
|
|
20
|
+
// =======================================================
|
|
21
|
+
|
|
22
|
+
export default function FrequentlyProductCard(props: Props) {
|
|
23
|
+
const {
|
|
24
|
+
imgUrl = "/assets/images/products/Rectangle 116.png",
|
|
25
|
+
price,
|
|
26
|
+
title,
|
|
27
|
+
slug,
|
|
28
|
+
} = props;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<BazaarCard
|
|
32
|
+
sx={{
|
|
33
|
+
p: 2,
|
|
34
|
+
width: "100%",
|
|
35
|
+
flex: "1 1 0",
|
|
36
|
+
minWidth: "160px",
|
|
37
|
+
margin: { xs: 0, sm: 1 },
|
|
38
|
+
maxWidth: { xs: "100%", sm: "220px" },
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<Link href={`/products/${slug}`}>
|
|
42
|
+
<HoverBox mb={1.5} borderRadius="8px">
|
|
43
|
+
<LazyImage alt={title} width={500} height={500} src={imgUrl} />
|
|
44
|
+
</HoverBox>
|
|
45
|
+
|
|
46
|
+
<Span title={title} mb={0.5} color="inherit" ellipsis display="block">
|
|
47
|
+
{title}
|
|
48
|
+
</Span>
|
|
49
|
+
|
|
50
|
+
<FlexBox alignItems="center" gap={1}>
|
|
51
|
+
<H6 color="primary.main">{currency(price)}</H6>
|
|
52
|
+
|
|
53
|
+
<Box component="del" fontWeight={600} color="grey.600">
|
|
54
|
+
{calculateDiscount(price, 35)}
|
|
55
|
+
</Box>
|
|
56
|
+
</FlexBox>
|
|
57
|
+
</Link>
|
|
58
|
+
</BazaarCard>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Box from "@mui/material/Box";
|
|
4
|
+
import Avatar from "@mui/material/Avatar";
|
|
5
|
+
import Rating from "@mui/material/Rating";
|
|
6
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
7
|
+
import { FlexBox } from "@/components/_components/flex-box";
|
|
8
|
+
import { H5, H6, Paragraph, Span } from "@/components/_components/Typography";
|
|
9
|
+
// CUSTOM UTILS LIBRARY FUNCTION
|
|
10
|
+
import { getDateDifference } from "@/lib/lib";
|
|
11
|
+
|
|
12
|
+
// ===========================================================
|
|
13
|
+
interface Props {
|
|
14
|
+
name: string;
|
|
15
|
+
date: string;
|
|
16
|
+
imgUrl: string;
|
|
17
|
+
rating: number;
|
|
18
|
+
comment: string;
|
|
19
|
+
}
|
|
20
|
+
// ===========================================================
|
|
21
|
+
|
|
22
|
+
export default function ProductComment(props: Props) {
|
|
23
|
+
const { name, imgUrl, rating, date, comment } = props || {};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Box mb={4} maxWidth={600}>
|
|
27
|
+
<FlexBox alignItems="center" mb={2} gap={2}>
|
|
28
|
+
<Avatar alt={name} src={imgUrl} sx={{ width: 48, height: 48 }} />
|
|
29
|
+
|
|
30
|
+
<div>
|
|
31
|
+
<H5 mb={1}>{name}</H5>
|
|
32
|
+
|
|
33
|
+
<FlexBox alignItems="center" gap={1.25}>
|
|
34
|
+
<Rating size="small" value={rating} color="warn" readOnly />
|
|
35
|
+
<H6>{rating}</H6>
|
|
36
|
+
<Span>{getDateDifference(date)}</Span>
|
|
37
|
+
</FlexBox>
|
|
38
|
+
</div>
|
|
39
|
+
</FlexBox>
|
|
40
|
+
|
|
41
|
+
<Paragraph color="grey.700">{comment}</Paragraph>
|
|
42
|
+
</Box>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { H3 } from "@/components/_components/Typography";
|
|
4
|
+
import { AttributeValue } from "@msdyn365-commerce/retail-proxy";
|
|
5
|
+
|
|
6
|
+
type Props = { attributes: AttributeValue[] };
|
|
7
|
+
|
|
8
|
+
export default function ProductDescription({ attributes }: Props) {
|
|
9
|
+
return (
|
|
10
|
+
<div>
|
|
11
|
+
<H3 mb={2}>Specifications:</H3>
|
|
12
|
+
{attributes?.map((attr, index) => {
|
|
13
|
+
if (attr?.TextValue && attr?.Name)
|
|
14
|
+
return (
|
|
15
|
+
<div key={index}>
|
|
16
|
+
{attr.Name}: {attr.TextValue}
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
})}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Fragment, useState } from "react";
|
|
4
|
+
// MUI
|
|
5
|
+
import Box from "@mui/material/Box";
|
|
6
|
+
import Rating from "@mui/material/Rating";
|
|
7
|
+
import Divider from "@mui/material/Divider";
|
|
8
|
+
import Collapse from "@mui/material/Collapse";
|
|
9
|
+
import TextField from "@mui/material/TextField";
|
|
10
|
+
import FormGroup from "@mui/material/FormGroup";
|
|
11
|
+
// LOCAL CUSTOM COMPONENTS
|
|
12
|
+
import CheckboxLabel from "./checkbox-label";
|
|
13
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
14
|
+
import { FlexBetween, FlexBox } from "@/components/_components/flex-box";
|
|
15
|
+
import { H5, H6, Paragraph, Span } from "@/components/_components/Typography";
|
|
16
|
+
import AccordionHeader from "@/components/_components/accordion/accordion-header";
|
|
17
|
+
// TYPE
|
|
18
|
+
import {
|
|
19
|
+
ProductFilterKeys,
|
|
20
|
+
ProductFilterValues,
|
|
21
|
+
ProductFilters,
|
|
22
|
+
} from "./types";
|
|
23
|
+
import { Slider } from "@mui/material";
|
|
24
|
+
|
|
25
|
+
// FILTER OPTIONS
|
|
26
|
+
const categoryList = [
|
|
27
|
+
{
|
|
28
|
+
title: "Bath Preparations",
|
|
29
|
+
subCategories: ["Bubble Bath", "Bath Capsules", "Others"],
|
|
30
|
+
},
|
|
31
|
+
{ title: "Eye Makeup Preparations" },
|
|
32
|
+
{ title: "Fragrance" },
|
|
33
|
+
{ title: "Hair Preparations" },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const BRANDS = [
|
|
37
|
+
{ label: "Mac", value: "mac" },
|
|
38
|
+
{ label: "Karts", value: "karts" },
|
|
39
|
+
{ label: "Baals", value: "baals" },
|
|
40
|
+
{ label: "Bukks", value: "bukks" },
|
|
41
|
+
{ label: "Luasis", value: "luasis" },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const OTHERS = [
|
|
45
|
+
{ label: "On Sale", value: "sale" },
|
|
46
|
+
{ label: "In Stock", value: "stock" },
|
|
47
|
+
{ label: "Featured", value: "featured" },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const colorList = [
|
|
51
|
+
"#1C1C1C",
|
|
52
|
+
"#FF7A7A",
|
|
53
|
+
"#FFC672",
|
|
54
|
+
"#84FFB5",
|
|
55
|
+
"#70F6FF",
|
|
56
|
+
"#6B7AFF",
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
interface Props {
|
|
61
|
+
filters: ProductFilters;
|
|
62
|
+
changeFilters: (key: ProductFilterKeys, values: ProductFilterValues) => void;
|
|
63
|
+
}
|
|
64
|
+
// ============================================================================
|
|
65
|
+
|
|
66
|
+
export default function ProductFilterCard({ filters, changeFilters }: Props) {
|
|
67
|
+
const [collapsed, setCollapsed] = useState(true);
|
|
68
|
+
|
|
69
|
+
const handleChangePrice = (values: number[]) => {
|
|
70
|
+
changeFilters("price", values);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleChangeColor = (value: string) => {
|
|
74
|
+
const values = filters.color.includes(value)
|
|
75
|
+
? filters.color.filter((item) => item !== value)
|
|
76
|
+
: [...filters.color, value];
|
|
77
|
+
|
|
78
|
+
changeFilters("color", values);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleChangeBrand = (value: string) => {
|
|
82
|
+
const values = filters.brand.includes(value)
|
|
83
|
+
? filters.brand.filter((item) => item !== value)
|
|
84
|
+
: [...filters.brand, value];
|
|
85
|
+
|
|
86
|
+
changeFilters("brand", values);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleChangeSales = (value: string) => {
|
|
90
|
+
const values = filters.sales.includes(value)
|
|
91
|
+
? filters.sales.filter((item) => item !== value)
|
|
92
|
+
: [...filters.sales, value];
|
|
93
|
+
|
|
94
|
+
changeFilters("sales", values);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleChangeRating = (value: number) => {
|
|
98
|
+
changeFilters("rating", value);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div>
|
|
103
|
+
{/* CATEGORY VARIANT FILTER */}
|
|
104
|
+
<H6 mb={1.25}>Categories</H6>
|
|
105
|
+
{categoryList.map((item) =>
|
|
106
|
+
item.subCategories ? (
|
|
107
|
+
<Fragment key={item.title}>
|
|
108
|
+
<AccordionHeader
|
|
109
|
+
open={collapsed}
|
|
110
|
+
onClick={() => setCollapsed((state) => !state)}
|
|
111
|
+
sx={{ padding: ".5rem 0", cursor: "pointer", color: "grey.600" }}
|
|
112
|
+
>
|
|
113
|
+
<Span>{item.title}</Span>
|
|
114
|
+
</AccordionHeader>
|
|
115
|
+
|
|
116
|
+
<Collapse in={collapsed}>
|
|
117
|
+
{item.subCategories.map((name) => (
|
|
118
|
+
<Paragraph
|
|
119
|
+
pl="22px"
|
|
120
|
+
py={0.75}
|
|
121
|
+
key={name}
|
|
122
|
+
fontSize="14px"
|
|
123
|
+
color="grey.600"
|
|
124
|
+
sx={{ cursor: "pointer" }}
|
|
125
|
+
>
|
|
126
|
+
{name}
|
|
127
|
+
</Paragraph>
|
|
128
|
+
))}
|
|
129
|
+
</Collapse>
|
|
130
|
+
</Fragment>
|
|
131
|
+
) : (
|
|
132
|
+
<Paragraph
|
|
133
|
+
key={item.title}
|
|
134
|
+
sx={{
|
|
135
|
+
py: 0.75,
|
|
136
|
+
fontSize: 14,
|
|
137
|
+
cursor: "pointer",
|
|
138
|
+
color: "grey.600",
|
|
139
|
+
}}
|
|
140
|
+
>
|
|
141
|
+
{item.title}
|
|
142
|
+
</Paragraph>
|
|
143
|
+
),
|
|
144
|
+
)}
|
|
145
|
+
|
|
146
|
+
<Box component={Divider} my={3} />
|
|
147
|
+
|
|
148
|
+
{/* PRICE VARIANT FILTER */}
|
|
149
|
+
<H6 mb={2}>Price Range</H6>
|
|
150
|
+
|
|
151
|
+
<Slider
|
|
152
|
+
min={0}
|
|
153
|
+
max={300}
|
|
154
|
+
size="small"
|
|
155
|
+
value={filters.price}
|
|
156
|
+
valueLabelDisplay="auto"
|
|
157
|
+
valueLabelFormat={(v) => `$${v}`}
|
|
158
|
+
onChange={(_, v) => handleChangePrice(v as number[])}
|
|
159
|
+
/>
|
|
160
|
+
<FlexBetween>
|
|
161
|
+
<TextField
|
|
162
|
+
fullWidth
|
|
163
|
+
size="small"
|
|
164
|
+
type="number"
|
|
165
|
+
placeholder="0"
|
|
166
|
+
value={filters.price[0]}
|
|
167
|
+
onChange={(e) =>
|
|
168
|
+
handleChangePrice([+e.target.value, filters.price[1]])
|
|
169
|
+
}
|
|
170
|
+
/>
|
|
171
|
+
|
|
172
|
+
<H5 color="grey.600" px={1}>
|
|
173
|
+
-
|
|
174
|
+
</H5>
|
|
175
|
+
|
|
176
|
+
<TextField
|
|
177
|
+
fullWidth
|
|
178
|
+
size="small"
|
|
179
|
+
type="number"
|
|
180
|
+
placeholder="250"
|
|
181
|
+
value={filters.price[1]}
|
|
182
|
+
onChange={(e) =>
|
|
183
|
+
handleChangePrice([filters.price[0], +e.target.value])
|
|
184
|
+
}
|
|
185
|
+
/>
|
|
186
|
+
</FlexBetween>
|
|
187
|
+
|
|
188
|
+
<Box component={Divider} my={3} />
|
|
189
|
+
|
|
190
|
+
{/* BRAND VARIANT FILTER */}
|
|
191
|
+
<H6 mb={2}>Brands</H6>
|
|
192
|
+
<FormGroup>
|
|
193
|
+
{BRANDS.map(({ label, value }) => (
|
|
194
|
+
<CheckboxLabel
|
|
195
|
+
key={value}
|
|
196
|
+
label={label}
|
|
197
|
+
checked={filters.brand.includes(value)}
|
|
198
|
+
onChange={() => handleChangeBrand(value)}
|
|
199
|
+
/>
|
|
200
|
+
))}
|
|
201
|
+
</FormGroup>
|
|
202
|
+
|
|
203
|
+
<Box component={Divider} my={3} />
|
|
204
|
+
|
|
205
|
+
{/* SALES OPTIONS */}
|
|
206
|
+
<FormGroup>
|
|
207
|
+
{OTHERS.map(({ label, value }) => (
|
|
208
|
+
<CheckboxLabel
|
|
209
|
+
key={value}
|
|
210
|
+
label={label}
|
|
211
|
+
checked={filters.sales.includes(value)}
|
|
212
|
+
onChange={() => handleChangeSales(value)}
|
|
213
|
+
/>
|
|
214
|
+
))}
|
|
215
|
+
</FormGroup>
|
|
216
|
+
|
|
217
|
+
<Box component={Divider} my={3} />
|
|
218
|
+
|
|
219
|
+
{/* RATINGS FILTER */}
|
|
220
|
+
<H6 mb={2}>Ratings</H6>
|
|
221
|
+
<FormGroup>
|
|
222
|
+
{[5, 4, 3, 2, 1].map((item) => (
|
|
223
|
+
<CheckboxLabel
|
|
224
|
+
key={item}
|
|
225
|
+
checked={filters.rating === item}
|
|
226
|
+
onChange={() => handleChangeRating(item)}
|
|
227
|
+
label={<Rating size="small" value={item} color="warn" readOnly />}
|
|
228
|
+
/>
|
|
229
|
+
))}
|
|
230
|
+
</FormGroup>
|
|
231
|
+
|
|
232
|
+
<Box component={Divider} my={3} />
|
|
233
|
+
|
|
234
|
+
{/* COLORS VARIANT FILTER */}
|
|
235
|
+
<H6 mb={2}>Colors</H6>
|
|
236
|
+
<FlexBox mb={2} flexWrap="wrap" gap={1.5}>
|
|
237
|
+
{colorList.map((item) => (
|
|
238
|
+
<Box
|
|
239
|
+
key={item}
|
|
240
|
+
width={25}
|
|
241
|
+
height={25}
|
|
242
|
+
flexShrink={0}
|
|
243
|
+
bgcolor={item}
|
|
244
|
+
borderRadius="50%"
|
|
245
|
+
onClick={() => handleChangeColor(item)}
|
|
246
|
+
sx={{
|
|
247
|
+
outlineOffset: 1,
|
|
248
|
+
cursor: "pointer",
|
|
249
|
+
outline: filters.color.includes(item) ? 1 : 0,
|
|
250
|
+
outlineColor: item,
|
|
251
|
+
}}
|
|
252
|
+
/>
|
|
253
|
+
))}
|
|
254
|
+
</FlexBox>
|
|
255
|
+
</div>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import Grid from "@mui/material/Grid2";
|
|
3
|
+
import Avatar from "@mui/material/Avatar";
|
|
4
|
+
|
|
5
|
+
// GLOBAL CUSTOM COMPONENTS
|
|
6
|
+
import { FlexBox, FlexRowCenter } from "@/components/_components/flex-box";
|
|
7
|
+
import ImageWithFallback from "@/components/ImageWithFallback";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Master product data comes in as prop, selected variant details are called
|
|
11
|
+
* every render, if there is a valid variant id selected.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// ================================================================
|
|
15
|
+
type Props = {
|
|
16
|
+
selectedImageIndex?: number;
|
|
17
|
+
primaryImageUrl: string;
|
|
18
|
+
handleImageClick: (ind: number) => () => void;
|
|
19
|
+
images: string[];
|
|
20
|
+
name: string | undefined;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// ================================================================
|
|
24
|
+
|
|
25
|
+
export default function ProductImages({
|
|
26
|
+
name,
|
|
27
|
+
selectedImageIndex,
|
|
28
|
+
primaryImageUrl,
|
|
29
|
+
images,
|
|
30
|
+
handleImageClick,
|
|
31
|
+
}: Props) {
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<Grid size={{ md: 6, xs: 12 }} alignItems="center">
|
|
35
|
+
{/*MAIN IMAGE*/}
|
|
36
|
+
<FlexBox
|
|
37
|
+
borderRadius={3}
|
|
38
|
+
overflow="hidden"
|
|
39
|
+
justifyContent="center"
|
|
40
|
+
mb={6}
|
|
41
|
+
>
|
|
42
|
+
<ImageWithFallback
|
|
43
|
+
alt={name ?? "product"}
|
|
44
|
+
width={300}
|
|
45
|
+
height={300}
|
|
46
|
+
loading="eager"
|
|
47
|
+
src={
|
|
48
|
+
Array.isArray(images) &&
|
|
49
|
+
selectedImageIndex !== undefined &&
|
|
50
|
+
images[selectedImageIndex]
|
|
51
|
+
? images[selectedImageIndex]
|
|
52
|
+
: primaryImageUrl
|
|
53
|
+
}
|
|
54
|
+
style={{ objectFit: "contain" }}
|
|
55
|
+
/>
|
|
56
|
+
</FlexBox>
|
|
57
|
+
|
|
58
|
+
{/*THUMBNAIL IMAGES*/}
|
|
59
|
+
<FlexBox overflow="auto" gap={1} justifyContent="center">
|
|
60
|
+
{images?.map((url, ind) => (
|
|
61
|
+
<FlexRowCenter
|
|
62
|
+
key={ind}
|
|
63
|
+
width={64}
|
|
64
|
+
height={64}
|
|
65
|
+
minWidth={64}
|
|
66
|
+
bgcolor="white"
|
|
67
|
+
border="1px solid"
|
|
68
|
+
borderRadius="10px"
|
|
69
|
+
style={{ cursor: "pointer" }}
|
|
70
|
+
onClick={handleImageClick(ind)}
|
|
71
|
+
borderColor={
|
|
72
|
+
selectedImageIndex === ind ? "primary.main" : "grey.400"
|
|
73
|
+
}
|
|
74
|
+
>
|
|
75
|
+
<Avatar
|
|
76
|
+
alt="product"
|
|
77
|
+
src={url}
|
|
78
|
+
variant="square"
|
|
79
|
+
sx={{ height: 40 }}
|
|
80
|
+
/>
|
|
81
|
+
</FlexRowCenter>
|
|
82
|
+
))}
|
|
83
|
+
</FlexBox>
|
|
84
|
+
</Grid>
|
|
85
|
+
</>
|
|
86
|
+
);
|
|
87
|
+
}
|