@evenicanpm/storefront-core 1.0.0 → 1.0.2
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/datasources/d365/d365-address.datasource.ts +1 -1
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +0 -19
- package/src/api-manager/datasources/e4/e4-cart.datasource.ts +2 -2
- package/src/api-manager/datasources/e4/e4-product.datasource.ts +19 -4
- package/src/api-manager/datasources/e4/e4.datasource.ts +1 -2
- package/src/api-manager/datasources/e4/e4.remaps.ts +24 -0
- package/src/api-manager/datasources/e4/e4.translator.ts +17 -0
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +131 -2168
- package/src/api-manager/datasources/e4/{e4-user.datasource.ts → user/e4-user.datasource.ts} +5 -7
- 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 +21 -6
- package/src/api-manager/schemas/product.schema.ts +1 -1
- package/src/api-manager/services/address/queries/get-states.ts +1 -1
- package/src/api-manager/services/create-query.ts +10 -3
- package/src/api-manager/types/Datasource.ts +9 -9
- package/src/components/Blocks/Components/ProductCarousel/index.tsx +6 -6
- 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-dimensions/product-dimensions.tsx +114 -0
- package/src/components/_components/product-quantity-buttons/product-quantity-buttons.tsx +88 -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-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 +1 -0
- package/src/hooks/use-cart.tsx +82 -0
- package/src/hooks/use-variants.tsx +85 -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/{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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import getGraphqlClient from "
|
|
1
|
+
import getGraphqlClient from "../../../lib/get-graphql-client";
|
|
2
2
|
import {
|
|
3
3
|
CopyCartToWishlistInput,
|
|
4
4
|
User,
|
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
WishlistDetailInput,
|
|
9
9
|
WishlistLinesInput,
|
|
10
10
|
} from "@/api-manager/schemas/user.schema";
|
|
11
|
-
import * as translator from "
|
|
12
|
-
import { userMap } from "
|
|
11
|
+
import * as translator from "../e4.translator";
|
|
12
|
+
import { userMap } from "../e4.remaps";
|
|
13
13
|
import { merge } from "object-mapper";
|
|
14
14
|
import { UserApi } from "@/api-manager/types/Datasource";
|
|
15
|
+
import { translateWishList } from "./e4-user.translator";
|
|
15
16
|
|
|
16
17
|
const user: UserApi = {
|
|
17
18
|
async updateUser(user: User): Promise<User> {
|
|
@@ -54,18 +55,15 @@ const user: UserApi = {
|
|
|
54
55
|
addressCount: data?.getAddressCount ?? 0,
|
|
55
56
|
};
|
|
56
57
|
},
|
|
57
|
-
|
|
58
|
-
//TODO: Fix the wishlist return and map to schema.
|
|
59
58
|
async getWishlists(): Promise<Wishlist[]> {
|
|
60
59
|
const e4Api = await getGraphqlClient(true);
|
|
61
60
|
|
|
62
61
|
const { data, errors } = await e4Api.e4WishListListSM();
|
|
63
|
-
|
|
64
62
|
if (errors) {
|
|
65
63
|
throw new Error("Error getting wishlists");
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
return (data
|
|
66
|
+
return translateWishList(data);
|
|
69
67
|
},
|
|
70
68
|
|
|
71
69
|
async getWishlistDetails(input: WishlistDetailInput): Promise<Wishlist> {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { E4WishListListSmQuery } from "../graphqlRequestSdk";
|
|
2
|
+
import { merge } from "object-mapper";
|
|
3
|
+
import { wishListMap } from "./e4-user.remaps";
|
|
4
|
+
import { Wishlist } from "@/api-manager/schemas/user.schema";
|
|
5
|
+
|
|
6
|
+
export const translateWishList = (data: E4WishListListSmQuery) => {
|
|
7
|
+
const e4WishList = data.wishlistList.nodes;
|
|
8
|
+
const translatedData = merge(e4WishList, wishListMap) as Wishlist[];
|
|
9
|
+
return translatedData;
|
|
10
|
+
};
|
package/src/api-manager/index.ts
CHANGED
|
@@ -83,26 +83,41 @@ async function createApiManager() {
|
|
|
83
83
|
);
|
|
84
84
|
|
|
85
85
|
const getDatasource = (entityName: EntityNames) => {
|
|
86
|
-
// Overrides
|
|
86
|
+
// Project-Level Overrides
|
|
87
|
+
let projectOverrides = {};
|
|
87
88
|
if (storefrontOverrides?.[OVERRIDES_FILE]?.["default"]?.[entityName]) {
|
|
88
|
-
|
|
89
|
+
projectOverrides =
|
|
90
|
+
storefrontOverrides[OVERRIDES_FILE]["default"][entityName];
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
// Config-Level Overrides
|
|
91
94
|
const override = config?.overrides?.[entityName];
|
|
95
|
+
|
|
96
|
+
// Build the base entity from api-manager config settings
|
|
97
|
+
let baseEntity = {};
|
|
92
98
|
if (override) {
|
|
93
|
-
|
|
99
|
+
baseEntity =
|
|
94
100
|
secondaryDatasources.find((ds) => ds.name === override) ??
|
|
95
|
-
primaryDatasource
|
|
96
|
-
|
|
101
|
+
primaryDatasource;
|
|
102
|
+
} else {
|
|
103
|
+
baseEntity = primaryDatasource;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Attach Project Overrides to Entity
|
|
107
|
+
for (const key in projectOverrides) {
|
|
108
|
+
baseEntity[entityName][key] = projectOverrides[key];
|
|
97
109
|
}
|
|
98
|
-
|
|
110
|
+
|
|
111
|
+
return baseEntity;
|
|
99
112
|
};
|
|
113
|
+
|
|
100
114
|
const datasources = {};
|
|
101
115
|
|
|
102
116
|
CORE_DATASOURCES.forEach((entityName) => {
|
|
103
117
|
const datasource = getDatasource(entityName as EntityNames);
|
|
104
118
|
datasources[entityName] = datasource[entityName];
|
|
105
119
|
});
|
|
120
|
+
|
|
106
121
|
storefrontOverrides[OVERRIDES_FILE]?.default &&
|
|
107
122
|
Object.keys(storefrontOverrides[OVERRIDES_FILE]["default"]).forEach(
|
|
108
123
|
(key) => {
|
|
@@ -59,7 +59,7 @@ export const productWCategorySchema = productSchema.extend({
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
export const getProductByIdInput = z.object({
|
|
62
|
-
id: z.number().or(z.string()),
|
|
62
|
+
id: z.number().or(z.string()).or(z.undefined()),
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
export const getRelatedProductsInput = z.object({
|
|
@@ -5,7 +5,7 @@ import { createQuery, createApiPath } from "../../create-query";
|
|
|
5
5
|
const apiPath = createApiPath("address", "getStates");
|
|
6
6
|
const config = {
|
|
7
7
|
queryKey: "states",
|
|
8
|
-
disableProperties: ["countryId"]
|
|
8
|
+
disableProperties: ["countryId"] as Array<keyof GetStatesInput>,
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
const getStates = createQuery<GetStatesInput, StateProvinceInfo[]>(
|
|
@@ -13,6 +13,7 @@ import { Mutex } from "async-mutex";
|
|
|
13
13
|
import { manuallyRetrieveSessionCookie } from "@/auth/next-auth-cookie-manager";
|
|
14
14
|
import { getSession } from "next-auth/react";
|
|
15
15
|
import jwt from "jsonwebtoken";
|
|
16
|
+
import signOut from "@/auth/signout";
|
|
16
17
|
|
|
17
18
|
const QUERY_ROUTE = "/api";
|
|
18
19
|
const mutex = new Mutex();
|
|
@@ -104,6 +105,11 @@ export const createQuery = <TInput, TOutput>(
|
|
|
104
105
|
body: JSON.stringify({ input, apiManagerPath }),
|
|
105
106
|
headers,
|
|
106
107
|
});
|
|
108
|
+
|
|
109
|
+
if (result.status === 401) {
|
|
110
|
+
// If unauthenticated , sign out the user
|
|
111
|
+
signOut();
|
|
112
|
+
}
|
|
107
113
|
return await result.json();
|
|
108
114
|
};
|
|
109
115
|
|
|
@@ -191,6 +197,9 @@ export const createQuery = <TInput, TOutput>(
|
|
|
191
197
|
|
|
192
198
|
const currentTime = Math.floor(Date.now() / 1000);
|
|
193
199
|
|
|
200
|
+
// Use this for testing - expires after 60 seconds (1 min) from issued-at time
|
|
201
|
+
// const isExpired = !!decoded && typeof decoded.iat === "number" && currentTime > decoded.iat + 60;
|
|
202
|
+
|
|
194
203
|
const isExpired =
|
|
195
204
|
!!decoded && typeof decoded.exp === "number" && decoded.exp < currentTime;
|
|
196
205
|
|
|
@@ -220,9 +229,7 @@ export const createQuery = <TInput, TOutput>(
|
|
|
220
229
|
headers.append("should-refresh-token", "true");
|
|
221
230
|
}
|
|
222
231
|
// Call do fetch with the headers
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return response.json();
|
|
232
|
+
return await doFetch(input, apiManagerPath, headers);
|
|
226
233
|
} catch (error) {
|
|
227
234
|
// Handle API Error
|
|
228
235
|
console.error("Error fetching data:", error);
|
|
@@ -54,16 +54,16 @@ export interface DatasourceApis {
|
|
|
54
54
|
organization: OrganizationApi;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export abstract class Datasource
|
|
57
|
+
export abstract class Datasource {
|
|
58
58
|
public name: string;
|
|
59
|
-
abstract user: UserApi
|
|
60
|
-
abstract cart: CartApi
|
|
61
|
-
abstract product: ProductApi
|
|
62
|
-
abstract categories: CategoryApi
|
|
63
|
-
abstract order: OrderApi
|
|
64
|
-
abstract address: AddressApi
|
|
65
|
-
abstract organization: OrganizationApi
|
|
66
|
-
abstract session: SessionApi
|
|
59
|
+
abstract user: Partial<UserApi>;
|
|
60
|
+
abstract cart: Partial<CartApi>;
|
|
61
|
+
abstract product: Partial<ProductApi>;
|
|
62
|
+
abstract categories: Partial<CategoryApi>;
|
|
63
|
+
abstract order: Partial<OrderApi>;
|
|
64
|
+
abstract address: Partial<AddressApi>;
|
|
65
|
+
abstract organization: Partial<OrganizationApi>;
|
|
66
|
+
abstract session: Partial<SessionApi>;
|
|
67
67
|
|
|
68
68
|
constructor(name: string) {
|
|
69
69
|
this.name = name;
|
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
// Template: Market1
|
|
2
2
|
import React from "react";
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// ICONS
|
|
5
5
|
import { Light } from "../../Icons/index";
|
|
6
6
|
import { Box } from "@mui/system";
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// GLOBAL COMPONENTS
|
|
9
9
|
import { Carousel } from "@/components/_components/carousel/index";
|
|
10
10
|
import { SectionCreator } from "@/components/_components/section-header/index";
|
|
11
|
-
import
|
|
11
|
+
import ProductCard1Wrapper from "@/components/_components/product-cards/product-card-1/product-card-1-wrapper";
|
|
12
12
|
|
|
13
|
-
//
|
|
13
|
+
// CMS
|
|
14
14
|
import { getImageUrl } from "@/cms/endpoints";
|
|
15
15
|
|
|
16
16
|
// API
|
|
17
17
|
import getProductByIds from "@/services/product/queries/get-product-by-ids";
|
|
18
18
|
import { getQueryClient } from "@/services/get-query-client";
|
|
19
19
|
import { SimpleProduct } from "@msdyn365-commerce/retail-proxy";
|
|
20
|
-
|
|
21
20
|
import { Product, ProductCarouselBlock } from "../../interfaces";
|
|
22
21
|
|
|
23
22
|
type CombinedProduct = Product & SimpleProduct;
|
|
@@ -63,7 +62,7 @@ export default async function ProductCarousel(data: ProductCarouselBlock) {
|
|
|
63
62
|
<Carousel responsive={responsive} height={445} autoplay={true}>
|
|
64
63
|
{combinedData?.map((item: CombinedProduct) => (
|
|
65
64
|
<Box pb={0.6} key={item.id}>
|
|
66
|
-
<
|
|
65
|
+
<ProductCard1Wrapper
|
|
67
66
|
id={item?.RecordId}
|
|
68
67
|
slug={item?.ItemId ?? ""}
|
|
69
68
|
title={item?.Name ?? ""}
|
|
@@ -71,6 +70,7 @@ export default async function ProductCarousel(data: ProductCarouselBlock) {
|
|
|
71
70
|
rating={0}
|
|
72
71
|
imgUrl={getImageUrl(item?.image?.[0]?.formats ?? {})}
|
|
73
72
|
discount={0}
|
|
73
|
+
mode="PLP"
|
|
74
74
|
//uom={item?.uom?.nodes[0].id}
|
|
75
75
|
/>
|
|
76
76
|
</Box>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ChevronRight from "@mui/icons-material/ChevronRight";
|
|
2
2
|
// GLOBAL CUSTOM HOOK
|
|
3
|
-
import useSettings from "@/
|
|
3
|
+
import useSettings from "@/hooks/use-settings";
|
|
4
4
|
// STYLED COMPONENTS
|
|
5
5
|
import { CategoryList, CategoryListItem } from "../styles";
|
|
6
6
|
|
|
@@ -5,7 +5,7 @@ import KeyboardArrowDown from "@mui/icons-material/KeyboardArrowDown";
|
|
|
5
5
|
// GLOBAL CUSTOM COMPONENTS
|
|
6
6
|
import FlexRowCenter from "@/components/_components/flex-box/flex-row-center";
|
|
7
7
|
// GLOBAL CUSTOM HOOKS
|
|
8
|
-
import useOverflowDetect from "@/
|
|
8
|
+
import useOverflowDetect from "@/hooks/use-overflow-detect";
|
|
9
9
|
// LOCAL CUSTOM COMPONENT
|
|
10
10
|
import CategoryList from "./category-list";
|
|
11
11
|
// STYLED COMPONENTS
|
|
@@ -7,8 +7,8 @@ import MenuItem from "@mui/material/MenuItem";
|
|
|
7
7
|
import ArrowLeft from "@mui/icons-material/ArrowLeft";
|
|
8
8
|
import ArrowRight from "@mui/icons-material/ArrowRight";
|
|
9
9
|
// GLOBAL CUSTOM HOOKS
|
|
10
|
-
import useOverflowDetect from "@/
|
|
11
|
-
import useSettings from "@/
|
|
10
|
+
import useOverflowDetect from "@/hooks/use-overflow-detect";
|
|
11
|
+
import useSettings from "@/hooks/use-settings";
|
|
12
12
|
// GLOBAL CUSTOM COMPONENTS
|
|
13
13
|
import { Span } from "@/components/_components/Typography";
|
|
14
14
|
import BazaarCard from "@/components/_components/BazaarCard";
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import Box from "@mui/material/Box";
|
|
3
|
+
import Chip from "@mui/material/Chip";
|
|
4
|
+
import { H6 } from "@/components/_components/Typography";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
ProductDimensionType,
|
|
8
|
+
ProductDimensionValueInventoryAvailability,
|
|
9
|
+
} from "@msdyn365-commerce/retail-proxy";
|
|
10
|
+
import { useTranslations } from "next-intl";
|
|
11
|
+
|
|
12
|
+
// ================================================================
|
|
13
|
+
type Props = {
|
|
14
|
+
dimensionGroups: ProductDimensionValueInventoryAvailability[][];
|
|
15
|
+
selectDimensions: Record<number, ProductDimensionValueInventoryAvailability>;
|
|
16
|
+
handleChangeVariant: (
|
|
17
|
+
dimension: ProductDimensionValueInventoryAvailability,
|
|
18
|
+
) => void;
|
|
19
|
+
showDimensionError?: boolean;
|
|
20
|
+
};
|
|
21
|
+
// ================================================================
|
|
22
|
+
|
|
23
|
+
export default function ProductDimensions({
|
|
24
|
+
dimensionGroups,
|
|
25
|
+
selectDimensions,
|
|
26
|
+
handleChangeVariant,
|
|
27
|
+
showDimensionError,
|
|
28
|
+
}: Props) {
|
|
29
|
+
const t = useTranslations("productDetail.Dimensions");
|
|
30
|
+
const hasSelectedDimension = (
|
|
31
|
+
dimension: ProductDimensionValueInventoryAvailability[],
|
|
32
|
+
) => {
|
|
33
|
+
return dimension.some((dim) =>
|
|
34
|
+
Object.values(selectDimensions).some(
|
|
35
|
+
(selectedDimension) =>
|
|
36
|
+
selectedDimension.DimensionValue?.RecordId ===
|
|
37
|
+
dim.DimensionValue?.RecordId,
|
|
38
|
+
),
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
{Array.isArray(dimensionGroups) &&
|
|
45
|
+
dimensionGroups.map((dimensions, i) => (
|
|
46
|
+
<Box key={i} mb={2}>
|
|
47
|
+
<H6 mb={1}>
|
|
48
|
+
{ProductDimensionType[dimensions[0]?.DimensionTypeValue || 0]}
|
|
49
|
+
</H6>
|
|
50
|
+
{dimensions.map((dimension) => {
|
|
51
|
+
// CHECK STOCK
|
|
52
|
+
const disabled =
|
|
53
|
+
dimension.TotalAvailableInventoryLevelCode !== "AVAIL";
|
|
54
|
+
const selected =
|
|
55
|
+
selectDimensions[dimension.DimensionTypeValue || 0]
|
|
56
|
+
?.DimensionValue === dimension.DimensionValue;
|
|
57
|
+
|
|
58
|
+
// SWATCH DIMENSIONS
|
|
59
|
+
if (
|
|
60
|
+
ProductDimensionType[dimension.DimensionTypeValue || 0] ===
|
|
61
|
+
"Color"
|
|
62
|
+
) {
|
|
63
|
+
return (
|
|
64
|
+
<Chip
|
|
65
|
+
key={dimension.DimensionValue?.RecordId}
|
|
66
|
+
onClick={() => {
|
|
67
|
+
handleChangeVariant(dimension);
|
|
68
|
+
}}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
sx={{
|
|
71
|
+
borderRadius: "100%",
|
|
72
|
+
mr: 1,
|
|
73
|
+
cursor: "pointer",
|
|
74
|
+
backgroundColor: dimension.DimensionValue?.Value,
|
|
75
|
+
width: 32,
|
|
76
|
+
height: 32,
|
|
77
|
+
border: selected ? "2px solid" : "2px solid transparent",
|
|
78
|
+
"&:hover": {
|
|
79
|
+
backgroundColor: dimension.DimensionValue?.Value,
|
|
80
|
+
},
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// TEXT DIMENSIONS
|
|
87
|
+
return (
|
|
88
|
+
<Chip
|
|
89
|
+
key={dimension.DimensionValue?.RecordId}
|
|
90
|
+
label={dimension.DimensionValue?.Value}
|
|
91
|
+
onClick={() => {
|
|
92
|
+
handleChangeVariant(dimension);
|
|
93
|
+
}}
|
|
94
|
+
disabled={disabled}
|
|
95
|
+
sx={{ borderRadius: "4px", mr: 1, cursor: "pointer", mb: 1 }}
|
|
96
|
+
color={selected ? "primary" : "default"}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
})}
|
|
100
|
+
{showDimensionError && !hasSelectedDimension(dimensions) && (
|
|
101
|
+
<H6 color="error.main" mb={1}>
|
|
102
|
+
{t("selectError", {
|
|
103
|
+
dimension:
|
|
104
|
+
ProductDimensionType[
|
|
105
|
+
dimensions[0]?.DimensionTypeValue || 0
|
|
106
|
+
],
|
|
107
|
+
})}
|
|
108
|
+
</H6>
|
|
109
|
+
)}
|
|
110
|
+
</Box>
|
|
111
|
+
))}
|
|
112
|
+
</>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FlexBox } from "@/components/_components/flex-box";
|
|
3
|
+
import Button from "@mui/material/Button";
|
|
4
|
+
import { H3 } from "@/components/_components/Typography";
|
|
5
|
+
import { MdAdd as Add } from "react-icons/md";
|
|
6
|
+
import { MdRemove as Remove } from "react-icons/md";
|
|
7
|
+
import CircularProgress from "@mui/material/CircularProgress";
|
|
8
|
+
import { CartLine } from "@msdyn365-commerce/retail-proxy";
|
|
9
|
+
import { useTranslations } from "next-intl";
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
loading: boolean;
|
|
13
|
+
productCartLine: CartLine | undefined;
|
|
14
|
+
handleCartAmountChange: (amount: number, currentCartLine: CartLine) => void;
|
|
15
|
+
handleAddToCart: () => void;
|
|
16
|
+
isOOS: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function QuantityButtons({
|
|
20
|
+
loading,
|
|
21
|
+
handleAddToCart,
|
|
22
|
+
handleCartAmountChange,
|
|
23
|
+
productCartLine,
|
|
24
|
+
isOOS,
|
|
25
|
+
}: Props) {
|
|
26
|
+
const t = useTranslations("productDetail");
|
|
27
|
+
|
|
28
|
+
if (!productCartLine?.Quantity || productCartLine.Quantity === 0) {
|
|
29
|
+
return (
|
|
30
|
+
<Button
|
|
31
|
+
color="primary"
|
|
32
|
+
variant="contained"
|
|
33
|
+
onClick={handleAddToCart}
|
|
34
|
+
disabled={isOOS}
|
|
35
|
+
>
|
|
36
|
+
{t("btnAddToCart")}
|
|
37
|
+
</Button>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<FlexBox alignItems="center">
|
|
43
|
+
<Button
|
|
44
|
+
size="small"
|
|
45
|
+
sx={{ p: 1 }}
|
|
46
|
+
color="primary"
|
|
47
|
+
variant="outlined"
|
|
48
|
+
disabled={loading}
|
|
49
|
+
onClick={() =>
|
|
50
|
+
handleCartAmountChange(
|
|
51
|
+
(productCartLine?.Quantity || 0) - 1,
|
|
52
|
+
productCartLine,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
>
|
|
56
|
+
<Remove fontSize="small" />
|
|
57
|
+
</Button>
|
|
58
|
+
|
|
59
|
+
<H3
|
|
60
|
+
fontWeight="600"
|
|
61
|
+
mx={2.5}
|
|
62
|
+
sx={{ width: "20px", display: "flex", justifyContent: "center" }}
|
|
63
|
+
>
|
|
64
|
+
{loading ? (
|
|
65
|
+
<CircularProgress size={"10px"} />
|
|
66
|
+
) : (
|
|
67
|
+
productCartLine.Quantity.toString().padStart(2, "0")
|
|
68
|
+
)}
|
|
69
|
+
</H3>
|
|
70
|
+
|
|
71
|
+
<Button
|
|
72
|
+
size="small"
|
|
73
|
+
sx={{ p: 1 }}
|
|
74
|
+
color="primary"
|
|
75
|
+
variant="outlined"
|
|
76
|
+
disabled={loading}
|
|
77
|
+
onClick={() =>
|
|
78
|
+
handleCartAmountChange(
|
|
79
|
+
(productCartLine?.Quantity || 0) + 1,
|
|
80
|
+
productCartLine,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
>
|
|
84
|
+
<Add fontSize="small" />
|
|
85
|
+
</Button>
|
|
86
|
+
</FlexBox>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -8,7 +8,7 @@ import ArrowRight from "@mui/icons-material/ArrowRight";
|
|
|
8
8
|
// LOCAL CUSTOM COMPONENTS
|
|
9
9
|
import { H2 } from "../Typography";
|
|
10
10
|
import { FlexBetween, FlexBox } from "../flex-box/index";
|
|
11
|
-
import useSettings from "
|
|
11
|
+
import useSettings from "../../../hooks/use-settings";
|
|
12
12
|
|
|
13
13
|
// ===================================================
|
|
14
14
|
interface Props {
|
|
@@ -15,7 +15,7 @@ import { H6 } from "@/components/_components/Typography";
|
|
|
15
15
|
import Scrollbar from "@/components/_components/scrollbar/scrollbar";
|
|
16
16
|
import FlexBox from "@/components/_components/flex-box/flex-box";
|
|
17
17
|
// GLOBAL CUSTOM HOOK
|
|
18
|
-
import useSettings from "@/
|
|
18
|
+
import useSettings from "@/hooks/use-settings";
|
|
19
19
|
// STYLED COMPONENTS
|
|
20
20
|
import {
|
|
21
21
|
BodyWrapper,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Wish List Component Dedupe
|
|
2
|
+
|
|
3
|
+
Found these components inside of the /app directory in storefront,
|
|
4
|
+
seems like someone created duplicates of components that were already
|
|
5
|
+
inside of our client packages wishlist folder.
|
|
6
|
+
|
|
7
|
+
Todo: Audit two folders and amalgamate duplicate code.
|
|
8
|
+
|
|
9
|
+
Date: 2025-07-10
|
|
10
|
+
By: Sam Forderer
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Button, ButtonProps } from "@mui/material";
|
|
5
|
+
import T from "@/components/T";
|
|
6
|
+
import AddToWishlistDialog from "./add-to-wishlist-dialog";
|
|
7
|
+
import { ProductDetails } from "@/api-manager/schemas/product.schema";
|
|
8
|
+
import { Cart } from "@/api-manager/schemas/cart.schema";
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
path: string;
|
|
12
|
+
product?: ProductDetails;
|
|
13
|
+
cart?: Cart;
|
|
14
|
+
variant?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const AddToWishlistButton = (props: Props) => {
|
|
18
|
+
const [openAddListDialog, setOpenAddListDialog] = useState(false);
|
|
19
|
+
|
|
20
|
+
const handleCloseDialog = () => {
|
|
21
|
+
setOpenAddListDialog(false);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const handleCreateNewList = () => {
|
|
25
|
+
setOpenAddListDialog(true);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// conditionally render button styles based on variant (pdp or minicart)
|
|
29
|
+
let style, btnFullwidth;
|
|
30
|
+
let btnColor: ButtonProps["color"];
|
|
31
|
+
let btnVariant: ButtonProps["variant"];
|
|
32
|
+
switch (props.variant) {
|
|
33
|
+
case "minicart":
|
|
34
|
+
style = {
|
|
35
|
+
px: 4,
|
|
36
|
+
};
|
|
37
|
+
btnFullwidth = true;
|
|
38
|
+
btnVariant = "outlined";
|
|
39
|
+
btnColor = "primary";
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
btnFullwidth = false;
|
|
43
|
+
btnVariant = "contained";
|
|
44
|
+
btnColor = "secondary";
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<>
|
|
50
|
+
<Button
|
|
51
|
+
onClick={handleCreateNewList}
|
|
52
|
+
sx={style}
|
|
53
|
+
fullWidth={btnFullwidth}
|
|
54
|
+
color={btnColor}
|
|
55
|
+
>
|
|
56
|
+
<T path={props.path} />
|
|
57
|
+
</Button>
|
|
58
|
+
<AddToWishlistDialog
|
|
59
|
+
open={openAddListDialog}
|
|
60
|
+
handleClose={handleCloseDialog}
|
|
61
|
+
productId={props.product?.RecordId}
|
|
62
|
+
cart={props.cart}
|
|
63
|
+
/>
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default AddToWishlistButton;
|