@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,167 @@
1
+ import React, { useEffect } from "react";
2
+ import isFunction from "lodash/isFunction";
3
+ import { useTranslations } from "next-intl";
4
+ import {
5
+ Box,
6
+ Button,
7
+ TextField,
8
+ Dialog,
9
+ DialogContent,
10
+ FormControlLabel,
11
+ Checkbox,
12
+ } from "@mui/material";
13
+ import Grid from "@mui/material/Grid2";
14
+ // Global Custom components
15
+ import { H5 } from "@/components/_components/Typography";
16
+ import { useNotification } from "@/components/Notifications/use-notification";
17
+ // Forms
18
+ import { useFormik } from "formik";
19
+ import { z } from "zod";
20
+ import { withZodSchema } from "formik-validator-zod";
21
+ // API
22
+ import useCreateWishlist from "@/api-manager/services/user/mutations/create-wishlist";
23
+
24
+ interface Props {
25
+ open: boolean;
26
+ handleClose: () => void;
27
+ }
28
+
29
+ export default function CreateNewList(props: Props) {
30
+ const t = useTranslations("Account.Wishlists");
31
+ const { setNotification } = useNotification();
32
+
33
+ const { mutateAsync: createWishlist } = useCreateWishlist();
34
+
35
+ const FormSchema = z.object({
36
+ Id: z.string(),
37
+ Name: z.string().nonempty(t("Validation.required", { field: t("name") })),
38
+ Comment: z.string().optional(),
39
+ IsPublic: z.boolean().optional(),
40
+ });
41
+ type FormSchemaType = z.infer<typeof FormSchema>;
42
+
43
+ const handleSubmit = async (values: FormSchemaType) => {
44
+ try {
45
+ await createWishlist({ ...values, IsPublic: !values.IsPublic });
46
+ handleClose();
47
+ setNotification({
48
+ message: t("wishlistSuccess"),
49
+ severity: "success",
50
+ });
51
+ } catch (error) {
52
+ setNotification({
53
+ message: t("wishlistFailed"),
54
+ severity: "error",
55
+ });
56
+ }
57
+ };
58
+
59
+ const formik = useFormik<FormSchemaType>({
60
+ initialValues: {
61
+ Id: "",
62
+ Name: "",
63
+ Comment: "",
64
+ IsPublic: false,
65
+ },
66
+ onSubmit: handleSubmit,
67
+ validate: withZodSchema(FormSchema),
68
+ });
69
+
70
+ // destructure to put in dependency array
71
+ // of use effect instead of whole formik
72
+ const { resetForm } = formik;
73
+
74
+ useEffect(() => {
75
+ if (props.open) {
76
+ resetForm();
77
+ }
78
+ }, [props.open, resetForm]);
79
+
80
+ const handleClose = () => {
81
+ if (isFunction(props.handleClose)) {
82
+ props.handleClose();
83
+ }
84
+ };
85
+
86
+ return (
87
+ <Dialog
88
+ open={props.open}
89
+ onClose={handleClose}
90
+ aria-labelledby="form-dialog-title"
91
+ >
92
+ <DialogContent>
93
+ <H5 mb={4}>{t("formTitle")}</H5>
94
+ <Box component="form" onSubmit={formik.handleSubmit}>
95
+ <Grid container spacing={3}>
96
+ <Grid size={{ sm: 6, xs: 12 }}>
97
+ <TextField
98
+ name="Name"
99
+ fullWidth
100
+ label={t("name")}
101
+ value={formik.values.Name}
102
+ onChange={formik.handleChange}
103
+ onBlur={formik.handleBlur}
104
+ error={formik.touched.Name && Boolean(formik.errors.Name)}
105
+ helperText={formik.touched.Name && formik.errors.Name}
106
+ required
107
+ />
108
+ </Grid>
109
+ <Grid size={{ sm: 6, xs: 12 }}>
110
+ <TextField
111
+ name="Comment"
112
+ fullWidth
113
+ label={t("comment")}
114
+ value={formik.values.Comment}
115
+ onChange={formik.handleChange}
116
+ onBlur={formik.handleBlur}
117
+ error={formik.touched.Comment && Boolean(formik.errors.Comment)}
118
+ helperText={formik.touched.Comment && formik.errors.Comment}
119
+ />
120
+ </Grid>
121
+ <Grid size={{ sm: 6, xs: 12 }}>
122
+ <FormControlLabel
123
+ control={
124
+ <Checkbox
125
+ name="IsPublic"
126
+ checked={formik.values.IsPublic}
127
+ onChange={formik.handleChange}
128
+ onBlur={formik.handleBlur}
129
+ />
130
+ }
131
+ label={t("private")}
132
+ />
133
+ </Grid>
134
+ <Grid
135
+ size={{ sm: 6, xs: 12 }}
136
+ sx={{
137
+ display: "flex",
138
+ justifyContent: "flex-end",
139
+ alignItems: "center",
140
+ gap: 2,
141
+ }}
142
+ >
143
+ <Button
144
+ color="primary"
145
+ variant="contained"
146
+ size="small"
147
+ type="submit"
148
+ sx={{ height: 30.75 }}
149
+ >
150
+ {t("btnSave")}
151
+ </Button>
152
+ <Button
153
+ color="primary"
154
+ variant="outlined"
155
+ size="small"
156
+ onClick={handleClose}
157
+ sx={{ height: 30.75 }}
158
+ >
159
+ {t("btnCancel")}
160
+ </Button>
161
+ </Grid>
162
+ </Grid>
163
+ </Box>
164
+ </DialogContent>
165
+ </Dialog>
166
+ );
167
+ }
@@ -0,0 +1,40 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Button } from "@mui/material";
5
+ import T from "@/components/T";
6
+ import CreateNewListDialog from "./create-new-list";
7
+
8
+ interface Props {
9
+ path: string;
10
+ }
11
+
12
+ const CreateWishlistButton = ({ path }: Props) => {
13
+ const [openCreateListDialog, setOpenCreateListDialog] = useState(false);
14
+
15
+ const handleCloseDialog = () => {
16
+ setOpenCreateListDialog(false);
17
+ };
18
+
19
+ const handleCreateNewList = () => {
20
+ setOpenCreateListDialog(true);
21
+ };
22
+
23
+ return (
24
+ <>
25
+ <Button
26
+ onClick={handleCreateNewList}
27
+ color="primary"
28
+ sx={{ bgcolor: "primary.light", px: 4 }}
29
+ >
30
+ <T path={path} />
31
+ </Button>
32
+ <CreateNewListDialog
33
+ open={openCreateListDialog}
34
+ handleClose={handleCloseDialog}
35
+ />
36
+ </>
37
+ );
38
+ };
39
+
40
+ export default CreateWishlistButton;
@@ -0,0 +1,82 @@
1
+ import { Box, IconButton } from "@mui/material";
2
+ import { useTranslations } from "next-intl";
3
+ import { useNotification } from "@/components/Notifications/use-notification";
4
+ import { format } from "date-fns";
5
+ // MUI ICON COMPONENTS
6
+ import { MdDelete as Delete } from "react-icons/md";
7
+ // GLOBAL CUSTOM COMPONENTS
8
+ import { Paragraph } from "@/components/_components/Typography";
9
+ import { NavLink3 } from "@/components/_components/nav-link";
10
+ // LOCAL CUSTOM COMPONENT
11
+ import TableRow from "../table-row";
12
+ // API
13
+ import useDeleteWishlist from "@/api-manager/services/user/mutations/delete-wishlist";
14
+ import {
15
+ Wishlist,
16
+ WishlistDetailInput,
17
+ } from "@/api-manager/schemas/user.schema";
18
+
19
+ interface Props {
20
+ wishlist: Wishlist;
21
+ }
22
+
23
+ export default function WishlistItem({ wishlist }: Props) {
24
+ const t = useTranslations("Account.Wishlists");
25
+ const { setNotification } = useNotification();
26
+
27
+ const { mutateAsync: deleteWishlist } = useDeleteWishlist();
28
+
29
+ const handleDelete = async (id: string) => {
30
+ const wishlistDeleteInput: WishlistDetailInput = { Id: id };
31
+ try {
32
+ await deleteWishlist(wishlistDeleteInput);
33
+ setNotification({
34
+ message: t("deleteWishlistSuccess"),
35
+ severity: "success",
36
+ });
37
+ } catch (error) {
38
+ setNotification({
39
+ message: t("deleteWishlistFailed"),
40
+ severity: "error",
41
+ });
42
+ }
43
+ };
44
+
45
+ return (
46
+ <TableRow
47
+ sx={{
48
+ display: "grid",
49
+ gridTemplateColumns: "2fr 1fr 1fr 1fr 1fr", // Define equal column structure
50
+ alignItems: "center",
51
+ gap: 2,
52
+ padding: "10px 15px",
53
+ }}
54
+ >
55
+ <Paragraph ellipsis>
56
+ <NavLink3
57
+ href={`/account/wish-lists/${wishlist?.Id}`}
58
+ text={wishlist?.Name ?? ""}
59
+ />
60
+ </Paragraph>
61
+ <Paragraph ellipsis>
62
+ {" "}
63
+ {format(
64
+ new Date(wishlist?.CreatedDateTime ?? wishlist?.LastUpdated ?? ""),
65
+ "MMM dd, yyyy",
66
+ )}
67
+ </Paragraph>
68
+ <Paragraph
69
+ ellipsis
70
+ >{`${wishlist?.ProductListLines?.length} ${t("products")}`}</Paragraph>
71
+ {/* <Paragraph ellipsis>
72
+ {wishlist?.isPublic ? "Public" : "Private"}
73
+ </Paragraph> */}
74
+
75
+ <Box display="flex" alignItems="center" gap={1} justifySelf="end">
76
+ <IconButton onClick={() => handleDelete(wishlist?.Id || "")}>
77
+ <Delete fontSize="large" color="inherit" />
78
+ </IconButton>
79
+ </Box>
80
+ </TableRow>
81
+ );
82
+ }
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import {
3
+ Pagination as MuiPagination,
4
+ PaginationItem,
5
+ PaginationRenderItemParams,
6
+ } from "@mui/material";
7
+ import Link from "next/link";
8
+
9
+ interface Props {
10
+ page?: number;
11
+ count: number;
12
+ pageSize?: number;
13
+ }
14
+
15
+ const Pagination = ({ count, page, pageSize }: Props) => {
16
+ const isSinglePage = count <= 1;
17
+ return (
18
+ <MuiPagination
19
+ variant="outlined"
20
+ color="primary"
21
+ shape="rounded"
22
+ page={page}
23
+ count={count}
24
+ renderItem={(item: PaginationRenderItemParams) =>
25
+ isSinglePage ? (
26
+ // If there's only 1 page, don't wrap with Link
27
+ <PaginationItem {...item} />
28
+ ) : (
29
+ <Link href={`?page=${item.page}&pageSize=${pageSize}`} passHref>
30
+ <PaginationItem {...item} />
31
+ </Link>
32
+ )
33
+ }
34
+ />
35
+ );
36
+ };
37
+
38
+ export default Pagination;
@@ -0,0 +1,312 @@
1
+ import { useState } from "react";
2
+ import Link from "next/link";
3
+ import {
4
+ Button,
5
+ IconButton,
6
+ Box,
7
+ CircularProgress,
8
+ Typography,
9
+ TextField,
10
+ } from "@mui/material";
11
+
12
+ import { useTranslations } from "next-intl";
13
+ // MUI ICON COMPONENTS
14
+ import Add from "@mui/icons-material/Add";
15
+ import Close from "@mui/icons-material/Close";
16
+ import Remove from "@mui/icons-material/Remove";
17
+ // GLOBAL CUSTOM COMPONENTS
18
+ import {
19
+ H6,
20
+ Span,
21
+ Tiny,
22
+ } from "@evenicanpm/storefront-core/src/components/_components/Typography";
23
+ import { FlexBox } from "@evenicanpm/storefront-core/src/components/_components/flex-box/index";
24
+ import Image from "@evenicanpm/storefront-core/src/components/ImageWithFallback";
25
+ // STYLED COMPONENT
26
+ import { Wrapper } from "./wrapper";
27
+ // API
28
+ import { ProductDimensionType } from "@msdyn365-commerce/retail-proxy";
29
+ import useUpdateCartLines from "@evenicanpm/storefront-core/src/api-manager/services/cart/mutations/update-cart-lines";
30
+ import useDeleteCartLine from "@evenicanpm/storefront-core/src/api-manager/services/cart/mutations/delete-cart-line";
31
+ import { CartLine } from "@evenicanpm/storefront-core/src/api-manager/schemas/cart.schema";
32
+ // Utils
33
+ import useCurrencyFormatter from "@evenicanpm/storefront-core/src/lib/currency-formatter";
34
+ import { getProductDetailUrl } from "@evenicanpm/storefront-core/src/lib/build-product-detail-url";
35
+
36
+ // =========================================================
37
+ type Props = {
38
+ cartLine: CartLine;
39
+ cartId: string;
40
+ cartVersion: number;
41
+ showCartLineComments?: boolean;
42
+ };
43
+ // =========================================================
44
+
45
+ export default function CartItem({
46
+ cartLine,
47
+ cartId,
48
+ cartVersion,
49
+ showCartLineComments = true,
50
+ }: Props) {
51
+ const [isWritingComment, setIsWritingComment] = useState(false);
52
+ const [comment, setComment] = useState(cartLine.Comment || "");
53
+ const t = useTranslations("Cart.CartLine");
54
+ const { mutate: updateCartLine, isPending: updatePending } =
55
+ useUpdateCartLines();
56
+ const { mutate: deleteCartLine, isPending: deletePending } =
57
+ useDeleteCartLine();
58
+ const { formatCurrency } = useCurrencyFormatter();
59
+ const handleCartAmountChange = (cartLineChange: CartLine) => () => {
60
+ if (cartLineChange.Quantity === 0) {
61
+ deleteCartLine({
62
+ cartLine: [cartLineChange],
63
+ cartId,
64
+ version: cartVersion,
65
+ });
66
+ } else {
67
+ updateCartLine({
68
+ cartLine: [cartLineChange],
69
+ cartId,
70
+ version: cartVersion,
71
+ });
72
+ }
73
+ };
74
+
75
+ const renderTotalPrice = () => {
76
+ if (cartLine.DiscountLines?.length) {
77
+ return (
78
+ <Span fontWeight={600} color="primary.main">
79
+ <s data-testid={"discount-strikethru"}>
80
+ {formatCurrency(cartLine.NetPrice ?? 0)}
81
+ </s>
82
+ </Span>
83
+ );
84
+ }
85
+
86
+ return (
87
+ <Span fontWeight={600} color="primary.main">
88
+ {formatCurrency(cartLine.NetPrice ?? 0)}
89
+ </Span>
90
+ );
91
+ };
92
+
93
+ const renderDiscountPrice = () => {
94
+ if (cartLine.DiscountLines?.length) {
95
+ return (
96
+ <Span fontWeight={600} color="primary.main">
97
+ {formatCurrency(cartLine.TotalAmount ?? 0)}
98
+ </Span>
99
+ );
100
+ }
101
+ };
102
+
103
+ const shouldDisplaySpinner = () => updatePending || deletePending;
104
+ const qty = cartLine.Quantity || 1;
105
+ return (
106
+ <Wrapper>
107
+ <Box className="img-wrapper">
108
+ <Image
109
+ alt={cartLine.Product?.Name || ""}
110
+ width={150}
111
+ height={150}
112
+ src={cartLine.Product?.PrimaryImageUrl || ""}
113
+ />
114
+ </Box>
115
+
116
+ {/* DELETE BUTTON */}
117
+ <IconButton
118
+ size="small"
119
+ onClick={handleCartAmountChange({ ...cartLine, Quantity: 0 })}
120
+ sx={{ position: "absolute", right: 15, top: 15 }}
121
+ title={t("removeItem")}
122
+ aria-label={t("removeItem")}
123
+ >
124
+ <Close fontSize="small" />
125
+ </IconButton>
126
+
127
+ <FlexBox p={2} rowGap={2} width="100%" flexDirection="column">
128
+ {cartLine.Product && (
129
+ <Link href={getProductDetailUrl(cartLine.Product)}>
130
+ <Span ellipsis fontWeight="600" fontSize={18}>
131
+ {cartLine.Product?.Name}
132
+ </Span>
133
+ </Link>
134
+ )}
135
+ <FlexBox
136
+ width="100%"
137
+ sx={{ flexDirection: { xs: "column", md: "row" } }}
138
+ >
139
+ <FlexBox rowGap={2} width="100%" flexDirection="column">
140
+ <Span ellipsis fontWeight="200" fontSize={14}>
141
+ {cartLine?.Product?.Dimensions?.map((d, i) => (
142
+ <span
143
+ key={i}
144
+ >{`${ProductDimensionType[d.DimensionTypeValue]}: ${
145
+ d?.DimensionValue?.Value
146
+ } ${
147
+ i + 1 !== cartLine?.Product?.Dimensions?.length ? "|" : ""
148
+ } `}</span>
149
+ ))}
150
+ </Span>
151
+ {/* PRODUCT PRICE SECTION */}
152
+ <FlexBox gap={1} flexWrap="wrap" alignItems="center">
153
+ <Span color="grey.600">
154
+ {formatCurrency(cartLine.Price ?? 0)} x {cartLine.Quantity}
155
+ <Tiny>{cartLine.UnitOfMeasureSymbol}</Tiny>
156
+ </Span>
157
+ {renderTotalPrice()}
158
+ {renderDiscountPrice()}
159
+ </FlexBox>
160
+
161
+ <FlexBox gap={1} flexWrap="wrap" alignItems="center">
162
+ {cartLine.DiscountLines?.map((discount, index) => (
163
+ <Span key={index} color="error.main">
164
+ {discount.DiscountCode} - {discount.OfferName}
165
+ </Span>
166
+ ))}
167
+ </FlexBox>
168
+
169
+ {/* PRODUCT QUANTITY INC/DEC BUTTONS */}
170
+ {!shouldDisplaySpinner() ? (
171
+ <FlexBox alignItems="center">
172
+ <Button
173
+ color="primary"
174
+ sx={{ p: "5px" }}
175
+ variant="outlined"
176
+ disabled={qty === 1}
177
+ onClick={handleCartAmountChange({
178
+ ...cartLine,
179
+ Quantity: qty - 1,
180
+ })}
181
+ title={t("decreaseQuantity")}
182
+ aria-label={t("decreaseQuantity")}
183
+ >
184
+ <Remove fontSize="small" />
185
+ </Button>
186
+
187
+ <Span mx={1} fontWeight={600} fontSize={15}>
188
+ {qty}
189
+ </Span>
190
+
191
+ <Button
192
+ color="primary"
193
+ sx={{ p: "5px" }}
194
+ variant="outlined"
195
+ onClick={handleCartAmountChange({
196
+ ...cartLine,
197
+ Quantity: qty + 1,
198
+ })}
199
+ title={t("increaseQuantity")}
200
+ aria-label={t("increaseQuantity")}
201
+ >
202
+ <Add fontSize="small" />
203
+ </Button>
204
+ </FlexBox>
205
+ ) : (
206
+ <CircularProgress size={30} />
207
+ )}
208
+ </FlexBox>
209
+ {/* CART LINE COMMENTS */}
210
+ {showCartLineComments && (
211
+ <FlexBox width="100%" flexDirection="column" mt={1}>
212
+ {isWritingComment ? (
213
+ <>
214
+ <TextField
215
+ label={t("addComment")}
216
+ variant="outlined"
217
+ multiline
218
+ minRows={2}
219
+ maxRows={2}
220
+ fullWidth
221
+ value={comment}
222
+ onChange={(e) => setComment(e.target.value)}
223
+ sx={{ marginBottom: 1 }}
224
+ />
225
+ <FlexBox
226
+ flexDirection="row"
227
+ sx={{ marginTop: "auto", marginLeft: "auto" }}
228
+ >
229
+ <Button
230
+ color="primary"
231
+ variant="outlined"
232
+ sx={{
233
+ width: "100px",
234
+ }}
235
+ onClick={() => {
236
+ setIsWritingComment(false);
237
+ setComment(cartLine.Comment || "");
238
+ }}
239
+ >
240
+ {t("cancelComment")}
241
+ </Button>
242
+ <Button
243
+ color="primary"
244
+ variant="contained"
245
+ sx={{
246
+ width: "150px",
247
+ marginLeft: 1,
248
+ }}
249
+ onClick={() => {
250
+ handleCartAmountChange({
251
+ ...cartLine,
252
+ Comment: comment,
253
+ })();
254
+ setIsWritingComment(false);
255
+ }}
256
+ >
257
+ {t("saveComment")}
258
+ </Button>
259
+ </FlexBox>
260
+ </>
261
+ ) : shouldDisplaySpinner() ? (
262
+ <CircularProgress
263
+ size={30}
264
+ sx={{ marginTop: "auto", marginLeft: "auto" }}
265
+ />
266
+ ) : (
267
+ <>
268
+ <Typography
269
+ sx={{
270
+ marginBottom: 1,
271
+ marginTop: "auto",
272
+ // marginLeft: "auto",
273
+ maxWidth: {
274
+ xs: "250px",
275
+ lg: "350px",
276
+ },
277
+ whiteSpace: "nowrap",
278
+ overflow: "hidden",
279
+ textOverflow: "ellipsis",
280
+ }}
281
+ title={cartLine.Comment || ""}
282
+ >
283
+ {cartLine.Comment ? (
284
+ <>
285
+ <H6 sx={{ display: "inline" }}>
286
+ {t("commentLabel")}:{" "}
287
+ </H6>
288
+ {cartLine.Comment}
289
+ </>
290
+ ) : null}
291
+ </Typography>
292
+ <Button
293
+ color="primary"
294
+ variant="outlined"
295
+ sx={{
296
+ width: "150px",
297
+ marginTop: "auto",
298
+ marginLeft: "auto",
299
+ }}
300
+ onClick={() => setIsWritingComment(true)}
301
+ >
302
+ {t("addComment")}
303
+ </Button>
304
+ </>
305
+ )}
306
+ </FlexBox>
307
+ )}
308
+ </FlexBox>
309
+ </FlexBox>
310
+ </Wrapper>
311
+ );
312
+ }