@evenicanpm/storefront-core 1.3.0 → 1.6.0

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 (188) hide show
  1. package/__mocks__/get-product-by-id.ts +0 -1
  2. package/__mocks__/query-client.ts +1 -1
  3. package/package.json +3 -3
  4. package/src/api-manager/datasources/d365/d365-address.datasource.ts +2 -2
  5. package/src/api-manager/datasources/d365/d365-cart.datasource.ts +4 -5
  6. package/src/api-manager/datasources/d365/d365-categories.datasource.ts +1 -1
  7. package/src/api-manager/datasources/d365/d365-product.datasource.ts +21 -7
  8. package/src/api-manager/datasources/d365/d365-session.datasource.ts +5 -7
  9. package/src/api-manager/datasources/d365/d365-user.datasource.ts +12 -23
  10. package/src/api-manager/datasources/d365/d365.datasource.ts +0 -2
  11. package/src/api-manager/datasources/d365/index.ts +2 -4
  12. package/src/api-manager/datasources/d365/utils/decode-jwt.ts +1 -2
  13. package/src/api-manager/datasources/d365/utils/parse-object.ts +1 -1
  14. package/src/api-manager/datasources/e4/address/e4-address.remaps.ts +2 -2
  15. package/src/api-manager/datasources/e4/address/e4-address.translator.ts +1 -1
  16. package/src/api-manager/datasources/e4/cart/e4-cart.datasource.ts +28 -27
  17. package/src/api-manager/datasources/e4/cart/e4-cart.remaps.ts +1 -1
  18. package/src/api-manager/datasources/e4/cart/e4-cart.translator.ts +1 -1
  19. package/src/api-manager/datasources/e4/categories/e4-categories.translator.ts +20 -20
  20. package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +471 -7
  21. package/src/api-manager/datasources/e4/order/e4-order.remaps.ts +2 -2
  22. package/src/api-manager/datasources/e4/organization/e4-organization.datasource.ts +2 -8
  23. package/src/api-manager/datasources/e4/product/e4-product.remaps.ts +3 -3
  24. package/src/api-manager/datasources/e4/product/e4-product.translator.ts +13 -29
  25. package/src/api-manager/datasources/e4/session/e4-session.datasource.ts +1 -3
  26. package/src/api-manager/datasources/e4/user/e4-user.remaps.ts +2 -4
  27. package/src/api-manager/datasources/e4/utils/unwrap-e4-variants.ts +4 -4
  28. package/src/api-manager/index.ts +1 -1
  29. package/src/api-manager/lib/category-helpers.ts +3 -2
  30. package/src/api-manager/lib/get-graphql-client.ts +1 -2
  31. package/src/api-manager/services/cart/mutations/add-discount-code.ts +2 -2
  32. package/src/api-manager/services/cart/mutations/add-to-cart.ts +2 -2
  33. package/src/api-manager/services/product/server/product.server.ts +1 -1
  34. package/src/api-manager/types/Datasource.ts +4 -9
  35. package/src/auth/auth-options.test.ts +2 -58
  36. package/src/auth/auth-options.ts +1 -2
  37. package/src/auth/next-auth-cookie-manager.ts +0 -2
  38. package/src/auth/refresh-token.ts +3 -8
  39. package/src/auth/signout.ts +1 -1
  40. package/src/cms/blocks/block-manager.tsx +3 -3
  41. package/src/cms/blocks/components/category-card.tsx +1 -1
  42. package/src/cms/blocks/components/category-carousel/index.tsx +3 -1
  43. package/src/cms/blocks/components/footer/index.tsx +2 -2
  44. package/src/cms/blocks/components/footer/sections/footer-links.tsx +2 -2
  45. package/src/cms/blocks/components/footer/sections/footer-logo.tsx +1 -1
  46. package/src/cms/blocks/components/footer/sections/footer-social-links.tsx +10 -2
  47. package/src/cms/blocks/components/hero-carousel/index.tsx +29 -25
  48. package/src/cms/blocks/components/media-card.tsx +1 -1
  49. package/src/cms/blocks/components/product-carousel/index.tsx +1 -1
  50. package/src/cms/blocks/components/product-section-fullwidth/index.tsx +2 -2
  51. package/src/cms/blocks/components/seo/index.tsx +5 -1
  52. package/src/cms/blocks/components/services/index.tsx +1 -1
  53. package/src/cms/blocks/components/services/service-card.tsx +1 -1
  54. package/src/cms/blocks/components/shared/featured-product-card.tsx +4 -1
  55. package/src/cms/blocks/components/shared/product-category-item.tsx +1 -1
  56. package/src/cms/blocks/components/shared/product-grid.tsx +4 -1
  57. package/src/cms/blocks/components/shared/top-categories-card.tsx +5 -1
  58. package/src/cms/blocks/components/shared/top-rating-product-card.tsx +1 -1
  59. package/src/components-v2/BazaarImage.tsx +1 -1
  60. package/src/components-v2/BazaarMenu.tsx +2 -2
  61. package/src/components-v2/BazaarTextField.tsx +3 -3
  62. package/src/components-v2/BorderShadowCard.stories.tsx +1 -1
  63. package/src/components-v2/DropZone.tsx +1 -2
  64. package/src/components-v2/Typography.tsx +12 -12
  65. package/src/components-v2/accordion/accordion-header.tsx +1 -1
  66. package/src/components-v2/categories/category-list/styles.ts +0 -7
  67. package/src/components-v2/categories/category-list-item/category-menu-item.tsx +1 -1
  68. package/src/components-v2/categories/category-menu.tsx +16 -13
  69. package/src/components-v2/categories/mega-menu/mega-menu-1.tsx +1 -1
  70. package/src/components-v2/categories/mega-menu/mega-menu-2.tsx +2 -2
  71. package/src/components-v2/header/components/user.tsx +5 -5
  72. package/src/components-v2/mini-cart/__tests__/cart-item.test.tsx +13 -13
  73. package/src/components-v2/mini-cart/components/bottom-actions.tsx +1 -1
  74. package/src/components-v2/mini-cart/components/cart-item.tsx +3 -3
  75. package/src/components-v2/mini-cart/components/top-header.tsx +2 -2
  76. package/src/components-v2/mobile-navigation/mobile-navigation-bar.tsx +1 -1
  77. package/src/components-v2/nav-link/nav-link-2.tsx +1 -1
  78. package/src/components-v2/nav-link/nav-link-3.tsx +1 -1
  79. package/src/components-v2/nav-link/nav-link.tsx +1 -1
  80. package/src/components-v2/navbar/categories.tsx +3 -1
  81. package/src/components-v2/navbar/category-based-menu/category-based-menu.tsx +2 -2
  82. package/src/components-v2/navbar/mobile-menu/mobile-menu.tsx +11 -3
  83. package/src/components-v2/navbar/nav-list/nav-item-child.tsx +1 -1
  84. package/src/components-v2/navbar/nav-list/nav-list.tsx +118 -80
  85. package/src/components-v2/navbar/navbar.tsx +1 -1
  86. package/src/components-v2/no-records.tsx +1 -1
  87. package/src/components-v2/product-cards/favorite-button.tsx +1 -1
  88. package/src/components-v2/product-cards/product-card/product-card.tsx +5 -7
  89. package/src/components-v2/product-cards/product-card-plp/product-card.tsx +7 -6
  90. package/src/components-v2/product-cards/product-card-plp-list/components/tags.tsx +1 -1
  91. package/src/components-v2/product-cards/product-card-plp-list/product-card.tsx +7 -4
  92. package/src/components-v2/product-cards/product-card-search/product-card.tsx +4 -2
  93. package/src/components-v2/product-cards/product-price.tsx +1 -1
  94. package/src/components-v2/product-cards/product-rating.tsx +1 -1
  95. package/src/components-v2/product-cards/product-title.tsx +1 -1
  96. package/src/components-v2/product-cards/quantity-buttons.tsx +1 -1
  97. package/src/components-v2/product-dialog/compound/product-dialog.tsx +2 -2
  98. package/src/components-v2/product-dialog/index.tsx +1 -1
  99. package/src/components-v2/product-dimensions/compound/dimensions-group-list.tsx +6 -1
  100. package/src/components-v2/product-dimensions/index.tsx +3 -3
  101. package/src/components-v2/product-quantity-buttons/compound/quantity-buttons-root.tsx +1 -1
  102. package/src/components-v2/product-quantity-variants/index.tsx +1 -2
  103. package/src/components-v2/products-view/ProductsList.stories.tsx +0 -2
  104. package/src/components-v2/products-view/compound/products-grid-view.tsx +2 -2
  105. package/src/components-v2/products-view/compound/products-list-view.tsx +3 -3
  106. package/src/components-v2/products-view/index.tsx +2 -2
  107. package/src/components-v2/progress/progress.tsx +1 -1
  108. package/src/components-v2/scrollbar/scrollbar.tsx +1 -1
  109. package/src/components-v2/search-bar/compound/results-suspense.tsx +1 -2
  110. package/src/components-v2/search-bar/compound/results.tsx +6 -3
  111. package/src/components-v2/search-bar/compound/search-bar-root.tsx +4 -2
  112. package/src/components-v2/search-bar/compound/textfield-adornment-category.tsx +25 -27
  113. package/src/components-v2/search-bar/compound/textfield.tsx +0 -1
  114. package/src/components-v2/search-bar/hooks/use-search.ts +1 -1
  115. package/src/components-v2/section/Section.stories.tsx +1 -1
  116. package/src/components-v2/side-nav/side-nav.tsx +1 -1
  117. package/src/components-v2/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-dialog.tsx +1 -1
  118. package/src/components-v2/wishlist-dialogs/add-to-wishlist/compound/add-to-wishlist-list.tsx +2 -2
  119. package/src/components-v2/wishlist-dialogs/add-to-wishlist/hooks/use-add-to-wishlist.ts +2 -0
  120. package/src/components-v2/wishlist-dialogs/add-to-wishlist/index.tsx +11 -13
  121. package/src/components-v2/wishlist-dialogs/create-wishlist/compound/create-wishlist-dialog.tsx +2 -1
  122. package/src/hooks/use-variants.ts +25 -27
  123. package/src/lib/build-detail-url.ts +1 -1
  124. package/src/lib/category-helpers.ts +3 -3
  125. package/src/lib/get-palette-color.ts +35 -15
  126. package/src/lib/hexToLuma.ts +2 -2
  127. package/src/lib/store-hours-helper.ts +5 -5
  128. package/src/pages-v2/account/addresses/address-form.tsx +10 -8
  129. package/src/pages-v2/account/addresses/address-item.tsx +1 -1
  130. package/src/pages-v2/account/orders/order-progress.tsx +1 -1
  131. package/src/pages-v2/account/orders/ordered-products.tsx +1 -1
  132. package/src/pages-v2/account/profile/user-info.tsx +1 -2
  133. package/src/pages-v2/account/wishlist/create-new-list.tsx +1 -0
  134. package/src/pages-v2/account/wishlist/wishlist-item.tsx +2 -1
  135. package/src/pages-v2/blog/blog-card.tsx +1 -1
  136. package/src/pages-v2/cart/__tests__/cart-item.test.tsx +0 -1
  137. package/src/pages-v2/cart/__tests__/checkout-form.test.tsx +0 -8
  138. package/src/pages-v2/cart/cart-item.tsx +86 -77
  139. package/src/pages-v2/cart/checkout-form.tsx +4 -4
  140. package/src/pages-v2/cart/coupon-entry.tsx +3 -3
  141. package/src/pages-v2/cart/estimate-shipping.tsx +6 -6
  142. package/src/pages-v2/checkout/checkout-alt-form/checkout-form.tsx +2 -2
  143. package/src/pages-v2/checkout/checkout-alt-form/checkout-step.tsx +2 -2
  144. package/src/pages-v2/checkout/checkout-alt-form/step-heading.tsx +1 -1
  145. package/src/pages-v2/checkout/checkout-alt-form/steps/address/delivery-address.tsx +12 -10
  146. package/src/pages-v2/checkout/checkout-alt-form/steps/address/new-address-form.tsx +5 -6
  147. package/src/pages-v2/checkout/checkout-alt-form/steps/customer-info/customer-information.tsx +8 -9
  148. package/src/pages-v2/checkout/checkout-alt-form/steps/payment/payment-details.tsx +17 -20
  149. package/src/pages-v2/checkout/checkout-alt-form/steps/shipping/delivery-date.tsx +1 -1
  150. package/src/pages-v2/checkout/checkout-alt-form/steps/shipping/delivery-options.tsx +14 -15
  151. package/src/pages-v2/confirmation/confirmation-summary.tsx +8 -7
  152. package/src/pages-v2/confirmation/ordered-products.tsx +3 -3
  153. package/src/pages-v2/product-details/bopis/pickup-option-select.tsx +1 -1
  154. package/src/pages-v2/product-details/bopis/store-card/shop-card.tsx +1 -1
  155. package/src/pages-v2/product-details/checkbox-label.tsx +1 -1
  156. package/src/pages-v2/product-details/frequently-bought.tsx +1 -1
  157. package/src/pages-v2/product-details/frequently-product-card.tsx +1 -1
  158. package/src/pages-v2/product-details/product-comment.tsx +1 -1
  159. package/src/pages-v2/product-details/product-description.tsx +2 -2
  160. package/src/pages-v2/product-details/product-filter-card.tsx +4 -1
  161. package/src/pages-v2/product-details/product-intro/compound/product-intro-images.tsx +36 -31
  162. package/src/pages-v2/product-details/product-intro/compound/product-intro.tsx +1 -1
  163. package/src/pages-v2/product-details/product-intro/compound/thumbnail-with-skeleton.tsx +1 -1
  164. package/src/pages-v2/product-details/product-intro/utils.ts +2 -4
  165. package/src/pages-v2/product-details/product-review.tsx +1 -1
  166. package/src/pages-v2/product-details/product-tabs.tsx +5 -2
  167. package/src/pages-v2/product-details/related-products.tsx +7 -4
  168. package/src/pages-v2/product-list/breadcrumbs.tsx +1 -1
  169. package/src/pages-v2/product-list/checkbox-label.tsx +1 -1
  170. package/src/pages-v2/product-list/facet-group.tsx +1 -1
  171. package/src/pages-v2/product-list/list-filter.tsx +2 -2
  172. package/src/pages-v2/product-list/product-list-context.tsx +5 -1
  173. package/src/pages-v2/product-list/product-list-view.tsx +4 -1
  174. package/src/pages-v2/product-list/product-rating.tsx +4 -1
  175. package/src/pages-v2/product-list/quick-view-dialog-content.tsx +2 -4
  176. package/src/pages-v2/product-list/quick-view-dialog.tsx +5 -1
  177. package/src/pages-v2/product-list/range-filter.tsx +0 -1
  178. package/src/pages-v2/product-list/scrollbar.tsx +1 -1
  179. package/src/pages-v2/product-list/side-nav.tsx +1 -1
  180. package/src/pages-v2/product-list/swatch.tsx +12 -7
  181. package/src/pages-v2/product-list/utils/getCategoryFilterFromSlug.ts +33 -68
  182. package/src/pages-v2/product-list/utils/product-list-helper.ts +1 -1
  183. package/src/pages-v2/product-list/utils/search-for-category.ts +2 -2
  184. package/src/pages-v2/quickorder/order-upload.tsx +90 -76
  185. package/src/pages-v2/quickorder/provider.tsx +6 -4
  186. package/src/pages-v2/quickorder/quick-order-row.tsx +1 -1
  187. package/src/pages-v2/quickorder/quick-order.tsx +28 -21
  188. package/src/providers/notifications/notification-context.tsx +0 -1
@@ -55,7 +55,7 @@ const salesLinesProduct = {
55
55
  selectedDimensions: {
56
56
  key: "Dimensions",
57
57
  transform: (dimensions: SelectedDimension[]) => {
58
- if (!(dimensions && dimensions.length)) return [];
58
+ if (!dimensions?.length) return [];
59
59
 
60
60
  const typeMap: Record<string, number> = {
61
61
  Color: 1,
@@ -81,7 +81,7 @@ export const orderDetailsMap = {
81
81
  lines: {
82
82
  key: "SalesLines",
83
83
  transform: (lines: OrderLine[]) => {
84
- if (!(lines && lines.length)) return [];
84
+ if (!lines?.length) return [];
85
85
  return lines.map((line) => {
86
86
  return {
87
87
  Product: merge(line, salesLinesProduct),
@@ -1,17 +1,11 @@
1
- /* eslint-disable */
2
-
3
1
  import type { ChannelConfiguration } from "@evenicanpm/storefront-core/src/api-manager/schemas/organization.schema";
4
2
  import type { OrganizationApi } from "@evenicanpm/storefront-core/src/api-manager/types/Datasource";
5
3
 
6
- // Todo: implement these stubs
7
-
8
4
  const organization: OrganizationApi = {
9
5
  getChannelConfiguration: async (): Promise<ChannelConfiguration> => {
10
- // @ts-expect-error: Stub implementation, to be implemented later
11
- return {};
6
+ return {} as ChannelConfiguration;
12
7
  },
13
- // @ts-expect-error: Stub implementation, to be implemented later
14
- getStoresList(input) {
8
+ async getStoresList(input) {
15
9
  console.log(input);
16
10
  return [];
17
11
  },
@@ -34,7 +34,7 @@ export const productMap = {
34
34
  {
35
35
  key: "RecordId",
36
36
  transform: (id: string) => {
37
- if (id !== null) return parseInt(id);
37
+ if (id !== null) return Number.parseInt(id);
38
38
  },
39
39
  },
40
40
  ],
@@ -54,13 +54,13 @@ export const productPricesMap = {
54
54
  {
55
55
  key: "ProductId",
56
56
  transform: (id: string) => {
57
- if (id !== null) return parseInt(id);
57
+ if (id !== null) return Number.parseInt(id);
58
58
  },
59
59
  },
60
60
  {
61
61
  key: "ListingId",
62
62
  transform: (id: string) => {
63
- if (id !== null) return parseInt(id);
63
+ if (id !== null) return Number.parseInt(id);
64
64
  },
65
65
  },
66
66
  ],
@@ -88,7 +88,7 @@ export const generateCategoryFilterString = async (
88
88
 
89
89
  const categoryMap = new Map<string, E4Category>();
90
90
  e4Categories.forEach((cat) => {
91
- if (cat && cat.id) {
91
+ if (cat?.id) {
92
92
  categoryMap.set(cat.id, cat);
93
93
  }
94
94
  });
@@ -160,21 +160,22 @@ export const productListInputTransform = async (
160
160
  let sortName =
161
161
  input?.searchState?.queryResultSettings?.Sorting?.Columns?.[0]?.ColumnName?.toLowerCase();
162
162
  // No relevance sort in e4 API.
163
- sortName =
164
- sortName === "relevance"
165
- ? ""
166
- : sortDesc
167
- ? `${sortName}-hl`
168
- : `${sortName}-lh`;
163
+ if (sortName === "relevance") {
164
+ sortName = "";
165
+ } else if (sortDesc) {
166
+ sortName = `${sortName}-hl`;
167
+ } else {
168
+ sortName = `${sortName}-lh`;
169
+ }
169
170
 
170
171
  return {
171
172
  categoryId: input?.categoryId ? String(input?.categoryId) : "1",
172
173
  categoryFilter,
173
174
  top: input?.searchState?.queryResultSettings?.Paging?.Top || 20,
174
175
  skip: input?.searchState?.queryResultSettings?.Paging?.Skip || 0,
175
- sort: sortName ? sortName : null,
176
+ sort: sortName || null,
176
177
  sortDesc,
177
- filters: filters && filters.length ? filters : [],
178
+ filters: filters?.length ? filters : [],
178
179
  search: input?.searchState?.searchTerm,
179
180
  };
180
181
  };
@@ -198,7 +199,7 @@ export const refinersTransformValidate = (
198
199
  "#Microsoft.Dynamics.Commerce.Runtime.DataModel.ProductRefiner";
199
200
  const ODATA_TYPE_REFINER_VALUE =
200
201
  "#Microsoft.Dynamics.Commerce.Runtime.DataModel.ProductRefinerValue";
201
- const DIMENSION_ATTRIBUTE_IDS = ["size", "color", "style"];
202
+ const DIMENSION_ATTRIBUTE_IDS = new Set(["size", "color", "style"]);
202
203
 
203
204
  // Treat price filter as list filter (DisplayTemplateValue as 0) for now, until e4 API supports price bar filtering.
204
205
  return rawFilters
@@ -253,7 +254,7 @@ export const refinersTransformValidate = (
253
254
  })
254
255
  .filter((value): value is TransformedRefinerValue => value !== null);
255
256
 
256
- const isDimension = DIMENSION_ATTRIBUTE_IDS.includes(
257
+ const isDimension = DIMENSION_ATTRIBUTE_IDS.has(
257
258
  rawRefiner.AttributeId?.toLowerCase() ?? "",
258
259
  );
259
260
 
@@ -310,7 +311,7 @@ export const productPricesTransformValidate = (
310
311
  (p) => merge(p, productPricesMap) as ProductPrice,
311
312
  );
312
313
  try {
313
- translatedData.map((pp: ProductPrice) => productPriceSchema.parse(pp));
314
+ translatedData.forEach((pp: ProductPrice) => productPriceSchema.parse(pp));
314
315
  } catch (e) {
315
316
  console.warn("ProductPrice data does not match retail-proxy schema", e);
316
317
  }
@@ -325,22 +326,5 @@ export const productWithCategoryTransformValidate = (
325
326
  productMap,
326
327
  ) as ProductSearchResult;
327
328
 
328
- // const productWithCategory = {
329
- // ...productTransform,
330
- // // Category: categoryTransformValidate([
331
- // // productData?.category?.[0],
332
- // // ] as E4Category[]),
333
- // } as unknown as ProductSearchResult;
334
-
335
- // try {
336
- // productWCategorySchema.parse(productWithCategory);
337
- // } catch (e) {
338
- // console.warn(
339
- // "Product data with category does not match retail-proxy schema",
340
- // e
341
- // );
342
- // throw e;
343
- // }
344
-
345
329
  return productTransform;
346
330
  };
@@ -34,9 +34,7 @@ const session = {
34
34
  return !!data?.customerLogout?.success;
35
35
  },
36
36
  async getSession(): Promise<Session> {
37
- if (true) {
38
- throw new Error("Error reading e4 session");
39
- }
37
+ throw new Error("Error reading e4 session");
40
38
  },
41
39
  };
42
40
 
@@ -19,9 +19,7 @@ import { merge } from "object-mapper";
19
19
  export const userMap = {
20
20
  id: {
21
21
  key: "RecordId",
22
- transform: (id: string) => {
23
- return Number(id);
24
- },
22
+ transform: Number,
25
23
  },
26
24
  code: {
27
25
  // we don't need AccountNumber in e4 storefront,
@@ -38,7 +36,7 @@ export const userMap = {
38
36
  addresses: {
39
37
  key: "Addresses",
40
38
  transform: (addresses: E4Address[], source: Me) => {
41
- if (!(addresses && addresses?.length)) return [];
39
+ if (!addresses?.length) return [];
42
40
  const mapped = merge(addresses || [], addressMap) as Address[];
43
41
  return mapped.map((address: Address) => ({
44
42
  ...address,
@@ -27,7 +27,7 @@ type UnwrappedAttribute = {
27
27
  export default function unwrapE4Variants(
28
28
  variants: Variants[],
29
29
  ): UnwrappedAttribute[][] {
30
- if (!(variants && variants?.length)) return [];
30
+ if (!variants?.length) return [];
31
31
  const attrs: UnwrappedAttribute[] = variants.flatMap(
32
32
  (v) =>
33
33
  v.Attributes?.map((attr: Maybe<Attributes>) => {
@@ -47,9 +47,7 @@ export default function unwrapE4Variants(
47
47
  (group) => group[0]?.DimensionTypeValue === item.DimensionTypeValue,
48
48
  );
49
49
 
50
- if (!group) {
51
- acc.push([item]);
52
- } else {
50
+ if (group) {
53
51
  const existingDimension = group.find(
54
52
  (attribute) =>
55
53
  attribute.DimensionValue.Value === item.DimensionValue.Value,
@@ -60,6 +58,8 @@ export default function unwrapE4Variants(
60
58
  } else if (!existingDimension.ProductIds.includes(item.ProductIds[0])) {
61
59
  existingDimension.ProductIds.push(item.ProductIds[0]);
62
60
  }
61
+ } else {
62
+ acc.push([item]);
63
63
  }
64
64
 
65
65
  return acc;
@@ -53,7 +53,7 @@ async function createApiManager() {
53
53
  Config.parse(rawConfig);
54
54
  } catch (error) {
55
55
  if (error instanceof z.ZodError) {
56
- error.issues;
56
+ console.error("Config validation failed:", error.issues);
57
57
  }
58
58
  }
59
59
 
@@ -86,7 +86,8 @@ const generateUrl = (
86
86
  );
87
87
  }
88
88
  categoryNames.unshift(currentCategory.RecordId.toString());
89
- return categoryNames.reverse().join("/");
89
+
90
+ return categoryNames.slice().reverse().join("/");
90
91
  };
91
92
 
92
93
  const getFriendlyName = (
@@ -99,7 +100,7 @@ const getFriendlyName = (
99
100
  (item) => item?.Language?.toLowerCase() === locale?.toLowerCase(),
100
101
  );
101
102
  }
102
- return nameTranslation && nameTranslation.Text;
103
+ return nameTranslation?.Text;
103
104
  };
104
105
 
105
106
  const normalizeCategoryName = (string_: string) =>
@@ -4,7 +4,6 @@ import { getSdk } from "@evenicanpm/storefront-core/src/api-manager/datasources/
4
4
  import { GraphQLClient } from "graphql-request";
5
5
  import { cookies } from "next/headers";
6
6
  import { getServerSession } from "next-auth/next";
7
- // TODO: Get auth inside client packages
8
7
  import authOptions from "@/auth/auth-options";
9
8
 
10
9
  /**
@@ -30,7 +29,7 @@ const getGraphQLClient = async (withAuth = false) => {
30
29
 
31
30
  if (withAuth) {
32
31
  const session = await getServerSession(authOptions());
33
- if (session && session?.accessToken) {
32
+ if (session?.accessToken) {
34
33
  headers["Authorization"] = `Bearer ${session.accessToken}`;
35
34
  }
36
35
  }
@@ -16,8 +16,8 @@ const useAddDiscountCode = () => {
16
16
  const { setNotification } = useNotification();
17
17
  const t = useTranslations("Cart.Messages");
18
18
  return useCreateMutation<ManageDiscountCodeInput, Cart>(apiPath, {
19
- onSuccess: async (result) => {
20
- await getCart.setQueryData(queryClient, result);
19
+ onSuccess: (result) => {
20
+ getCart.setQueryData(queryClient, result);
21
21
  },
22
22
  onError: () => {
23
23
  setNotification({
@@ -17,8 +17,8 @@ const useAddToCart = () => {
17
17
  const queryClient = useQueryClient();
18
18
 
19
19
  return useCreateMutation<AddToCartInput, Cart>(apiPath, {
20
- onSuccess: async (result, input) => {
21
- await getCart.setQueryData(queryClient, result);
20
+ onSuccess: (result, input) => {
21
+ getCart.setQueryData(queryClient, result);
22
22
 
23
23
  // Handle single cart line
24
24
  if (input.cartLine.length === 1) {
@@ -11,6 +11,6 @@ export const searchProductsServer = async (
11
11
  const apiManager = await createApiManager();
12
12
  const searchProducts = apiManager.product.searchProducts
13
13
  ? apiManager.product.searchProducts(input)
14
- : Promise.reject("product.searchProducts() is not defined");
14
+ : Promise.reject(new Error("product.searchProducts() is not defined"));
15
15
  return parseObject(searchProducts);
16
16
  };
@@ -23,10 +23,6 @@ import type {
23
23
  SessionInitInput,
24
24
  } from "@evenicanpm/storefront-core/src/api-manager/schemas/session.schema";
25
25
  import type * as userSchemas from "@evenicanpm/storefront-core/src/api-manager/schemas/user.schema";
26
- import type {
27
- User,
28
- UserCounts,
29
- } from "@evenicanpm/storefront-core/src/api-manager/schemas/user.schema";
30
26
  import type {
31
27
  CardPaymentAcceptPoint,
32
28
  CardPaymentAcceptResult,
@@ -92,7 +88,7 @@ export interface OrganizationApi {
92
88
  export interface UserApi {
93
89
  updateUser(user: userSchemas.User): Promise<userSchemas.User>;
94
90
  getUser(): Promise<userSchemas.User | null>;
95
- getUserCounts(userId: string): Promise<UserCounts>;
91
+ getUserCounts(userId: string): Promise<userSchemas.UserCounts>;
96
92
  // wishlists
97
93
  getWishlists(): Promise<userSchemas.Wishlist[]>;
98
94
  getWishlistDetails(
@@ -115,9 +111,9 @@ export interface UserApi {
115
111
  copyCartToWishlist(
116
112
  input: userSchemas.CopyCartToWishlistInput,
117
113
  ): Promise<userSchemas.Wishlist>;
118
- updateUser(user: User): Promise<User>;
119
- getUser(): Promise<User | null>;
120
- getUserCounts(userId: string): Promise<UserCounts>;
114
+ updateUser(user: userSchemas.User): Promise<userSchemas.User>;
115
+ getUser(): Promise<userSchemas.User | null>;
116
+ getUserCounts(userId: string): Promise<userSchemas.UserCounts>;
121
117
  }
122
118
 
123
119
  /**
@@ -129,7 +125,6 @@ export interface SessionApi {
129
125
  getSession(): Promise<Session>;
130
126
  }
131
127
 
132
- // export interface Datasource extends Datasource {};
133
128
  /**
134
129
  * Cart Data Actions
135
130
  */
@@ -3,15 +3,6 @@ import type { JWT } from "next-auth/jwt";
3
3
  import { describe, expect, it } from "vitest";
4
4
  import authOptions, { type ExtendedJwtToken } from "@/auth/auth-options";
5
5
 
6
- // vi.mock("./aadb2c-provider", () => ({
7
- // azureADB2CProvider: {},
8
- // refreshAccessToken: vi.fn(async (token) => ({
9
- // ...token,
10
- // accessToken: "newAccessToken",
11
- // accessTokenExpiresAt: Date.now() + 3600 * 1000,
12
- // })),
13
- // }));
14
-
15
6
  describe("next-auth option callbacks", () => {
16
7
  describe("jwt callback", () => {
17
8
  it("should store accessToken and refreshToken when account and profile are available", async () => {
@@ -33,11 +24,7 @@ describe("next-auth option callbacks", () => {
33
24
  };
34
25
 
35
26
  let newToken;
36
- if (
37
- authOptions &&
38
- authOptions().callbacks &&
39
- authOptions().callbacks.jwt
40
- ) {
27
+ if (authOptions()?.callbacks?.jwt) {
41
28
  newToken = await authOptions().callbacks.jwt({
42
29
  token,
43
30
  user: { id: "123" },
@@ -51,45 +38,6 @@ describe("next-auth option callbacks", () => {
51
38
  expect(newToken?.accessTokenExpiresAt).toBe(token.accessTokenExpiresAt); // We're not setting this or using it anymore
52
39
  expect(newToken?.refreshTokenExpiresAt).toBe(token.refreshTokenExpiresAt); // We're not setting this or using it anymore);
53
40
  });
54
-
55
- // it("should return token if access token is not expired", async () => {
56
- // const token: JWT = {
57
- // accessToken: "validToken",
58
- // accessTokenExpiresAt: Date.now() + 3600 * 1000,
59
- // refreshToken: "",
60
- // refreshTokenExpiresAt: 0
61
- // };
62
-
63
- // let newToken;
64
- // if (authOptions && authOptions.callbacks && authOptions.callbacks.jwt) {
65
- // newToken = await authOptions.callbacks.jwt({ token, user: { id: '123' }, account: null });
66
- // }
67
-
68
- // expect(newToken).toEqual(token);
69
- // });
70
-
71
- // it("should return error if refresh token is expired", async () => {
72
- // const token = { accessTokenExpiresAt: Date.now() - 1000, refreshTokenExpiresAt: Date.now() - 1000 };
73
-
74
- // let newToken;
75
- // if (authOptions && authOptions.callbacks && authOptions.callbacks.jwt) {
76
- // newToken = await authOptions.callbacks.jwt({ token, user: { id: '123' }, account: null });
77
- // }
78
-
79
- // expect(newToken?.error).toBe("RefreshTokenExpired");
80
- // });
81
-
82
- // it("should refresh access token if access token is expired but refresh token is valid", async () => {
83
- // const token = { accessToken: "oldToken", accessTokenExpiresAt: Date.now() - 1000, refreshToken: "validRefreshToken", refreshTokenExpiresAt: Date.now() + 3600 * 1000 };
84
-
85
- // let newToken;
86
- // if (authOptions && authOptions.callbacks && authOptions.callbacks.jwt) {
87
- // newToken = await authOptions.callbacks.jwt({ token, user: { id: '123' }, account: null });
88
- // }
89
-
90
- // // expect(refreshAccessToken).toHaveBeenCalledWith(token);
91
- // expect(newToken?.accessToken).toBe("newAccessToken");
92
- // });
93
41
  });
94
42
 
95
43
  describe("session callback", () => {
@@ -103,11 +51,7 @@ describe("next-auth option callbacks", () => {
103
51
  error: "SomeError",
104
52
  };
105
53
  let newSession: Session | null;
106
- if (
107
- authOptions &&
108
- authOptions().callbacks &&
109
- authOptions().callbacks.session
110
- ) {
54
+ if (authOptions()?.callbacks?.session) {
111
55
  newSession = await authOptions().callbacks.session({
112
56
  session,
113
57
  token,
@@ -1,4 +1,3 @@
1
- // import { getSecret } from "@evenicanpm/common-core/src/lib/keyvault";
2
1
  import { deleteCartCookie } from "@evenicanpm/storefront-core/src/lib/cart-cookie-handler";
3
2
  import type {
4
3
  GetServerSidePropsContext,
@@ -20,7 +19,7 @@ export type ExtendedSession = Session & { token?: JWT };
20
19
 
21
20
  const createAuthOptions = (): NextAuthOptions => {
22
21
  const providers = getProviders();
23
- const secret = process.env.NEXTAUTH_SECRET; //getSecret("NEXTAUTH_SECRET");
22
+ const secret = process.env.NEXTAUTH_SECRET; // internally used by next-auth, shouldn't be from key vault.
24
23
  return {
25
24
  providers,
26
25
  secret,
@@ -7,8 +7,6 @@ import type { ExtendedJwtToken } from "@/auth/auth-options";
7
7
  const NEXT_AUTH_COOKIE_NAME = "next-auth.session-token";
8
8
  const SECURE_COOKIE_PREFIX = "__Secure-";
9
9
 
10
- // import { getSecret } from "@evenicanpm/common-core/src/lib/keyvault";
11
-
12
10
  /**
13
11
  * Retrieves and decodes the NextAuth session cookie from the request.
14
12
  *
@@ -1,11 +1,7 @@
1
1
  "use server";
2
2
  import jwt from "jsonwebtoken";
3
3
  import type { JWT } from "next-auth/jwt";
4
- // TODO: what is going on here?
5
- import type {
6
- AuthorizationEndpointHandler,
7
- OAuthConfig,
8
- } from "next-auth/providers";
4
+ import type { AuthorizationEndpointHandler } from "next-auth/providers";
9
5
  import type { ExtendedJwtToken } from "@/auth/auth-options";
10
6
  import { getProviders } from "@/auth/providers";
11
7
 
@@ -23,8 +19,7 @@ const checkAccessTokenExpiry = async (
23
19
  token.error = "Token Invalid";
24
20
  return { token, isRefreshed: false };
25
21
  }
26
- // console.log('Token Expires:', new Date(decryptedToken.exp! * 1000).toLocaleString());
27
- // console.log('Time now', new Date(Date.now()).toLocaleString());
22
+
28
23
  if (!decryptedToken.exp) {
29
24
  token.error = "noExpiryOnToken";
30
25
  return {
@@ -57,7 +52,7 @@ const checkAccessTokenExpiry = async (
57
52
  //Find the provider that matches the provider cookie
58
53
  const matchedProvider = providers.find(
59
54
  (p) => p.id.toLowerCase() === token.provider.toLowerCase(),
60
- ) as OAuthConfig<Record<string, unknown>> | undefined;
55
+ );
61
56
  if (!matchedProvider) {
62
57
  token.error = "NoProviderMatch";
63
58
  return {
@@ -37,7 +37,7 @@ const signOut = async (): Promise<void> => {
37
37
  throw new Error(`Failed to sign out: ${response.statusText}`);
38
38
  }
39
39
 
40
- window.location.href = "/";
40
+ globalThis.location.href = "/";
41
41
  } catch (error) {
42
42
  console.error(error);
43
43
  }
@@ -37,11 +37,11 @@ const componentMap = {
37
37
  const getBlockComponent: <B extends Block>(
38
38
  block: B,
39
39
  idx: number,
40
- ) => JSX.Element | null = (block: Block, idx: number) => {
40
+ ) => JSX.Element | null = (block: Block) => {
41
41
  const Component = componentMap[
42
42
  block.__component as keyof typeof componentMap
43
43
  ] as React.ComponentType<Block>;
44
- return <Component key={`block-${idx}`} {...block} />;
44
+ return <Component key={`block-${block.id}`} {...block} />;
45
45
  };
46
46
 
47
47
  interface BlockManagerProps {
@@ -55,7 +55,7 @@ const BlockManager: React.FC<BlockManagerProps> = ({ blocks }) => {
55
55
  {blocks.map((block, idx) => {
56
56
  const isLast = idx === blocks.length - 1;
57
57
  return (
58
- <Box key={`block-wrapper-${idx}`} mb={isLast ? 0 : 6}>
58
+ <Box key={`block-wrapper-${block.id}`} mb={isLast ? 0 : 6}>
59
59
  {getBlockComponent(block, idx)}
60
60
  </Box>
61
61
  );
@@ -11,7 +11,7 @@ interface Props {
11
11
  imgUrl: string;
12
12
  }
13
13
 
14
- export default function CategoryCard({ name, imgUrl }: Props) {
14
+ export default function CategoryCard({ name, imgUrl }: Readonly<Props>) {
15
15
  return (
16
16
  <Card sx={{ width: "100%", boxShadow: 10 }}>
17
17
  <Box
@@ -13,7 +13,9 @@ import CategoryIcon from "@/cms/blocks/icons/components/category";
13
13
  import type { CategoryCarouselBlock } from "@/cms/blocks/interfaces";
14
14
  import { getImageUrl } from "@/cms/endpoints";
15
15
 
16
- export default function CategoryCarousel(data: CategoryCarouselBlock) {
16
+ export default function CategoryCarousel(
17
+ data: Readonly<CategoryCarouselBlock>,
18
+ ) {
17
19
  const responsive = [
18
20
  { breakpoint: 959, settings: { slidesToShow: 2 } },
19
21
  { breakpoint: 650, settings: { slidesToShow: 1 } },
@@ -59,7 +59,7 @@ const Footer = async () => {
59
59
  const data = await fetchFooterData();
60
60
  const sections = data?.data?.sections;
61
61
 
62
- if (!(sections && sections.length)) {
62
+ if (!sections?.length) {
63
63
  return null;
64
64
  }
65
65
 
@@ -77,7 +77,7 @@ const Footer = async () => {
77
77
  py={{ sm: 10, xs: 4 }}
78
78
  >
79
79
  <Grid container spacing={6}>
80
- {sections.map(getBlockComponent)}
80
+ {sections.map((block, index) => getBlockComponent(block, index))}
81
81
  </Grid>
82
82
  </Box>
83
83
  </Box>
@@ -18,8 +18,8 @@ const Links = (props: Props) => {
18
18
  <Grid size={{ lg: 3, md: 6, sm: 6, xs: 12 }}>
19
19
  <Heading>{props.title}</Heading>
20
20
  <Box>
21
- {props?.links?.map((item, ind) => (
22
- <Fragment key={ind}>
21
+ {props?.links?.map((item) => (
22
+ <Fragment key={item.slug}>
23
23
  <StyledLink isDark={props.isDark} href={`pages/${item.slug}`}>
24
24
  {item.title}
25
25
  </StyledLink>
@@ -11,7 +11,7 @@ interface Props {
11
11
  description: string;
12
12
  }
13
13
 
14
- export default function LogoSection(props: Props) {
14
+ export default function LogoSection(props: Readonly<Props>) {
15
15
  const theme = useTheme();
16
16
  const downMd = useMediaQuery(theme.breakpoints.down(1150)); // same like in header
17
17
  return (
@@ -36,7 +36,10 @@ interface Props {
36
36
  socials?: SocialLink[];
37
37
  }
38
38
 
39
- export default function SocialLinks({ variant = "light", socials }: Props) {
39
+ export default function SocialLinks({
40
+ variant = "light",
41
+ socials,
42
+ }: Readonly<Props>) {
40
43
  return (
41
44
  <FlexBox className="flex" mx={-0.625}>
42
45
  {socials?.map(({ name, link }, i) => {
@@ -58,7 +61,12 @@ export default function SocialLinks({ variant = "light", socials }: Props) {
58
61
  }
59
62
 
60
63
  return (
61
- <a href={link} target="_blank" rel="noreferrer noopenner" key={i}>
64
+ <a
65
+ href={link}
66
+ target="_blank"
67
+ rel="noreferrer noopenner"
68
+ key={name + i}
69
+ >
62
70
  <StyledIconButton variant={variant}>
63
71
  {Icon && <Icon fontSize="inherit" className="icon" />}
64
72
  </StyledIconButton>