@akinon/projectzero 1.87.0 → 1.88.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @akinon/projectzero
2
2
 
3
+ ## 1.88.0
4
+
3
5
  ## 1.87.0
4
6
 
5
7
  ## 1.86.0
@@ -1,9 +1,11 @@
1
1
  NEXTAUTH_SECRET=PDpBb/aSJESgBbPLHw1+jveHXqyvkC7GC1Z82jvE04s=
2
+ # When using dev-ssl command, NEXTAUTH_URL should be updated to https://localhost:3000
2
3
  NEXTAUTH_URL=http://localhost:3000
3
4
  NEXT_PUBLIC_MAP_API_KEY=YOUR_MAP_API_KEY
4
5
  NEXT_PUBLIC_GTM_KEY=GTM_KEY
5
6
  NEXT_PUBLIC_URL=http://localhost:3000
6
7
  SERVICE_BACKEND_URL=https://02fde10fee4440269e695aa10707dfaf.lb.akinoncloud.com
8
+ SITEMAP_S3_BUCKET_NAME=0fb534
7
9
 
8
10
  # LOG_LEVEL_DEV=debug # For more details, please refer to the Logging documentation.
9
11
 
@@ -1,5 +1,38 @@
1
1
  # projectzeronext
2
2
 
3
+ ## 1.88.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3037704: ZERO-3328: Add data-testid attributes for improved testing in BasketItem and BasketGiftPack components
8
+ - dbddebc: ZERO-3330: Update environment variables and enhance XML sitemap route error handling
9
+ - a786e0e: ZERO-3335: Use optional chaining to prevent runtime errors
10
+ - 054b94d: ZERO-3261: Add rejected return image to order page
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [ce64181]
15
+ - Updated dependencies [3037704]
16
+ - Updated dependencies [c4f0568]
17
+ - Updated dependencies [f3dcb1e]
18
+ - Updated dependencies [8154859]
19
+ - @akinon/pz-checkout-gift-pack@1.88.0
20
+ - @akinon/pz-basket-gift-pack@1.88.0
21
+ - @akinon/next@1.88.0
22
+ - @akinon/pz-akifast@1.88.0
23
+ - @akinon/pz-b2b@1.88.0
24
+ - @akinon/pz-bkm@1.88.0
25
+ - @akinon/pz-click-collect@1.88.0
26
+ - @akinon/pz-credit-payment@1.88.0
27
+ - @akinon/pz-gpay@1.88.0
28
+ - @akinon/pz-masterpass@1.88.0
29
+ - @akinon/pz-one-click-checkout@1.88.0
30
+ - @akinon/pz-otp@1.88.0
31
+ - @akinon/pz-pay-on-delivery@1.88.0
32
+ - @akinon/pz-saved-card@1.88.0
33
+ - @akinon/pz-tabby-extension@1.88.0
34
+ - @akinon/pz-tamara-extension@1.88.0
35
+
3
36
  ## 1.87.0
4
37
 
5
38
  ### Minor Changes
@@ -6,6 +6,12 @@ Headless Akinon Commerce Cloud Storefront.
6
6
 
7
7
  Run `cp .env.example .env && yarn` command at project root and all dependencies should be installed.
8
8
 
9
+ ## Environment Variables
10
+
11
+ ### Required Environment Variables
12
+
13
+ - `SITEMAP_S3_BUCKET_NAME`: S3 bucket name for XML sitemaps (e.g., "0fb534"). This is required for the sitemap route to function correctly.
14
+
9
15
  ### Build
10
16
 
11
17
  To build the app, run the following command:
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "projectzeronext",
3
- "version": "1.87.0",
3
+ "version": "1.88.0",
4
4
  "private": true,
5
5
  "license": "MIT",
6
6
  "scripts": {
7
7
  "clean": "rm -rf node_modules && rm -rf .next",
8
8
  "dev": "next dev",
9
+ "dev-ssl": "next dev --experimental-https",
9
10
  "build": "next build",
10
11
  "start": "next start -p 8008",
11
12
  "type-check": "tsc",
@@ -23,22 +24,22 @@
23
24
  "test:middleware": "jest middleware-matcher.test.ts --bail"
24
25
  },
25
26
  "dependencies": {
26
- "@akinon/next": "1.87.0",
27
- "@akinon/pz-akifast": "1.87.0",
28
- "@akinon/pz-b2b": "1.87.0",
29
- "@akinon/pz-basket-gift-pack": "1.87.0",
30
- "@akinon/pz-bkm": "1.87.0",
31
- "@akinon/pz-checkout-gift-pack": "1.87.0",
32
- "@akinon/pz-click-collect": "1.87.0",
33
- "@akinon/pz-credit-payment": "1.87.0",
34
- "@akinon/pz-gpay": "1.87.0",
35
- "@akinon/pz-masterpass": "1.87.0",
36
- "@akinon/pz-one-click-checkout": "1.87.0",
37
- "@akinon/pz-otp": "1.87.0",
38
- "@akinon/pz-pay-on-delivery": "1.87.0",
39
- "@akinon/pz-saved-card": "1.87.0",
40
- "@akinon/pz-tabby-extension": "1.87.0",
41
- "@akinon/pz-tamara-extension": "1.87.0",
27
+ "@akinon/next": "1.88.0",
28
+ "@akinon/pz-akifast": "1.88.0",
29
+ "@akinon/pz-b2b": "1.88.0",
30
+ "@akinon/pz-basket-gift-pack": "1.88.0",
31
+ "@akinon/pz-bkm": "1.88.0",
32
+ "@akinon/pz-checkout-gift-pack": "1.88.0",
33
+ "@akinon/pz-click-collect": "1.88.0",
34
+ "@akinon/pz-credit-payment": "1.88.0",
35
+ "@akinon/pz-gpay": "1.88.0",
36
+ "@akinon/pz-masterpass": "1.88.0",
37
+ "@akinon/pz-one-click-checkout": "1.88.0",
38
+ "@akinon/pz-otp": "1.88.0",
39
+ "@akinon/pz-pay-on-delivery": "1.88.0",
40
+ "@akinon/pz-saved-card": "1.88.0",
41
+ "@akinon/pz-tabby-extension": "1.88.0",
42
+ "@akinon/pz-tamara-extension": "1.88.0",
42
43
  "@hookform/resolvers": "2.9.0",
43
44
  "@next/third-parties": "14.1.0",
44
45
  "@react-google-maps/api": "2.17.1",
@@ -61,7 +62,7 @@
61
62
  "yup": "0.32.11"
62
63
  },
63
64
  "devDependencies": {
64
- "@akinon/eslint-plugin-projectzero": "1.87.0",
65
+ "@akinon/eslint-plugin-projectzero": "1.88.0",
65
66
  "@semantic-release/changelog": "6.0.2",
66
67
  "@semantic-release/exec": "6.0.3",
67
68
  "@semantic-release/git": "10.0.1",
@@ -254,6 +254,10 @@
254
254
  "cancelled": "Cancelled",
255
255
  "cancellation_request_recieved": "Cancellation Request Recieved",
256
256
  "close_button": "Close",
257
+ "return_status": "Return Status",
258
+ "rejected": "Return Rejected",
259
+ "completed": "Return Completed",
260
+ "explanation": "Explanation",
257
261
  "success": {
258
262
  "title": "Success",
259
263
  "description": "Your request have been recieved and will be evaluated as soon as possible."
@@ -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."
@@ -206,8 +206,7 @@ const AccountOrderDetail = ({ params: { id } }) => {
206
206
  </div>
207
207
 
208
208
  {(item.is_cancellable || item.is_refundable) &&
209
- order.is_cancellable &&
210
- item.status.value == '400' && (
209
+ order.is_cancellable && (
211
210
  <div className="lg:ml-24">
212
211
  <Link
213
212
  href={`${ROUTES.ACCOUNT_ORDERS}/${order.id}/cancellation`}
@@ -225,7 +224,6 @@ const AccountOrderDetail = ({ params: { id } }) => {
225
224
  </div>
226
225
  )}
227
226
  </div>
228
-
229
227
  <div className="flex flex-col justify-center items-end lg:ml-6 lg:min-w-[7rem]">
230
228
  {parseFloat(item.retail_price) >
231
229
  parseFloat(item.price) && (
@@ -249,6 +247,70 @@ const AccountOrderDetail = ({ params: { id } }) => {
249
247
  );
250
248
  })}
251
249
  </div>
250
+
251
+ {group.map((item) =>
252
+ item.cancellationrequest_set?.map((cancellationItem) => {
253
+ const status = cancellationItem.status?.value;
254
+ const isRejected = status === 'rejected';
255
+ const isCompleted = status === 'completed';
256
+
257
+ const filteredImages =
258
+ cancellationItem.cancellation_request_image_set?.filter(
259
+ (img) =>
260
+ isRejected
261
+ ? !img.is_uploaded_by_user
262
+ : isCompleted
263
+ ? img.is_uploaded_by_user
264
+ : false
265
+ ) || [];
266
+
267
+ const statusText = isRejected
268
+ ? t('account.my_orders.return.rejected')
269
+ : isCompleted
270
+ ? t('account.my_orders.return.completed')
271
+ : null;
272
+
273
+ if (!statusText || filteredImages.length === 0) return null;
274
+
275
+ return (
276
+ <div
277
+ className="w-full px-4 lg:px-7"
278
+ key={cancellationItem.id}
279
+ >
280
+ <div className="flex flex-col py-2 gap-4 border-t border-gray">
281
+ <div className="flex flex-col">
282
+ <div className="text-sm font-semibold">
283
+ {t('account.my_orders.return.return_status')}
284
+ <span className="font-normal"> {statusText}</span>
285
+ </div>
286
+
287
+ <div className="flex gap-2 mt-2 flex-wrap">
288
+ {filteredImages.map((img) => (
289
+ <div className="flex flex-col gap-2" key={img.id}>
290
+ <Link href={img.image} target="_blank">
291
+ <Image
292
+ src={img.image}
293
+ width={112}
294
+ height={150}
295
+ alt={img.description}
296
+ />
297
+ </Link>
298
+
299
+ {img.description && (
300
+ <p className="text-xs">
301
+ {t('account.my_orders.return.explanation')}:
302
+ {img.description}
303
+ </p>
304
+ )}
305
+ </div>
306
+ ))}
307
+ </div>
308
+ </div>
309
+ </div>
310
+ </div>
311
+ );
312
+ })
313
+ )}
252
314
  </div>
253
315
  );
254
316
  })}
@@ -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(
@@ -48,7 +48,7 @@ const Select = forwardRef<HTMLSelectElement, SelectProps>((props, ref) => {
48
48
  className
49
49
  )}
50
50
  >
51
- {options.map((option) => (
51
+ {options?.map((option) => (
52
52
  <option
53
53
  key={option.value}
54
54
  value={option.value}
@@ -39,7 +39,7 @@ export const Tabs = (props: Props) => {
39
39
  tabBarPosition === 'left' || tabBarPosition === 'right'
40
40
  })}
41
41
  >
42
- {children.map((item, index) => (
42
+ {children?.map((item, index) => (
43
43
  <Tab
44
44
  key={item.props.title}
45
45
  title={item.props.title}
@@ -52,7 +52,7 @@ export const Tabs = (props: Props) => {
52
52
  ))}
53
53
  </ul>
54
54
 
55
- <div className="w-full">{children[selectedTabIndex]}</div>
55
+ <div className="w-full">{children?.[selectedTabIndex]}</div>
56
56
  </div>
57
57
  );
58
58
  };
@@ -4,7 +4,7 @@ import { setSelectedFacets } from '@theme/redux/reducers/category';
4
4
 
5
5
  const getSelectedFacets = (facets: Array<Facet>) => {
6
6
  return facets
7
- .map((facet) => {
7
+ ?.map((facet) => {
8
8
  return {
9
9
  ...facet,
10
10
  data: {
@@ -32,7 +32,7 @@ const categorySlice = createSlice({
32
32
  toggleFacet(state, action) {
33
33
  const facets = JSON.parse(JSON.stringify(state.facets));
34
34
 
35
- state.selectedFacets = facets.map((facet) => {
35
+ state.selectedFacets = facets?.map((facet) => {
36
36
  if (facet.key === action.payload.facet.key) {
37
37
  facet.data.choices = facet.data.choices
38
38
  .map((choice) => {
@@ -1,7 +1,7 @@
1
1
  import { Facet } from '@akinon/next/types';
2
2
 
3
3
  const convertFacetSearchParams = (facets: Facet[]) => {
4
- const _facets: string[][] = facets.flatMap((facet) => {
4
+ const _facets: string[][] = facets?.flatMap((facet) => {
5
5
  return [
6
6
  ...facet.data.choices.map((choice) => {
7
7
  return [facet.search_key, choice.value as string];
@@ -94,25 +94,6 @@ export const OrderCancellationItem = ({
94
94
  {selectOption}
95
95
  </div>
96
96
  <div>{fileInput}</div>
97
- {item.active_cancellation_request?.cancellation_request_image_set && (
98
- <div className="flex flex-wrap gap-2">
99
- {item.active_cancellation_request?.cancellation_request_image_set?.map(
100
- (item) => {
101
- return (
102
- <div className="relative w-16 h-16" key={item.id}>
103
- <Image
104
- src={item.image}
105
- alt={item.description}
106
- aspectRatio={1}
107
- sizes="64px"
108
- fill
109
- />
110
- </div>
111
- );
112
- }
113
- )}
114
- </div>
115
- )}
116
97
  </div>
117
98
  </div>
118
99
  );
@@ -87,6 +87,7 @@ export const BasketItem = (props: Props) => {
87
87
  <li
88
88
  key={basketItem.id}
89
89
  className="flex border-b border-gray-200 py-3 relative"
90
+ data-testid="basket-item"
90
91
  >
91
92
  <div className="w-20 lg:w-[105px] mr-4 shrink-0">
92
93
  <Link href={basketItem.product.absolute_url} passHref>
@@ -69,7 +69,7 @@ const CategoryActiveFilters = () => {
69
69
 
70
70
  return (
71
71
  <div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-2 mb-4">
72
- {facets.map((facet) =>
72
+ {facets?.map((facet) =>
73
73
  facet?.data?.choices
74
74
  ?.filter((choice) => choice.is_selected)
75
75
  ?.map(
@@ -121,7 +121,7 @@ export const CategoryHeader = (props: Props) => {
121
121
  </Button>
122
122
  <Select
123
123
  options={sortOptions}
124
- value={sortOptions.find(({ is_selected }) => is_selected).value}
124
+ value={sortOptions?.find(({ is_selected }) => is_selected)?.value}
125
125
  data-testid="list-sorter"
126
126
  onChange={(e) => {
127
127
  handleSelectFilter({
@@ -102,7 +102,7 @@ export default function ListPage(props: ListPageProps) {
102
102
  </div>
103
103
  )}
104
104
 
105
- {data.products.length === 0 && page > 1 && <LoaderSpinner />}
105
+ {data.products?.length === 0 && page > 1 && <LoaderSpinner />}
106
106
 
107
107
  <div
108
108
  className={clsx('grid gap-x-4 gap-y-12 grid-cols-2', {
@@ -111,7 +111,7 @@ export default function ListPage(props: ListPageProps) {
111
111
  'lg:grid-cols-3': layoutSize === 3
112
112
  })}
113
113
  >
114
- {data.products.map((product, index) => (
114
+ {data?.products?.map((product, index) => (
115
115
  <ProductItem
116
116
  key={product.pk}
117
117
  product={product}
@@ -121,7 +121,7 @@ export default function ListPage(props: ListPageProps) {
121
121
  />
122
122
  ))}
123
123
  </div>
124
- {data.products.length > 0 && (
124
+ {data?.products?.length > 0 && (
125
125
  <Pagination
126
126
  total={data.pagination.total_count}
127
127
  limit={data.pagination.page_size}
@@ -23,7 +23,7 @@ export const Filters = (props: Props) => {
23
23
  const [isPending, startTransition] = useTransition();
24
24
 
25
25
  const haveFilter = useMemo(() => {
26
- return facets.some((facet) =>
26
+ return facets?.some((facet) =>
27
27
  facet?.data?.choices?.some((choice) => choice.is_selected)
28
28
  );
29
29
  }, [facets]);
@@ -50,7 +50,7 @@ export const Filters = (props: Props) => {
50
50
  <span>{t('category.filters.ready_to_wear')}</span>
51
51
  </div>
52
52
 
53
- {facets.map((facet) => {
53
+ {facets?.map((facet) => {
54
54
  return (
55
55
  <FilterItem
56
56
  key={facet.key}
@@ -18,7 +18,7 @@ const PaymentStep = () => {
18
18
  'pointer-events-none opacity-30': isPaymentStepBusy
19
19
  })}
20
20
  >
21
- <div className="w-full mt-4 flex justify-start border border-gray-400 -mb-px z-10 md:mt-0 md:border-r-0 md:border-b-0 md:w-auto order-2 md:order-none">
21
+ <div className="w-full mt-4 flex justify-start border border-gray-400 -mb-px z-10 md:mt-0 md:border-r-0 md:border-b-0 md:w-auto order-2 md:order-none overflow-x-auto">
22
22
  <PaymentOptionButtons />
23
23
  </div>
24
24
  <div className="w-full border border-solid border-gray-400 bg-white">
@@ -22,7 +22,7 @@ const InstallmentOptions = (props: InstallmentProps) => {
22
22
 
23
23
  useEffect(() => {
24
24
  if (isSuccess && data) {
25
- setActiveCardSlug(data.results[0].slug);
25
+ setActiveCardSlug(data.results?.[0]?.slug);
26
26
  }
27
27
  }, [isSuccess, data]);
28
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/projectzero",
3
- "version": "1.87.0",
3
+ "version": "1.88.0",
4
4
  "private": false,
5
5
  "description": "CLI tool to manage your Project Zero Next project",
6
6
  "bin": {