@akinon/projectzero 2.0.0-beta.0 → 2.0.0-beta.10

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 (121) 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 +250 -0
  5. package/app-template/README.md +6 -0
  6. package/app-template/config/prebuild-tests.json +5 -0
  7. package/app-template/next-env.d.ts +1 -1
  8. package/app-template/{next.config.mjs → next.config.ts} +6 -3
  9. package/app-template/package.json +43 -40
  10. package/app-template/postcss.config.mjs +8 -0
  11. package/app-template/public/locales/en/account.json +4 -0
  12. package/app-template/public/locales/en/common.json +10 -0
  13. package/app-template/public/locales/tr/account.json +4 -0
  14. package/app-template/public/locales/tr/common.json +10 -0
  15. package/app-template/src/__tests__/middleware-matcher.test.ts +135 -0
  16. package/app-template/src/app/[commerce]/[locale]/[currency]/[...prettyurl]/page.tsx +6 -4
  17. package/app-template/src/app/[commerce]/[locale]/[currency]/account/orders/[id]/cancellation/page.tsx +98 -7
  18. package/app-template/src/app/[commerce]/[locale]/[currency]/account/orders/[id]/page.tsx +71 -7
  19. package/app-template/src/app/[commerce]/[locale]/[currency]/account/page.tsx +1 -1
  20. package/app-template/src/app/[commerce]/[locale]/[currency]/account/profile/page.tsx +1 -1
  21. package/app-template/src/app/[commerce]/[locale]/[currency]/address/stores/page.tsx +2 -2
  22. package/app-template/src/app/[commerce]/[locale]/[currency]/auth/page.tsx +1 -1
  23. package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +2 -2
  24. package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +4 -1
  25. package/app-template/src/app/[commerce]/[locale]/[currency]/error.tsx +12 -15
  26. package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +3 -1
  27. package/app-template/src/app/[commerce]/[locale]/[currency]/forms/[pk]/generate/page.tsx +5 -3
  28. package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +7 -5
  29. package/app-template/src/app/[commerce]/[locale]/[currency]/landing-page/[pk]/page.tsx +3 -1
  30. package/app-template/src/app/[commerce]/[locale]/[currency]/layout.tsx +3 -1
  31. package/app-template/src/app/[commerce]/[locale]/[currency]/list/page.tsx +3 -1
  32. package/app-template/src/app/[commerce]/[locale]/[currency]/{pz-not-found/page.tsx → not-found.tsx} +2 -2
  33. package/app-template/src/app/[commerce]/[locale]/[currency]/orders/checkout/page.tsx +7 -4
  34. package/app-template/src/app/[commerce]/[locale]/[currency]/orders/completed/[token]/page.tsx +3 -7
  35. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +8 -5
  36. package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +3 -1
  37. package/app-template/src/app/[commerce]/[locale]/[currency]/users/email-set-primary/[[...id]]/page.tsx +4 -1
  38. package/app-template/src/app/[commerce]/[locale]/[currency]/users/registration/account-confirm-email/[[...id]]/page.tsx +3 -1
  39. package/app-template/src/app/[commerce]/[locale]/[currency]/users/reset/[[...id]]/page.tsx +11 -3
  40. package/app-template/src/app/[commerce]/[locale]/[currency]/xml-sitemap/[node]/route.ts +48 -2
  41. package/app-template/src/assets/globals.scss +162 -34
  42. package/app-template/src/components/__tests__/badge.test.tsx +2 -2
  43. package/app-template/src/components/accordion.tsx +1 -1
  44. package/app-template/src/components/button.tsx +50 -35
  45. package/app-template/src/components/file-input.tsx +44 -2
  46. package/app-template/src/components/input.tsx +3 -3
  47. package/app-template/src/components/modal.tsx +1 -1
  48. package/app-template/src/components/pwa-tags.tsx +0 -1
  49. package/app-template/src/components/select.tsx +2 -2
  50. package/app-template/src/components/shimmer.tsx +1 -1
  51. package/app-template/src/components/tabs.tsx +2 -2
  52. package/app-template/src/components/types/index.ts +4 -1
  53. package/app-template/src/middleware.ts +1 -0
  54. package/app-template/src/plugins.js +2 -1
  55. package/app-template/src/redux/middlewares/category.ts +1 -1
  56. package/app-template/src/redux/reducers/category.ts +1 -1
  57. package/app-template/src/redux/store.ts +4 -3
  58. package/app-template/src/settings.js +1 -2
  59. package/app-template/src/utils/convert-facet-search-params.ts +1 -1
  60. package/app-template/src/views/account/address-form.tsx +2 -2
  61. package/app-template/src/views/account/contact-form.tsx +4 -9
  62. package/app-template/src/views/account/content-header.tsx +2 -3
  63. package/app-template/src/views/account/order.tsx +1 -1
  64. package/app-template/src/views/account/orders/order-cancellation-item.tsx +5 -4
  65. package/app-template/src/views/anonymous-tracking/order-detail/index.tsx +1 -1
  66. package/app-template/src/views/basket/basket-item.tsx +1 -0
  67. package/app-template/src/views/category/category-active-filters.tsx +1 -1
  68. package/app-template/src/views/category/category-header.tsx +12 -6
  69. package/app-template/src/views/category/category-info.tsx +4 -4
  70. package/app-template/src/views/category/filters/index.tsx +2 -2
  71. package/app-template/src/views/checkout/auth.tsx +1 -1
  72. package/app-template/src/views/checkout/layout/header.tsx +1 -1
  73. package/app-template/src/views/checkout/steps/payment/index.tsx +1 -1
  74. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +1 -1
  75. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +4 -4
  76. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +3 -3
  77. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +1 -1
  78. package/app-template/src/views/checkout/summary.tsx +2 -2
  79. package/app-template/src/views/guest-login/index.tsx +1 -1
  80. package/app-template/src/views/header/action-menu.tsx +6 -3
  81. package/app-template/src/views/header/band.tsx +2 -2
  82. package/app-template/src/views/header/mini-basket.tsx +16 -5
  83. package/app-template/src/views/header/mobile-menu.tsx +6 -6
  84. package/app-template/src/views/header/navbar.tsx +1 -1
  85. package/app-template/src/views/header/pwa-back-button.tsx +1 -1
  86. package/app-template/src/views/header/search/index.tsx +16 -4
  87. package/app-template/src/views/header/search/results.tsx +1 -1
  88. package/app-template/src/views/header/user-menu.tsx +3 -1
  89. package/app-template/src/views/installment-options/index.tsx +1 -1
  90. package/app-template/src/views/login/index.tsx +30 -6
  91. package/app-template/src/views/otp-login/index.tsx +13 -15
  92. package/app-template/src/views/product/product-info.tsx +2 -2
  93. package/app-template/src/views/product/slider.tsx +1 -1
  94. package/app-template/src/views/product-pointer-banner-item.tsx +1 -1
  95. package/app-template/src/views/register/index.tsx +30 -5
  96. package/app-template/src/views/sales-contract-modal/index.tsx +17 -17
  97. package/app-template/src/widgets/footer-info.tsx +1 -1
  98. package/app-template/src/widgets/footer-menu.tsx +1 -1
  99. package/app-template/src/widgets/footer-subscription/index.tsx +1 -1
  100. package/app-template/src/widgets/home-stories-eng.tsx +1 -1
  101. package/app-template/tailwind.config.js +1 -137
  102. package/codemods/sentry-9/index.js +30 -0
  103. package/codemods/sentry-9/remove-sentry-configs.js +14 -0
  104. package/codemods/sentry-9/remove-sentry-dependency.js +25 -0
  105. package/codemods/sentry-9/replace-error-page.js +32 -0
  106. package/commands/codemod.ts +18 -0
  107. package/commands/index.ts +3 -1
  108. package/commands/plugins.ts +4 -0
  109. package/dist/codemods/sentry-9/templates/error.js +14 -0
  110. package/dist/commands/codemod.js +16 -0
  111. package/dist/commands/commerce-url.js +17 -7
  112. package/dist/commands/create.js +17 -7
  113. package/dist/commands/index.js +3 -1
  114. package/dist/commands/plugins.js +21 -7
  115. package/package.json +1 -1
  116. package/app-template/postcss.config.js +0 -6
  117. package/app-template/sentry.client.config.ts +0 -16
  118. package/app-template/sentry.edge.config.ts +0 -3
  119. package/app-template/sentry.properties +0 -4
  120. package/app-template/sentry.server.config.ts +0 -3
  121. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +0 -67
@@ -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
+ });
@@ -56,7 +56,8 @@ const resolvePrettyUrlHandler =
56
56
  return prettyUrlResult;
57
57
  };
58
58
 
59
- export async function generateMetadata({ params }: PageProps) {
59
+ export async function generateMetadata(props: PageProps) {
60
+ const params = await props.params;
60
61
  let result: Metadata = {};
61
62
  const { prettyurl } = params;
62
63
  const pageSlug = prettyurl
@@ -81,7 +82,7 @@ export async function generateMetadata({ params }: PageProps) {
81
82
  ...params,
82
83
  pk: prettyUrlResult.pk
83
84
  },
84
- searchParams
85
+ searchParams: Promise.resolve(searchParams)
85
86
  };
86
87
 
87
88
  try {
@@ -123,7 +124,8 @@ export async function generateMetadata({ params }: PageProps) {
123
124
  export const dynamic = 'force-static';
124
125
  export const revalidate = 300;
125
126
 
126
- export default async function Page({ params }) {
127
+ export default async function Page(props) {
128
+ const params = await props.params;
127
129
  const { prettyurl } = params;
128
130
  const pageSlug = prettyurl
129
131
  .filter((x) => !x.startsWith('searchparams'))
@@ -159,7 +161,7 @@ export default async function Page({ params }) {
159
161
  ...params,
160
162
  pk: result.pk
161
163
  },
162
- searchParams: urlSearchParams
164
+ searchParams: Promise.resolve(urlSearchParams)
163
165
  };
164
166
 
165
167
  if (result.path.startsWith('/category/')) {
@@ -8,16 +8,17 @@ import {
8
8
  useGetOrderQuery,
9
9
  useGetCancellationReasonsQuery
10
10
  } from '@akinon/next/data/client/account';
11
- import { AccountOrderCancellation } from '@akinon/next/types';
11
+ import type { AccountOrderCancellation } from '@akinon/next/types';
12
12
  import {
13
13
  Button,
14
14
  Checkbox,
15
15
  Select,
16
16
  Modal,
17
17
  LoaderSpinner,
18
- Link
18
+ Link,
19
+ FileInput
19
20
  } from '@theme/components';
20
- import { useState } from 'react';
21
+ import { useState, use } from 'react';
21
22
  import { OrderDetailHeader } from '@theme/views/account/orders/order-detail-header';
22
23
  import { OrderCancellationItem } from '@theme/views/account/orders/order-cancellation-item';
23
24
  import { useLocalization } from '@akinon/next/hooks';
@@ -27,6 +28,8 @@ const accountOrderCancellationSchema = yup.object().shape({
27
28
  });
28
29
 
29
30
  const AccountOrderCancellation = ({ params }) => {
31
+ const pageParams = use(params) as { id: string };
32
+
30
33
  const { t } = useLocalization();
31
34
  const {
32
35
  register,
@@ -39,6 +42,7 @@ const AccountOrderCancellation = ({ params }) => {
39
42
  } = useForm<AccountOrderCancellation>({
40
43
  resolver: yupResolver(accountOrderCancellationSchema)
41
44
  });
45
+
42
46
  const { data: cancellationReasons, isSuccess: cancellationReasonsSuccess } =
43
47
  useGetCancellationReasonsQuery();
44
48
 
@@ -46,7 +50,7 @@ const AccountOrderCancellation = ({ params }) => {
46
50
  data: order,
47
51
  isLoading,
48
52
  isSuccess: orderSuccess
49
- } = useGetOrderQuery(params.id);
53
+ } = useGetOrderQuery(pageParams.id);
50
54
 
51
55
  const [isModalOpen, setIsModalOpen] = useState(false);
52
56
  const [responseMessage, setResponseMessage] = useState({
@@ -56,6 +60,9 @@ const AccountOrderCancellation = ({ params }) => {
56
60
  const watchAllFields = watch();
57
61
  const cancelItemsLength = watchAllFields?.cancel_order_items?.length;
58
62
  const [cancelOrder] = useCancelOrderMutation();
63
+ const [files, setFiles] = useState<
64
+ { itemId: string; image: string; description: string }[]
65
+ >([]);
59
66
 
60
67
  const modalHandleClick = () => {
61
68
  setIsModalOpen(false);
@@ -112,8 +119,68 @@ const AccountOrderCancellation = ({ params }) => {
112
119
  ]);
113
120
  };
114
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
+
115
171
  const onSubmit: SubmitHandler<AccountOrderCancellation> = (orderItems) => {
116
- 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 })
117
184
  .unwrap()
118
185
  .then(() => {
119
186
  setResponseMessage({
@@ -122,10 +189,33 @@ const AccountOrderCancellation = ({ params }) => {
122
189
  });
123
190
  setIsModalOpen(true);
124
191
  })
125
- .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
+
126
216
  setResponseMessage({
127
217
  title: t('account.my_orders.return.error.title').toString(),
128
- content: t('account.my_orders.return.error.description').toString()
218
+ content: errorContent
129
219
  });
130
220
  setIsModalOpen(true);
131
221
  });
@@ -180,6 +270,7 @@ const AccountOrderCancellation = ({ params }) => {
180
270
  onChange={onChange}
181
271
  value={value}
182
272
  selectOption={selectOption}
273
+ fileInput={fileInputCondition(item, item.product.name)}
183
274
  />
184
275
  );
185
276
  }}
@@ -5,7 +5,7 @@ import { Image } from '@akinon/next/components/image';
5
5
  import clsx from 'clsx';
6
6
  import { SalesContractModal } from '@theme/views/sales-contract-modal';
7
7
  import { useGetOrderQuery } from '@akinon/next/data/client/account';
8
- import { useEffect, useState } from 'react';
8
+ import { useEffect, useState, use } from 'react';
9
9
 
10
10
  import { OrderDetailHeader } from '@theme/views/account/orders/order-detail-header';
11
11
  import { ROUTES } from '@theme/routes';
@@ -13,7 +13,9 @@ import { useLocalization } from '@akinon/next/hooks';
13
13
  import settings from 'settings';
14
14
  import { getOrderStatus } from 'utils';
15
15
 
16
- const AccountOrderDetail = ({ params: { id } }) => {
16
+ const AccountOrderDetail = ({ params }) => {
17
+ const pageParams = use(params) as { id: string };
18
+
17
19
  const { locale, t } = useLocalization();
18
20
 
19
21
  const localeValue = settings.localization.locales.find(
@@ -25,7 +27,7 @@ const AccountOrderDetail = ({ params: { id } }) => {
25
27
  isLoading,
26
28
  isSuccess,
27
29
  isFetching
28
- } = useGetOrderQuery(id);
30
+ } = useGetOrderQuery(pageParams.id);
29
31
  const [orderDate, setOrderDate] = useState('');
30
32
 
31
33
  const groupedOrder = [];
@@ -142,7 +144,7 @@ const AccountOrderDetail = ({ params: { id } }) => {
142
144
  key={index}
143
145
  >
144
146
  <div className="flex gap-3 mb-5 lg:mb-0">
145
- <div className="flex-shrink-0">
147
+ <div className="shrink-0">
146
148
  <Link
147
149
  className="block"
148
150
  href={item.product.absolute_url}
@@ -206,8 +208,7 @@ const AccountOrderDetail = ({ params: { id } }) => {
206
208
  </div>
207
209
 
208
210
  {(item.is_cancellable || item.is_refundable) &&
209
- order.is_cancellable &&
210
- item.status.value == '400' && (
211
+ order.is_cancellable && (
211
212
  <div className="lg:ml-24">
212
213
  <Link
213
214
  href={`${ROUTES.ACCOUNT_ORDERS}/${order.id}/cancellation`}
@@ -225,7 +226,6 @@ const AccountOrderDetail = ({ params: { id } }) => {
225
226
  </div>
226
227
  )}
227
228
  </div>
228
-
229
229
  <div className="flex flex-col justify-center items-end lg:ml-6 lg:min-w-[7rem]">
230
230
  {parseFloat(item.retail_price) >
231
231
  parseFloat(item.price) && (
@@ -249,6 +249,70 @@ const AccountOrderDetail = ({ params: { id } }) => {
249
249
  );
250
250
  })}
251
251
  </div>
252
+
253
+ {group.map((item) =>
254
+ item.cancellationrequest_set?.map((cancellationItem) => {
255
+ const status = cancellationItem.status?.value;
256
+ const isRejected = status === 'rejected';
257
+ const isCompleted = status === 'completed';
258
+
259
+ const filteredImages =
260
+ cancellationItem.cancellation_request_image_set?.filter(
261
+ (img) =>
262
+ isRejected
263
+ ? !img.is_uploaded_by_user
264
+ : isCompleted
265
+ ? img.is_uploaded_by_user
266
+ : false
267
+ ) || [];
268
+
269
+ const statusText = isRejected
270
+ ? t('account.my_orders.return.rejected')
271
+ : isCompleted
272
+ ? t('account.my_orders.return.completed')
273
+ : null;
274
+
275
+ if (!statusText || filteredImages.length === 0) return null;
276
+
277
+ return (
278
+ <div
279
+ className="w-full px-4 lg:px-7"
280
+ key={cancellationItem.id}
281
+ >
282
+ <div className="flex flex-col py-2 gap-4 border-t border-gray">
283
+ <div className="flex flex-col">
284
+ <div className="text-sm font-semibold">
285
+ {t('account.my_orders.return.return_status')}
286
+ <span className="font-normal"> {statusText}</span>
287
+ </div>
288
+
289
+ <div className="flex gap-2 mt-2 flex-wrap">
290
+ {filteredImages.map((img) => (
291
+ <div className="flex flex-col gap-2" key={img.id}>
292
+ <Link href={img.image} target="_blank">
293
+ <Image
294
+ src={img.image}
295
+ width={112}
296
+ height={150}
297
+ alt={img.description}
298
+ />
299
+ </Link>
300
+
301
+ {img.description && (
302
+ <p className="text-xs">
303
+ {t('account.my_orders.return.explanation')}:
304
+ {img.description}
305
+ </p>
306
+ )}
307
+ </div>
308
+ ))}
309
+ </div>
310
+ </div>
311
+ </div>
312
+ </div>
313
+ );
314
+ })
315
+ )}
252
316
  </div>
253
317
  );
254
318
  })}
@@ -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" />
@@ -252,7 +252,7 @@ export default function Page() {
252
252
  <Input
253
253
  label={t('account.my_profile.form.phone.placeholder')}
254
254
  type="tel"
255
- format={user_phone_format.replace(/\9/g, '#')}
255
+ format={user_phone_format.replace(/9/g, '#')}
256
256
  mask="_"
257
257
  allowEmptyFormatting={true}
258
258
  control={control}
@@ -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 && (
@@ -51,7 +51,7 @@ export default function Auth() {
51
51
  {t('auth.register.mobile_title')}
52
52
  </Button>
53
53
  </div>
54
- <div className="w-full flex flex-wrap border md:border-0">
54
+ <div className="w-full flex flex-wrap border border-gray-200 md:border-0">
55
55
  <div
56
56
  className={twMerge(
57
57
  clsx('w-full md:block md:w-1/2', {
@@ -25,7 +25,7 @@ export default function Page() {
25
25
  }, [basket, isSuccess]);
26
26
 
27
27
  return (
28
- <div className="max-w-screen-xl p-4 flex flex-col text-primary-800 lg:p-8 xl:flex-row xl:mx-auto">
28
+ <div className="max-w-(--breakpoint-xl) p-4 flex flex-col text-primary-800 lg:p-8 xl:flex-row xl:mx-auto">
29
29
  {isLoading && (
30
30
  <div className="flex justify-center w-full">
31
31
  <LoaderSpinner />
@@ -62,7 +62,7 @@ export default function Page() {
62
62
  <Summary basket={basket} />
63
63
  </>
64
64
  ) : (
65
- <div className="flex flex-col items-center container max-w-screen-sm py-4 px-4 xs:py-6 xs:px-6 sm:py-8 sm:px-8 lg:max-w-screen-xl">
65
+ <div className="flex flex-col items-center container max-w-(--breakpoint-sm) py-4 px-4 xs:py-6 xs:px-6 sm:py-8 sm:px-8 lg:max-w-(--breakpoint-xl)">
66
66
  <h1
67
67
  className="w-full text-xl font-light text-secondary text-center sm:text-2xl"
68
68
  data-testid="basket-empty"
@@ -3,7 +3,10 @@ import { withSegmentDefaults } from '@akinon/next/hocs/server';
3
3
  import { PageProps } from '@akinon/next/types';
4
4
  import CategoryLayout from '@theme/views/category/layout';
5
5
 
6
- async function Page({ params, searchParams }: PageProps<{ pk: number }>) {
6
+ async function Page(props: PageProps<{ pk: number }>) {
7
+ const params = await props.params;
8
+ const searchParams = await props.searchParams;
9
+
7
10
  const { data, breadcrumbData } = await getCategoryData({
8
11
  pk: params.pk,
9
12
  searchParams
@@ -1,20 +1,17 @@
1
1
  'use client';
2
2
 
3
- import { useLocalization } from '@akinon/next/hooks';
4
- import { Link } from '@theme/components';
5
- import { ROUTES } from '@theme/routes';
3
+ import { useSentryUncaughtErrors } from '@akinon/next/hooks';
4
+ import PzErrorPage from '@akinon/next/views/error-page';
6
5
 
7
- export default function Error() {
8
- const { t } = useLocalization();
6
+ export default function ErrorPage({
7
+ error,
8
+ reset
9
+ }: {
10
+ error: Error & { digest?: string; isServerError?: boolean };
11
+ reset: () => void;
12
+ }) {
13
+ // DO NOT REMOVE THIS LINE TO REPORT UNCAUGHT ERRORS TO SENTRY
14
+ useSentryUncaughtErrors(error);
9
15
 
10
- return (
11
- <section className="text-center px-6 my-14 md:px-0 md:m-14">
12
- <div className="text-7xl font-bold md:text-8xl">500</div>
13
- <h1 className="text-lg md:text-xl"> {t('common.page_500.title')} </h1>
14
- <p className="text-lg md:text-xl"> {t('common.page_500.description')} </p>
15
- <Link href={ROUTES.HOME} className="text-lg underline">
16
- {t('common.page_500.link_text')}
17
- </Link>
18
- </section>
19
- );
16
+ return <PzErrorPage error={error} reset={reset} />;
20
17
  }
@@ -2,7 +2,9 @@ import { getFlatPageData } from '@akinon/next/data/server';
2
2
  import { withSegmentDefaults } from '@akinon/next/hocs/server';
3
3
  import { PageProps } from '@akinon/next/types';
4
4
 
5
- async function Page({ params }: PageProps<{ pk: number }>) {
5
+ async function Page(props: PageProps<{ pk: number }>) {
6
+ const params = await props.params;
7
+
6
8
  const data = await getFlatPageData({ pk: params.pk });
7
9
 
8
10
  return (
@@ -2,7 +2,9 @@ import { getFormData } from '@akinon/next/data/server';
2
2
  import { t } from '@akinon/next/utils/server-translation';
3
3
  import { GenerateFormFields } from '@theme/components/generate-form-fields';
4
4
 
5
- export default async function Page({ params }) {
5
+ export default async function Page(props) {
6
+ const params = await props.params;
7
+
6
8
  const data = await getFormData({ pk: params.pk });
7
9
  const { schema, is_active, name, pk } = data;
8
10
 
@@ -19,7 +21,7 @@ export default async function Page({ params }) {
19
21
  schema={schema}
20
22
  allFieldClasses={{
21
23
  className:
22
- 'border border-[#d4d4d4] text-[#4a4f54] mt-1.5 h-[38px] p-2.5 text-xs outline-none focus:border-black',
24
+ 'border border-[#d4d4d4] text-[#4a4f54] mt-1.5 h-[38px] p-2.5 text-xs outline-hidden focus:border-black',
23
25
  labelClassName: 'text-[#4a4f54] text-xs',
24
26
  wrapperClassName: 'flex flex-col mb-6'
25
27
  }}
@@ -39,7 +41,7 @@ export default async function Page({ params }) {
39
41
  formProperties={{
40
42
  actionUrl: `/api/form/${pk}/`,
41
43
  className:
42
- 'w-[calc(100%-36px)] md:w-[570px] px-[18px] py-[60px] md:p-[100px] border border-[#cbc8c8] border-t-[3px] border-t-[#e95151] mx-auto -mt-[100px] bg-white mb-14',
44
+ 'w-[calc(100%-36px)] md:w-[570px] px-[18px] py-[60px] md:p-[100px] border border-[#cbc8c8] border-t-[3px] border-t-[#e95151] mx-auto -mt-[100px] bg-white mb-14'
43
45
  }}
44
46
  submitButtonText={t('form.form_page.submit_button_text')}
45
47
  />
@@ -5,11 +5,10 @@ import { withSegmentDefaults } from '@akinon/next/hocs/server';
5
5
  import { PageProps, Metadata } from '@akinon/next/types';
6
6
  import { generateJsonLd } from '@theme/utils/generate-jsonld';
7
7
 
8
- export async function generateMetadata({
9
- params,
10
- searchParams
11
- }: PageProps<{ pk: number }>) {
8
+ export async function generateMetadata(props: PageProps): Promise<Metadata> {
12
9
  let result: Metadata = {};
10
+ const searchParams = await props.searchParams;
11
+ const params = await props.params;
13
12
 
14
13
  try {
15
14
  const {
@@ -41,7 +40,10 @@ export async function generateMetadata({
41
40
  return result;
42
41
  }
43
42
 
44
- async function Page({ params, searchParams }: PageProps<{ pk: number }>) {
43
+ async function Page(props: PageProps<{ pk: number }>) {
44
+ const params = await props.params;
45
+ const searchParams = await props.searchParams;
46
+
45
47
  const [{ data, breadcrumbData }, deliveryReturn] = await Promise.all([
46
48
  getProductData({
47
49
  pk: params.pk,
@@ -2,7 +2,9 @@ import { getLandingPageData } from '@akinon/next/data/server';
2
2
  import { withSegmentDefaults } from '@akinon/next/hocs/server';
3
3
  import { PageProps } from '@akinon/next/types';
4
4
 
5
- async function Page({ params }: PageProps<{ pk: number }>) {
5
+ async function Page(props: PageProps<{ pk: number }>) {
6
+ const params = await props.params;
7
+
6
8
  const data = await getLandingPageData({ pk: params.pk });
7
9
 
8
10
  const content = data.landing_page;