@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.
- package/package.json +3 -2
- package/src/api-manager/api-manager.config.json +1 -1
- package/src/api-manager/datasources/d365/d365-address.datasource.ts +4 -4
- package/src/api-manager/datasources/d365/d365-cart.datasource.ts +100 -7
- package/src/api-manager/datasources/d365/d365-product.datasource.ts +39 -17
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +220 -68
- package/src/api-manager/datasources/d365/utils/decode-jwt.ts +2 -2
- package/src/api-manager/datasources/e4/e4-cart.datasource.ts +6 -0
- package/src/api-manager/datasources/e4/e4-product.datasource.ts +22 -2
- package/src/api-manager/datasources/e4/e4.datasource.ts +1 -1
- package/src/api-manager/datasources/e4/e4.remaps.ts +24 -0
- package/src/api-manager/datasources/e4/e4.translator.ts +19 -1
- package/src/api-manager/datasources/e4/{e4-user.datasource.ts → user/e4-user.datasource.ts} +41 -19
- package/src/api-manager/datasources/e4/user/e4-user.remaps.ts +4 -0
- package/src/api-manager/datasources/e4/user/e4-user.translator.ts +10 -0
- package/src/api-manager/index.ts +77 -15
- package/src/api-manager/lib/get-graphql-client.ts +7 -7
- package/src/api-manager/schemas/address.schema.ts +1 -19
- package/src/api-manager/schemas/cart.schema.ts +18 -0
- package/src/api-manager/schemas/product.schema.ts +8 -1
- package/src/api-manager/schemas/user.schema.ts +52 -1
- package/src/api-manager/services/address/queries/get-states.ts +1 -1
- package/src/api-manager/services/cart/mutations/checkout.ts +16 -1
- package/src/api-manager/services/cart/queries/get-cart-lines-inventory.ts +15 -0
- package/src/api-manager/services/create-extension-query.ts +26 -0
- package/src/api-manager/services/create-mutation.ts +5 -1
- package/src/api-manager/services/create-query.ts +10 -4
- package/src/api-manager/services/organization/queries/get-channel-configuration.ts +1 -1
- package/src/api-manager/services/product/queries/get-product-prices.ts +16 -0
- package/src/api-manager/services/product/queries/search-products.ts +1 -0
- package/src/api-manager/services/user/mutations/add-to-wishlist.ts +24 -0
- package/src/api-manager/services/user/mutations/copy-cart-to-wishlist.ts +23 -0
- package/src/api-manager/services/user/mutations/create-wishlist.ts +4 -4
- package/src/api-manager/services/user/mutations/delete-wishlist.ts +4 -3
- package/src/api-manager/services/user/mutations/remove-from-wishlist.ts +21 -0
- package/src/api-manager/services/user/mutations/update-wishlist.ts +23 -0
- package/src/api-manager/services/user/queries/get-wishlist-details.ts +11 -4
- package/src/api-manager/services/user/queries/get-wishlists.ts +4 -3
- package/src/api-manager/types/Datasource.ts +42 -14
- package/src/auth/auth-options.ts +2 -2
- package/src/auth/msal.ts +9 -9
- package/src/auth/next-auth-cookie-manager.ts +5 -3
- package/src/auth/providers/aadb2c-provider.ts +3 -4
- package/src/cms/endpoints.ts +2 -3
- package/src/components/Blocks/Components/ProductCarousel/index.tsx +6 -6
- package/src/components/_components/LazyImage.tsx +5 -2
- package/src/components/_components/header/components/user.tsx +1 -2
- package/src/components/_components/navbar/category-based-menu/components/categories.tsx +1 -1
- package/src/components/_components/navbar/mega-menu/mega-menu.tsx +1 -1
- package/src/components/_components/navbar/nav-list/nav-item-child.tsx +2 -2
- package/src/components/_components/product-cards/product-card-1/components/quantity-buttons.tsx +17 -8
- package/src/components/_components/product-cards/product-card-1/product-card-1-wrapper.tsx +304 -0
- package/src/components/_components/product-cards/product-card-1/product-card.tsx +16 -55
- package/src/components/_components/product-cards/product-card-8/product-card.tsx +80 -60
- package/src/components/_components/product-cards/product-card-8/styles/index.ts +10 -0
- package/src/components/_components/product-cards/product-card-wishlist/components/hover-actions.tsx +37 -0
- package/src/components/_components/product-cards/product-card-wishlist/index.ts +1 -0
- package/src/components/_components/product-cards/product-card-wishlist/product-card.tsx +118 -0
- package/src/components/_components/product-cards/product-card-wishlist/styles/index.ts +51 -0
- package/src/components/_components/product-dimensions/product-dimensions.tsx +114 -0
- package/src/components/_components/product-quantity-buttons/product-quantity-buttons.tsx +88 -0
- package/src/components/_components/products-view/product-view-dialog.tsx +48 -10
- package/src/components/_components/products-view/products-grid-view.tsx +31 -17
- package/src/components/_components/products-view/products-list-view.tsx +30 -16
- package/src/components/_components/search-box/__test__/search-input-category.test.tsx +110 -0
- package/src/components/_components/search-box/__test__/search-input.test.tsx +137 -0
- package/src/components/_components/search-box/__test__/search-results.json +165 -0
- package/src/components/_components/search-box/components/search-result.tsx +66 -12
- package/src/components/_components/search-box/search-input-category.tsx +135 -32
- package/src/components/_components/search-box/styles/index.ts +2 -0
- package/src/components/_components/section-header/section-creator.tsx +1 -0
- package/src/components/_components/section-header/section-header.tsx +1 -1
- package/src/components/_components/settings/setting.tsx +1 -1
- package/src/components/_components/wishlist/add-to-wishlist-dialog.tsx +161 -0
- package/src/components/_components/wishlist/components/create-new-list.tsx +167 -0
- package/src/components/_components/wishlist/components/create-wishlist-button.tsx +40 -0
- package/src/components/_components/wishlist/components/dashboard-header.tsx +123 -0
- package/src/components/_components/wishlist/components/navigation.tsx +87 -0
- package/src/components/_components/wishlist/components/no-records.tsx +20 -0
- package/src/components/_components/wishlist/components/wishlist-modal-item.tsx +66 -0
- package/src/components/_components/wishlist/remove-from-wishlist-dialog.tsx +174 -0
- package/src/components/_components/wishlist/styles.ts +61 -0
- package/src/components/_components/wishlist-modal/TODO.md +10 -0
- package/src/components/_components/wishlist-modal/add-to-wishlist-button.tsx +68 -0
- package/src/components/_components/wishlist-modal/add-to-wishlist-dialog.tsx +161 -0
- package/src/components/_components/wishlist-modal/remove-from-wishlist-dialog.tsx +174 -0
- package/src/components/_components/wishlist-modal/wishlist-modal-item.tsx +65 -0
- package/src/components/countries-input.tsx +7 -12
- package/src/hooks/use-cart.tsx +82 -0
- package/src/hooks/use-variants.tsx +85 -0
- package/src/lib/cart-cookie-handler-constants.ts +8 -0
- package/src/lib/cart-cookie-handler.ts +27 -5
- package/src/lib/create-graphql-client.ts +1 -2
- package/src/lib/product-list-types.ts +41 -0
- package/src/pages/account/addresses/address-form.tsx +289 -0
- package/src/pages/account/addresses/address-item.tsx +104 -0
- package/src/pages/account/dashboard-header.tsx +123 -0
- package/src/pages/account/navigation.tsx +84 -0
- package/src/pages/account/no-records.tsx +20 -0
- package/src/pages/account/orders/icons/delivery.tsx +14 -0
- package/src/pages/account/orders/icons/package-box.tsx +13 -0
- package/src/pages/account/orders/icons/truck-filled.tsx +14 -0
- package/src/pages/account/orders/order-progress.tsx +111 -0
- package/src/pages/account/orders/order-row.tsx +61 -0
- package/src/pages/account/orders/order-summary.tsx +83 -0
- package/src/pages/account/orders/ordered-products.tsx +114 -0
- package/src/pages/account/profile/profile-button.test.tsx +59 -0
- package/src/pages/account/profile/profile-button.tsx +50 -0
- package/src/pages/account/profile/user-info.tsx +59 -0
- package/src/pages/account/styles.ts +32 -0
- package/src/pages/account/table-row.tsx +19 -0
- package/src/pages/account/wishlist/create-new-list.tsx +167 -0
- package/src/pages/account/wishlist/create-wishlist-button.tsx +40 -0
- package/src/pages/account/wishlist/wishlist-item.tsx +82 -0
- package/src/pages/blog/pagination.tsx +38 -0
- package/src/pages/cart/cart-item.tsx +312 -0
- package/src/pages/cart/checkout-form.tsx +122 -0
- package/src/pages/cart/coupon-entry.tsx +90 -0
- package/src/pages/cart/estimate-shipping.tsx +183 -0
- package/src/pages/cart/wrapper.tsx +30 -0
- package/src/pages/checkout/checkout-alt-form/address-card.tsx +73 -0
- package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +133 -0
- package/src/pages/checkout/checkout-alt-form/checkout-step.tsx +120 -0
- package/src/pages/checkout/checkout-alt-form/customer-information.tsx +94 -0
- package/src/pages/checkout/checkout-alt-form/delivery-address.tsx +170 -0
- package/src/pages/checkout/checkout-alt-form/delivery-date.tsx +114 -0
- package/src/pages/checkout/checkout-alt-form/delivery-options.tsx +186 -0
- package/src/pages/checkout/checkout-alt-form/edit-address-form.tsx +130 -0
- package/src/pages/checkout/checkout-alt-form/heading.tsx +50 -0
- package/src/pages/checkout/checkout-alt-form/index.ts +1 -0
- package/src/pages/checkout/checkout-alt-form/new-address-form.tsx +294 -0
- package/src/pages/checkout/checkout-alt-form/node.tsx +9 -0
- package/src/pages/checkout/checkout-alt-form/payment-details.tsx +344 -0
- package/src/pages/checkout/checkout-alt-form/safe-card-preview-data.tsx +36 -0
- package/src/pages/checkout/checkout-alt-form/types.ts +20 -0
- package/src/pages/checkout/checkout-alt-summary/cart-item.tsx +39 -0
- package/src/pages/checkout/checkout-alt-summary/checkout-alt-summary.tsx +40 -0
- package/src/pages/checkout/checkout-alt-summary/index.ts +1 -0
- package/src/pages/checkout/checkout-alt-summary/list-item.tsx +31 -0
- package/src/pages/confirmation/address.tsx +22 -0
- package/src/pages/confirmation/confirmation-summary.tsx +52 -0
- package/src/pages/confirmation/ordered-products.tsx +108 -0
- package/src/pages/product-details/available-shops.tsx +48 -0
- package/src/pages/product-details/bopis/find-in-store-button.tsx +63 -0
- package/src/pages/product-details/bopis/find-in-store-modal.tsx +257 -0
- package/src/pages/product-details/bopis/pickup-option-select.tsx +127 -0
- package/src/pages/product-details/bopis/search-header.tsx +69 -0
- package/src/pages/product-details/checkbox-label.tsx +20 -0
- package/src/pages/product-details/currency.ts +64 -0
- package/src/pages/product-details/frequently-bought.tsx +90 -0
- package/src/pages/product-details/frequently-product-card.tsx +60 -0
- package/src/pages/product-details/product-comment.tsx +44 -0
- package/src/pages/product-details/product-description.tsx +22 -0
- package/src/pages/product-details/product-filter-card.tsx +257 -0
- package/src/pages/product-details/product-intro/product-intro-images.tsx +87 -0
- package/src/pages/product-details/product-intro/product-intro.tsx +250 -0
- package/src/pages/product-details/product-review.tsx +131 -0
- package/src/pages/product-details/product-tabs.tsx +51 -0
- package/src/pages/product-details/related-products.tsx +42 -0
- package/src/pages/product-details/types.ts +11 -0
- package/src/pages/product-list/breadcrumbs.tsx +39 -0
- package/src/pages/product-list/checkbox-label.tsx +20 -0
- package/src/pages/product-list/facet-group.tsx +125 -0
- package/src/pages/product-list/facet.tsx +36 -0
- package/src/pages/product-list/list-filter.tsx +40 -0
- package/src/pages/product-list/pagination.tsx +80 -0
- package/src/pages/product-list/product-list-context.tsx +302 -0
- package/src/pages/product-list/product-list-state.ts +187 -0
- package/src/pages/product-list/product-rating.tsx +16 -0
- package/src/pages/product-list/quick-view-dialog-content.tsx +205 -0
- package/src/pages/product-list/quick-view-dialog.tsx +54 -0
- package/src/pages/product-list/range-filter.tsx +125 -0
- package/src/pages/product-list/scrollbar.tsx +38 -0
- package/src/pages/product-list/search-bar.tsx +162 -0
- package/src/pages/product-list/selected-facets.tsx +80 -0
- package/src/pages/product-list/side-nav.tsx +49 -0
- package/src/pages/product-list/swatch.tsx +80 -0
- package/src/pages/product-list/types.ts +18 -0
- package/src/pages/product-list/use-product-list.ts +12 -0
- package/src/pages/product-list/useCategoryFilter.tsx +26 -0
- package/src/pages/product-list/utils/facet-helpers.ts +5 -0
- package/src/pages/product-list/utils/generate-breadcrumbs.ts +30 -0
- package/src/pages/product-list/utils/getCategoryFilterFromSlug.ts +153 -0
- package/src/pages/product-list/utils/product-list-helper.ts +111 -0
- package/src/pages/product-list/utils/product-list-types.ts +41 -0
- package/src/pages/product-list/utils/search-for-category.ts +76 -0
- package/src/pages/product-list/utils/sort-options.ts +44 -0
- package/src/pages/product-list/utils/use-previous-refiners.ts +14 -0
- package/src/pages/quickorder/order-upload.tsx +150 -0
- package/src/pages/quickorder/quick-order-form.tsx +343 -0
- package/src/pages/quickorder/quick-order-row.tsx +144 -0
- package/tsconfig.json +1 -0
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +0 -43515
- package/src/config.ts +0 -47
- /package/src/{components/_components/hooks/useOverflowDetect.ts → hooks/use-overflow-detect.ts} +0 -0
- /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
|
-
|
|
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={
|
|
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,
|
|
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
|
+
}
|
|
@@ -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 ()
|
|
8
|
+
const getCartCookie = async (): Promise<string | null> =>
|
|
9
|
+
(await getCartCookieWithAuthStatus())?.id ?? null;
|
|
5
10
|
|
|
6
|
-
const setCartCookie = async (cartId: string) => {
|
|
7
|
-
|
|
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(
|
|
17
|
+
(await cookies()).delete(CART_COOKIE_NAME);
|
|
12
18
|
};
|
|
13
19
|
|
|
14
|
-
|
|
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 =
|
|
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: {
|