@akinon/projectzero 2.0.0-beta.1 → 2.0.0-beta.11

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 (107) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/app-template/.env.example +5 -0
  3. package/app-template/.gitignore +2 -0
  4. package/app-template/CHANGELOG.md +251 -0
  5. package/app-template/README.md +6 -0
  6. package/app-template/config/prebuild-tests.json +5 -0
  7. package/app-template/docs/plugins.md +60 -25
  8. package/app-template/jest.config.ts +2 -2
  9. package/app-template/{next.config.mjs → next.config.ts} +6 -3
  10. package/app-template/package.json +30 -27
  11. package/app-template/postcss.config.mjs +8 -0
  12. package/app-template/public/locales/en/account.json +4 -0
  13. package/app-template/public/locales/en/common.json +10 -0
  14. package/app-template/public/locales/tr/account.json +4 -0
  15. package/app-template/public/locales/tr/common.json +10 -0
  16. package/app-template/src/__tests__/middleware-matcher.test.ts +135 -0
  17. package/app-template/src/app/[commerce]/[locale]/[currency]/account/orders/[id]/cancellation/page.tsx +99 -7
  18. package/app-template/src/app/[commerce]/[locale]/[currency]/account/orders/[id]/page.tsx +112 -47
  19. package/app-template/src/app/[commerce]/[locale]/[currency]/account/page.tsx +1 -1
  20. package/app-template/src/app/[commerce]/[locale]/[currency]/address/stores/page.tsx +2 -2
  21. package/app-template/src/app/[commerce]/[locale]/[currency]/auth/page.tsx +1 -1
  22. package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +2 -2
  23. package/app-template/src/app/[commerce]/[locale]/[currency]/error.tsx +12 -15
  24. package/app-template/src/app/[commerce]/[locale]/[currency]/forms/[pk]/generate/page.tsx +1 -1
  25. package/app-template/src/app/[commerce]/[locale]/[currency]/{pz-not-found/page.tsx → not-found.tsx} +2 -2
  26. package/app-template/src/app/[commerce]/[locale]/[currency]/orders/checkout/page.tsx +7 -4
  27. package/app-template/src/app/[commerce]/[locale]/[currency]/xml-sitemap/[node]/route.ts +47 -1
  28. package/app-template/src/assets/globals.scss +162 -34
  29. package/app-template/src/components/__tests__/badge.test.tsx +2 -2
  30. package/app-template/src/components/accordion.tsx +1 -1
  31. package/app-template/src/components/button.tsx +50 -35
  32. package/app-template/src/components/checkbox.tsx +1 -0
  33. package/app-template/src/components/file-input.tsx +44 -2
  34. package/app-template/src/components/input.tsx +3 -3
  35. package/app-template/src/components/modal.tsx +1 -1
  36. package/app-template/src/components/select.tsx +2 -2
  37. package/app-template/src/components/shimmer.tsx +1 -1
  38. package/app-template/src/components/tabs.tsx +2 -2
  39. package/app-template/src/components/types/index.ts +4 -1
  40. package/app-template/src/middleware.ts +1 -0
  41. package/app-template/src/plugins.js +2 -1
  42. package/app-template/src/redux/middlewares/category.ts +1 -1
  43. package/app-template/src/redux/reducers/category.ts +1 -1
  44. package/app-template/src/redux/store.ts +4 -3
  45. package/app-template/src/utils/convert-facet-search-params.ts +1 -1
  46. package/app-template/src/views/account/contact-form.tsx +3 -8
  47. package/app-template/src/views/account/content-header.tsx +2 -3
  48. package/app-template/src/views/account/order.tsx +11 -9
  49. package/app-template/src/views/account/orders/order-cancellation-item.tsx +5 -4
  50. package/app-template/src/views/anonymous-tracking/order-detail/index.tsx +45 -38
  51. package/app-template/src/views/basket/basket-item.tsx +1 -0
  52. package/app-template/src/views/category/category-active-filters.tsx +1 -1
  53. package/app-template/src/views/category/category-header.tsx +12 -6
  54. package/app-template/src/views/category/category-info.tsx +4 -4
  55. package/app-template/src/views/category/filters/index.tsx +2 -2
  56. package/app-template/src/views/checkout/auth.tsx +1 -1
  57. package/app-template/src/views/checkout/layout/header.tsx +1 -1
  58. package/app-template/src/views/checkout/steps/payment/index.tsx +1 -1
  59. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +1 -1
  60. package/app-template/src/views/checkout/steps/payment/options/redirection.tsx +5 -1
  61. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +4 -4
  62. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +3 -3
  63. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +1 -1
  64. package/app-template/src/views/checkout/summary.tsx +2 -2
  65. package/app-template/src/views/header/action-menu.tsx +11 -4
  66. package/app-template/src/views/header/band.tsx +2 -2
  67. package/app-template/src/views/header/mini-basket.tsx +15 -4
  68. package/app-template/src/views/header/mobile-menu.tsx +6 -6
  69. package/app-template/src/views/header/navbar.tsx +1 -1
  70. package/app-template/src/views/header/pwa-back-button.tsx +1 -1
  71. package/app-template/src/views/header/search/index.tsx +16 -4
  72. package/app-template/src/views/header/search/results.tsx +1 -1
  73. package/app-template/src/views/header/user-menu.tsx +3 -1
  74. package/app-template/src/views/installment-options/index.tsx +1 -1
  75. package/app-template/src/views/login/index.tsx +30 -6
  76. package/app-template/src/views/otp-login/index.tsx +12 -14
  77. package/app-template/src/views/product/price-wrapper.tsx +7 -2
  78. package/app-template/src/views/product/product-info.tsx +35 -5
  79. package/app-template/src/views/product/slider.tsx +1 -1
  80. package/app-template/src/views/product-pointer-banner-item.tsx +1 -1
  81. package/app-template/src/views/register/index.tsx +29 -4
  82. package/app-template/src/views/sales-contract-modal/index.tsx +17 -17
  83. package/app-template/src/widgets/footer-info.tsx +1 -1
  84. package/app-template/src/widgets/footer-menu.tsx +1 -1
  85. package/app-template/src/widgets/footer-subscription/index.tsx +1 -1
  86. package/app-template/src/widgets/home-stories-eng.tsx +1 -1
  87. package/app-template/tailwind.config.js +2 -134
  88. package/codemods/sentry-9/index.js +30 -0
  89. package/codemods/sentry-9/remove-sentry-configs.js +14 -0
  90. package/codemods/sentry-9/remove-sentry-dependency.js +25 -0
  91. package/codemods/sentry-9/replace-error-page.js +32 -0
  92. package/codemods/update-tailwind-config/index.js +30 -0
  93. package/codemods/update-tailwind-config/transform.js +102 -0
  94. package/commands/codemod.ts +17 -0
  95. package/commands/index.ts +3 -1
  96. package/commands/plugins.ts +24 -30
  97. package/dist/codemods/sentry-9/templates/error.js +14 -0
  98. package/dist/commands/codemod.js +15 -0
  99. package/dist/commands/index.js +3 -1
  100. package/dist/commands/plugins.js +23 -20
  101. package/package.json +3 -2
  102. package/app-template/postcss.config.js +0 -6
  103. package/app-template/sentry.client.config.ts +0 -16
  104. package/app-template/sentry.edge.config.ts +0 -3
  105. package/app-template/sentry.properties +0 -4
  106. package/app-template/sentry.server.config.ts +0 -3
  107. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +0 -67
@@ -254,6 +254,10 @@
254
254
  "cancelled": "İptal edildi",
255
255
  "cancellation_request_recieved": "İptal talebi alındı",
256
256
  "close_button": "Kapat",
257
+ "return_status": "İade Durumu",
258
+ "rejected": "İade reddedildi",
259
+ "completed": "İade tamamlandı",
260
+ "explanation": "Açıklama",
257
261
  "success": {
258
262
  "title": "Başarılı",
259
263
  "description": "Talebiniz alınmış olup en kısa sürede değerlendirilecektir."
@@ -38,6 +38,12 @@
38
38
  "description": "Lütfen daha sonra tekrar deneyiniz.",
39
39
  "link_text": "Ana sayfaya dön"
40
40
  },
41
+ "client_error": {
42
+ "title": "Sayfada bir sorunla karşılaştık.",
43
+ "description": "Bu bir tarayıcı hatası gibi görünüyor. Lütfen sayfayı yenileyin veya tarayıcı önbelleğinizi temizleyin.",
44
+ "link_text": "Ana sayfaya dön"
45
+ },
46
+ "try_again": "Tekrar Dene",
41
47
  "breadcrumb": {
42
48
  "homepage": "Anasayfa"
43
49
  },
@@ -61,5 +67,9 @@
61
67
  "description": "Döviz kuru değiştiği için sepetinizdeki ürünler silinecektir.",
62
68
  "close": "Kapat",
63
69
  "continue": "Devam Et"
70
+ },
71
+ "file_input": {
72
+ "select_file": "Dosya Seç",
73
+ "no_file": "Dosya seçilmedi"
64
74
  }
65
75
  }
@@ -0,0 +1,135 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ describe('Middleware matcher regex tests', () => {
5
+ const middlewareFilePath = path.resolve(__dirname, '../middleware.ts');
6
+ const middlewareContent = fs.readFileSync(middlewareFilePath, 'utf8');
7
+
8
+ let actualMatcherStrings: string[] = [];
9
+ let matcherPatterns: RegExp[] = [];
10
+
11
+ const matcherBlockRegex = middlewareContent.match(/matcher:\s*\[([\s\S]*?)\](?=\s*[,}])/);
12
+
13
+ if (matcherBlockRegex && matcherBlockRegex[1]) {
14
+ const matcherContentInsideBrackets = matcherBlockRegex[1];
15
+
16
+ actualMatcherStrings = matcherContentInsideBrackets
17
+ .split(',')
18
+ .map(line => {
19
+ const uncommentedLine = line.replace(/\/\/.*$/, '').trim();
20
+ const quoteMatch = uncommentedLine.match(/^(['"])(.*)\1$/);
21
+ return quoteMatch ? quoteMatch[2] : null;
22
+ })
23
+ .filter((pattern): pattern is string => pattern !== null && pattern !== '');
24
+
25
+ matcherPatterns = matcherContentInsideBrackets
26
+ .split(',')
27
+ .map((pattern) => pattern.trim())
28
+ .filter(Boolean)
29
+ .map((pattern) => {
30
+ let cleanPattern = pattern
31
+ .replace(/^['"]|['"]$/g, '');
32
+
33
+ try {
34
+ if (cleanPattern.includes('(?!') && cleanPattern.includes('api') && cleanPattern.includes('_next')) {
35
+ cleanPattern = '^(?!/(?:api|_next)/)(?!.*\\.\\w+$).*$';
36
+ }
37
+
38
+ return new RegExp(cleanPattern);
39
+ } catch (error) {
40
+ console.error(`Invalid simplified regex: ${cleanPattern}`, error);
41
+ return null;
42
+ }
43
+ })
44
+ .filter(Boolean) as RegExp[];
45
+ }
46
+
47
+ const testPath = (path: string): boolean => {
48
+ return matcherPatterns.some((pattern) => {
49
+ try {
50
+ const result = pattern.test(path);
51
+ return result;
52
+ } catch (error) {
53
+ console.error(
54
+ `Error testing path: ${path} | Pattern: ${pattern.toString()}`,
55
+ error
56
+ );
57
+ return false;
58
+ }
59
+ });
60
+ };
61
+
62
+ it('should NOT match api routes', () => {
63
+ const apiPaths = ['/api/products', '/api/auth/login', '/api/v1/users'];
64
+ apiPaths.forEach((path) => {
65
+ expect(testPath(path)).toBe(false);
66
+ });
67
+ });
68
+
69
+ it('should NOT match _next routes', () => {
70
+ const nextPaths = [
71
+ '/_next/static/chunks/main.js',
72
+ '/_next/image',
73
+ '/_next/data/build-id/products.json'
74
+ ];
75
+ nextPaths.forEach((path) => {
76
+ expect(testPath(path)).toBe(false);
77
+ });
78
+ });
79
+
80
+ it('should NOT match static files with extensions', () => {
81
+ const staticFiles = [
82
+ '/images/logo.png',
83
+ '/styles/main.css',
84
+ '/fonts/roboto.woff2',
85
+ '/favicon.ico',
86
+ '/manifest.json'
87
+ ];
88
+ staticFiles.forEach((path) => {
89
+ expect(testPath(path)).toBe(false);
90
+ });
91
+ });
92
+
93
+ it('should match dynamic routes and specific patterns', () => {
94
+ const validPaths = [
95
+ '/profile/settings',
96
+ '/dashboard/stats',
97
+ '/products/123'
98
+ ];
99
+ validPaths.forEach((path) => {
100
+ expect(testPath(path)).toBe(true);
101
+ });
102
+ });
103
+
104
+ it('should match checkout-with-token routes', () => {
105
+ const expectedRegexString = '\'/(.*orders\\\\/checkout-with-token.*)\'';
106
+ expect(middlewareContent.includes(expectedRegexString)).toBe(true);
107
+
108
+ const checkoutPaths = [
109
+ '/orders/checkout-with-token/123',
110
+ '/orders/checkout-with-token/abc-xyz',
111
+ '/orders/checkout-with-token'
112
+ ];
113
+ checkoutPaths.forEach((path) => {
114
+ expect(testPath(path)).toBe(true);
115
+ });
116
+ });
117
+
118
+ it('should contain the exact specific extensions regex string in the file content', () => {
119
+ const expectedRegexString = '\'/(.+\\\\.)(html|htm|aspx|asp|php)\'';
120
+ expect(middlewareContent.includes(expectedRegexString)).toBe(true);
121
+ });
122
+
123
+ it('should include the sitemap pattern specifically within the matcher array', () => {
124
+ const sitemapPattern = '/(.*sitemap\\\\.xml)';
125
+ expect(actualMatcherStrings).toContain(sitemapPattern);
126
+ });
127
+
128
+ it('should verify that api pattern is excluded in the matcher configuration', () => {
129
+ expect(/api/.test(middlewareContent)).toBe(true);
130
+ });
131
+
132
+ it('should verify that _next pattern is excluded in the matcher configuration', () => {
133
+ expect(/_next/.test(middlewareContent)).toBe(true);
134
+ });
135
+ });
@@ -15,7 +15,8 @@ import {
15
15
  Select,
16
16
  Modal,
17
17
  LoaderSpinner,
18
- Link
18
+ Link,
19
+ FileInput
19
20
  } from '@theme/components';
20
21
  import { useState, use } from 'react';
21
22
  import { OrderDetailHeader } from '@theme/views/account/orders/order-detail-header';
@@ -41,6 +42,7 @@ const AccountOrderCancellation = ({ params }) => {
41
42
  } = useForm<AccountOrderCancellation>({
42
43
  resolver: yupResolver(accountOrderCancellationSchema)
43
44
  });
45
+
44
46
  const { data: cancellationReasons, isSuccess: cancellationReasonsSuccess } =
45
47
  useGetCancellationReasonsQuery();
46
48
 
@@ -58,6 +60,9 @@ const AccountOrderCancellation = ({ params }) => {
58
60
  const watchAllFields = watch();
59
61
  const cancelItemsLength = watchAllFields?.cancel_order_items?.length;
60
62
  const [cancelOrder] = useCancelOrderMutation();
63
+ const [files, setFiles] = useState<
64
+ { itemId: string; image: string; description: string }[]
65
+ >([]);
61
66
 
62
67
  const modalHandleClick = () => {
63
68
  setIsModalOpen(false);
@@ -114,8 +119,68 @@ const AccountOrderCancellation = ({ params }) => {
114
119
  ]);
115
120
  };
116
121
 
122
+ const handleFileChange = async (
123
+ e: React.ChangeEvent<HTMLInputElement>,
124
+ itemId: string,
125
+ description: string
126
+ ) => {
127
+ const selectedFiles = Array.from(e.target.files || []);
128
+
129
+ const base64Files = await Promise.all(
130
+ selectedFiles.map((file) => {
131
+ return new Promise<string>((resolve, reject) => {
132
+ const reader = new FileReader();
133
+ reader.onload = () => resolve(reader.result as string);
134
+ reader.onerror = reject;
135
+ reader.readAsDataURL(file);
136
+ });
137
+ })
138
+ );
139
+
140
+ const validFiles = base64Files.filter((file) =>
141
+ /^data:image\/(jpeg|png|jpg);base64,.+/.test(file)
142
+ );
143
+
144
+ const formattedFiles = validFiles.map((file) => ({
145
+ itemId,
146
+ image: file,
147
+ description
148
+ }));
149
+
150
+ setFiles((prevFiles) => [
151
+ ...prevFiles.filter((f) => f.itemId !== itemId),
152
+ ...formattedFiles
153
+ ]);
154
+ };
155
+
156
+ const fileInputCondition = (item, description: string) => {
157
+ if (item.is_refundable && !item.active_cancellation_request) {
158
+ return (
159
+ <FileInput
160
+ name="files"
161
+ title="files"
162
+ multiple
163
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
164
+ handleFileChange(e, item.id, description)
165
+ }
166
+ />
167
+ );
168
+ }
169
+ };
170
+
117
171
  const onSubmit: SubmitHandler<AccountOrderCancellation> = (orderItems) => {
118
- cancelOrder({ id: order.number, ...orderItems })
172
+ const mergedData = {
173
+ ...orderItems,
174
+ cancel_order_items: orderItems.cancel_order_items.map((orderItem) => ({
175
+ ...orderItem,
176
+ ...(files.length > 0 && {
177
+ cancellation_request_image_set: files.filter(
178
+ (file) => file.itemId === orderItem.order_item
179
+ )
180
+ })
181
+ }))
182
+ };
183
+ cancelOrder({ id: order.number, ...mergedData })
119
184
  .unwrap()
120
185
  .then(() => {
121
186
  setResponseMessage({
@@ -124,10 +189,33 @@ const AccountOrderCancellation = ({ params }) => {
124
189
  });
125
190
  setIsModalOpen(true);
126
191
  })
127
- .catch(() => {
192
+ .catch((err) => {
193
+ console.error('Err', err);
194
+
195
+ const errorMessages = new Set();
196
+
197
+ if (err?.data?.cancel_order_items) {
198
+ err.data.cancel_order_items.forEach((item) => {
199
+ if (item.cancellation_request_image_set) {
200
+ item.cancellation_request_image_set.forEach((error) => {
201
+ if (typeof error === 'string') {
202
+ errorMessages.add(error);
203
+ } else if (typeof error === 'object' && error?.image) {
204
+ error.image.forEach((msg) => errorMessages.add(msg));
205
+ }
206
+ });
207
+ }
208
+ });
209
+ }
210
+
211
+ const errorContent =
212
+ errorMessages.size > 0
213
+ ? Array.from(errorMessages).join('\n')
214
+ : t('account.my_orders.return.error.description').toString();
215
+
128
216
  setResponseMessage({
129
217
  title: t('account.my_orders.return.error.title').toString(),
130
- content: t('account.my_orders.return.error.description').toString()
218
+ content: errorContent
131
219
  });
132
220
  setIsModalOpen(true);
133
221
  });
@@ -149,13 +237,13 @@ const AccountOrderCancellation = ({ params }) => {
149
237
  <div>
150
238
  <div className="pb-2 mb-3 text-lg border-gray border-b">
151
239
  <span data-testid="account-orders-return-order-count">
152
- {order.orderitem_set.length}
240
+ {order?.orderitem_set?.length}
153
241
  </span>{' '}
154
242
  <span>{t('account.my_orders.detail.products')}</span>
155
243
  </div>
156
244
 
157
245
  <form onSubmit={handleSubmit(onSubmit)}>
158
- {order.orderitem_set.map((item, index: number) => (
246
+ {order?.orderitem_set?.map((item, index: number) => (
159
247
  <Controller
160
248
  defaultValue={[]}
161
249
  control={control}
@@ -166,7 +254,7 @@ const AccountOrderCancellation = ({ params }) => {
166
254
  return cancelItem.order_item === item.id;
167
255
  });
168
256
 
169
- const cancellationType = item.is_refundable
257
+ const cancellationType = item?.is_refundable
170
258
  ? 'refund'
171
259
  : 'cancel';
172
260
 
@@ -182,6 +270,10 @@ const AccountOrderCancellation = ({ params }) => {
182
270
  onChange={onChange}
183
271
  value={value}
184
272
  selectOption={selectOption}
273
+ fileInput={fileInputCondition(
274
+ item,
275
+ item?.product?.name
276
+ )}
185
277
  />
186
278
  );
187
279
  }}
@@ -33,14 +33,15 @@ const AccountOrderDetail = ({ params }) => {
33
33
  const groupedOrder = [];
34
34
 
35
35
  if (order) {
36
- const groupedData = order.orderitem_set.reduce((groups, item) => {
37
- const { tracking_number } = item;
38
- if (!groups[tracking_number]) {
39
- groups[tracking_number] = [];
40
- }
41
- groups[tracking_number].push(item);
42
- return groups;
43
- }, {});
36
+ const groupedData =
37
+ order?.orderitem_set?.reduce((groups, item) => {
38
+ const { tracking_number } = item;
39
+ if (!groups[tracking_number]) {
40
+ groups[tracking_number] = [];
41
+ }
42
+ groups[tracking_number].push(item);
43
+ return groups;
44
+ }, {}) || {};
44
45
 
45
46
  const result = Object.values(groupedData);
46
47
 
@@ -77,8 +78,8 @@ const AccountOrderDetail = ({ params }) => {
77
78
 
78
79
  <div>
79
80
  <span className="text-base font-bold">
80
- {order?.orderitem_set.length}{' '}
81
- {t('account.my_orders.detail.products')} {groupedOrder.length}{' '}
81
+ {order?.orderitem_set?.length}{' '}
82
+ {t('account.my_orders.detail.products')} {groupedOrder?.length}{' '}
82
83
  {t('account.my_orders.detail.packages')}
83
84
  </span>
84
85
  </div>
@@ -88,16 +89,17 @@ const AccountOrderDetail = ({ params }) => {
88
89
  className="break-words"
89
90
  >
90
91
  <span>{t('account.my_orders.detail.delivery_address')}</span>:{' '}
91
- {order.shipping_address.line} {order.shipping_address.district.name}{' '}
92
- {order.shipping_address.township.name}{' '}
93
- {order.shipping_address.city.name}
92
+ {order?.shipping_address?.line}{' '}
93
+ {order?.shipping_address?.district?.name}{' '}
94
+ {order?.shipping_address?.township?.name}{' '}
95
+ {order?.shipping_address?.city?.name}
94
96
  </div>
95
97
  </OrderDetailHeader>
96
98
 
97
99
  <div>
98
100
  {groupedOrder.map((group, i) => {
99
101
  const orderStatus = getOrderStatus(
100
- group[0].status.value.toString(),
102
+ group[0]?.status?.value?.toString(),
101
103
  t
102
104
  );
103
105
 
@@ -121,8 +123,8 @@ const AccountOrderDetail = ({ params }) => {
121
123
  <div className="flex justify-between items-center lg:gap-x-12">
122
124
  <div className="text-base">{orderStatus.label}</div>
123
125
 
124
- {group[0].tracking_number && group[0].tracking_url && (
125
- <Link href={group[0].tracking_url}>
126
+ {group[0]?.tracking_number && group[0]?.tracking_url && (
127
+ <Link href={group[0]?.tracking_url}>
126
128
  <Button className="px-7" appearance="filled">
127
129
  {t('account.my_orders.detail.track_shipment')}
128
130
  </Button>
@@ -134,7 +136,7 @@ const AccountOrderDetail = ({ params }) => {
134
136
  <div className="px-4 lg:px-7">
135
137
  {group.map((item, index) => {
136
138
  const itemStatus = getOrderStatus(
137
- item.status.value.toString(),
139
+ item?.status?.value?.toString(),
138
140
  t
139
141
  );
140
142
 
@@ -144,18 +146,18 @@ const AccountOrderDetail = ({ params }) => {
144
146
  key={index}
145
147
  >
146
148
  <div className="flex gap-3 mb-5 lg:mb-0">
147
- <div className="flex-shrink-0">
149
+ <div className="shrink-0">
148
150
  <Link
149
151
  className="block"
150
- href={item.product.absolute_url}
152
+ href={item?.product?.absolute_url}
151
153
  >
152
154
  <Image
153
155
  src={
154
- item.product.image
156
+ item?.product?.image
155
157
  ? item.product.image
156
158
  : '/noimage.jpg'
157
159
  }
158
- alt={item.product.name}
160
+ alt={item?.product?.name}
159
161
  width={112}
160
162
  height={150}
161
163
  />
@@ -164,33 +166,33 @@ const AccountOrderDetail = ({ params }) => {
164
166
 
165
167
  <div className="flex flex-col justify-between lg:max-w-48">
166
168
  <div className="text-sm">
167
- <Link href={item.product.absolute_url}>
168
- {item.product.name}
169
+ <Link href={item?.product?.absolute_url}>
170
+ {item?.product?.name}
169
171
  </Link>
170
172
  </div>
171
173
 
172
174
  <div className="text-gray-900 text-xs">
173
- {item.product.attributes.filterable_color && (
175
+ {item?.product?.attributes?.filterable_color && (
174
176
  <div>
175
177
  <span>
176
178
  {t('account.my_orders.detail.color')}
177
179
  </span>
178
- : {item.product.attributes.filterable_color}
180
+ : {item?.product?.attributes?.filterable_color}
179
181
  </div>
180
182
  )}
181
183
 
182
- {item.product.attributes.size && (
184
+ {item?.product?.attributes?.size && (
183
185
  <div>
184
186
  <span>
185
187
  {t('account.my_orders.detail.size')}
186
188
  </span>
187
- :{item.product.attributes.size}
189
+ :{item?.product?.attributes?.size}
188
190
  </div>
189
191
  )}
190
192
 
191
193
  <div>
192
194
  <span>{t('account.my_orders.detail.code')}</span>:{' '}
193
- {item.product.base_code}
195
+ {item?.product?.base_code}
194
196
  </div>
195
197
  </div>
196
198
  </div>
@@ -207,12 +209,11 @@ const AccountOrderDetail = ({ params }) => {
207
209
  {itemStatus.label}
208
210
  </div>
209
211
 
210
- {(item.is_cancellable || item.is_refundable) &&
211
- order.is_cancellable &&
212
- item.status.value == '400' && (
212
+ {(item?.is_cancellable || item?.is_refundable) &&
213
+ order?.is_cancellable && (
213
214
  <div className="lg:ml-24">
214
215
  <Link
215
- href={`${ROUTES.ACCOUNT_ORDERS}/${order.id}/cancellation`}
216
+ href={`${ROUTES.ACCOUNT_ORDERS}/${order?.id}/cancellation`}
216
217
  >
217
218
  <Button
218
219
  className="px-4 uppercase font-bold h-7"
@@ -227,23 +228,22 @@ const AccountOrderDetail = ({ params }) => {
227
228
  </div>
228
229
  )}
229
230
  </div>
230
-
231
231
  <div className="flex flex-col justify-center items-end lg:ml-6 lg:min-w-[7rem]">
232
- {parseFloat(item.retail_price) >
233
- parseFloat(item.price) && (
232
+ {parseFloat(item?.retail_price || '0') >
233
+ parseFloat(item?.price || '0') && (
234
234
  <Price
235
235
  className="font-normal line-through"
236
- value={item.retail_price}
236
+ value={item?.retail_price}
237
237
  />
238
238
  )}
239
239
 
240
240
  <Price
241
241
  className={clsx('font-normal', {
242
242
  'text-secondary-600':
243
- parseFloat(item.retail_price) >
244
- parseFloat(item.price)
243
+ parseFloat(item?.retail_price || '0') >
244
+ parseFloat(item?.price || '0')
245
245
  })}
246
- value={item.price}
246
+ value={item?.price}
247
247
  />
248
248
  </div>
249
249
  </div>
@@ -251,6 +251,70 @@ const AccountOrderDetail = ({ params }) => {
251
251
  );
252
252
  })}
253
253
  </div>
254
+
255
+ {group.map((item) =>
256
+ item.cancellationrequest_set?.map((cancellationItem) => {
257
+ const status = cancellationItem.status?.value;
258
+ const isRejected = status === 'rejected';
259
+ const isCompleted = status === 'completed';
260
+
261
+ const filteredImages =
262
+ cancellationItem.cancellation_request_image_set?.filter(
263
+ (img) =>
264
+ isRejected
265
+ ? !img.is_uploaded_by_user
266
+ : isCompleted
267
+ ? img.is_uploaded_by_user
268
+ : false
269
+ ) || [];
270
+
271
+ const statusText = isRejected
272
+ ? t('account.my_orders.return.rejected')
273
+ : isCompleted
274
+ ? t('account.my_orders.return.completed')
275
+ : null;
276
+
277
+ if (!statusText || filteredImages.length === 0) return null;
278
+
279
+ return (
280
+ <div
281
+ className="w-full px-4 lg:px-7"
282
+ key={cancellationItem.id}
283
+ >
284
+ <div className="flex flex-col py-2 gap-4 border-t border-gray">
285
+ <div className="flex flex-col">
286
+ <div className="text-sm font-semibold">
287
+ {t('account.my_orders.return.return_status')}
288
+ <span className="font-normal"> {statusText}</span>
289
+ </div>
290
+
291
+ <div className="flex gap-2 mt-2 flex-wrap">
292
+ {filteredImages.map((img) => (
293
+ <div className="flex flex-col gap-2" key={img.id}>
294
+ <Link href={img.image} target="_blank">
295
+ <Image
296
+ src={img.image}
297
+ width={112}
298
+ height={150}
299
+ alt={img.description}
300
+ />
301
+ </Link>
302
+
303
+ {img.description && (
304
+ <p className="text-xs">
305
+ {t('account.my_orders.return.explanation')}:
306
+ {img.description}
307
+ </p>
308
+ )}
309
+ </div>
310
+ ))}
311
+ </div>
312
+ </div>
313
+ </div>
314
+ </div>
315
+ );
316
+ })
317
+ )}
254
318
  </div>
255
319
  );
256
320
  })}
@@ -263,29 +327,29 @@ const AccountOrderDetail = ({ params }) => {
263
327
  <div className="flex justify-between text-sm text-black-700 mb-2">
264
328
  <p>
265
329
  <span>{t('account.my_orders.detail.subtotal')}</span> (
266
- {order.orderitem_set.length}{' '}
330
+ {order?.orderitem_set?.length}{' '}
267
331
  <span>{t('account.my_orders.detail.items')}</span>)
268
332
  </p>
269
333
 
270
334
  <Price
271
335
  className="font-normal min-w-max"
272
336
  value={
273
- parseFloat(order.amount_without_discount) -
274
- parseFloat(order.shipping_amount)
337
+ parseFloat(order?.amount_without_discount || '0') -
338
+ parseFloat(order?.shipping_amount || '0')
275
339
  }
276
340
  />
277
341
  </div>
278
342
 
279
- {order.discountitem_set &&
280
- order.discountitem_set.map((item, index) => (
343
+ {order?.discountitem_set &&
344
+ order?.discountitem_set?.map((item, index) => (
281
345
  <div
282
346
  className="flex justify-between text-sm text-black-700 mb-2"
283
347
  key={index}
284
348
  >
285
- <p>{item.name}</p>
349
+ <p>{item?.name}</p>
286
350
  <Price
287
351
  className="font-normal min-w-max"
288
- value={item.amount}
352
+ value={item?.amount}
289
353
  useNegative
290
354
  />
291
355
  </div>
@@ -298,7 +362,8 @@ const AccountOrderDetail = ({ params }) => {
298
362
  className="font-normal min-w-max"
299
363
  data-testid="account-orders-detail-total"
300
364
  value={
301
- parseFloat(order.amount) - parseFloat(order.shipping_amount)
365
+ parseFloat(order?.amount || '0') -
366
+ parseFloat(order?.shipping_amount || '0')
302
367
  }
303
368
  />
304
369
  </div>
@@ -53,7 +53,7 @@ export default function Page() {
53
53
  <div className="hidden lg:block">
54
54
  <div className="bg-gray-150">
55
55
  {orderLoading && (
56
- <SkeletonWrapper className="w-full px-6 mb-12 h-28 items-center justify-center !flex-row xl:h-[5.5rem]">
56
+ <SkeletonWrapper className="w-full px-6 mb-12 h-28 items-center justify-center flex-row! xl:h-[5.5rem]">
57
57
  <Skeleton className="w-[11.375rem] h-16 mr-4 xl:w-[16rem] xl:h-10" />
58
58
  <Skeleton className="w-56 h-10 mr-4" />
59
59
  <Skeleton className="w-[12.75rem] h-10 xl:w-56" />
@@ -122,7 +122,7 @@ export default function Stores() {
122
122
 
123
123
  <div className="flex gap-6 mt-4 flex-col md:flex-row">
124
124
  {city && (
125
- <div className="w-full flex-shrink-0 md:w-60 overflow-y-scroll max-h-[36rem]">
125
+ <div className="w-full shrink-0 md:w-60 overflow-y-scroll max-h-[36rem]">
126
126
  {cityLoading && (
127
127
  <SkeletonWrapper>
128
128
  <Skeleton className="w-full h-20" />
@@ -174,7 +174,7 @@ export default function Stores() {
174
174
  title={store.name}
175
175
  titleClassName="text-xs font-bold"
176
176
  iconSize={12}
177
- className="relative py-3 border-b justify-center mb-0"
177
+ className="relative py-3 border-b border-gray-200 justify-center mb-0"
178
178
  >
179
179
  <div className="text-xs">
180
180
  {store.address && (