@akinon/next 1.93.0 → 1.95.0-rc.54

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 (52) hide show
  1. package/CHANGELOG.md +1252 -24
  2. package/__tests__/next-config.test.ts +1 -10
  3. package/__tests__/redirect.test.ts +319 -0
  4. package/api/image-proxy.ts +75 -0
  5. package/api/similar-product-list.ts +84 -0
  6. package/api/similar-products.ts +120 -0
  7. package/components/accordion.tsx +20 -5
  8. package/components/file-input.tsx +65 -3
  9. package/components/input.tsx +2 -0
  10. package/components/link.tsx +16 -12
  11. package/components/modal.tsx +32 -16
  12. package/components/plugin-module.tsx +35 -3
  13. package/components/selected-payment-option-view.tsx +11 -0
  14. package/data/client/checkout.ts +25 -4
  15. package/data/server/basket.ts +72 -0
  16. package/data/server/category.ts +48 -28
  17. package/data/server/flatpage.ts +16 -12
  18. package/data/server/landingpage.ts +16 -12
  19. package/data/server/list.ts +23 -13
  20. package/data/server/product.ts +66 -39
  21. package/data/server/special-page.ts +16 -12
  22. package/data/urls.ts +7 -2
  23. package/hocs/server/with-segment-defaults.tsx +5 -2
  24. package/hooks/use-localization.ts +2 -3
  25. package/hooks/use-loyalty-availability.ts +21 -0
  26. package/instrumentation/node.ts +15 -13
  27. package/jest.config.js +7 -1
  28. package/lib/cache.ts +2 -0
  29. package/middlewares/checkout-provider.ts +1 -1
  30. package/middlewares/complete-gpay.ts +6 -2
  31. package/middlewares/complete-masterpass.ts +7 -2
  32. package/middlewares/default.ts +232 -183
  33. package/middlewares/index.ts +3 -1
  34. package/middlewares/locale.ts +9 -1
  35. package/middlewares/redirection-payment.ts +6 -2
  36. package/middlewares/saved-card-redirection.ts +7 -2
  37. package/middlewares/three-d-redirection.ts +7 -2
  38. package/middlewares/url-redirection.ts +9 -15
  39. package/middlewares/wallet-complete-redirection.ts +203 -0
  40. package/package.json +3 -3
  41. package/plugins.d.ts +10 -0
  42. package/plugins.js +4 -1
  43. package/redux/middlewares/checkout.ts +15 -2
  44. package/redux/reducers/checkout.ts +9 -1
  45. package/sentry/index.ts +54 -17
  46. package/types/commerce/order.ts +1 -0
  47. package/types/index.ts +42 -1
  48. package/utils/app-fetch.ts +7 -2
  49. package/utils/index.ts +34 -10
  50. package/utils/redirect-ignore.ts +35 -0
  51. package/utils/redirect.ts +31 -6
  52. package/with-pz-config.js +1 -5
@@ -1,8 +1,70 @@
1
+ import { useState } from 'react';
1
2
  import { forwardRef } from 'react';
2
- import { FileInputProps } from '../types/index';
3
+ import { useLocalization } from '@akinon/next/hooks';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { FileInputProps } from '../types';
3
6
 
4
7
  export const FileInput = forwardRef<HTMLInputElement, FileInputProps>(
5
- function fileInput(props, ref) {
6
- return <input type="file" {...props} ref={ref} />;
8
+ function FileInput(
9
+ {
10
+ buttonClassName,
11
+ onChange,
12
+ fileClassName,
13
+ fileNameWrapperClassName,
14
+ fileInputClassName,
15
+ ...props
16
+ },
17
+ ref
18
+ ) {
19
+ const { t } = useLocalization();
20
+ const [fileNames, setFileNames] = useState<string[]>([]);
21
+
22
+ const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
23
+ const files = Array.from(event.target.files || []);
24
+ setFileNames(files.map((file) => file.name));
25
+
26
+ if (onChange) {
27
+ onChange(event);
28
+ }
29
+ };
30
+
31
+ return (
32
+ <div className="relative">
33
+ <input
34
+ type="file"
35
+ {...props}
36
+ ref={ref}
37
+ className={twMerge(
38
+ 'absolute inset-0 w-full h-full opacity-0 cursor-pointer',
39
+ fileInputClassName
40
+ )}
41
+ onChange={handleFileChange}
42
+ />
43
+ <button
44
+ type="button"
45
+ className={twMerge(
46
+ 'bg-primary text-white py-2 px-4 text-sm',
47
+ buttonClassName
48
+ )}
49
+ >
50
+ {t('common.file_input.select_file')}
51
+ </button>
52
+ <div
53
+ className={twMerge('mt-1 text-gray-500', fileNameWrapperClassName)}
54
+ >
55
+ {fileNames.length > 0 ? (
56
+ <ul className={twMerge('list-disc pl-4 text-xs', fileClassName)}>
57
+ {fileNames.map((name, index) => (
58
+ <li key={index}>{name}</li>
59
+ ))}
60
+ </ul>
61
+ ) : (
62
+ <span className={twMerge('text-xs', fileClassName)}>
63
+ {t('common.file_input.no_file')}
64
+ </span>
65
+ )}
66
+ </div>
67
+ </div>
68
+ );
7
69
  }
8
70
  );
@@ -1,6 +1,8 @@
1
1
  import clsx from 'clsx';
2
2
  import { forwardRef, FocusEvent, useState, Ref } from 'react';
3
3
  import { Controller } from 'react-hook-form';
4
+
5
+ // @ts-ignore
4
6
  import { PatternFormat, PatternFormatProps } from 'react-number-format';
5
7
  import { InputProps } from '../types';
6
8
  import { twMerge } from 'tailwind-merge';
@@ -10,7 +10,9 @@ type LinkProps = Omit<
10
10
  React.AnchorHTMLAttributes<HTMLAnchorElement>,
11
11
  keyof NextLinkProps
12
12
  > &
13
- NextLinkProps;
13
+ NextLinkProps & {
14
+ href: string;
15
+ };
14
16
 
15
17
  export const Link = ({ children, href, ...rest }: LinkProps) => {
16
18
  const { locale, defaultLocaleValue, localeUrlStrategy } = useLocalization();
@@ -26,19 +28,21 @@ export const Link = ({ children, href, ...rest }: LinkProps) => {
26
28
  return href;
27
29
  }
28
30
 
29
- const pathnameWithoutLocale = href.replace(urlLocaleMatcherRegex, '');
30
- const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
31
-
32
- if (localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales) {
33
- return hrefWithLocale;
34
- } else if (
35
- localeUrlStrategy === LocaleUrlStrategy.HideDefaultLocale &&
36
- locale !== defaultLocaleValue
37
- ) {
38
- return hrefWithLocale;
31
+ if (typeof href === 'string' && !href.startsWith('http')) {
32
+ const pathnameWithoutLocale = href.replace(urlLocaleMatcherRegex, '');
33
+ const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
34
+
35
+ if (localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales) {
36
+ return hrefWithLocale;
37
+ } else if (
38
+ localeUrlStrategy === LocaleUrlStrategy.HideDefaultLocale &&
39
+ locale !== defaultLocaleValue
40
+ ) {
41
+ return hrefWithLocale;
42
+ }
39
43
  }
40
44
 
41
- return href || '#';
45
+ return href;
42
46
  }, [href, defaultLocaleValue, locale, localeUrlStrategy]);
43
47
 
44
48
  return (
@@ -4,16 +4,7 @@ import { ReactPortal } from './react-portal';
4
4
  import { Icon } from './icon';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { useEffect } from 'react';
7
-
8
- export interface ModalProps {
9
- portalId: string;
10
- children?: React.ReactNode;
11
- open?: boolean;
12
- setOpen?: (open: boolean) => void;
13
- title?: React.ReactNode;
14
- showCloseButton?: React.ReactNode;
15
- className?: string;
16
- }
7
+ import { ModalProps } from '../types';
17
8
 
18
9
  export const Modal = (props: ModalProps) => {
19
10
  const {
@@ -23,7 +14,14 @@ export const Modal = (props: ModalProps) => {
23
14
  setOpen,
24
15
  title = '',
25
16
  showCloseButton = true,
26
- className
17
+ className,
18
+ overlayClassName,
19
+ headerWrapperClassName,
20
+ titleClassName,
21
+ closeButtonClassName,
22
+ iconName = 'close',
23
+ iconSize = 16,
24
+ iconClassName
27
25
  } = props;
28
26
 
29
27
  useEffect(() => {
@@ -38,7 +36,12 @@ export const Modal = (props: ModalProps) => {
38
36
 
39
37
  return (
40
38
  <ReactPortal wrapperId={portalId}>
41
- <div className="fixed top-0 left-0 w-screen h-screen bg-primary bg-opacity-60 z-50" />
39
+ <div
40
+ className={twMerge(
41
+ 'fixed top-0 left-0 w-screen h-screen bg-primary bg-opacity-60 z-50',
42
+ overlayClassName
43
+ )}
44
+ />
42
45
  <section
43
46
  className={twMerge(
44
47
  'fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 bg-white',
@@ -46,15 +49,28 @@ export const Modal = (props: ModalProps) => {
46
49
  )}
47
50
  >
48
51
  {(showCloseButton || title) && (
49
- <div className="flex px-6 py-4 border-b border-gray-400">
50
- {title && <h3 className="text-lg font-light">{title}</h3>}
52
+ <div
53
+ className={twMerge(
54
+ 'flex px-6 py-4 border-b border-gray-400',
55
+ headerWrapperClassName
56
+ )}
57
+ >
58
+ {title && (
59
+ <h3 className={twMerge('text-lg font-light', titleClassName)}>
60
+ {title}
61
+ </h3>
62
+ )}
51
63
  {showCloseButton && (
52
64
  <button
53
65
  type="button"
54
66
  onClick={() => setOpen(false)}
55
- className="ml-auto"
67
+ className={twMerge('ml-auto', closeButtonClassName)}
56
68
  >
57
- <Icon name="close" size={16} />
69
+ <Icon
70
+ name={iconName}
71
+ size={iconSize}
72
+ className={iconClassName}
73
+ />
58
74
  </button>
59
75
  )}
60
76
  </div>
@@ -20,7 +20,10 @@ enum Plugin {
20
20
  B2B = 'pz-b2b',
21
21
  Akifast = 'pz-akifast',
22
22
  MultiBasket = 'pz-multi-basket',
23
- SavedCard = 'pz-saved-card'
23
+ SavedCard = 'pz-saved-card',
24
+ Hepsipay = 'pz-hepsipay',
25
+ FlowPayment = 'pz-flow-payment',
26
+ SimilarProducts = 'pz-similar-products'
24
27
  }
25
28
 
26
29
  export enum Component {
@@ -45,7 +48,16 @@ export enum Component {
45
48
  AkifastQuickLoginButton = 'QuickLoginButton',
46
49
  AkifastCheckoutButton = 'CheckoutButton',
47
50
  MultiBasket = 'MultiBasket',
48
- SavedCard = 'SavedCardOption'
51
+ SavedCard = 'SavedCardOption',
52
+ Hepsipay = 'Hepsipay',
53
+ FlowPayment = 'FlowPayment',
54
+ SimilarProductsModal = 'SimilarProductsModal',
55
+ SimilarProductsFilterSidebar = 'SimilarProductsFilterSidebar',
56
+ SimilarProductsResultsGrid = 'SimilarProductsResultsGrid',
57
+ SimilarProductsPlugin = 'SimilarProductsPlugin',
58
+ ProductImageSearchFeature = 'ProductImageSearchFeature',
59
+ ImageSearchButton = 'ImageSearchButton',
60
+ HeaderImageSearchFeature = 'HeaderImageSearchFeature'
49
61
  }
50
62
 
51
63
  const PluginComponents = new Map([
@@ -78,7 +90,21 @@ const PluginComponents = new Map([
78
90
  [Component.AkifastQuickLoginButton, Component.AkifastCheckoutButton]
79
91
  ],
80
92
  [Plugin.MultiBasket, [Component.MultiBasket]],
81
- [Plugin.SavedCard, [Component.SavedCard]]
93
+ [Plugin.SavedCard, [Component.SavedCard]],
94
+ [Plugin.Hepsipay, [Component.Hepsipay]],
95
+ [Plugin.FlowPayment, [Component.FlowPayment]],
96
+ [
97
+ Plugin.SimilarProducts,
98
+ [
99
+ Component.SimilarProductsModal,
100
+ Component.SimilarProductsFilterSidebar,
101
+ Component.SimilarProductsResultsGrid,
102
+ Component.SimilarProductsPlugin,
103
+ Component.ProductImageSearchFeature,
104
+ Component.ImageSearchButton,
105
+ Component.HeaderImageSearchFeature
106
+ ]
107
+ ]
82
108
  ]);
83
109
 
84
110
  const getPlugin = (component: Component) => {
@@ -143,6 +169,12 @@ export default function PluginModule({
143
169
  promise = import(`${'@akinon/pz-multi-basket'}`);
144
170
  } else if (plugin === Plugin.SavedCard) {
145
171
  promise = import(`${'@akinon/pz-saved-card'}`);
172
+ } else if (plugin === Plugin.Hepsipay) {
173
+ promise = import(`${'@akinon/pz-hepsipay'}`);
174
+ } else if (plugin === Plugin.FlowPayment) {
175
+ promise = import(`${'@akinon/pz-flow-payment'}`);
176
+ } else if (plugin === Plugin.SimilarProducts) {
177
+ promise = import(`${'@akinon/pz-similar-products'}`);
146
178
  }
147
179
  } catch (error) {
148
180
  logger.error(error);
@@ -62,6 +62,17 @@ export default function SelectedPaymentOptionView({
62
62
  : fallbackView;
63
63
  }
64
64
 
65
+ if (
66
+ payment_option?.payment_type === 'wallet' &&
67
+ wallet_method === 'checkout_flow'
68
+ ) {
69
+ const mod = await import('@akinon/pz-flow-payment');
70
+
71
+ return typeof mod?.default === 'function'
72
+ ? mod.default
73
+ : fallbackView;
74
+ }
75
+
65
76
  const view = paymentTypeToView[payment_option?.payment_type] || null;
66
77
 
67
78
  if (view) {
@@ -35,8 +35,10 @@ import {
35
35
 
36
36
  interface CheckoutResponse {
37
37
  pre_order?: PreOrder;
38
- errors: {
39
- non_field_errors: string;
38
+ errors?: {
39
+ non_field_errors?: string;
40
+ sample_products?: string[];
41
+ [key: string]: string | string[] | undefined;
40
42
  };
41
43
  context_list?: CheckoutContext[];
42
44
  template_name?: string;
@@ -181,7 +183,10 @@ export const checkoutApi = api.injectEndpoints({
181
183
  endpoints: (build) => ({
182
184
  fetchCheckout: build.query<CheckoutResponse, void>({
183
185
  query: () => ({
184
- url: buildClientRequestUrl(checkout.fetchCheckout, {})
186
+ url: buildClientRequestUrl(
187
+ `${checkout.fetchCheckout}?page=IndexPage`,
188
+ {}
189
+ )
185
190
  }),
186
191
  providesTags: ['Checkout']
187
192
  }),
@@ -867,6 +872,21 @@ export const checkoutApi = api.injectEndpoints({
867
872
  method: 'POST',
868
873
  body
869
874
  })
875
+ }),
876
+ saveSampleProducts: build.mutation<CheckoutResponse, number[] | undefined>({
877
+ query: (products = []) => {
878
+ const formData = new FormData();
879
+
880
+ products.forEach((product) => {
881
+ formData.append('sample_products', String(product));
882
+ });
883
+
884
+ return {
885
+ url: buildClientRequestUrl(checkout.saveSampleProducts),
886
+ method: 'POST',
887
+ body: formData
888
+ };
889
+ }
870
890
  })
871
891
  }),
872
892
  overrideExisting: false
@@ -914,5 +934,6 @@ export const {
914
934
  useSetWalletCompletePageMutation,
915
935
  useSendSmsMutation,
916
936
  useVerifySmsMutation,
917
- useResetCheckoutStateQuery
937
+ useResetCheckoutStateQuery,
938
+ useSaveSampleProductsMutation
918
939
  } = checkoutApi;
@@ -0,0 +1,72 @@
1
+ import { Cache, CacheKey } from '../../lib/cache';
2
+ import { basket } from '../../data/urls';
3
+ import { Basket } from '../../types';
4
+ import appFetch from '../../utils/app-fetch';
5
+ import { ServerVariables } from '../../utils/server-variables';
6
+ import logger from '../../utils/log';
7
+
8
+ type GetBasketParams = {
9
+ locale?: string;
10
+ currency?: string;
11
+ namespace?: string;
12
+ };
13
+
14
+ const getBasketDataHandler = ({
15
+ locale,
16
+ currency,
17
+ namespace
18
+ }: GetBasketParams) => {
19
+ return async function () {
20
+ try {
21
+ const url = namespace
22
+ ? basket.getBasketDetail(namespace)
23
+ : basket.getBasket;
24
+
25
+ const basketData = await appFetch<{ basket: Basket }>({
26
+ url,
27
+ locale,
28
+ currency,
29
+ init: {
30
+ headers: {
31
+ Accept: 'application/json',
32
+ 'Content-Type': 'application/json'
33
+ }
34
+ }
35
+ });
36
+
37
+ if (!basketData?.basket) {
38
+ logger.warn('Basket data is undefined', {
39
+ handler: 'getBasketDataHandler',
40
+ namespace
41
+ });
42
+ }
43
+
44
+ return basketData;
45
+ } catch (error) {
46
+ logger.error('Error fetching basket data', {
47
+ handler: 'getBasketDataHandler',
48
+ error,
49
+ namespace
50
+ });
51
+ throw error;
52
+ }
53
+ };
54
+ };
55
+
56
+ export const getBasketData = async ({
57
+ locale = ServerVariables.locale,
58
+ currency = ServerVariables.currency,
59
+ namespace
60
+ }: GetBasketParams = {}) => {
61
+ return Cache.wrap(
62
+ CacheKey.Basket(namespace),
63
+ locale,
64
+ getBasketDataHandler({ locale, currency, namespace }),
65
+ {
66
+ expire: 0,
67
+ cache: false
68
+ }
69
+ );
70
+ };
71
+
72
+
@@ -5,7 +5,6 @@ import { category, product } from '../urls';
5
5
  import { Cache, CacheKey } from '../../lib/cache';
6
6
  import { parse } from 'lossless-json';
7
7
  import logger from '../../utils/log';
8
- import { headers as nHeaders } from 'next/headers';
9
8
  import { ServerVariables } from '../../utils/server-variables';
10
9
 
11
10
  function getCategoryDataHandler(
@@ -18,19 +17,30 @@ function getCategoryDataHandler(
18
17
  return async function () {
19
18
  const params = generateCommerceSearchParams(searchParams);
20
19
 
21
- const rawData = await appFetch<string>({
22
- url: `${category.getCategoryByPk(pk)}${params ? params : ''}`,
23
- locale,
24
- currency,
25
- init: {
26
- headers: {
27
- Accept: 'application/json',
28
- 'Content-Type': 'application/json',
29
- ...(headers ?? {})
30
- }
31
- },
32
- responseType: FetchResponseType.TEXT
33
- });
20
+ let rawData: string;
21
+
22
+ try {
23
+ rawData = await appFetch<string>({
24
+ url: `${category.getCategoryByPk(pk)}${params ? params : ''}`,
25
+ locale,
26
+ currency,
27
+ init: {
28
+ headers: {
29
+ Accept: 'application/json',
30
+ 'Content-Type': 'application/json',
31
+ ...(headers ?? {})
32
+ }
33
+ },
34
+ responseType: FetchResponseType.TEXT
35
+ });
36
+ } catch (error) {
37
+ logger.error('Failed to fetch category data', {
38
+ handler: 'getCategoryDataHandler',
39
+ pk,
40
+ error: error.message
41
+ });
42
+ return null;
43
+ }
34
44
 
35
45
  let data: GetCategoryResponse;
36
46
 
@@ -48,8 +58,8 @@ function getCategoryDataHandler(
48
58
  logger.fatal('Error while parsing category data', {
49
59
  handler: 'getCategoryDataHandler',
50
60
  error,
51
- rawData: rawData.startsWith('<!DOCTYPE html>')
52
- ? `${rawData.substring(0, 50)}...`
61
+ rawData: rawData?.startsWith?.('<!DOCTYPE html>')
62
+ ? `${rawData?.substring(0, 50)}...`
53
63
  : rawData
54
64
  });
55
65
  }
@@ -64,17 +74,27 @@ function getCategoryDataHandler(
64
74
  return { data, breadcrumbData: undefined };
65
75
  }
66
76
 
67
- const breadcrumbData = await appFetch<any>({
68
- url: product.breadcrumbUrl(menuItemModel),
69
- locale,
70
- currency,
71
- init: {
72
- headers: {
73
- Accept: 'application/json',
74
- 'Content-Type': 'application/json'
77
+ let breadcrumbData: { menu?: unknown } = {};
78
+
79
+ try {
80
+ breadcrumbData = await appFetch<{ menu?: unknown }>({
81
+ url: product.breadcrumbUrl(menuItemModel),
82
+ locale,
83
+ currency,
84
+ init: {
85
+ headers: {
86
+ Accept: 'application/json',
87
+ 'Content-Type': 'application/json'
88
+ }
75
89
  }
76
- }
77
- });
90
+ });
91
+ } catch (error) {
92
+ logger.warn('Failed to fetch breadcrumb data', {
93
+ handler: 'getCategoryDataHandler',
94
+ pk,
95
+ error: error.message
96
+ });
97
+ }
78
98
 
79
99
  return { data, breadcrumbData: breadcrumbData?.menu };
80
100
  };
@@ -138,8 +158,8 @@ function getCategoryBySlugDataHandler(
138
158
  logger.fatal('Error while parsing category data', {
139
159
  handler: 'getCategoryBySlugDataHandler',
140
160
  error,
141
- rawData: rawData.startsWith('<!DOCTYPE html>')
142
- ? `${rawData.substring(0, 50)}...`
161
+ rawData: rawData?.startsWith?.('<!DOCTYPE html>')
162
+ ? `${rawData?.substring(0, 50)}...`
143
163
  : rawData
144
164
  });
145
165
  }
@@ -11,20 +11,24 @@ const getFlatPageDataHandler = (
11
11
  headers?: Record<string, string>
12
12
  ) => {
13
13
  return async function () {
14
- const data = await appFetch<FlatPage>({
15
- url: flatpage.getFlatPageByPk(pk),
16
- locale,
17
- currency,
18
- init: {
19
- headers: {
20
- Accept: 'application/json',
21
- 'Content-Type': 'application/json',
22
- ...(headers ?? {})
14
+ try {
15
+ const data = await appFetch<FlatPage>({
16
+ url: flatpage.getFlatPageByPk(pk),
17
+ locale,
18
+ currency,
19
+ init: {
20
+ headers: {
21
+ Accept: 'application/json',
22
+ 'Content-Type': 'application/json',
23
+ ...(headers ?? {})
24
+ }
23
25
  }
24
- }
25
- });
26
+ });
26
27
 
27
- return data;
28
+ return data;
29
+ } catch (error) {
30
+ return null;
31
+ }
28
32
  };
29
33
  };
30
34
 
@@ -11,20 +11,24 @@ const getLandingPageHandler = (
11
11
  headers?: Record<string, string>
12
12
  ) => {
13
13
  return async function () {
14
- const data = await appFetch<LandingPage>({
15
- url: landingpage.getLandingPageByPk(pk),
16
- locale,
17
- currency,
18
- init: {
19
- headers: {
20
- Accept: 'application/json',
21
- 'Content-Type': 'application/json',
22
- ...(headers ?? {})
14
+ try {
15
+ const data = await appFetch<LandingPage>({
16
+ url: landingpage.getLandingPageByPk(pk),
17
+ locale,
18
+ currency,
19
+ init: {
20
+ headers: {
21
+ Accept: 'application/json',
22
+ 'Content-Type': 'application/json',
23
+ ...(headers ?? {})
24
+ }
23
25
  }
24
- }
25
- });
26
+ });
26
27
 
27
- return data;
28
+ return data;
29
+ } catch (error) {
30
+ return null;
31
+ }
28
32
  };
29
33
  };
30
34
 
@@ -16,19 +16,29 @@ const getListDataHandler = (
16
16
  return async function () {
17
17
  const params = generateCommerceSearchParams(searchParams);
18
18
 
19
- const rawData = await appFetch<string>({
20
- url: `${category.list}${params}`,
21
- locale,
22
- currency,
23
- init: {
24
- headers: {
25
- Accept: 'application/json',
26
- 'Content-Type': 'application/json',
27
- ...(headers ?? {})
28
- }
29
- },
30
- responseType: FetchResponseType.TEXT
31
- });
19
+ let rawData: string;
20
+
21
+ try {
22
+ rawData = await appFetch<string>({
23
+ url: `${category.list}${params}`,
24
+ locale,
25
+ currency,
26
+ init: {
27
+ headers: {
28
+ Accept: 'application/json',
29
+ 'Content-Type': 'application/json',
30
+ ...(headers ?? {})
31
+ }
32
+ },
33
+ responseType: FetchResponseType.TEXT
34
+ });
35
+ } catch (error) {
36
+ logger.error('Failed to fetch list data', {
37
+ handler: 'getListDataHandler',
38
+ error: error.message
39
+ });
40
+ return null;
41
+ }
32
42
 
33
43
  let data: GetCategoryResponse;
34
44