@evenicanpm/storefront-core 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/package.json +3 -2
  2. package/src/api-manager/datasources/d365/d365-address.datasource.ts +1 -1
  3. package/src/api-manager/datasources/d365/d365-user.datasource.ts +0 -19
  4. package/src/api-manager/datasources/e4/e4-cart.datasource.ts +2 -2
  5. package/src/api-manager/datasources/e4/e4-product.datasource.ts +19 -4
  6. package/src/api-manager/datasources/e4/e4.datasource.ts +1 -2
  7. package/src/api-manager/datasources/e4/e4.remaps.ts +24 -0
  8. package/src/api-manager/datasources/e4/e4.translator.ts +17 -0
  9. package/src/api-manager/datasources/e4/{e4-user.datasource.ts → user/e4-user.datasource.ts} +5 -7
  10. package/src/api-manager/datasources/e4/user/e4-user.remaps.ts +4 -0
  11. package/src/api-manager/datasources/e4/user/e4-user.translator.ts +10 -0
  12. package/src/api-manager/index.ts +21 -6
  13. package/src/api-manager/schemas/product.schema.ts +1 -1
  14. package/src/api-manager/services/address/queries/get-states.ts +1 -1
  15. package/src/api-manager/services/create-query.ts +10 -3
  16. package/src/api-manager/types/Datasource.ts +9 -9
  17. package/src/components/Blocks/Components/ProductCarousel/index.tsx +6 -6
  18. package/src/components/_components/navbar/category-based-menu/components/categories.tsx +1 -1
  19. package/src/components/_components/navbar/mega-menu/mega-menu.tsx +1 -1
  20. package/src/components/_components/navbar/nav-list/nav-item-child.tsx +2 -2
  21. package/src/components/_components/product-dimensions/product-dimensions.tsx +114 -0
  22. package/src/components/_components/product-quantity-buttons/product-quantity-buttons.tsx +88 -0
  23. package/src/components/_components/section-header/section-header.tsx +1 -1
  24. package/src/components/_components/settings/setting.tsx +1 -1
  25. package/src/components/_components/wishlist-modal/TODO.md +10 -0
  26. package/src/components/_components/wishlist-modal/add-to-wishlist-button.tsx +68 -0
  27. package/src/components/_components/wishlist-modal/add-to-wishlist-dialog.tsx +161 -0
  28. package/src/components/_components/wishlist-modal/remove-from-wishlist-dialog.tsx +174 -0
  29. package/src/components/_components/wishlist-modal/wishlist-modal-item.tsx +65 -0
  30. package/src/components/countries-input.tsx +1 -0
  31. package/src/hooks/use-cart.tsx +82 -0
  32. package/src/hooks/use-variants.tsx +85 -0
  33. package/src/pages/account/addresses/address-form.tsx +289 -0
  34. package/src/pages/account/addresses/address-item.tsx +104 -0
  35. package/src/pages/account/dashboard-header.tsx +123 -0
  36. package/src/pages/account/navigation.tsx +84 -0
  37. package/src/pages/account/no-records.tsx +20 -0
  38. package/src/pages/account/orders/icons/delivery.tsx +14 -0
  39. package/src/pages/account/orders/icons/package-box.tsx +13 -0
  40. package/src/pages/account/orders/icons/truck-filled.tsx +14 -0
  41. package/src/pages/account/orders/order-progress.tsx +111 -0
  42. package/src/pages/account/orders/order-row.tsx +61 -0
  43. package/src/pages/account/orders/order-summary.tsx +83 -0
  44. package/src/pages/account/orders/ordered-products.tsx +114 -0
  45. package/src/pages/account/profile/profile-button.test.tsx +59 -0
  46. package/src/pages/account/profile/profile-button.tsx +50 -0
  47. package/src/pages/account/profile/user-info.tsx +59 -0
  48. package/src/pages/account/styles.ts +32 -0
  49. package/src/pages/account/table-row.tsx +19 -0
  50. package/src/pages/account/wishlist/create-new-list.tsx +167 -0
  51. package/src/pages/account/wishlist/create-wishlist-button.tsx +40 -0
  52. package/src/pages/account/wishlist/wishlist-item.tsx +82 -0
  53. package/src/pages/blog/pagination.tsx +38 -0
  54. package/src/pages/cart/cart-item.tsx +312 -0
  55. package/src/pages/cart/checkout-form.tsx +122 -0
  56. package/src/pages/cart/coupon-entry.tsx +90 -0
  57. package/src/pages/cart/estimate-shipping.tsx +183 -0
  58. package/src/pages/cart/wrapper.tsx +30 -0
  59. package/src/pages/checkout/checkout-alt-form/address-card.tsx +73 -0
  60. package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +133 -0
  61. package/src/pages/checkout/checkout-alt-form/checkout-step.tsx +120 -0
  62. package/src/pages/checkout/checkout-alt-form/customer-information.tsx +94 -0
  63. package/src/pages/checkout/checkout-alt-form/delivery-address.tsx +170 -0
  64. package/src/pages/checkout/checkout-alt-form/delivery-date.tsx +114 -0
  65. package/src/pages/checkout/checkout-alt-form/delivery-options.tsx +186 -0
  66. package/src/pages/checkout/checkout-alt-form/edit-address-form.tsx +130 -0
  67. package/src/pages/checkout/checkout-alt-form/heading.tsx +50 -0
  68. package/src/pages/checkout/checkout-alt-form/index.ts +1 -0
  69. package/src/pages/checkout/checkout-alt-form/new-address-form.tsx +294 -0
  70. package/src/pages/checkout/checkout-alt-form/node.tsx +9 -0
  71. package/src/pages/checkout/checkout-alt-form/payment-details.tsx +344 -0
  72. package/src/pages/checkout/checkout-alt-form/safe-card-preview-data.tsx +36 -0
  73. package/src/pages/checkout/checkout-alt-form/types.ts +20 -0
  74. package/src/pages/checkout/checkout-alt-summary/cart-item.tsx +39 -0
  75. package/src/pages/checkout/checkout-alt-summary/checkout-alt-summary.tsx +40 -0
  76. package/src/pages/checkout/checkout-alt-summary/index.ts +1 -0
  77. package/src/pages/checkout/checkout-alt-summary/list-item.tsx +31 -0
  78. package/src/pages/confirmation/address.tsx +22 -0
  79. package/src/pages/confirmation/confirmation-summary.tsx +52 -0
  80. package/src/pages/confirmation/ordered-products.tsx +108 -0
  81. package/src/pages/product-details/available-shops.tsx +48 -0
  82. package/src/pages/product-details/bopis/find-in-store-button.tsx +63 -0
  83. package/src/pages/product-details/bopis/find-in-store-modal.tsx +257 -0
  84. package/src/pages/product-details/bopis/pickup-option-select.tsx +127 -0
  85. package/src/pages/product-details/bopis/search-header.tsx +69 -0
  86. package/src/pages/product-details/checkbox-label.tsx +20 -0
  87. package/src/pages/product-details/currency.ts +64 -0
  88. package/src/pages/product-details/frequently-bought.tsx +90 -0
  89. package/src/pages/product-details/frequently-product-card.tsx +60 -0
  90. package/src/pages/product-details/product-comment.tsx +44 -0
  91. package/src/pages/product-details/product-description.tsx +22 -0
  92. package/src/pages/product-details/product-filter-card.tsx +257 -0
  93. package/src/pages/product-details/product-intro/product-intro-images.tsx +87 -0
  94. package/src/pages/product-details/product-intro/product-intro.tsx +250 -0
  95. package/src/pages/product-details/product-review.tsx +131 -0
  96. package/src/pages/product-details/product-tabs.tsx +51 -0
  97. package/src/pages/product-details/related-products.tsx +42 -0
  98. package/src/pages/product-details/types.ts +11 -0
  99. package/src/pages/product-list/breadcrumbs.tsx +39 -0
  100. package/src/pages/product-list/checkbox-label.tsx +20 -0
  101. package/src/pages/product-list/facet-group.tsx +125 -0
  102. package/src/pages/product-list/facet.tsx +36 -0
  103. package/src/pages/product-list/list-filter.tsx +40 -0
  104. package/src/pages/product-list/pagination.tsx +80 -0
  105. package/src/pages/product-list/product-list-context.tsx +302 -0
  106. package/src/pages/product-list/product-list-state.ts +187 -0
  107. package/src/pages/product-list/product-rating.tsx +16 -0
  108. package/src/pages/product-list/quick-view-dialog-content.tsx +205 -0
  109. package/src/pages/product-list/quick-view-dialog.tsx +54 -0
  110. package/src/pages/product-list/range-filter.tsx +125 -0
  111. package/src/pages/product-list/scrollbar.tsx +38 -0
  112. package/src/pages/product-list/search-bar.tsx +162 -0
  113. package/src/pages/product-list/selected-facets.tsx +80 -0
  114. package/src/pages/product-list/side-nav.tsx +49 -0
  115. package/src/pages/product-list/swatch.tsx +80 -0
  116. package/src/pages/product-list/types.ts +18 -0
  117. package/src/pages/product-list/use-product-list.ts +12 -0
  118. package/src/pages/product-list/useCategoryFilter.tsx +26 -0
  119. package/src/pages/product-list/utils/facet-helpers.ts +5 -0
  120. package/src/pages/product-list/utils/generate-breadcrumbs.ts +30 -0
  121. package/src/pages/product-list/utils/getCategoryFilterFromSlug.ts +153 -0
  122. package/src/pages/product-list/utils/product-list-helper.ts +111 -0
  123. package/src/pages/product-list/utils/product-list-types.ts +41 -0
  124. package/src/pages/product-list/utils/search-for-category.ts +76 -0
  125. package/src/pages/product-list/utils/sort-options.ts +44 -0
  126. package/src/pages/product-list/utils/use-previous-refiners.ts +14 -0
  127. package/src/pages/quickorder/order-upload.tsx +150 -0
  128. package/src/pages/quickorder/quick-order-form.tsx +343 -0
  129. package/src/pages/quickorder/quick-order-row.tsx +144 -0
  130. package/tsconfig.json +1 -0
  131. package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +0 -10464
  132. /package/src/{components/_components/hooks/useOverflowDetect.ts → hooks/use-overflow-detect.ts} +0 -0
  133. /package/src/{components/_components/hooks/useSettings.ts → hooks/use-settings.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evenicanpm/storefront-core",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Core module for D365/e4 Headless Storefront",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -31,6 +31,7 @@
31
31
  "@tanstack/react-query": "^5.76.1",
32
32
  "next": "^15.2.1",
33
33
  "next-auth": "^4.24.11",
34
- "typescript": "^5.8.3"
34
+ "typescript": "^5.8.3",
35
+ "object-mapper": "^6.2.0"
35
36
  }
36
37
  }
@@ -70,7 +70,7 @@ class D365AddressDatasource extends D365DatasourceBase implements AddressApi {
70
70
  }
71
71
 
72
72
  const updatedCustomer = await updateAsync(context as any, customer);
73
- const rec = updatedCustomer.Addresses[idx];
73
+ const rec = updatedCustomer?.Addresses?.[idx];
74
74
  if (!rec || rec.RecordId == null) {
75
75
  throw new Error(
76
76
  `Failed to update address Id=${address.Id}. Updated address record with a matching id not found.`,
@@ -282,22 +282,3 @@ class D365UserDatasource extends D365DatasourceBase implements UserApi {
282
282
  }
283
283
  }
284
284
  export default D365UserDatasource;
285
-
286
- type TokenData = {
287
- aud: string;
288
- iss: string;
289
- exp: number;
290
- nbf: number;
291
- oid: string;
292
- sub: string;
293
- emails: string[];
294
- email?: string;
295
- newUser: boolean;
296
- given_name: string;
297
- family_name: string;
298
- tfp: string;
299
- scp: string;
300
- azp: string;
301
- ver: string;
302
- iat: number;
303
- };
@@ -244,8 +244,8 @@ const cart: CartApi = {
244
244
  SalesLines: [],
245
245
  };
246
246
  },
247
- getCartLinesInventory: function (
248
- input: cartSchema.InventorySearchCartLinesCriteriaInput,
247
+ getCartLinesInventory: function(
248
+ _input: cartSchema.InventorySearchCartLinesCriteriaInput,
249
249
  ): Promise<ProductWarehouseInventoryInformation> {
250
250
  throw new Error("Function not implemented.");
251
251
  },
@@ -12,7 +12,8 @@ import {
12
12
  import * as translator from "./e4.translator";
13
13
  import getGraphqlClient from "../../lib/get-graphql-client";
14
14
  import { ProductApi } from "../../types/Datasource";
15
- import { generateCategoryFilterString } from "./e4.translator";
15
+ // See TODO below
16
+ //import { generateCategoryFilterString } from "./e4.translator";
16
17
 
17
18
  const product: ProductApi = {
18
19
  async getProductById(
@@ -61,7 +62,9 @@ const product: ProductApi = {
61
62
  const { data, errors } = await e4Api.productTypeaheadSearchByCategory({
62
63
  input: {
63
64
  search: input.searchText,
64
- categoryId: await generateCategoryFilterString(input.category ?? 0),
65
+ // TODO: investigate why this was added..
66
+ // it's throwing a type error.
67
+ //categoryId: await generateCategoryFilterString(input.category ?? 0),
65
68
  top: 10,
66
69
  skip: 0,
67
70
  },
@@ -152,10 +155,22 @@ const product: ProductApi = {
152
155
  ): Promise<ProductSearchResult[]> {
153
156
  throw new Error(`Function not implemented.${input}`);
154
157
  },
155
- getProductPrices: function (
158
+ async getProductPrices(
156
159
  input: productSchema.ProductPriceInput,
157
160
  ): Promise<productSchema.ProductPrice[]> {
158
- throw new Error("Function not implemented.");
161
+ const e4Api = await getGraphqlClient();
162
+ const { data, errors } = await e4Api.GetProductsByIds({
163
+ ids: input.productIds?.map(String),
164
+ });
165
+
166
+ if (errors) {
167
+ throw new Error("Error getting products");
168
+ }
169
+
170
+ const translatedData = translator.productPricesTransformValidate(
171
+ data?.getProductsByIds as Product[],
172
+ );
173
+ return translatedData;
159
174
  },
160
175
  };
161
176
 
@@ -3,7 +3,7 @@ import getGraphqlClient from "../../lib/get-graphql-client";
3
3
  import { Category as E4Category } from "./graphqlRequestSdk";
4
4
  import * as translator from "./e4.translator";
5
5
  // Data source files
6
- import user from "./e4-user.datasource";
6
+ import user from "./user/e4-user.datasource";
7
7
  import session from "./e4-session.datasource";
8
8
  import { cart } from "./e4-cart.datasource";
9
9
  import order from "./e4-order.datasource";
@@ -14,7 +14,6 @@ import organization from "./e4-organization.datasource";
14
14
 
15
15
  const E4Datasource: PrimaryDatasource = {
16
16
  name: "e4",
17
-
18
17
  user,
19
18
  session,
20
19
  cart,
@@ -63,6 +63,30 @@ export const productMap = {
63
63
  },
64
64
  };
65
65
 
66
+ export const productPricesMap = {
67
+ id: [
68
+ {
69
+ key: "ProductId",
70
+ transform: (id: string) => {
71
+ if (id !== null) return parseInt(id);
72
+ },
73
+ },
74
+ {
75
+ key: "ListingId",
76
+ transform: (id: string) => {
77
+ if (id !== null) return parseInt(id);
78
+ },
79
+ },
80
+ ],
81
+ price: [
82
+ { key: "Price", default: 0 },
83
+ { key: "BasePrice", default: 0 },
84
+ { key: "AdjustedPrice", default: 0 },
85
+ { key: "CustomerContextualPrice", default: 0 },
86
+ ],
87
+ sku: "ItemId",
88
+ };
89
+
66
90
  export const productDetailsMap = {
67
91
  ...productMap,
68
92
  MasterProductId: "MasterProductId",
@@ -4,6 +4,7 @@ import {
4
4
  productSchema,
5
5
  ProductDetails,
6
6
  SearchByCriteriaInput,
7
+ ProductPrice,
7
8
  } from "@/api-manager/schemas/product.schema";
8
9
  import { Address } from "@/api-manager/schemas/address.schema";
9
10
  import { User } from "@/api-manager/schemas/user.schema";
@@ -31,12 +32,14 @@ import {
31
32
  cartMap,
32
33
  productDetailsMap,
33
34
  productMap,
35
+ productPricesMap,
34
36
  addressReverseMap,
35
37
  countriesMap,
36
38
  countryMap,
37
39
  deliveryOptionsMap,
38
40
  } from "./e4.remaps";
39
41
  import { CategoryHierarchy } from "@/lib/category-helpers";
42
+ import { productPriceSchema } from "@/api-manager/schemas/d365.schema";
40
43
 
41
44
  interface TransformedRefinerValue {
42
45
  RefinerRecordId: number;
@@ -338,6 +341,20 @@ export const productTransformValidate = (
338
341
  return translatedData;
339
342
  };
340
343
 
344
+ export const productPricesTransformValidate = (
345
+ products: Product[],
346
+ ): ProductPrice[] => {
347
+ const translatedData = products.map(
348
+ (p) => merge(p, productPricesMap) as ProductPrice,
349
+ );
350
+ try {
351
+ translatedData.map((pp: ProductPrice) => productPriceSchema.parse(pp));
352
+ } catch (e) {
353
+ console.warn("ProductPrice data does not match retail-proxy schema", e);
354
+ }
355
+ return translatedData;
356
+ };
357
+
341
358
  export const productWithCategoryTransformValidate = (
342
359
  productData: Product,
343
360
  ): ProductSearchResult => {
@@ -1,4 +1,4 @@
1
- import getGraphqlClient from "../../lib/get-graphql-client";
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 "./e4.translator";
12
- import { userMap } from "./e4.remaps";
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?.wishlistList?.nodes as Wishlist[]) ?? [];
66
+ return translateWishList(data);
69
67
  },
70
68
 
71
69
  async getWishlistDetails(input: WishlistDetailInput): Promise<Wishlist> {
@@ -0,0 +1,4 @@
1
+ export const wishListMap = {
2
+ "[].id": "[].Id",
3
+ "[].name": "[].Name",
4
+ };
@@ -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
+ };
@@ -83,26 +83,41 @@ async function createApiManager() {
83
83
  );
84
84
 
85
85
  const getDatasource = (entityName: EntityNames) => {
86
- // Overrides from storefront
86
+ // Project-Level Overrides
87
+ let projectOverrides = {};
87
88
  if (storefrontOverrides?.[OVERRIDES_FILE]?.["default"]?.[entityName]) {
88
- return storefrontOverrides[OVERRIDES_FILE]["default"];
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
- return (
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
- return primaryDatasource;
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
- const response = await doFetch(input, apiManagerPath, headers);
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 implements DatasourceApis {
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
- // Custom Icon Component
4
+ // ICONS
5
5
  import { Light } from "../../Icons/index";
6
6
  import { Box } from "@mui/system";
7
7
 
8
- // Global Custom Components
8
+ // GLOBAL COMPONENTS
9
9
  import { Carousel } from "@/components/_components/carousel/index";
10
10
  import { SectionCreator } from "@/components/_components/section-header/index";
11
- import ProductCard1 from "@/components/_components/product-cards/product-card-1/index";
11
+ import ProductCard1Wrapper from "@/components/_components/product-cards/product-card-1/product-card-1-wrapper";
12
12
 
13
- // @/cms
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
- <ProductCard1
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 "@/components/_components/hooks/useSettings";
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 "@/components/_components/hooks/useOverflowDetect";
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 "@/components/_components/hooks/useOverflowDetect";
11
- import useSettings from "@/components/_components/hooks/useSettings";
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
+ }