@evenicanpm/storefront-core 1.0.0-alpha → 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 (196) hide show
  1. package/package.json +3 -2
  2. package/src/api-manager/api-manager.config.json +1 -1
  3. package/src/api-manager/datasources/d365/d365-address.datasource.ts +4 -4
  4. package/src/api-manager/datasources/d365/d365-cart.datasource.ts +100 -7
  5. package/src/api-manager/datasources/d365/d365-product.datasource.ts +39 -17
  6. package/src/api-manager/datasources/d365/d365-user.datasource.ts +220 -68
  7. package/src/api-manager/datasources/d365/utils/decode-jwt.ts +2 -2
  8. package/src/api-manager/datasources/e4/e4-cart.datasource.ts +6 -0
  9. package/src/api-manager/datasources/e4/e4-product.datasource.ts +22 -2
  10. package/src/api-manager/datasources/e4/e4.datasource.ts +1 -1
  11. package/src/api-manager/datasources/e4/e4.remaps.ts +24 -0
  12. package/src/api-manager/datasources/e4/e4.translator.ts +19 -1
  13. package/src/api-manager/datasources/e4/{e4-user.datasource.ts → user/e4-user.datasource.ts} +41 -19
  14. package/src/api-manager/datasources/e4/user/e4-user.remaps.ts +4 -0
  15. package/src/api-manager/datasources/e4/user/e4-user.translator.ts +10 -0
  16. package/src/api-manager/index.ts +77 -15
  17. package/src/api-manager/lib/get-graphql-client.ts +7 -7
  18. package/src/api-manager/schemas/address.schema.ts +1 -19
  19. package/src/api-manager/schemas/cart.schema.ts +18 -0
  20. package/src/api-manager/schemas/product.schema.ts +8 -1
  21. package/src/api-manager/schemas/user.schema.ts +52 -1
  22. package/src/api-manager/services/address/queries/get-states.ts +1 -1
  23. package/src/api-manager/services/cart/mutations/checkout.ts +16 -1
  24. package/src/api-manager/services/cart/queries/get-cart-lines-inventory.ts +15 -0
  25. package/src/api-manager/services/create-extension-query.ts +26 -0
  26. package/src/api-manager/services/create-mutation.ts +5 -1
  27. package/src/api-manager/services/create-query.ts +10 -4
  28. package/src/api-manager/services/organization/queries/get-channel-configuration.ts +1 -1
  29. package/src/api-manager/services/product/queries/get-product-prices.ts +16 -0
  30. package/src/api-manager/services/product/queries/search-products.ts +1 -0
  31. package/src/api-manager/services/user/mutations/add-to-wishlist.ts +24 -0
  32. package/src/api-manager/services/user/mutations/copy-cart-to-wishlist.ts +23 -0
  33. package/src/api-manager/services/user/mutations/create-wishlist.ts +4 -4
  34. package/src/api-manager/services/user/mutations/delete-wishlist.ts +4 -3
  35. package/src/api-manager/services/user/mutations/remove-from-wishlist.ts +21 -0
  36. package/src/api-manager/services/user/mutations/update-wishlist.ts +23 -0
  37. package/src/api-manager/services/user/queries/get-wishlist-details.ts +11 -4
  38. package/src/api-manager/services/user/queries/get-wishlists.ts +4 -3
  39. package/src/api-manager/types/Datasource.ts +42 -14
  40. package/src/auth/auth-options.ts +2 -2
  41. package/src/auth/msal.ts +9 -9
  42. package/src/auth/next-auth-cookie-manager.ts +5 -3
  43. package/src/auth/providers/aadb2c-provider.ts +3 -4
  44. package/src/cms/endpoints.ts +2 -3
  45. package/src/components/Blocks/Components/ProductCarousel/index.tsx +6 -6
  46. package/src/components/_components/LazyImage.tsx +5 -2
  47. package/src/components/_components/header/components/user.tsx +1 -2
  48. package/src/components/_components/navbar/category-based-menu/components/categories.tsx +1 -1
  49. package/src/components/_components/navbar/mega-menu/mega-menu.tsx +1 -1
  50. package/src/components/_components/navbar/nav-list/nav-item-child.tsx +2 -2
  51. package/src/components/_components/product-cards/product-card-1/components/quantity-buttons.tsx +17 -8
  52. package/src/components/_components/product-cards/product-card-1/product-card-1-wrapper.tsx +304 -0
  53. package/src/components/_components/product-cards/product-card-1/product-card.tsx +16 -55
  54. package/src/components/_components/product-cards/product-card-8/product-card.tsx +80 -60
  55. package/src/components/_components/product-cards/product-card-8/styles/index.ts +10 -0
  56. package/src/components/_components/product-cards/product-card-wishlist/components/hover-actions.tsx +37 -0
  57. package/src/components/_components/product-cards/product-card-wishlist/index.ts +1 -0
  58. package/src/components/_components/product-cards/product-card-wishlist/product-card.tsx +118 -0
  59. package/src/components/_components/product-cards/product-card-wishlist/styles/index.ts +51 -0
  60. package/src/components/_components/product-dimensions/product-dimensions.tsx +114 -0
  61. package/src/components/_components/product-quantity-buttons/product-quantity-buttons.tsx +88 -0
  62. package/src/components/_components/products-view/product-view-dialog.tsx +48 -10
  63. package/src/components/_components/products-view/products-grid-view.tsx +31 -17
  64. package/src/components/_components/products-view/products-list-view.tsx +30 -16
  65. package/src/components/_components/search-box/__test__/search-input-category.test.tsx +110 -0
  66. package/src/components/_components/search-box/__test__/search-input.test.tsx +137 -0
  67. package/src/components/_components/search-box/__test__/search-results.json +165 -0
  68. package/src/components/_components/search-box/components/search-result.tsx +66 -12
  69. package/src/components/_components/search-box/search-input-category.tsx +135 -32
  70. package/src/components/_components/search-box/styles/index.ts +2 -0
  71. package/src/components/_components/section-header/section-creator.tsx +1 -0
  72. package/src/components/_components/section-header/section-header.tsx +1 -1
  73. package/src/components/_components/settings/setting.tsx +1 -1
  74. package/src/components/_components/wishlist/add-to-wishlist-dialog.tsx +161 -0
  75. package/src/components/_components/wishlist/components/create-new-list.tsx +167 -0
  76. package/src/components/_components/wishlist/components/create-wishlist-button.tsx +40 -0
  77. package/src/components/_components/wishlist/components/dashboard-header.tsx +123 -0
  78. package/src/components/_components/wishlist/components/navigation.tsx +87 -0
  79. package/src/components/_components/wishlist/components/no-records.tsx +20 -0
  80. package/src/components/_components/wishlist/components/wishlist-modal-item.tsx +66 -0
  81. package/src/components/_components/wishlist/remove-from-wishlist-dialog.tsx +174 -0
  82. package/src/components/_components/wishlist/styles.ts +61 -0
  83. package/src/components/_components/wishlist-modal/TODO.md +10 -0
  84. package/src/components/_components/wishlist-modal/add-to-wishlist-button.tsx +68 -0
  85. package/src/components/_components/wishlist-modal/add-to-wishlist-dialog.tsx +161 -0
  86. package/src/components/_components/wishlist-modal/remove-from-wishlist-dialog.tsx +174 -0
  87. package/src/components/_components/wishlist-modal/wishlist-modal-item.tsx +65 -0
  88. package/src/components/countries-input.tsx +7 -12
  89. package/src/hooks/use-cart.tsx +82 -0
  90. package/src/hooks/use-variants.tsx +85 -0
  91. package/src/lib/cart-cookie-handler-constants.ts +8 -0
  92. package/src/lib/cart-cookie-handler.ts +27 -5
  93. package/src/lib/create-graphql-client.ts +1 -2
  94. package/src/lib/product-list-types.ts +41 -0
  95. package/src/pages/account/addresses/address-form.tsx +289 -0
  96. package/src/pages/account/addresses/address-item.tsx +104 -0
  97. package/src/pages/account/dashboard-header.tsx +123 -0
  98. package/src/pages/account/navigation.tsx +84 -0
  99. package/src/pages/account/no-records.tsx +20 -0
  100. package/src/pages/account/orders/icons/delivery.tsx +14 -0
  101. package/src/pages/account/orders/icons/package-box.tsx +13 -0
  102. package/src/pages/account/orders/icons/truck-filled.tsx +14 -0
  103. package/src/pages/account/orders/order-progress.tsx +111 -0
  104. package/src/pages/account/orders/order-row.tsx +61 -0
  105. package/src/pages/account/orders/order-summary.tsx +83 -0
  106. package/src/pages/account/orders/ordered-products.tsx +114 -0
  107. package/src/pages/account/profile/profile-button.test.tsx +59 -0
  108. package/src/pages/account/profile/profile-button.tsx +50 -0
  109. package/src/pages/account/profile/user-info.tsx +59 -0
  110. package/src/pages/account/styles.ts +32 -0
  111. package/src/pages/account/table-row.tsx +19 -0
  112. package/src/pages/account/wishlist/create-new-list.tsx +167 -0
  113. package/src/pages/account/wishlist/create-wishlist-button.tsx +40 -0
  114. package/src/pages/account/wishlist/wishlist-item.tsx +82 -0
  115. package/src/pages/blog/pagination.tsx +38 -0
  116. package/src/pages/cart/cart-item.tsx +312 -0
  117. package/src/pages/cart/checkout-form.tsx +122 -0
  118. package/src/pages/cart/coupon-entry.tsx +90 -0
  119. package/src/pages/cart/estimate-shipping.tsx +183 -0
  120. package/src/pages/cart/wrapper.tsx +30 -0
  121. package/src/pages/checkout/checkout-alt-form/address-card.tsx +73 -0
  122. package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +133 -0
  123. package/src/pages/checkout/checkout-alt-form/checkout-step.tsx +120 -0
  124. package/src/pages/checkout/checkout-alt-form/customer-information.tsx +94 -0
  125. package/src/pages/checkout/checkout-alt-form/delivery-address.tsx +170 -0
  126. package/src/pages/checkout/checkout-alt-form/delivery-date.tsx +114 -0
  127. package/src/pages/checkout/checkout-alt-form/delivery-options.tsx +186 -0
  128. package/src/pages/checkout/checkout-alt-form/edit-address-form.tsx +130 -0
  129. package/src/pages/checkout/checkout-alt-form/heading.tsx +50 -0
  130. package/src/pages/checkout/checkout-alt-form/index.ts +1 -0
  131. package/src/pages/checkout/checkout-alt-form/new-address-form.tsx +294 -0
  132. package/src/pages/checkout/checkout-alt-form/node.tsx +9 -0
  133. package/src/pages/checkout/checkout-alt-form/payment-details.tsx +344 -0
  134. package/src/pages/checkout/checkout-alt-form/safe-card-preview-data.tsx +36 -0
  135. package/src/pages/checkout/checkout-alt-form/types.ts +20 -0
  136. package/src/pages/checkout/checkout-alt-summary/cart-item.tsx +39 -0
  137. package/src/pages/checkout/checkout-alt-summary/checkout-alt-summary.tsx +40 -0
  138. package/src/pages/checkout/checkout-alt-summary/index.ts +1 -0
  139. package/src/pages/checkout/checkout-alt-summary/list-item.tsx +31 -0
  140. package/src/pages/confirmation/address.tsx +22 -0
  141. package/src/pages/confirmation/confirmation-summary.tsx +52 -0
  142. package/src/pages/confirmation/ordered-products.tsx +108 -0
  143. package/src/pages/product-details/available-shops.tsx +48 -0
  144. package/src/pages/product-details/bopis/find-in-store-button.tsx +63 -0
  145. package/src/pages/product-details/bopis/find-in-store-modal.tsx +257 -0
  146. package/src/pages/product-details/bopis/pickup-option-select.tsx +127 -0
  147. package/src/pages/product-details/bopis/search-header.tsx +69 -0
  148. package/src/pages/product-details/checkbox-label.tsx +20 -0
  149. package/src/pages/product-details/currency.ts +64 -0
  150. package/src/pages/product-details/frequently-bought.tsx +90 -0
  151. package/src/pages/product-details/frequently-product-card.tsx +60 -0
  152. package/src/pages/product-details/product-comment.tsx +44 -0
  153. package/src/pages/product-details/product-description.tsx +22 -0
  154. package/src/pages/product-details/product-filter-card.tsx +257 -0
  155. package/src/pages/product-details/product-intro/product-intro-images.tsx +87 -0
  156. package/src/pages/product-details/product-intro/product-intro.tsx +250 -0
  157. package/src/pages/product-details/product-review.tsx +131 -0
  158. package/src/pages/product-details/product-tabs.tsx +51 -0
  159. package/src/pages/product-details/related-products.tsx +42 -0
  160. package/src/pages/product-details/types.ts +11 -0
  161. package/src/pages/product-list/breadcrumbs.tsx +39 -0
  162. package/src/pages/product-list/checkbox-label.tsx +20 -0
  163. package/src/pages/product-list/facet-group.tsx +125 -0
  164. package/src/pages/product-list/facet.tsx +36 -0
  165. package/src/pages/product-list/list-filter.tsx +40 -0
  166. package/src/pages/product-list/pagination.tsx +80 -0
  167. package/src/pages/product-list/product-list-context.tsx +302 -0
  168. package/src/pages/product-list/product-list-state.ts +187 -0
  169. package/src/pages/product-list/product-rating.tsx +16 -0
  170. package/src/pages/product-list/quick-view-dialog-content.tsx +205 -0
  171. package/src/pages/product-list/quick-view-dialog.tsx +54 -0
  172. package/src/pages/product-list/range-filter.tsx +125 -0
  173. package/src/pages/product-list/scrollbar.tsx +38 -0
  174. package/src/pages/product-list/search-bar.tsx +162 -0
  175. package/src/pages/product-list/selected-facets.tsx +80 -0
  176. package/src/pages/product-list/side-nav.tsx +49 -0
  177. package/src/pages/product-list/swatch.tsx +80 -0
  178. package/src/pages/product-list/types.ts +18 -0
  179. package/src/pages/product-list/use-product-list.ts +12 -0
  180. package/src/pages/product-list/useCategoryFilter.tsx +26 -0
  181. package/src/pages/product-list/utils/facet-helpers.ts +5 -0
  182. package/src/pages/product-list/utils/generate-breadcrumbs.ts +30 -0
  183. package/src/pages/product-list/utils/getCategoryFilterFromSlug.ts +153 -0
  184. package/src/pages/product-list/utils/product-list-helper.ts +111 -0
  185. package/src/pages/product-list/utils/product-list-types.ts +41 -0
  186. package/src/pages/product-list/utils/search-for-category.ts +76 -0
  187. package/src/pages/product-list/utils/sort-options.ts +44 -0
  188. package/src/pages/product-list/utils/use-previous-refiners.ts +14 -0
  189. package/src/pages/quickorder/order-upload.tsx +150 -0
  190. package/src/pages/quickorder/quick-order-form.tsx +343 -0
  191. package/src/pages/quickorder/quick-order-row.tsx +144 -0
  192. package/tsconfig.json +1 -0
  193. package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +0 -43515
  194. package/src/config.ts +0 -47
  195. /package/src/{components/_components/hooks/useOverflowDetect.ts → hooks/use-overflow-detect.ts} +0 -0
  196. /package/src/{components/_components/hooks/useSettings.ts → hooks/use-settings.ts} +0 -0
@@ -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
+ }
@@ -3,7 +3,6 @@ import {
3
3
  Autocomplete,
4
4
  AutocompleteRenderInputParams,
5
5
  Box,
6
- InputProps,
7
6
  TextField,
8
7
  } from "@mui/material";
9
8
  import React from "react";
@@ -28,12 +27,14 @@ export default function CountryInput({
28
27
  ) => setCountryInputValue(value);
29
28
 
30
29
  const renderOption = (
31
- optionProps: React.HTMLAttributes<HTMLLIElement>,
30
+ props: React.HTMLAttributes<HTMLLIElement> & {
31
+ key: any;
32
+ },
32
33
  option: CountryRegionInfo,
33
- index: number,
34
34
  ) => {
35
+ const { key, ...optionProps } = props;
35
36
  return (
36
- <Box key={index} component="li" {...optionProps}>
37
+ <Box key={key} component="li" {...optionProps}>
37
38
  {option.ShortName}
38
39
  </Box>
39
40
  );
@@ -44,12 +45,8 @@ export default function CountryInput({
44
45
  size="small"
45
46
  label={label}
46
47
  variant="outlined"
48
+ data-testid="country-input"
47
49
  placeholder="Select Country"
48
- slotProps={{
49
- input: {
50
- "data-testid": "country-input",
51
- } as InputProps,
52
- }}
53
50
  />
54
51
  );
55
52
 
@@ -66,9 +63,7 @@ export default function CountryInput({
66
63
  data-testid="country-autocomplete"
67
64
  inputValue={countryInputValue}
68
65
  onInputChange={handleAutoCompleteChange}
69
- renderOption={(props, option, state) =>
70
- renderOption(props, option, state?.index)
71
- }
66
+ renderOption={(props, option) => renderOption(props, option)}
72
67
  renderInput={renderInput}
73
68
  getOptionLabel={getOptionLabel}
74
69
  />
@@ -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
+ }
@@ -0,0 +1,85 @@
1
+ "use client";
2
+ import { useState } from "react";
3
+
4
+ // CUSTOM UTILS LIBRARY FUNCTION
5
+ import { ProductDetails } from "@/api-manager/schemas/product.schema";
6
+ import { ProductDimensionValueInventoryAvailability } from "@msdyn365-commerce/retail-proxy";
7
+ import getProductById from "@/api-manager/services/product/queries/get-product-by-id";
8
+
9
+ //=========================================
10
+ type Props = {
11
+ product: ProductDetails;
12
+ };
13
+
14
+ type SelectedDimension = Record<
15
+ number,
16
+ ProductDimensionValueInventoryAvailability
17
+ >;
18
+ //========================================
19
+
20
+ /**
21
+ * This Hook handles the product variant selection and updates the selected variant id
22
+ * based on the selected dimensions.
23
+ */
24
+ export default function useVariants({ product }: Props) {
25
+ const { DimensionValues } = product;
26
+
27
+ const [selectVariantId, setSelectVariantId] = useState<number>();
28
+ const [selectDimensions, setSelectDimensions] = useState<SelectedDimension>(
29
+ {},
30
+ );
31
+
32
+ const { data: productVariant, isPending: variantPending } =
33
+ getProductById.useData({ id: selectVariantId });
34
+
35
+ // HELPERS
36
+ const allDimensionsSelected = (currentDimensions: SelectedDimension) =>
37
+ Object.keys(currentDimensions).length === DimensionValues.length;
38
+
39
+ /**
40
+ * Adds selected dimension to the current selectDimensions state
41
+ * Updates the selectVariantId if all dimensions are selected
42
+ * @param dimensionValue dimension that was last change to trigger this function
43
+ */
44
+ const handleChangeVariant = async (
45
+ dimensionValue: ProductDimensionValueInventoryAvailability,
46
+ ) => {
47
+ // Wrap the selection logic inside the callback
48
+ // to ensure we have the correct current value of state
49
+ setSelectDimensions((state) => {
50
+ if (!dimensionValue?.DimensionTypeValue) {
51
+ console.error("DimensionTypeValue is missing");
52
+ return state;
53
+ }
54
+
55
+ const newDimensionsState = {
56
+ ...state,
57
+ [dimensionValue?.DimensionTypeValue]: dimensionValue,
58
+ };
59
+
60
+ if (!allDimensionsSelected(newDimensionsState)) return newDimensionsState;
61
+
62
+ const dimensionsArray = Object.values(newDimensionsState);
63
+
64
+ // DEDUCE PRODUCT ID FROM SELECTED DIMENSIONS
65
+ const selectedVariantId = dimensionsArray.reduce(
66
+ (intersectedIds: number[], { ProductIds }) => {
67
+ return intersectedIds.filter((id) => ProductIds?.includes(id));
68
+ },
69
+ dimensionsArray[0]?.ProductIds || [],
70
+ );
71
+
72
+ setSelectVariantId(selectedVariantId[0]);
73
+ return newDimensionsState;
74
+ });
75
+ };
76
+
77
+ return {
78
+ allDimensionsSelected,
79
+ handleChangeVariant,
80
+ productVariant,
81
+ selectVariantId,
82
+ variantPending,
83
+ selectDimensions,
84
+ };
85
+ }
@@ -0,0 +1,8 @@
1
+ export const CART_COOKIE_NAME = "cart_cookie";
2
+ export const CartCookieType = {
3
+ Auth: "AU",
4
+ Anon: "AN",
5
+ } as const;
6
+
7
+ export type CartCookieType =
8
+ (typeof CartCookieType)[keyof typeof CartCookieType];
@@ -1,14 +1,36 @@
1
1
  "use server";
2
2
  import { cookies } from "next/headers";
3
+ import {
4
+ CART_COOKIE_NAME,
5
+ CartCookieType,
6
+ } from "./cart-cookie-handler-constants";
3
7
 
4
- const getCartCookie = async () => (await cookies()).get("cart_cookie")?.value;
8
+ const getCartCookie = async (): Promise<string | null> =>
9
+ (await getCartCookieWithAuthStatus())?.id ?? null;
5
10
 
6
- const setCartCookie = async (cartId: string) => {
7
- (await cookies()).set("cart_cookie", cartId);
11
+ const setCartCookie = async (cartId: string, isAuthenticated?: boolean) => {
12
+ const cookieValue = `${isAuthenticated ? CartCookieType.Auth : CartCookieType.Anon}:${cartId}`;
13
+ (await cookies()).set(CART_COOKIE_NAME, cookieValue);
8
14
  };
9
15
 
10
16
  const deleteCartCookie = async () => {
11
- (await cookies()).delete("cart_cookie");
17
+ (await cookies()).delete(CART_COOKIE_NAME);
12
18
  };
13
19
 
14
- export { getCartCookie, setCartCookie, deleteCartCookie };
20
+ const getCartCookieWithAuthStatus = async () => {
21
+ const cartCookie = (await cookies()).get(CART_COOKIE_NAME)?.value;
22
+ if (!cartCookie) return null;
23
+ const parts = cartCookie.split(":");
24
+ if (parts.length !== 2) return null;
25
+ return {
26
+ type: parts[0] as CartCookieType,
27
+ id: parts[1] as string,
28
+ };
29
+ };
30
+
31
+ export {
32
+ getCartCookie,
33
+ setCartCookie,
34
+ deleteCartCookie,
35
+ getCartCookieWithAuthStatus,
36
+ };
@@ -5,7 +5,6 @@ import { GraphQLClient } from "graphql-request";
5
5
  // and one here to isntantiate because graphql-request library must be same
6
6
  // for this file and the sdk
7
7
  import { getSdk } from "@/lib/graphqlRequestSdk";
8
- import { getConfig } from "@config";
9
8
 
10
9
  // typescript can't infer type without reference
11
10
  export type E4Sdk = ReturnType<typeof getSdk>;
@@ -15,7 +14,7 @@ export type E4Sdk = ReturnType<typeof getSdk>;
15
14
  * @returns a new graphql request client with all of E4 queries and mutations
16
15
  */
17
16
  const createGraphQLClient = async (): Promise<E4Sdk> => {
18
- const API_URL = getConfig().E4_API_ENDPOINT;
17
+ const API_URL = process.env.NEXT_PUBLIC_E4_API_ENDPOINT || "";
19
18
  const client = new GraphQLClient(API_URL, {
20
19
  credentials: "include", // Ensures cookies are included
21
20
  headers: {