@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.
Files changed (133) hide show
  1. package/package.json +3 -2
  2. package/src/api-manager/datasources/d365/d365-address.datasource.ts +1 -1
  3. package/src/api-manager/datasources/d365/d365-user.datasource.ts +0 -19
  4. package/src/api-manager/datasources/e4/e4-cart.datasource.ts +2 -2
  5. package/src/api-manager/datasources/e4/e4-product.datasource.ts +19 -4
  6. package/src/api-manager/datasources/e4/e4.datasource.ts +1 -2
  7. package/src/api-manager/datasources/e4/e4.remaps.ts +24 -0
  8. package/src/api-manager/datasources/e4/e4.translator.ts +17 -0
  9. package/src/api-manager/datasources/e4/{e4-user.datasource.ts → user/e4-user.datasource.ts} +5 -7
  10. package/src/api-manager/datasources/e4/user/e4-user.remaps.ts +4 -0
  11. package/src/api-manager/datasources/e4/user/e4-user.translator.ts +10 -0
  12. package/src/api-manager/index.ts +21 -6
  13. package/src/api-manager/schemas/product.schema.ts +1 -1
  14. package/src/api-manager/services/address/queries/get-states.ts +1 -1
  15. package/src/api-manager/services/create-query.ts +10 -3
  16. package/src/api-manager/types/Datasource.ts +9 -9
  17. package/src/components/Blocks/Components/ProductCarousel/index.tsx +6 -6
  18. package/src/components/_components/navbar/category-based-menu/components/categories.tsx +1 -1
  19. package/src/components/_components/navbar/mega-menu/mega-menu.tsx +1 -1
  20. package/src/components/_components/navbar/nav-list/nav-item-child.tsx +2 -2
  21. package/src/components/_components/product-dimensions/product-dimensions.tsx +114 -0
  22. package/src/components/_components/product-quantity-buttons/product-quantity-buttons.tsx +88 -0
  23. package/src/components/_components/section-header/section-header.tsx +1 -1
  24. package/src/components/_components/settings/setting.tsx +1 -1
  25. package/src/components/_components/wishlist-modal/TODO.md +10 -0
  26. package/src/components/_components/wishlist-modal/add-to-wishlist-button.tsx +68 -0
  27. package/src/components/_components/wishlist-modal/add-to-wishlist-dialog.tsx +161 -0
  28. package/src/components/_components/wishlist-modal/remove-from-wishlist-dialog.tsx +174 -0
  29. package/src/components/_components/wishlist-modal/wishlist-modal-item.tsx +65 -0
  30. package/src/components/countries-input.tsx +1 -0
  31. package/src/hooks/use-cart.tsx +82 -0
  32. package/src/hooks/use-variants.tsx +85 -0
  33. package/src/pages/account/addresses/address-form.tsx +289 -0
  34. package/src/pages/account/addresses/address-item.tsx +104 -0
  35. package/src/pages/account/dashboard-header.tsx +123 -0
  36. package/src/pages/account/navigation.tsx +84 -0
  37. package/src/pages/account/no-records.tsx +20 -0
  38. package/src/pages/account/orders/icons/delivery.tsx +14 -0
  39. package/src/pages/account/orders/icons/package-box.tsx +13 -0
  40. package/src/pages/account/orders/icons/truck-filled.tsx +14 -0
  41. package/src/pages/account/orders/order-progress.tsx +111 -0
  42. package/src/pages/account/orders/order-row.tsx +61 -0
  43. package/src/pages/account/orders/order-summary.tsx +83 -0
  44. package/src/pages/account/orders/ordered-products.tsx +114 -0
  45. package/src/pages/account/profile/profile-button.test.tsx +59 -0
  46. package/src/pages/account/profile/profile-button.tsx +50 -0
  47. package/src/pages/account/profile/user-info.tsx +59 -0
  48. package/src/pages/account/styles.ts +32 -0
  49. package/src/pages/account/table-row.tsx +19 -0
  50. package/src/pages/account/wishlist/create-new-list.tsx +167 -0
  51. package/src/pages/account/wishlist/create-wishlist-button.tsx +40 -0
  52. package/src/pages/account/wishlist/wishlist-item.tsx +82 -0
  53. package/src/pages/blog/pagination.tsx +38 -0
  54. package/src/pages/cart/cart-item.tsx +312 -0
  55. package/src/pages/cart/checkout-form.tsx +122 -0
  56. package/src/pages/cart/coupon-entry.tsx +90 -0
  57. package/src/pages/cart/estimate-shipping.tsx +183 -0
  58. package/src/pages/cart/wrapper.tsx +30 -0
  59. package/src/pages/checkout/checkout-alt-form/address-card.tsx +73 -0
  60. package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +133 -0
  61. package/src/pages/checkout/checkout-alt-form/checkout-step.tsx +120 -0
  62. package/src/pages/checkout/checkout-alt-form/customer-information.tsx +94 -0
  63. package/src/pages/checkout/checkout-alt-form/delivery-address.tsx +170 -0
  64. package/src/pages/checkout/checkout-alt-form/delivery-date.tsx +114 -0
  65. package/src/pages/checkout/checkout-alt-form/delivery-options.tsx +186 -0
  66. package/src/pages/checkout/checkout-alt-form/edit-address-form.tsx +130 -0
  67. package/src/pages/checkout/checkout-alt-form/heading.tsx +50 -0
  68. package/src/pages/checkout/checkout-alt-form/index.ts +1 -0
  69. package/src/pages/checkout/checkout-alt-form/new-address-form.tsx +294 -0
  70. package/src/pages/checkout/checkout-alt-form/node.tsx +9 -0
  71. package/src/pages/checkout/checkout-alt-form/payment-details.tsx +344 -0
  72. package/src/pages/checkout/checkout-alt-form/safe-card-preview-data.tsx +36 -0
  73. package/src/pages/checkout/checkout-alt-form/types.ts +20 -0
  74. package/src/pages/checkout/checkout-alt-summary/cart-item.tsx +39 -0
  75. package/src/pages/checkout/checkout-alt-summary/checkout-alt-summary.tsx +40 -0
  76. package/src/pages/checkout/checkout-alt-summary/index.ts +1 -0
  77. package/src/pages/checkout/checkout-alt-summary/list-item.tsx +31 -0
  78. package/src/pages/confirmation/address.tsx +22 -0
  79. package/src/pages/confirmation/confirmation-summary.tsx +52 -0
  80. package/src/pages/confirmation/ordered-products.tsx +108 -0
  81. package/src/pages/product-details/available-shops.tsx +48 -0
  82. package/src/pages/product-details/bopis/find-in-store-button.tsx +63 -0
  83. package/src/pages/product-details/bopis/find-in-store-modal.tsx +257 -0
  84. package/src/pages/product-details/bopis/pickup-option-select.tsx +127 -0
  85. package/src/pages/product-details/bopis/search-header.tsx +69 -0
  86. package/src/pages/product-details/checkbox-label.tsx +20 -0
  87. package/src/pages/product-details/currency.ts +64 -0
  88. package/src/pages/product-details/frequently-bought.tsx +90 -0
  89. package/src/pages/product-details/frequently-product-card.tsx +60 -0
  90. package/src/pages/product-details/product-comment.tsx +44 -0
  91. package/src/pages/product-details/product-description.tsx +22 -0
  92. package/src/pages/product-details/product-filter-card.tsx +257 -0
  93. package/src/pages/product-details/product-intro/product-intro-images.tsx +87 -0
  94. package/src/pages/product-details/product-intro/product-intro.tsx +250 -0
  95. package/src/pages/product-details/product-review.tsx +131 -0
  96. package/src/pages/product-details/product-tabs.tsx +51 -0
  97. package/src/pages/product-details/related-products.tsx +42 -0
  98. package/src/pages/product-details/types.ts +11 -0
  99. package/src/pages/product-list/breadcrumbs.tsx +39 -0
  100. package/src/pages/product-list/checkbox-label.tsx +20 -0
  101. package/src/pages/product-list/facet-group.tsx +125 -0
  102. package/src/pages/product-list/facet.tsx +36 -0
  103. package/src/pages/product-list/list-filter.tsx +40 -0
  104. package/src/pages/product-list/pagination.tsx +80 -0
  105. package/src/pages/product-list/product-list-context.tsx +302 -0
  106. package/src/pages/product-list/product-list-state.ts +187 -0
  107. package/src/pages/product-list/product-rating.tsx +16 -0
  108. package/src/pages/product-list/quick-view-dialog-content.tsx +205 -0
  109. package/src/pages/product-list/quick-view-dialog.tsx +54 -0
  110. package/src/pages/product-list/range-filter.tsx +125 -0
  111. package/src/pages/product-list/scrollbar.tsx +38 -0
  112. package/src/pages/product-list/search-bar.tsx +162 -0
  113. package/src/pages/product-list/selected-facets.tsx +80 -0
  114. package/src/pages/product-list/side-nav.tsx +49 -0
  115. package/src/pages/product-list/swatch.tsx +80 -0
  116. package/src/pages/product-list/types.ts +18 -0
  117. package/src/pages/product-list/use-product-list.ts +12 -0
  118. package/src/pages/product-list/useCategoryFilter.tsx +26 -0
  119. package/src/pages/product-list/utils/facet-helpers.ts +5 -0
  120. package/src/pages/product-list/utils/generate-breadcrumbs.ts +30 -0
  121. package/src/pages/product-list/utils/getCategoryFilterFromSlug.ts +153 -0
  122. package/src/pages/product-list/utils/product-list-helper.ts +111 -0
  123. package/src/pages/product-list/utils/product-list-types.ts +41 -0
  124. package/src/pages/product-list/utils/search-for-category.ts +76 -0
  125. package/src/pages/product-list/utils/sort-options.ts +44 -0
  126. package/src/pages/product-list/utils/use-previous-refiners.ts +14 -0
  127. package/src/pages/quickorder/order-upload.tsx +150 -0
  128. package/src/pages/quickorder/quick-order-form.tsx +343 -0
  129. package/src/pages/quickorder/quick-order-row.tsx +144 -0
  130. package/tsconfig.json +1 -0
  131. package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +0 -10464
  132. /package/src/{components/_components/hooks/useOverflowDetect.ts → hooks/use-overflow-detect.ts} +0 -0
  133. /package/src/{components/_components/hooks/useSettings.ts → hooks/use-settings.ts} +0 -0
@@ -0,0 +1,250 @@
1
+ "use client";
2
+ import { useState, useMemo } from "react";
3
+ import Box from "@mui/material/Box";
4
+ import Grid from "@mui/material/Grid2";
5
+ import Rating from "@mui/material/Rating";
6
+ import { CircularProgress, Typography } from "@mui/material";
7
+
8
+ // GLOBAL CUSTOM COMPONENTS
9
+ import useCurrencyFormatter from "@/lib/currency-formatter";
10
+ import { H1, H2, H6 } from "@/components/_components/Typography";
11
+ import { FlexBox } from "@/components/_components/flex-box";
12
+ import QuantityButtons from "@/components/_components/product-quantity-buttons/product-quantity-buttons";
13
+ import AddToWishlistButton from "@/components/_components/wishlist-modal/add-to-wishlist-button";
14
+ // D365
15
+ import { ProductDimensionValueInventoryAvailability } from "@msdyn365-commerce/retail-proxy";
16
+
17
+ // API MANAGER
18
+ import {
19
+ InventorySearchCriteriaInput,
20
+ ProductDetails,
21
+ } from "@/api-manager/schemas/product.schema";
22
+ import { Cart } from "@/api-manager/schemas/cart.schema";
23
+ import getProductPrices from "@/api-manager/services/product/queries/get-product-prices";
24
+ import getProductAvailability from "@/api-manager/services/product/queries/get-product-availability";
25
+
26
+ // LOCAL HOOKS
27
+ import useVariants from "../../../hooks/use-variants";
28
+ import useCart, { AddToCartBopisInput } from "@/hooks/use-cart";
29
+
30
+ // LOCAL COMPONENTS
31
+ import ProductDimensions from "@/_components/product-dimensions/product-dimensions";
32
+ import ProductImages from "./product-intro-images";
33
+ import FindInStoreButton from "../bopis/find-in-store-button";
34
+
35
+ /**
36
+ * Master product data comes in as prop, selected variant details are called
37
+ * every render, if there is a valid variant id selected.
38
+ */
39
+
40
+ // ================================================================
41
+ type Props = { product: ProductDetails; cart: Cart };
42
+ // ================================================================
43
+
44
+ export default function ProductIntro({ product, cart }: Props) {
45
+ const [selectedImageIndex, setSelectedImage] = useState<number>(0);
46
+ const [showDimensionError, setShowDimensionError] = useState(false);
47
+ const [productRedId, setProductRedId] = useState<number>(product.RecordId);
48
+
49
+ const { Name, DimensionValues, Images, ItemId, RecordId } = product;
50
+
51
+ const { data: productPrices } = getProductPrices.useData({
52
+ productIds: [Number(productRedId)],
53
+ });
54
+
55
+ // HOOKS
56
+ const {
57
+ productVariant,
58
+ selectVariantId,
59
+ selectDimensions,
60
+ handleChangeVariant,
61
+ allDimensionsSelected,
62
+ } = useVariants({ product });
63
+
64
+ const { handleCartAmountChange, handleAddToCart, loading } = useCart({
65
+ cart,
66
+ selectVariantId,
67
+ recordId: RecordId,
68
+ });
69
+
70
+ const { formatCurrency } = useCurrencyFormatter();
71
+
72
+ const searchCriteria = useMemo<InventorySearchCriteriaInput>(
73
+ () => ({
74
+ allDimensionsSelected: allDimensionsSelected(selectDimensions),
75
+ searchCriteria: {
76
+ defaultWarehouseOnly: true,
77
+ productIds: [selectVariantId || RecordId],
78
+ },
79
+ }),
80
+ [allDimensionsSelected, selectDimensions, selectVariantId, RecordId],
81
+ );
82
+
83
+ const { data: shippingInventoryData, isLoading: inventoryLoading } =
84
+ getProductAvailability.useData(searchCriteria);
85
+ // HELPERS
86
+ const currentCartLine = cart?.CartLines?.find((item) => {
87
+ return item?.ProductId === (Number(selectVariantId) || RecordId);
88
+ });
89
+
90
+ const getInventoryLabel = (): string | null => {
91
+ const aggregate =
92
+ shippingInventoryData?.AggregatedProductInventoryAvailabilities &&
93
+ shippingInventoryData?.AggregatedProductInventoryAvailabilities[0];
94
+ if (aggregate) {
95
+ const { TotalAvailableInventoryLevelLabel } = aggregate;
96
+ return TotalAvailableInventoryLevelLabel || null;
97
+ }
98
+ return null;
99
+ };
100
+
101
+ const shouldDisableAddToCart = () => {
102
+ //TODO: Incorporate global inventory enabled flag.
103
+ if (!allDimensionsSelected(selectDimensions)) {
104
+ return false; // Don't disable add to cart if dimensions are not selected, so clicking the button will provide an error message showing the user what to do.
105
+ }
106
+ const OOSCode = "OOS"; // Always means out of stock
107
+ const aggregate =
108
+ shippingInventoryData?.AggregatedProductInventoryAvailabilities &&
109
+ shippingInventoryData?.AggregatedProductInventoryAvailabilities[0];
110
+ if (aggregate) {
111
+ const { TotalAvailableInventoryLevelCode } = aggregate;
112
+ return TotalAvailableInventoryLevelCode === OOSCode;
113
+ }
114
+ return true;
115
+ };
116
+
117
+ // HANDLE SELECT IMAGE
118
+ const handleImageClick = (ind: number) => () => {
119
+ setSelectedImage(ind);
120
+ };
121
+
122
+ const images =
123
+ Array.isArray(productVariant?.Images) && productVariant.Images.length > 0
124
+ ? productVariant.Images
125
+ : Array.isArray(Images) && Images.length > 0
126
+ ? Images
127
+ : [];
128
+
129
+ const handleChangeVariantWrapper = (
130
+ dimension: ProductDimensionValueInventoryAvailability,
131
+ ) => {
132
+ handleChangeVariant(dimension);
133
+
134
+ const newRecID = dimension?.ProductIds?.[0];
135
+ if (newRecID) setProductRedId(newRecID);
136
+ };
137
+
138
+ const onAddToCart = (bopisInput?: AddToCartBopisInput) => {
139
+ if (!allDimensionsSelected(selectDimensions)) {
140
+ setShowDimensionError(true);
141
+ return;
142
+ }
143
+ setShowDimensionError(false);
144
+ handleAddToCart(bopisInput);
145
+ };
146
+
147
+ const shouldOpenHelperModal = () => {
148
+ if (!allDimensionsSelected(selectDimensions)) {
149
+ setShowDimensionError(true);
150
+ return false;
151
+ }
152
+ setShowDimensionError(false);
153
+ return true;
154
+ };
155
+
156
+ return (
157
+ <Box width="100%">
158
+ <Grid container spacing={3} justifyContent="space-around">
159
+ <ProductImages
160
+ name={Name}
161
+ selectedImageIndex={selectedImageIndex}
162
+ primaryImageUrl={
163
+ Array.isArray(productVariant?.Images) &&
164
+ productVariant.Images.length > 0
165
+ ? productVariant.Images[0]
166
+ : Array.isArray(Images) && Images.length > 0
167
+ ? Images[0]
168
+ : ""
169
+ }
170
+ images={images}
171
+ handleImageClick={handleImageClick}
172
+ />
173
+
174
+ {/* PRODUCT INFO AREA */}
175
+ <Grid size={{ md: 6, xs: 12 }} alignItems="center">
176
+ {/* PRODUCT NAME */}
177
+ <H1 mb={1}>{Name}</H1>
178
+
179
+ {/* PRODUCT BRAND */}
180
+ <FlexBox alignItems="center" mb={1}>
181
+ <H6>SKU: {productVariant?.ItemId || ItemId}</H6>
182
+ </FlexBox>
183
+
184
+ {/* PRODUCT RATING */}
185
+ <FlexBox alignItems="center" gap={1} mb={2}>
186
+ <Box lineHeight="1">Rated:</Box>
187
+ <Rating color="warn" value={0} readOnly />
188
+ <H6 lineHeight="1">(0)</H6>
189
+ </FlexBox>
190
+
191
+ {/* PRODUCT VARIANTS */}
192
+ <ProductDimensions
193
+ dimensionGroups={DimensionValues}
194
+ selectDimensions={selectDimensions}
195
+ handleChangeVariant={handleChangeVariantWrapper}
196
+ showDimensionError={showDimensionError}
197
+ />
198
+
199
+ {/* PRODUCT PRICE & STOCK */}
200
+ <Box pt={1} mb={3}>
201
+ <H2 color="primary.main" mb={0.5} lineHeight="1">
202
+ {productPrices && productPrices[0].CustomerContextualPrice ? (
203
+ formatCurrency(productPrices[0].CustomerContextualPrice)
204
+ ) : (
205
+ <CircularProgress size={20} />
206
+ )}
207
+ </H2>
208
+ <Box color="inherit">{}</Box>
209
+ </Box>
210
+ <FlexBox alignItems="center" gap={1} mb={2}>
211
+ {/* INVENTORY */}
212
+ <Box sx={{ my: 3 }}>
213
+ {inventoryLoading ? (
214
+ <CircularProgress size={20} />
215
+ ) : (
216
+ <Typography
217
+ variant="body2"
218
+ color="text.secondary"
219
+ fontWeight={600}
220
+ >
221
+ {getInventoryLabel()}
222
+ </Typography>
223
+ )}
224
+ </Box>
225
+
226
+ <FlexBox gap={2}>
227
+ <QuantityButtons
228
+ loading={loading}
229
+ handleAddToCart={onAddToCart}
230
+ productCartLine={currentCartLine}
231
+ handleCartAmountChange={handleCartAmountChange}
232
+ isOOS={shouldDisableAddToCart()}
233
+ />
234
+ <FindInStoreButton
235
+ productId={selectVariantId || RecordId}
236
+ canOpenModal={shouldOpenHelperModal}
237
+ handleAddToCart={handleAddToCart}
238
+ allDimensionsSelected={allDimensionsSelected(selectDimensions)}
239
+ />
240
+ <AddToWishlistButton
241
+ path="Account.Wishlists.btnAddToWishlist"
242
+ product={product}
243
+ />
244
+ </FlexBox>
245
+ </FlexBox>
246
+ </Grid>
247
+ </Grid>
248
+ </Box>
249
+ );
250
+ }
@@ -0,0 +1,131 @@
1
+ "use client";
2
+
3
+ import Box from "@mui/material/Box";
4
+ import Button from "@mui/material/Button";
5
+ import Rating from "@mui/material/Rating";
6
+ import TextField from "@mui/material/TextField";
7
+ import * as yup from "yup";
8
+ import { useFormik } from "formik";
9
+ // Local CUSTOM COMPONENT
10
+ import ProductComment from "./product-comment";
11
+ // GLOBAL CUSTOM COMPONENTS
12
+ import { FlexBox } from "@/components/_components/flex-box";
13
+ import { H2, H5 } from "@/components/_components/Typography";
14
+
15
+ export default function ProductReview() {
16
+ const initialValues = {
17
+ rating: 0,
18
+ comment: "",
19
+ date: new Date().toISOString(),
20
+ };
21
+
22
+ const validationSchema = yup.object().shape({
23
+ rating: yup.number().required("required"),
24
+ comment: yup.string().required("required"),
25
+ });
26
+
27
+ const {
28
+ dirty,
29
+ values,
30
+ errors,
31
+ touched,
32
+ isValid,
33
+ handleBlur,
34
+ handleChange,
35
+ handleSubmit,
36
+ setFieldValue,
37
+ } = useFormik({
38
+ initialValues,
39
+ validationSchema,
40
+ onSubmit: async (_values, { resetForm }) => {
41
+ resetForm();
42
+ },
43
+ });
44
+
45
+ return (
46
+ <div>
47
+ {commentList.map((item, ind) => (
48
+ <ProductComment {...item} key={ind} />
49
+ ))}
50
+
51
+ <H2 fontWeight="600" mt={7} mb={2.5}>
52
+ Write a Review for this product
53
+ </H2>
54
+
55
+ <form onSubmit={handleSubmit}>
56
+ <Box mb={2.5}>
57
+ <FlexBox mb={1.5} gap={0.5}>
58
+ <H5 color="grey.700">Your Rating</H5>
59
+ <H5 color="error.main">*</H5>
60
+ </FlexBox>
61
+
62
+ <Rating
63
+ color="warn"
64
+ size="medium"
65
+ value={values.rating}
66
+ onChange={(_, value: number | null) =>
67
+ setFieldValue("rating", value)
68
+ }
69
+ />
70
+ </Box>
71
+
72
+ <Box mb={3}>
73
+ <FlexBox mb={1.5} gap={0.5}>
74
+ <H5 color="grey.700">Your Review</H5>
75
+ <H5 color="error.main">*</H5>
76
+ </FlexBox>
77
+
78
+ <TextField
79
+ rows={8}
80
+ multiline
81
+ fullWidth
82
+ name="comment"
83
+ variant="outlined"
84
+ onBlur={handleBlur}
85
+ value={values.comment}
86
+ onChange={handleChange}
87
+ placeholder="Write a review here..."
88
+ error={!!touched.comment && !!errors.comment}
89
+ helperText={(touched.comment && errors.comment) as string}
90
+ />
91
+ </Box>
92
+
93
+ <Button
94
+ variant="contained"
95
+ color="primary"
96
+ type="submit"
97
+ disabled={!(dirty && isValid)}
98
+ >
99
+ Submit
100
+ </Button>
101
+ </form>
102
+ </div>
103
+ );
104
+ }
105
+
106
+ const commentList = [
107
+ {
108
+ name: "Jannie Schumm",
109
+ imgUrl: "/assets/images/faces/7.png",
110
+ rating: 4.7,
111
+ date: "2021-02-14",
112
+ comment:
113
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Varius massa id ut mattis. Facilisis vitae gravida egestas ac account.",
114
+ },
115
+ {
116
+ name: "Joe Kenan",
117
+ imgUrl: "/assets/images/faces/6.png",
118
+ rating: 4.7,
119
+ date: "2019-08-10",
120
+ comment:
121
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Varius massa id ut mattis. Facilisis vitae gravida egestas ac account.",
122
+ },
123
+ {
124
+ name: "Jenifer Tulio",
125
+ imgUrl: "/assets/images/faces/8.png",
126
+ rating: 4.7,
127
+ date: "2021-02-05",
128
+ comment:
129
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Varius massa id ut mattis. Facilisis vitae gravida egestas ac account.",
130
+ },
131
+ ];
@@ -0,0 +1,51 @@
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+ import Box from "@mui/material/Box";
5
+ import Tab from "@mui/material/Tab";
6
+ import Tabs from "@mui/material/Tabs";
7
+ import styled from "@mui/material/styles/styled";
8
+ // LOCAL CUSTOM COMPONENTS
9
+ import ProductDescription from "./product-description";
10
+ import { AttributeValue } from "@msdyn365-commerce/retail-proxy";
11
+ // STYLED COMPONENT
12
+ const StyledTabs = styled(Tabs)(({ theme }) => ({
13
+ minHeight: 0,
14
+ marginTop: 80,
15
+ marginBottom: 24,
16
+ borderBottom: `1px solid ${theme.palette.text.disabled}`,
17
+ "& .inner-tab": {
18
+ minHeight: 40,
19
+ fontWeight: 600,
20
+ textTransform: "capitalize",
21
+ },
22
+ }));
23
+
24
+ type Props = { productAttributes: AttributeValue[] };
25
+
26
+ export default function ProductTabs({ productAttributes }: Props) {
27
+ const [selectedOption, setSelectedOption] = useState(0);
28
+ const handleOptionClick = (_: React.SyntheticEvent, value: number) =>
29
+ setSelectedOption(value);
30
+
31
+ return (
32
+ <>
33
+ <StyledTabs
34
+ textColor="primary"
35
+ value={selectedOption}
36
+ indicatorColor="primary"
37
+ onChange={handleOptionClick}
38
+ >
39
+ <Tab className="inner-tab" label="Description" />
40
+ <Tab className="inner-tab" label="Review (3)" />
41
+ </StyledTabs>
42
+
43
+ <Box mb={6}>
44
+ {selectedOption === 0 && (
45
+ <ProductDescription attributes={productAttributes} />
46
+ )}
47
+ {/*selectedOption === 1 && <ProductReview />*/}
48
+ </Box>
49
+ </>
50
+ );
51
+ }
@@ -0,0 +1,42 @@
1
+ "use client";
2
+
3
+ import Box from "@mui/material/Box";
4
+ import Grid from "@mui/material/Grid2";
5
+ // GLOBAL CUSTOM COMPONENTS
6
+ import { H3 } from "@/components/_components/Typography";
7
+ import ProductCard1Wrapper from "@/components/_components/product-cards/product-card-1/product-card-1-wrapper";
8
+ // CUSTOM DATA MODEL
9
+ import GetRelatedProductsDataFetcher from "@/api-manager/services/product/queries/get-related-products";
10
+
11
+ // ==============================================================
12
+ type Props = { RecordId: number };
13
+ // ==============================================================
14
+
15
+ export default function RelatedProducts({ RecordId }: Props) {
16
+ const { data: products } = GetRelatedProductsDataFetcher.useData({
17
+ id: RecordId,
18
+ });
19
+ return (
20
+ <Box mb={7.5}>
21
+ <H3 mb={3}>Related Products</H3>
22
+
23
+ <Grid container spacing={3}>
24
+ {products?.map((item, ind) => (
25
+ <Grid size={{ lg: 3, md: 4, sm: 6, xs: 12 }} key={ind}>
26
+ <ProductCard1Wrapper
27
+ hoverEffect
28
+ id={item?.ItemId || ""}
29
+ slug={String(item?.RecordId) || ""}
30
+ title={item?.Name || ""}
31
+ price={item.Price}
32
+ rating={4.5}
33
+ imgUrl={item.PrimaryImageUrl || ""}
34
+ discount={10}
35
+ mode="PLP"
36
+ />
37
+ </Grid>
38
+ ))}
39
+ </Grid>
40
+ </Box>
41
+ );
42
+ }
@@ -0,0 +1,11 @@
1
+ export interface ProductFilters {
2
+ brand: string[];
3
+ color: string[];
4
+ sales: string[];
5
+ price: number[];
6
+ rating: number;
7
+ // category: string[];
8
+ }
9
+
10
+ export type ProductFilterKeys = keyof ProductFilters;
11
+ export type ProductFilterValues = ProductFilters[ProductFilterKeys];
@@ -0,0 +1,39 @@
1
+ import { Breadcrumbs, Typography } from "@mui/material";
2
+ import React from "react";
3
+ import Link from "next/link";
4
+ import {
5
+ getCategoryTranslatedName,
6
+ getCategoryUrl,
7
+ } from "./utils/search-for-category";
8
+ import { CategoryHierarchy } from "@/lib/category-helpers";
9
+
10
+ type Props = {
11
+ breadcrumbs: CategoryHierarchy[];
12
+ locale: string;
13
+ productName?: string;
14
+ };
15
+
16
+ export default function CategoryBreadcrumbs({
17
+ breadcrumbs,
18
+ locale,
19
+ productName,
20
+ }: Props) {
21
+ return (
22
+ <Breadcrumbs>
23
+ {breadcrumbs.map((bc, index) =>
24
+ index < breadcrumbs.length - 1 || productName ? (
25
+ <Link href={getCategoryUrl(bc)} key={bc.Name}>
26
+ {getCategoryTranslatedName(locale, bc.NameTranslations)}
27
+ </Link>
28
+ ) : (
29
+ <Typography variant={"body1"} key={bc.Name}>
30
+ {getCategoryTranslatedName(locale, bc.NameTranslations)}
31
+ </Typography>
32
+ ),
33
+ )}
34
+ {productName ? (
35
+ <Typography variant={"body1"}>{productName}</Typography>
36
+ ) : undefined}
37
+ </Breadcrumbs>
38
+ );
39
+ }
@@ -0,0 +1,20 @@
1
+ import { ReactNode } from "react";
2
+ // MUI
3
+ import FormControlLabel from "@mui/material/FormControlLabel";
4
+ import Checkbox, { CheckboxProps } from "@mui/material/Checkbox";
5
+
6
+ // ==============================================================
7
+ interface Props extends CheckboxProps {
8
+ label: ReactNode;
9
+ }
10
+ // ==============================================================
11
+
12
+ export default function CheckboxLabel({ label, ...props }: Props) {
13
+ return (
14
+ <FormControlLabel
15
+ label={label}
16
+ control={<Checkbox size="small" color="primary" {...props} />}
17
+ slotProps={{ typography: { fontSize: 14, lineHeight: 1 } }}
18
+ />
19
+ );
20
+ }
@@ -0,0 +1,125 @@
1
+ import { H6 } from "@/components/_components/Typography";
2
+ import {
3
+ AttributeDataType,
4
+ DisplayTemplate,
5
+ ProductRefiner,
6
+ ProductRefinerSource,
7
+ ProductRefinerValue,
8
+ } from "@msdyn365-commerce/retail-proxy";
9
+ import { Box, ButtonBase, Collapse, FormGroup } from "@mui/material";
10
+ import React from "react";
11
+ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
12
+ import ExpandLessIcon from "@mui/icons-material/ExpandLess";
13
+ import Swatch from "./swatch";
14
+ import RangeFilter from "./range-filter";
15
+ import ListFilter from "./list-filter";
16
+ import { areRefinersEqual } from "@/lib/refiner-utils";
17
+
18
+ type Props = {
19
+ facet: ProductRefiner;
20
+ selectedRefiners: ProductRefinerValue[];
21
+ handleRefinerSelect(refiner: ProductRefinerValue): void;
22
+ };
23
+
24
+ export type RenderedFacetProps = {
25
+ facet: ProductRefiner;
26
+ handleRefinerSelect(refiner: ProductRefinerValue): void;
27
+ isRefinerSelected(r1: ProductRefinerValue): boolean;
28
+ };
29
+
30
+ export default function FacetGroup({
31
+ facet,
32
+ selectedRefiners,
33
+ handleRefinerSelect,
34
+ }: Props) {
35
+ const isAnyRefinerFromFacetSelected = () =>
36
+ facet.Values?.some((v) => isRefinerSelected(v));
37
+ const isRefinerSelected = (r1: ProductRefinerValue) =>
38
+ selectedRefiners.some((r2) => areRefinersEqual(r1, r2));
39
+
40
+ const [expanded, setExpanded] = React.useState(
41
+ isAnyRefinerFromFacetSelected(),
42
+ );
43
+
44
+ const renderRefinerType = () => {
45
+ let component: React.ReactNode | null = null;
46
+ //Swatch
47
+ if (facet.KeyName?.toLowerCase() === "color") {
48
+ component = (
49
+ <Box mt={1}>
50
+ <Swatch
51
+ facet={facet}
52
+ handleRefinerSelect={handleRefinerSelect}
53
+ isRefinerSelected={isRefinerSelected}
54
+ />
55
+ </Box>
56
+ );
57
+ } else if (
58
+ facet.DataTypeValue === AttributeDataType.Currency &&
59
+ facet.DisplayTemplateValue === DisplayTemplate.Slider
60
+ ) {
61
+ component = (
62
+ <RangeFilter
63
+ facet={facet}
64
+ handleRefinerSelect={handleRefinerSelect}
65
+ isRefinerSelected={isRefinerSelected}
66
+ selectedValues={getPriceRefinerValue()}
67
+ />
68
+ );
69
+ } else if (facet.SourceValue === 4) {
70
+ const updatedFacet = {
71
+ ...facet,
72
+ Values: facet?.Values?.map((item) => ({
73
+ ...item,
74
+ RightValueBoundString: item.LeftValueBoundString,
75
+ })),
76
+ };
77
+ component = (
78
+ <ListFilter
79
+ facet={updatedFacet}
80
+ handleRefinerSelect={handleRefinerSelect}
81
+ isRefinerSelected={isRefinerSelected}
82
+ />
83
+ );
84
+ } else {
85
+ component = (
86
+ <ListFilter
87
+ facet={facet}
88
+ handleRefinerSelect={handleRefinerSelect}
89
+ isRefinerSelected={isRefinerSelected}
90
+ />
91
+ );
92
+ }
93
+ return component;
94
+ };
95
+
96
+ function getPriceRefinerValue(): ProductRefinerValue | undefined {
97
+ return selectedRefiners.find(
98
+ (refiner) => refiner.RefinerSourceValue === ProductRefinerSource.Price,
99
+ );
100
+ }
101
+
102
+ return (
103
+ <Box pb={2} pt={2}>
104
+ <ButtonBase
105
+ sx={{ display: "block", width: "100%", borderRadius: 1 }}
106
+ onClick={() => setExpanded(!expanded)}
107
+ >
108
+ <Box display="flex" alignItems="center" pl={1}>
109
+ <H6 component="p" fontWeight="bold">
110
+ {facet.KeyName}
111
+ </H6>
112
+ <Box flexGrow={1} />
113
+ {expanded ? (
114
+ <ExpandLessIcon color="action" />
115
+ ) : (
116
+ <ExpandMoreIcon color="action" />
117
+ )}
118
+ </Box>
119
+ </ButtonBase>
120
+ <FormGroup>
121
+ <Collapse in={expanded}>{renderRefinerType()}</Collapse>
122
+ </FormGroup>
123
+ </Box>
124
+ );
125
+ }