@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,88 @@
1
+ import React from "react";
2
+ import { FlexBox } from "@/components/_components/flex-box";
3
+ import Button from "@mui/material/Button";
4
+ import { H3 } from "@/components/_components/Typography";
5
+ import { MdAdd as Add } from "react-icons/md";
6
+ import { MdRemove as Remove } from "react-icons/md";
7
+ import CircularProgress from "@mui/material/CircularProgress";
8
+ import { CartLine } from "@msdyn365-commerce/retail-proxy";
9
+ import { useTranslations } from "next-intl";
10
+
11
+ interface Props {
12
+ loading: boolean;
13
+ productCartLine: CartLine | undefined;
14
+ handleCartAmountChange: (amount: number, currentCartLine: CartLine) => void;
15
+ handleAddToCart: () => void;
16
+ isOOS: boolean;
17
+ }
18
+
19
+ export default function QuantityButtons({
20
+ loading,
21
+ handleAddToCart,
22
+ handleCartAmountChange,
23
+ productCartLine,
24
+ isOOS,
25
+ }: Props) {
26
+ const t = useTranslations("productDetail");
27
+
28
+ if (!productCartLine?.Quantity || productCartLine.Quantity === 0) {
29
+ return (
30
+ <Button
31
+ color="primary"
32
+ variant="contained"
33
+ onClick={handleAddToCart}
34
+ disabled={isOOS}
35
+ >
36
+ {t("btnAddToCart")}
37
+ </Button>
38
+ );
39
+ }
40
+
41
+ return (
42
+ <FlexBox alignItems="center">
43
+ <Button
44
+ size="small"
45
+ sx={{ p: 1 }}
46
+ color="primary"
47
+ variant="outlined"
48
+ disabled={loading}
49
+ onClick={() =>
50
+ handleCartAmountChange(
51
+ (productCartLine?.Quantity || 0) - 1,
52
+ productCartLine,
53
+ )
54
+ }
55
+ >
56
+ <Remove fontSize="small" />
57
+ </Button>
58
+
59
+ <H3
60
+ fontWeight="600"
61
+ mx={2.5}
62
+ sx={{ width: "20px", display: "flex", justifyContent: "center" }}
63
+ >
64
+ {loading ? (
65
+ <CircularProgress size={"10px"} />
66
+ ) : (
67
+ productCartLine.Quantity.toString().padStart(2, "0")
68
+ )}
69
+ </H3>
70
+
71
+ <Button
72
+ size="small"
73
+ sx={{ p: 1 }}
74
+ color="primary"
75
+ variant="outlined"
76
+ disabled={loading}
77
+ onClick={() =>
78
+ handleCartAmountChange(
79
+ (productCartLine?.Quantity || 0) + 1,
80
+ productCartLine,
81
+ )
82
+ }
83
+ >
84
+ <Add fontSize="small" />
85
+ </Button>
86
+ </FlexBox>
87
+ );
88
+ }
@@ -8,7 +8,7 @@ import ArrowRight from "@mui/icons-material/ArrowRight";
8
8
  // LOCAL CUSTOM COMPONENTS
9
9
  import { H2 } from "../Typography";
10
10
  import { FlexBetween, FlexBox } from "../flex-box/index";
11
- import useSettings from "../hooks/useSettings";
11
+ import useSettings from "../../../hooks/use-settings";
12
12
 
13
13
  // ===================================================
14
14
  interface Props {
@@ -15,7 +15,7 @@ import { H6 } from "@/components/_components/Typography";
15
15
  import Scrollbar from "@/components/_components/scrollbar/scrollbar";
16
16
  import FlexBox from "@/components/_components/flex-box/flex-box";
17
17
  // GLOBAL CUSTOM HOOK
18
- import useSettings from "@/components/_components/hooks/useSettings";
18
+ import useSettings from "@/hooks/use-settings";
19
19
  // STYLED COMPONENTS
20
20
  import {
21
21
  BodyWrapper,
@@ -0,0 +1,10 @@
1
+ # Wish List Component Dedupe
2
+
3
+ Found these components inside of the /app directory in storefront,
4
+ seems like someone created duplicates of components that were already
5
+ inside of our client packages wishlist folder.
6
+
7
+ Todo: Audit two folders and amalgamate duplicate code.
8
+
9
+ Date: 2025-07-10
10
+ By: Sam Forderer
@@ -0,0 +1,68 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { Button, ButtonProps } from "@mui/material";
5
+ import T from "@/components/T";
6
+ import AddToWishlistDialog from "./add-to-wishlist-dialog";
7
+ import { ProductDetails } from "@/api-manager/schemas/product.schema";
8
+ import { Cart } from "@/api-manager/schemas/cart.schema";
9
+
10
+ interface Props {
11
+ path: string;
12
+ product?: ProductDetails;
13
+ cart?: Cart;
14
+ variant?: string;
15
+ }
16
+
17
+ const AddToWishlistButton = (props: Props) => {
18
+ const [openAddListDialog, setOpenAddListDialog] = useState(false);
19
+
20
+ const handleCloseDialog = () => {
21
+ setOpenAddListDialog(false);
22
+ };
23
+
24
+ const handleCreateNewList = () => {
25
+ setOpenAddListDialog(true);
26
+ };
27
+
28
+ // conditionally render button styles based on variant (pdp or minicart)
29
+ let style, btnFullwidth;
30
+ let btnColor: ButtonProps["color"];
31
+ let btnVariant: ButtonProps["variant"];
32
+ switch (props.variant) {
33
+ case "minicart":
34
+ style = {
35
+ px: 4,
36
+ };
37
+ btnFullwidth = true;
38
+ btnVariant = "outlined";
39
+ btnColor = "primary";
40
+ break;
41
+ default:
42
+ btnFullwidth = false;
43
+ btnVariant = "contained";
44
+ btnColor = "secondary";
45
+ break;
46
+ }
47
+
48
+ return (
49
+ <>
50
+ <Button
51
+ onClick={handleCreateNewList}
52
+ sx={style}
53
+ fullWidth={btnFullwidth}
54
+ color={btnColor}
55
+ >
56
+ <T path={props.path} />
57
+ </Button>
58
+ <AddToWishlistDialog
59
+ open={openAddListDialog}
60
+ handleClose={handleCloseDialog}
61
+ productId={props.product?.RecordId}
62
+ cart={props.cart}
63
+ />
64
+ </>
65
+ );
66
+ };
67
+
68
+ export default AddToWishlistButton;
@@ -0,0 +1,161 @@
1
+ import React, { Fragment } from "react";
2
+ import isFunction from "lodash/isFunction";
3
+ import { useTranslations } from "next-intl";
4
+ import { Grid, Button, Dialog, DialogContent } from "@mui/material";
5
+ import { MdFavorite } from "react-icons/md";
6
+ // Global Custom components
7
+ import DashboardHeader from "../../../pages/account/dashboard-header";
8
+ import CreateWishlistButton from "../../../pages/account/wishlist/create-wishlist-button";
9
+ import WishlistModalItem from "./wishlist-modal-item";
10
+ import NoRecords from "../../../pages/account/no-records";
11
+ // API
12
+ import {
13
+ CopyCartToWishlistInput,
14
+ Wishlist,
15
+ WishlistLinesInput,
16
+ } from "@/api-manager/schemas/user.schema";
17
+ import { Cart } from "@/api-manager/schemas/cart.schema";
18
+ import useAddToWishlist from "@/api-manager/services/user/mutations/add-to-wishlist";
19
+ import { useNotification } from "@/components/Notifications/use-notification";
20
+ import useCopyCartToWishlist from "@/api-manager/services/user/mutations/copy-cart-to-wishlist";
21
+ import { useSession } from "next-auth/react";
22
+ import getWishlists from "@/api-manager/services/user/queries/get-wishlists";
23
+
24
+ interface Props {
25
+ open: boolean;
26
+ handleClose: () => void;
27
+ productId?: number;
28
+ cart?: Cart;
29
+ onSuccess?: () => void;
30
+ }
31
+
32
+ export default function AddToWishlistDialog(props: Props) {
33
+ const { data: wishlists, isLoading } = getWishlists.useData();
34
+
35
+ const t = useTranslations("Account.Wishlists");
36
+ const { setNotification } = useNotification();
37
+
38
+ const { mutateAsync: addToWishlist } = useAddToWishlist();
39
+ const { mutateAsync: copyCartToWishlist } = useCopyCartToWishlist();
40
+
41
+ const { data: session } = useSession();
42
+ const isAuthenticated = !!session;
43
+
44
+ const handleClose = () => {
45
+ if (isFunction(props.handleClose)) {
46
+ props.handleClose();
47
+ }
48
+ };
49
+
50
+ const handleAddToWishlist = async (wishlistId: string) => {
51
+ if (props.productId) {
52
+ try {
53
+ const wishlistLinesInput: WishlistLinesInput = {
54
+ Id: wishlistId,
55
+ ProductListLines: [],
56
+ };
57
+
58
+ wishlistLinesInput.ProductListLines.push({
59
+ ProductListId: wishlistId,
60
+ ProductId: props.productId,
61
+ Quantity: 1,
62
+ UnitOfMeasure: "ea",
63
+ });
64
+ await addToWishlist(wishlistLinesInput);
65
+ setNotification({
66
+ message: t("wishlistLineAddSuccess"),
67
+ severity: "success",
68
+ });
69
+ if (isFunction(props.onSuccess)) {
70
+ props.onSuccess();
71
+ }
72
+ } catch (error) {
73
+ setNotification({
74
+ message: t("wishlistLineAddFailed"),
75
+ severity: "error",
76
+ });
77
+ }
78
+ } else if (props.cart) {
79
+ try {
80
+ const copyCartToWishlistInput: CopyCartToWishlistInput = {
81
+ Id: wishlistId,
82
+ CartID: props.cart.Id,
83
+ IsRewrite: false,
84
+ IsQuantityAggregate: true,
85
+ };
86
+
87
+ await copyCartToWishlist(copyCartToWishlistInput);
88
+ setNotification({
89
+ message: t("wishlistCartCopySuccess"),
90
+ severity: "success",
91
+ });
92
+ } catch (error) {
93
+ setNotification({
94
+ message: t("wishlistCartCopyFailed"),
95
+ severity: "error",
96
+ });
97
+ }
98
+ }
99
+ };
100
+
101
+ return (
102
+ <Dialog
103
+ open={props.open}
104
+ onClose={handleClose}
105
+ aria-labelledby="form-dialog-title"
106
+ >
107
+ <DialogContent>
108
+ <Fragment>
109
+ <DashboardHeader
110
+ title="Account.Wishlists.heading"
111
+ Icon={MdFavorite}
112
+ href=""
113
+ buttonText=""
114
+ buttons={
115
+ isAuthenticated && (
116
+ <CreateWishlistButton path="Account.Wishlists.btnCreateNewList" />
117
+ )
118
+ }
119
+ />
120
+ {isAuthenticated && !isLoading ? (
121
+ wishlists && wishlists.length > 0 ? (
122
+ wishlists?.map((wishlist: Wishlist) => (
123
+ <WishlistModalItem
124
+ key={wishlist.Id}
125
+ wishlist={wishlist}
126
+ addToWishlist={handleAddToWishlist}
127
+ />
128
+ ))
129
+ ) : (
130
+ <NoRecords message="Account.Wishlists.noWishlists" />
131
+ )
132
+ ) : (
133
+ <NoRecords message="Account.Wishlists.wishlistAddUnauthenticated" />
134
+ )}
135
+ </Fragment>
136
+ <Grid container spacing={3}>
137
+ <Grid
138
+ item
139
+ sm={12}
140
+ xs={12}
141
+ sx={{
142
+ display: "flex",
143
+ justifyContent: "flex-start",
144
+ gap: 2,
145
+ }}
146
+ >
147
+ <Button
148
+ color="primary"
149
+ variant="outlined"
150
+ size="small"
151
+ onClick={handleClose}
152
+ sx={{ height: 30.75 }}
153
+ >
154
+ {t("btnCancel")}
155
+ </Button>
156
+ </Grid>
157
+ </Grid>
158
+ </DialogContent>
159
+ </Dialog>
160
+ );
161
+ }
@@ -0,0 +1,174 @@
1
+ import React, { Fragment, useCallback, useEffect } from "react";
2
+ import isFunction from "lodash/isFunction";
3
+ import { useTranslations } from "next-intl";
4
+ import { Grid, Button, Dialog, DialogContent } from "@mui/material";
5
+ import { MdFavorite } from "react-icons/md";
6
+ // Global Custom components
7
+ import DashboardHeader from "../../../pages/account/dashboard-header";
8
+ import WishlistModalItem from "./wishlist-modal-item";
9
+ import NoRecords from "../../../pages/account/no-records";
10
+ // API
11
+ import getWishlists from "@/api-manager/services/user/queries/get-wishlists";
12
+ import {
13
+ ExtendedProductListLine,
14
+ Wishlist,
15
+ WishlistLinesInput,
16
+ } from "@evenicanpm/storefront-core/src/api-manager/schemas/user.schema";
17
+ import { useNotification } from "@evenicanpm/storefront-core/src/components/Notifications/use-notification";
18
+ import { useSession } from "next-auth/react";
19
+ import useRemoveFromWishlist from "@/api-manager/services/user/mutations/remove-from-wishlist";
20
+
21
+ interface Props {
22
+ open: boolean;
23
+ // product?: ProductDetails;
24
+ productId?: number;
25
+ onSuccess?: () => void;
26
+ handleClose: () => void;
27
+ }
28
+
29
+ export default function RemoveFromWishlistDialog({
30
+ open,
31
+ productId,
32
+ handleClose,
33
+ onSuccess,
34
+ }: Props) {
35
+ const { data: wishlists, isLoading } = getWishlists.useSuspenseData();
36
+ const t = useTranslations("Account.Wishlists");
37
+ const { setNotification } = useNotification();
38
+
39
+ const { mutateAsync: removeFromWishlist } = useRemoveFromWishlist();
40
+
41
+ const { data: session } = useSession();
42
+ const isAuthenticated = !!session;
43
+
44
+ const [relevantWishlists, setRelevantWishlists] = React.useState<Wishlist[]>(
45
+ [],
46
+ );
47
+
48
+ const handleCloseDialog = () => {
49
+ if (isFunction(handleClose)) {
50
+ handleClose();
51
+ }
52
+ };
53
+
54
+ // get wishlists that the product is in
55
+ const getRelevantWishlists = useCallback(
56
+ (wishlists: Wishlist[]) => {
57
+ return wishlists.filter((wishlist) =>
58
+ wishlist.ProductListLines?.some((line) => line.ProductId === productId),
59
+ );
60
+ },
61
+ [productId],
62
+ );
63
+
64
+ const handleRemoveFromWishlist = useCallback(
65
+ async (wishlistId: string) => {
66
+ // get the selected wishlist
67
+ const wishlist = relevantWishlists.find(
68
+ (wishlist) => wishlist.Id === wishlistId,
69
+ );
70
+ if (wishlist) {
71
+ try {
72
+ const extendedWishlistLine = wishlist.ProductListLines?.find(
73
+ (item: ExtendedProductListLine) => item.ProductId === productId,
74
+ );
75
+
76
+ if (extendedWishlistLine) {
77
+ const wishlistLinesInput: WishlistLinesInput = {
78
+ Id: wishlist.Id,
79
+ ProductListLines: [extendedWishlistLine],
80
+ };
81
+
82
+ await removeFromWishlist(wishlistLinesInput);
83
+
84
+ setNotification({
85
+ message: t("wishlistLineRemoveSuccess"),
86
+ severity: "success",
87
+ });
88
+ if (isFunction(onSuccess)) {
89
+ onSuccess();
90
+ }
91
+ }
92
+ } catch (error) {
93
+ setNotification({
94
+ message: t("wishlistLineRemoveFailed"),
95
+ severity: "error",
96
+ });
97
+ }
98
+ }
99
+ },
100
+ [
101
+ relevantWishlists,
102
+ removeFromWishlist,
103
+ setNotification,
104
+ t,
105
+ onSuccess,
106
+ productId,
107
+ ],
108
+ );
109
+
110
+ useEffect(() => {
111
+ const withlistsWithProduct = getRelevantWishlists(wishlists);
112
+ setRelevantWishlists(withlistsWithProduct);
113
+ if (open && withlistsWithProduct.length === 1) {
114
+ // delete right away
115
+ handleRemoveFromWishlist(withlistsWithProduct[0].Id);
116
+ }
117
+ }, [open, getRelevantWishlists, handleRemoveFromWishlist, wishlists]);
118
+
119
+ return (
120
+ <Dialog
121
+ open={open}
122
+ onClose={handleCloseDialog}
123
+ aria-labelledby="form-dialog-title"
124
+ >
125
+ <DialogContent>
126
+ <Fragment>
127
+ <DashboardHeader
128
+ title="Account.Wishlists.heading"
129
+ Icon={MdFavorite}
130
+ href=""
131
+ buttonText=""
132
+ />
133
+ {isAuthenticated && !isLoading ? (
134
+ relevantWishlists && relevantWishlists.length > 0 ? (
135
+ relevantWishlists?.map((wishlist: Wishlist) => (
136
+ <WishlistModalItem
137
+ key={wishlist.Id}
138
+ wishlist={wishlist}
139
+ removeFromWishlist={handleRemoveFromWishlist}
140
+ />
141
+ ))
142
+ ) : (
143
+ <NoRecords message="Account.Wishlists.noWishlists" />
144
+ )
145
+ ) : (
146
+ <NoRecords message="Account.Wishlists.wishlistAddUnauthenticated" />
147
+ )}
148
+ </Fragment>
149
+ <Grid container spacing={3}>
150
+ <Grid
151
+ item
152
+ sm={12}
153
+ xs={12}
154
+ sx={{
155
+ display: "flex",
156
+ justifyContent: "flex-start",
157
+ gap: 2,
158
+ }}
159
+ >
160
+ <Button
161
+ color="primary"
162
+ variant="outlined"
163
+ size="small"
164
+ onClick={handleCloseDialog}
165
+ sx={{ height: 30.75 }}
166
+ >
167
+ {t("btnCancel")}
168
+ </Button>
169
+ </Grid>
170
+ </Grid>
171
+ </DialogContent>
172
+ </Dialog>
173
+ );
174
+ }
@@ -0,0 +1,65 @@
1
+ import { Button } from "@mui/material";
2
+ import { format } from "date-fns";
3
+ // MUI ICON COMPONENTS
4
+ // GLOBAL CUSTOM COMPONENTS
5
+ import { Paragraph } from "@/components/_components/Typography";
6
+ // LOCAL CUSTOM COMPONENT
7
+ import TableRow from "../../../pages/account/table-row";
8
+ // API
9
+ import { Wishlist } from "@/api-manager/schemas/user.schema";
10
+
11
+ interface Props {
12
+ wishlist: Wishlist;
13
+ addToWishlist?: (wishlistId: string) => void;
14
+ removeFromWishlist?: (wishlistId: string) => void;
15
+ }
16
+
17
+ export default function WishlistModalItem({
18
+ wishlist,
19
+ addToWishlist,
20
+ removeFromWishlist,
21
+ }: Props) {
22
+ const handleClick = () => {
23
+ if (addToWishlist) {
24
+ addToWishlist(wishlist.Id);
25
+ } else if (removeFromWishlist) {
26
+ removeFromWishlist(wishlist.Id);
27
+ }
28
+ };
29
+ return (
30
+ <TableRow
31
+ sx={{
32
+ display: "grid",
33
+ gridTemplateColumns: "2fr 1fr 1fr 1fr", // Define equal column structure
34
+ alignItems: "center",
35
+ gap: 2,
36
+ padding: "10px 15px",
37
+ width: "500px",
38
+ cursor: "default",
39
+ }}
40
+ >
41
+ <Paragraph ellipsis sx={{ fontWeight: "bold" }}>
42
+ {wishlist?.Name ?? ""}
43
+ </Paragraph>
44
+ <Paragraph ellipsis>
45
+ {" "}
46
+ {format(
47
+ new Date(wishlist?.CreatedDateTime ?? wishlist?.LastUpdated ?? ""),
48
+ "MMM dd, yyyy",
49
+ )}
50
+ </Paragraph>
51
+ <Paragraph ellipsis>
52
+ {" "}
53
+ {`${wishlist?.ProductListLines?.length} products`}
54
+ </Paragraph>
55
+
56
+ <Button
57
+ onClick={handleClick}
58
+ color="primary"
59
+ sx={{ bgcolor: "primary.light", px: 4 }}
60
+ >
61
+ {addToWishlist ? "Add" : "Remove"}
62
+ </Button>
63
+ </TableRow>
64
+ );
65
+ }
@@ -45,6 +45,7 @@ export default function CountryInput({
45
45
  size="small"
46
46
  label={label}
47
47
  variant="outlined"
48
+ data-testid="country-input"
48
49
  placeholder="Select Country"
49
50
  />
50
51
  );
@@ -0,0 +1,82 @@
1
+ import useDeleteCartLine from "@/api-manager/services/cart/mutations/delete-cart-line";
2
+ import useAddToCart from "@/api-manager/services/cart/mutations/add-to-cart";
3
+ import useUpdateCartLines from "@/api-manager/services/cart/mutations/update-cart-lines";
4
+ import { Cart } from "@/api-manager/schemas/cart.schema";
5
+ import { CartLine } from "@msdyn365-commerce/retail-proxy";
6
+
7
+ type UseCartProps = {
8
+ cart: Cart;
9
+ selectVariantId: number | undefined;
10
+ recordId: number;
11
+ };
12
+
13
+ export type AddToCartBopisInput = {
14
+ deliveryMode: string;
15
+ warehouseId: string;
16
+ };
17
+
18
+ export default function useCart({
19
+ cart,
20
+ selectVariantId,
21
+ recordId,
22
+ }: UseCartProps) {
23
+ const { mutate: deleteCartLine, isPending: deletePending } =
24
+ useDeleteCartLine();
25
+ const { mutate: updateCartLine, isPending: updatePending } =
26
+ useUpdateCartLines();
27
+ const { mutate: addToCart, isPending: addPending } = useAddToCart();
28
+
29
+ const loading = deletePending || updatePending || addPending;
30
+
31
+ const handleCartAmountChange = (
32
+ amount: number,
33
+ currentCartLine?: CartLine,
34
+ ) => {
35
+ if (amount === 0) {
36
+ deleteCartLine({
37
+ cartId: cart.Id,
38
+ version: cart.Version,
39
+ cartLine: [
40
+ {
41
+ LineId: currentCartLine?.LineId,
42
+ ProductId: selectVariantId,
43
+ },
44
+ ],
45
+ });
46
+ return;
47
+ }
48
+
49
+ updateCartLine({
50
+ cartId: cart.Id,
51
+ version: cart.Version,
52
+ cartLine: [
53
+ {
54
+ LineId: currentCartLine?.LineId,
55
+ Quantity: amount,
56
+ ProductId: selectVariantId,
57
+ },
58
+ ],
59
+ });
60
+ };
61
+
62
+ const handleAddToCart = (bopisInput?: AddToCartBopisInput) => {
63
+ addToCart({
64
+ cartId: cart.Id,
65
+ version: cart.Version,
66
+ cartLine: [
67
+ {
68
+ Quantity: 1,
69
+ ProductId: selectVariantId || recordId,
70
+ WarehouseId: bopisInput?.warehouseId || "",
71
+ DeliveryMode: bopisInput?.deliveryMode || "",
72
+ },
73
+ ],
74
+ });
75
+ };
76
+
77
+ return {
78
+ loading,
79
+ handleCartAmountChange,
80
+ handleAddToCart,
81
+ };
82
+ }