@akinon/projectzero 2.0.0-beta.1 → 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 (101) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/app-template/.env.example +5 -0
  3. package/app-template/.gitignore +2 -0
  4. package/app-template/CHANGELOG.md +225 -0
  5. package/app-template/README.md +6 -0
  6. package/app-template/config/prebuild-tests.json +5 -0
  7. package/app-template/{next.config.mjs → next.config.ts} +6 -3
  8. package/app-template/package.json +30 -27
  9. package/app-template/postcss.config.mjs +8 -0
  10. package/app-template/public/locales/en/account.json +4 -0
  11. package/app-template/public/locales/en/common.json +10 -0
  12. package/app-template/public/locales/tr/account.json +4 -0
  13. package/app-template/public/locales/tr/common.json +10 -0
  14. package/app-template/src/__tests__/middleware-matcher.test.ts +135 -0
  15. package/app-template/src/app/[commerce]/[locale]/[currency]/account/orders/[id]/cancellation/page.tsx +93 -4
  16. package/app-template/src/app/[commerce]/[locale]/[currency]/account/orders/[id]/page.tsx +66 -4
  17. package/app-template/src/app/[commerce]/[locale]/[currency]/account/page.tsx +1 -1
  18. package/app-template/src/app/[commerce]/[locale]/[currency]/address/stores/page.tsx +2 -2
  19. package/app-template/src/app/[commerce]/[locale]/[currency]/auth/page.tsx +1 -1
  20. package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +2 -2
  21. package/app-template/src/app/[commerce]/[locale]/[currency]/error.tsx +12 -15
  22. package/app-template/src/app/[commerce]/[locale]/[currency]/forms/[pk]/generate/page.tsx +1 -1
  23. package/app-template/src/app/[commerce]/[locale]/[currency]/{pz-not-found/page.tsx → not-found.tsx} +2 -2
  24. package/app-template/src/app/[commerce]/[locale]/[currency]/orders/checkout/page.tsx +7 -4
  25. package/app-template/src/app/[commerce]/[locale]/[currency]/xml-sitemap/[node]/route.ts +47 -1
  26. package/app-template/src/assets/globals.scss +162 -34
  27. package/app-template/src/components/__tests__/badge.test.tsx +2 -2
  28. package/app-template/src/components/accordion.tsx +1 -1
  29. package/app-template/src/components/button.tsx +50 -35
  30. package/app-template/src/components/file-input.tsx +44 -2
  31. package/app-template/src/components/input.tsx +3 -3
  32. package/app-template/src/components/modal.tsx +1 -1
  33. package/app-template/src/components/select.tsx +2 -2
  34. package/app-template/src/components/shimmer.tsx +1 -1
  35. package/app-template/src/components/tabs.tsx +2 -2
  36. package/app-template/src/components/types/index.ts +4 -1
  37. package/app-template/src/middleware.ts +1 -0
  38. package/app-template/src/plugins.js +2 -1
  39. package/app-template/src/redux/middlewares/category.ts +1 -1
  40. package/app-template/src/redux/reducers/category.ts +1 -1
  41. package/app-template/src/redux/store.ts +4 -3
  42. package/app-template/src/settings.js +1 -2
  43. package/app-template/src/utils/convert-facet-search-params.ts +1 -1
  44. package/app-template/src/views/account/contact-form.tsx +3 -8
  45. package/app-template/src/views/account/content-header.tsx +2 -3
  46. package/app-template/src/views/account/order.tsx +1 -1
  47. package/app-template/src/views/account/orders/order-cancellation-item.tsx +5 -4
  48. package/app-template/src/views/anonymous-tracking/order-detail/index.tsx +1 -1
  49. package/app-template/src/views/basket/basket-item.tsx +1 -0
  50. package/app-template/src/views/category/category-active-filters.tsx +1 -1
  51. package/app-template/src/views/category/category-header.tsx +12 -6
  52. package/app-template/src/views/category/category-info.tsx +4 -4
  53. package/app-template/src/views/category/filters/index.tsx +2 -2
  54. package/app-template/src/views/checkout/auth.tsx +1 -1
  55. package/app-template/src/views/checkout/layout/header.tsx +1 -1
  56. package/app-template/src/views/checkout/steps/payment/index.tsx +1 -1
  57. package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +1 -1
  58. package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +4 -4
  59. package/app-template/src/views/checkout/steps/shipping/address-box.tsx +3 -3
  60. package/app-template/src/views/checkout/steps/shipping/addresses.tsx +1 -1
  61. package/app-template/src/views/checkout/summary.tsx +2 -2
  62. package/app-template/src/views/header/action-menu.tsx +6 -3
  63. package/app-template/src/views/header/band.tsx +2 -2
  64. package/app-template/src/views/header/mini-basket.tsx +15 -4
  65. package/app-template/src/views/header/mobile-menu.tsx +6 -6
  66. package/app-template/src/views/header/navbar.tsx +1 -1
  67. package/app-template/src/views/header/pwa-back-button.tsx +1 -1
  68. package/app-template/src/views/header/search/index.tsx +16 -4
  69. package/app-template/src/views/header/search/results.tsx +1 -1
  70. package/app-template/src/views/header/user-menu.tsx +3 -1
  71. package/app-template/src/views/installment-options/index.tsx +1 -1
  72. package/app-template/src/views/login/index.tsx +30 -6
  73. package/app-template/src/views/otp-login/index.tsx +12 -14
  74. package/app-template/src/views/product/product-info.tsx +2 -2
  75. package/app-template/src/views/product/slider.tsx +1 -1
  76. package/app-template/src/views/product-pointer-banner-item.tsx +1 -1
  77. package/app-template/src/views/register/index.tsx +29 -4
  78. package/app-template/src/views/sales-contract-modal/index.tsx +17 -17
  79. package/app-template/src/widgets/footer-info.tsx +1 -1
  80. package/app-template/src/widgets/footer-menu.tsx +1 -1
  81. package/app-template/src/widgets/footer-subscription/index.tsx +1 -1
  82. package/app-template/src/widgets/home-stories-eng.tsx +1 -1
  83. package/app-template/tailwind.config.js +1 -137
  84. package/codemods/sentry-9/index.js +30 -0
  85. package/codemods/sentry-9/remove-sentry-configs.js +14 -0
  86. package/codemods/sentry-9/remove-sentry-dependency.js +25 -0
  87. package/codemods/sentry-9/replace-error-page.js +32 -0
  88. package/commands/codemod.ts +18 -0
  89. package/commands/index.ts +3 -1
  90. package/commands/plugins.ts +4 -0
  91. package/dist/codemods/sentry-9/templates/error.js +14 -0
  92. package/dist/commands/codemod.js +16 -0
  93. package/dist/commands/index.js +3 -1
  94. package/dist/commands/plugins.js +4 -0
  95. package/package.json +1 -1
  96. package/app-template/postcss.config.js +0 -6
  97. package/app-template/sentry.client.config.ts +0 -16
  98. package/app-template/sentry.edge.config.ts +0 -3
  99. package/app-template/sentry.properties +0 -4
  100. package/app-template/sentry.server.config.ts +0 -3
  101. package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +0 -67
@@ -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
  });
@@ -182,6 +270,7 @@ const AccountOrderCancellation = ({ params }) => {
182
270
  onChange={onChange}
183
271
  value={value}
184
272
  selectOption={selectOption}
273
+ fileInput={fileInputCondition(item, item.product.name)}
185
274
  />
186
275
  );
187
276
  }}
@@ -144,7 +144,7 @@ const AccountOrderDetail = ({ params }) => {
144
144
  key={index}
145
145
  >
146
146
  <div className="flex gap-3 mb-5 lg:mb-0">
147
- <div className="flex-shrink-0">
147
+ <div className="shrink-0">
148
148
  <Link
149
149
  className="block"
150
150
  href={item.product.absolute_url}
@@ -208,8 +208,7 @@ const AccountOrderDetail = ({ params }) => {
208
208
  </div>
209
209
 
210
210
  {(item.is_cancellable || item.is_refundable) &&
211
- order.is_cancellable &&
212
- item.status.value == '400' && (
211
+ order.is_cancellable && (
213
212
  <div className="lg:ml-24">
214
213
  <Link
215
214
  href={`${ROUTES.ACCOUNT_ORDERS}/${order.id}/cancellation`}
@@ -227,7 +226,6 @@ const AccountOrderDetail = ({ params }) => {
227
226
  </div>
228
227
  )}
229
228
  </div>
230
-
231
229
  <div className="flex flex-col justify-center items-end lg:ml-6 lg:min-w-[7rem]">
232
230
  {parseFloat(item.retail_price) >
233
231
  parseFloat(item.price) && (
@@ -251,6 +249,70 @@ const AccountOrderDetail = ({ params }) => {
251
249
  );
252
250
  })}
253
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
+ )}
254
316
  </div>
255
317
  );
256
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" />
@@ -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"
@@ -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
  }
@@ -21,7 +21,7 @@ export default async function Page(props) {
21
21
  schema={schema}
22
22
  allFieldClasses={{
23
23
  className:
24
- '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',
25
25
  labelClassName: 'text-[#4a4f54] text-xs',
26
26
  wrapperClassName: 'flex flex-col mb-6'
27
27
  }}
@@ -8,10 +8,10 @@ const NotFound = () => {
8
8
  const { t } = useLocalization();
9
9
 
10
10
  return (
11
- <div className="py-6 flex flex-col items-center justify-center">
11
+ <div className="py-10 lg:py-8 flex flex-col items-center justify-center px-4 lg:px-0">
12
12
  <div className="text-8xl font-bold">404</div>
13
13
  <h1 className="text-4xl font-bold mb-4">{t('not_found.title')}</h1>
14
- <p className="text-lg mb-6">{t('not_found.sub_title')}</p>
14
+ <p className="text-lg mb-6 text-center">{t('not_found.sub_title')}</p>
15
15
  <Link href={'/'}>
16
16
  <Button className="h-auto mt-4 text-base py-3 px-6">
17
17
  {t('not_found.button')}
@@ -11,7 +11,7 @@ import {
11
11
  } from '@akinon/next/redux/reducers/checkout';
12
12
  import { RootState } from '@theme/redux/store';
13
13
  import { ROUTES } from '@theme/routes';
14
- import { useFetchCheckoutQuery } from '@akinon/next/data/client/checkout';
14
+ import { useFetchCheckoutQuery, useResetCheckoutStateQuery } from '@akinon/next/data/client/checkout';
15
15
  import { Button, LoaderSpinner } from '@theme/components';
16
16
  import { pushAddPaymentInfo, pushAddShippingInfo } from '@theme/utils/gtm';
17
17
  import { CheckoutStep } from '@akinon/next/types';
@@ -25,6 +25,8 @@ const Checkout = () => {
25
25
  (state: RootState) => state.checkout
26
26
  );
27
27
 
28
+ const { data: indexData, isLoading: isResetStateLoading } = useResetCheckoutStateQuery(null);
29
+
28
30
  const {
29
31
  data: checkoutData,
30
32
  isFetching,
@@ -32,7 +34,8 @@ const Checkout = () => {
32
34
  isSuccess,
33
35
  refetch: refetchCheckout
34
36
  } = useFetchCheckoutQuery(null, {
35
- refetchOnMountOrArgChange: true
37
+ refetchOnMountOrArgChange: true,
38
+ skip: isResetStateLoading || !indexData
36
39
  });
37
40
  const initialStepChanged = useRef<boolean>(false);
38
41
  const router = useRouter();
@@ -94,10 +97,10 @@ const Checkout = () => {
94
97
  );
95
98
  }
96
99
 
97
- if (isFetching || isError) {
100
+ if (isResetStateLoading || isFetching || isError) {
98
101
  return (
99
102
  <div className="flex flex-col items-center justify-center h-80">
100
- {isFetching ? (
103
+ {isResetStateLoading || isFetching ? (
101
104
  <LoaderSpinner />
102
105
  ) : (
103
106
  <>
@@ -1,5 +1,18 @@
1
1
  import { urlLocaleMatcherRegex } from '@akinon/next/utils';
2
2
 
3
+ /**
4
+ * XML Sitemap Route
5
+ *
6
+ * This route serves XML sitemaps from an S3 bucket.
7
+ *
8
+ * Required environment variables:
9
+ * - SITEMAP_S3_BUCKET_NAME: The name of the S3 bucket containing the sitemaps
10
+ * Example: "0fb534"
11
+ *
12
+ * If the environment variable is not set, the route will return a 503 Service Unavailable
13
+ * response with a JSON error message.
14
+ */
15
+
3
16
  export const dynamic = 'force-dynamic';
4
17
 
5
18
  export async function GET(request: Request, context: { params }) {
@@ -7,9 +20,42 @@ export async function GET(request: Request, context: { params }) {
7
20
  const url = new URL(request.url);
8
21
  const matchedLocale = url.pathname.match(urlLocaleMatcherRegex);
9
22
 
23
+ const s3BucketName = process.env.SITEMAP_S3_BUCKET_NAME;
24
+
25
+ if (!s3BucketName) {
26
+ return new Response(
27
+ JSON.stringify({
28
+ error: 'Configuration error',
29
+ message: 'Please set the SITEMAP_S3_BUCKET_NAME environment variable'
30
+ }),
31
+ {
32
+ status: 503,
33
+ headers: {
34
+ 'Content-Type': 'application/json'
35
+ }
36
+ }
37
+ );
38
+ }
39
+
10
40
  const sitemap = await fetch(
11
- `https://s3.eu-central-1.amazonaws.com/0fb534/sitemaps/sitemaps/sitemap-${node}.xml.gz`
41
+ `https://s3.eu-central-1.amazonaws.com/${s3BucketName}/sitemaps/sitemaps/sitemap-${node}.xml.gz`
12
42
  );
43
+
44
+ if (!sitemap.ok) {
45
+ return new Response(
46
+ JSON.stringify({
47
+ error: 'Sitemap not found',
48
+ message: `Failed to fetch sitemap for node: ${node}`
49
+ }),
50
+ {
51
+ status: 503,
52
+ headers: {
53
+ 'Content-Type': 'application/json'
54
+ }
55
+ }
56
+ );
57
+ }
58
+
13
59
  let sitemapContent = await sitemap.text();
14
60
 
15
61
  sitemapContent = sitemapContent.replace(
@@ -2,48 +2,176 @@
2
2
  @use './fonts/index.scss' as fonts;
3
3
  @use './fonts/pz-icon.css' as icons;
4
4
 
5
- @tailwind base;
6
- @tailwind components;
7
- @tailwind utilities;
8
-
9
- @layer utilities {
10
- .header-grid-template-areas {
11
- grid-template-areas:
12
- 'band-l band-m band-r'
13
- 'main-l main-m main-r'
14
- 'nav nav nav';
15
- }
5
+ @import 'tailwindcss';
16
6
 
17
- .header-grid-area-band-l {
18
- grid-area: band-l;
19
- }
7
+ @plugin '@tailwindcss/typography';
20
8
 
21
- .header-grid-area-band-m {
22
- grid-area: band-m;
23
- }
9
+ @config '../../tailwind.config.js';
24
10
 
25
- .header-grid-area-band-r {
26
- grid-area: band-r;
27
- }
11
+ @source '../app/**/*.{js,ts,jsx,tsx}';
12
+ @source '../pages/**/*.{js,ts,jsx,tsx}';
13
+ @source '../components/**/*.{js,ts,jsx,tsx}';
14
+ @source '../views/**/*.{js,ts,jsx,tsx}';
15
+ @source '../widgets/**/*.{js,ts,jsx,tsx}';
16
+ @source '../hooks/**/*.{js,ts,jsx,tsx}';
17
+ @source '../utils/**/*.{js,ts,jsx,tsx}';
28
18
 
29
- .header-grid-area-main-l {
30
- grid-area: main-l;
31
- }
19
+ @theme {
20
+ --font-sans: 'Jost', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
21
+ 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
22
+ --font-size-2xs: 0.5rem;
23
+ --outline-off: none;
32
24
 
33
- .header-grid-area-main-m {
34
- grid-area: main-m;
35
- }
25
+ --width-1-10: 10%;
26
+ --width-2-10: 20%;
27
+ --width-3-10: 30%;
28
+ --width-4-10: 40%;
29
+ --width-5-10: 50%;
30
+ --width-6-10: 60%;
31
+ --width-7-10: 70%;
32
+ --width-8-10: 80%;
33
+ --width-9-10: 90%;
36
34
 
37
- .header-grid-area-main-r {
38
- grid-area: main-r;
39
- }
35
+ --transition-property-max-width: max-width;
36
+
37
+ --background-image-skeleton-shimmer: linear-gradient(
38
+ 90deg,
39
+ #d7d7d7 0%,
40
+ #ebebeb 40%,
41
+ #eeeeee 60%,
42
+ #d7d7d7
43
+ );
40
44
 
41
- .header-grid-area-nav {
42
- grid-area: nav;
45
+ --animate-skeleton-shimmer: skeleton-shimmer 2s linear infinite;
46
+
47
+ @keyframes skeleton-shimmer {
48
+ to {
49
+ transform: translateX(100%);
50
+ }
43
51
  }
44
52
 
45
- .header-m-template-cols {
46
- grid-template-columns: auto 1fr 1fr;
53
+ --color-transparent: transparent;
54
+ --color-white: #ffffff;
55
+ --color-primary: #000000;
56
+ --color-primary-hover: #181818;
57
+ --color-primary-foreground: #ffffff;
58
+ --color-primary-100: #525252;
59
+ --color-primary-200: #404040;
60
+ --color-primary-300: #3d3d3d;
61
+ --color-primary-400: #333333;
62
+ --color-primary-500: #2d2d2d;
63
+ --color-primary-600: #292929;
64
+ --color-primary-700: #2b2b2b;
65
+ --color-primary-800: #181818;
66
+ --color-primary-900: #000000;
67
+
68
+ --color-secondary: #e95151;
69
+ --color-secondary-hover: #d03838;
70
+ --color-secondary-foreground: #ffffff;
71
+ --color-secondary-100: #ffb7b7;
72
+ --color-secondary-200: #ff9e9e;
73
+ --color-secondary-300: #ff8484;
74
+ --color-secondary-400: #ff6b6b;
75
+ --color-secondary-500: #e95151;
76
+ --color-secondary-600: #d72b01;
77
+ --color-secondary-700: #b61e1e;
78
+ --color-secondary-800: #9d0505;
79
+ --color-secondary-900: #830000;
80
+
81
+ --color-black: #000000;
82
+ --color-black-100: #525252;
83
+ --color-black-200: #404040;
84
+ --color-black-300: #3d3d3d;
85
+ --color-black-400: #333333;
86
+ --color-black-500: #2d2d2d;
87
+ --color-black-600: #292929;
88
+ --color-black-700: #2b2b2b;
89
+ --color-black-800: #181818;
90
+ --color-black-900: #000000;
91
+
92
+ --color-gray: #ebebeb;
93
+ --color-gray-25: #fdfdfd;
94
+ --color-gray-50: #f7f7f7;
95
+ --color-gray-100: #f5f5f5;
96
+ --color-gray-150: #f4f4f4;
97
+ --color-gray-200: #eeeeee;
98
+ --color-gray-300: #ebebeb;
99
+ --color-gray-400: #d7d7d7;
100
+ --color-gray-450: #d4d4d4;
101
+ --color-gray-500: #c9c9c9;
102
+ --color-gray-600: #9d9d9d;
103
+ --color-gray-700: #686868;
104
+ --color-gray-800: #615f62;
105
+ --color-gray-850: #58585a;
106
+ --color-gray-900: #4a4f54;
107
+ --color-gray-950: #424242;
108
+
109
+ --color-error: #d72b01;
110
+ --color-error-100: #e20008;
111
+
112
+ --color-success: #7b9d76;
113
+ --color-success-100: #7b9d76;
114
+
115
+ --container-center: true;
116
+ --container-padding: 0rem;
117
+ --container-padding-sm: 2rem;
118
+ --container-padding-2xl: 0rem;
119
+
120
+ --breakpoint-xs: 575px;
121
+ --breakpoint-sm: 640px;
122
+ --breakpoint-md: 768px;
123
+ --breakpoint-lg: 1024px;
124
+ --breakpoint-xl: 1170px;
125
+ --breakpoint-2xl: 1370px;
126
+ }
127
+
128
+ @utility header-grid-template-areas {
129
+ grid-template-areas:
130
+ 'band-l band-m band-r'
131
+ 'main-l main-m main-r'
132
+ 'nav nav nav';
133
+ }
134
+
135
+ @utility header-grid-area-band-l {
136
+ grid-area: band-l;
137
+ }
138
+
139
+ @utility header-grid-area-band-m {
140
+ grid-area: band-m;
141
+ }
142
+
143
+ @utility header-grid-area-band-r {
144
+ grid-area: band-r;
145
+ }
146
+
147
+ @utility header-grid-area-main-l {
148
+ grid-area: main-l;
149
+ }
150
+
151
+ @utility header-grid-area-main-m {
152
+ grid-area: main-m;
153
+ }
154
+
155
+ @utility header-grid-area-main-r {
156
+ grid-area: main-r;
157
+ }
158
+
159
+ @utility header-grid-area-nav {
160
+ grid-area: nav;
161
+ }
162
+
163
+ @utility header-m-template-cols {
164
+ grid-template-columns: auto 1fr 1fr;
165
+ }
166
+
167
+ @utility container {
168
+ margin-inline: auto;
169
+ }
170
+
171
+ @layer base {
172
+ input::placeholder,
173
+ textarea::placeholder {
174
+ color: theme(--color-gray-400);
47
175
  }
48
176
 
49
177
  input {
@@ -51,7 +179,7 @@
51
179
  &:not(:placeholder-shown),
52
180
  &:autofill {
53
181
  & + label {
54
- @apply top-1/3;
182
+ top: calc(1 / 3 * 100%);
55
183
  }
56
184
  }
57
185
  }