@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,123 @@
1
+ import React, { ReactNode } from "react";
2
+ import Link from "next/link";
3
+ import { Box, Button, IconButton, Theme, styled } from "@mui/material";
4
+ import useMediaQuery from "@mui/material/useMediaQuery";
5
+ import { useTranslations } from "next-intl";
6
+ // ICON COMPONENTS
7
+ import { MdMenu as Menu } from "react-icons/md";
8
+ // GLOBAL CUSTOM COMPONENTS
9
+ import SideNav from "@evenicanpm/storefront-core/src/components/_components/side-nav/index";
10
+ import { H2 } from "@evenicanpm/storefront-core/src/components/_components/Typography";
11
+ import FlexBox from "@evenicanpm/storefront-core/src/components/_components/flex-box/flex-box";
12
+ // LOCAL CUSTOM COMPONENTS
13
+ import Navigation from "./navigation";
14
+ import { IconType } from "react-icons";
15
+
16
+ // STYLED COMPONENT
17
+ const StyledBox = styled("div")(({ theme }) => ({
18
+ display: "flex",
19
+ marginTop: theme.spacing(-2),
20
+ marginBottom: theme.spacing(3),
21
+ "& .headerHold": {
22
+ flexGrow: 1,
23
+ alignItems: "center",
24
+ justifyContent: "space-between",
25
+ },
26
+ [theme.breakpoints.up("md")]: {
27
+ "& .sidenav": { display: "none" },
28
+ },
29
+ [theme.breakpoints.down("md")]: {
30
+ flexDirection: "column",
31
+ },
32
+ }));
33
+
34
+ // ==============================================================
35
+
36
+ type WithButtons = {
37
+ title: string;
38
+ titleMessage?: string;
39
+ buttons: ReactNode;
40
+ Icon: IconType;
41
+ href?: never;
42
+ buttonText?: never;
43
+ };
44
+
45
+ type WithGeneratedButton = {
46
+ title: string;
47
+ titleMessage?: string;
48
+ href?: string;
49
+ buttonText?: string;
50
+ Icon: IconType;
51
+ buttons?: ReactNode;
52
+ };
53
+
54
+ type Props = WithButtons | WithGeneratedButton;
55
+
56
+ // ==============================================================
57
+
58
+ export default function DashboardHeader({
59
+ title,
60
+ titleMessage,
61
+ buttons,
62
+ href,
63
+ buttonText,
64
+ Icon,
65
+ }: Props) {
66
+ const t = useTranslations();
67
+ const isTablet = useMediaQuery((theme: Theme) =>
68
+ theme.breakpoints.down(1025),
69
+ );
70
+
71
+ const GENERATED_BUTTON = href ? (
72
+ <Button
73
+ href={href}
74
+ color="primary"
75
+ LinkComponent={Link}
76
+ sx={{ bgcolor: "primary.light", px: 4 }}
77
+ >
78
+ {t(buttonText)}
79
+ </Button>
80
+ ) : null;
81
+
82
+ const HEADER_LINKS = (
83
+ <FlexBox gap={1} justifyContent="flex-end">
84
+ {buttons || GENERATED_BUTTON}
85
+ </FlexBox>
86
+ );
87
+
88
+ return (
89
+ <StyledBox>
90
+ <FlexBox mt={2} className="headerHold">
91
+ <FlexBox alignItems="center" gap={0.5}>
92
+ {Icon && (
93
+ <IconButton color="primary">
94
+ <Icon />
95
+ </IconButton>
96
+ )}
97
+ <H2 my={0} lineHeight={1} ellipsis>
98
+ {titleMessage ? titleMessage : t(title)}
99
+ </H2>
100
+ </FlexBox>
101
+
102
+ <FlexBox className="button-container" justifyContent="flex-end">
103
+ {!isTablet && HEADER_LINKS}
104
+ </FlexBox>
105
+
106
+ <div className="sidenav">
107
+ <SideNav
108
+ position="left"
109
+ handler={(close) => (
110
+ <IconButton onClick={close}>
111
+ <Menu fontSize="small" />
112
+ </IconButton>
113
+ )}
114
+ >
115
+ <Navigation />
116
+ </SideNav>
117
+ </div>
118
+ </FlexBox>
119
+
120
+ {isTablet ? <Box mt={2}>{HEADER_LINKS}</Box> : null}
121
+ </StyledBox>
122
+ );
123
+ }
@@ -0,0 +1,87 @@
1
+ "use client";
2
+
3
+ import { usePathname } from "next/navigation";
4
+ import { Fragment } from "react";
5
+ import { useTranslations } from "next-intl";
6
+
7
+ // ICON COMPONENTS
8
+ import {
9
+ MdOutlineShoppingBag as ShoppingBagOutlined,
10
+ MdFavoriteBorder as FavoriteBorder,
11
+ MdPerson as Person,
12
+ MdPlace as Place,
13
+ } from "react-icons/md";
14
+
15
+ // GLOBAL CUSTOM COMPONENTS
16
+ import FlexBox from "@evenicanpm/storefront-core/src/components/_components/flex-box/flex-box";
17
+ import {
18
+ Paragraph,
19
+ Span,
20
+ } from "@evenicanpm/storefront-core/src/components/_components/Typography";
21
+
22
+ // API
23
+ import getUser from "@evenicanpm/storefront-core/src/api-manager/services/user/queries/get-user";
24
+ import getUserCounts from "@evenicanpm/storefront-core/src/api-manager/services/user/queries/get-user-counts";
25
+
26
+ // STYLED COMPONENTS
27
+ import { MainContainer, StyledNavLink } from "../styles";
28
+
29
+ const MENU_ITEMS = [
30
+ {
31
+ title: "DASHBOARD",
32
+ list: [
33
+ {
34
+ href: "/account/orders",
35
+ title: "orders",
36
+ Icon: ShoppingBagOutlined,
37
+ count: 0,
38
+ },
39
+ { href: "/account/wish-lists", title: "wishlists", Icon: FavoriteBorder },
40
+ ],
41
+ },
42
+ {
43
+ title: "ACCOUNT SETTINGS",
44
+ list: [
45
+ { href: "/account/profile", title: "profile", Icon: Person },
46
+ { href: "/account/addresses", title: "addresses", Icon: Place, count: 0 },
47
+ ],
48
+ },
49
+ ];
50
+
51
+ export default function Navigation() {
52
+ const pathname = usePathname();
53
+ const t = useTranslations("Account.Menu");
54
+
55
+ const { data: user } = getUser.useData();
56
+ const { data: counts } = getUserCounts.useData(String(user?.RecordId) || "");
57
+
58
+ MENU_ITEMS[0].list[0].count = counts?.orderCount;
59
+ MENU_ITEMS[1].list[1].count = counts?.addressCount;
60
+
61
+ return (
62
+ <MainContainer>
63
+ {MENU_ITEMS.map((item) => (
64
+ <Fragment key={item.title}>
65
+ <Paragraph p="26px 30px 1rem" color="grey.600" fontSize={12}>
66
+ {item.title}
67
+ </Paragraph>
68
+
69
+ {item.list.map(({ Icon, count, href, title }) => (
70
+ <StyledNavLink
71
+ href={href}
72
+ key={title}
73
+ isCurrentPath={pathname.includes(href)}
74
+ >
75
+ <FlexBox alignItems="center" gap={1}>
76
+ <Icon color="inherit" fontSize="large" className="nav-icon" />
77
+ <Span>{t(title)}</Span>
78
+ </FlexBox>
79
+
80
+ <Span>{count}</Span>
81
+ </StyledNavLink>
82
+ ))}
83
+ </Fragment>
84
+ ))}
85
+ </MainContainer>
86
+ );
87
+ }
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import { Box } from "@mui/material";
3
+ import { H4 } from "@evenicanpm/storefront-core/src/components/_components/Typography";
4
+ import { useTranslations } from "next-intl";
5
+
6
+ type Props = { message: string };
7
+
8
+ export default function NoRecords({ message }: Props) {
9
+ const t = useTranslations();
10
+ return (
11
+ <Box
12
+ display="flex"
13
+ justifyContent="center"
14
+ alignItems="center"
15
+ height="50vh"
16
+ >
17
+ <H4 color="grey.500">{t(message)}</H4>
18
+ </Box>
19
+ );
20
+ }
@@ -0,0 +1,66 @@
1
+ import { Button } from "@mui/material";
2
+ import { format } from "date-fns";
3
+ // MUI ICON COMPONENTS
4
+ // GLOBAL CUSTOM COMPONENTS
5
+ import { Paragraph } from "@evenicanpm/storefront-core/src/components/_components/Typography";
6
+ // LOCAL CUSTOM COMPONENT
7
+
8
+ // API
9
+ import { Wishlist } from "@/api-manager/schemas/user.schema";
10
+ import { StyledTableRow } from "../styles";
11
+
12
+ interface Props {
13
+ wishlist: Wishlist;
14
+ addToWishlist?: (wishlistId: string) => void;
15
+ removeFromWishlist?: (wishlistId: string) => void;
16
+ }
17
+
18
+ export default function WishlistModalItem({
19
+ wishlist,
20
+ addToWishlist,
21
+ removeFromWishlist,
22
+ }: Props) {
23
+ const handleClick = () => {
24
+ if (addToWishlist) {
25
+ addToWishlist(wishlist.Id);
26
+ } else if (removeFromWishlist) {
27
+ removeFromWishlist(wishlist.Id);
28
+ }
29
+ };
30
+ return (
31
+ <StyledTableRow
32
+ sx={{
33
+ display: "grid",
34
+ gridTemplateColumns: "2fr 1fr 1fr 1fr", // Define equal column structure
35
+ alignItems: "center",
36
+ gap: 2,
37
+ padding: "10px 15px",
38
+ width: "500px",
39
+ cursor: "default",
40
+ }}
41
+ >
42
+ <Paragraph ellipsis sx={{ fontWeight: "bold" }}>
43
+ {wishlist?.Name ?? ""}
44
+ </Paragraph>
45
+ <Paragraph ellipsis>
46
+ {" "}
47
+ {format(
48
+ new Date(wishlist?.CreatedDateTime ?? wishlist?.LastUpdated ?? ""),
49
+ "MMM dd, yyyy",
50
+ )}
51
+ </Paragraph>
52
+ <Paragraph ellipsis>
53
+ {" "}
54
+ {`${wishlist?.ProductListLines?.length} products`}
55
+ </Paragraph>
56
+
57
+ <Button
58
+ onClick={handleClick}
59
+ color="primary"
60
+ sx={{ bgcolor: "primary.light", px: 4 }}
61
+ >
62
+ {addToWishlist ? "Add" : "Remove"}
63
+ </Button>
64
+ </StyledTableRow>
65
+ );
66
+ }
@@ -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 "./components/dashboard-header";
8
+ import WishlistModalItem from "./components/wishlist-modal-item";
9
+ import NoRecords from "./components/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,61 @@
1
+ import Card, { CardProps } from "@mui/material/Card";
2
+ import styled from "@mui/material/styles/styled";
3
+ import NavLink, {
4
+ NavLinkProps,
5
+ } from "@evenicanpm/storefront-core/src/components/_components/nav-link/nav-link";
6
+ import { ComponentType } from "react";
7
+
8
+ type ExtendedNavLinkProps = NavLinkProps & {
9
+ isCurrentPath?: boolean;
10
+ };
11
+
12
+ export const MainContainer: ComponentType<CardProps> = styled(Card)(
13
+ ({ theme }) => ({
14
+ paddingBottom: "1.5rem",
15
+ [theme.breakpoints.down("md")]: {
16
+ boxShadow: "none",
17
+ overflowY: "auto",
18
+ height: "calc(100vh - 64px)",
19
+ },
20
+ }),
21
+ ) as typeof Card;
22
+
23
+ export const StyledNavLink: ComponentType<ExtendedNavLinkProps> = styled(
24
+ NavLink,
25
+ {
26
+ shouldForwardProp: (prop) => prop !== "isCurrentPath",
27
+ },
28
+ )<ExtendedNavLinkProps>(({ theme, isCurrentPath }) => ({
29
+ display: "flex",
30
+ alignItems: "center",
31
+ borderLeft: "4px solid",
32
+ paddingLeft: "1.5rem",
33
+ paddingRight: "1.5rem",
34
+ marginBottom: "1.25rem",
35
+ justifyContent: "space-between",
36
+ borderColor: isCurrentPath ? theme.palette.primary.main : "transparent",
37
+ "& .nav-icon": {
38
+ color: isCurrentPath ? theme.palette.primary.main : theme.palette.grey[600],
39
+ },
40
+ "&:hover": {
41
+ borderColor: theme.palette.primary.main,
42
+ "& .nav-icon": { color: theme.palette.primary.main },
43
+ },
44
+ }));
45
+
46
+ export const StyledTableRow: ComponentType<CardProps> = styled(Card)(
47
+ ({ theme }) => ({
48
+ gap: 16,
49
+ marginBlock: 16,
50
+ display: "grid",
51
+ borderRadius: 10,
52
+ cursor: "pointer",
53
+ alignItems: "center",
54
+ padding: ".6rem 1.2rem",
55
+ gridTemplateColumns: "1.5fr 2fr 1.5fr auto",
56
+ [theme.breakpoints.down("sm")]: {
57
+ gap: 8,
58
+ gridTemplateColumns: "repeat(2, 1fr)",
59
+ },
60
+ }),
61
+ );
@@ -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;