@akinon/projectzero 1.77.0 → 1.78.0-rc.1

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 (27) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/app-template/.gitignore +2 -0
  3. package/app-template/CHANGELOG.md +2655 -124
  4. package/app-template/package.json +18 -18
  5. package/app-template/public/locales/en/common.json +4 -0
  6. package/app-template/public/locales/tr/common.json +4 -0
  7. package/app-template/src/app/[commerce]/[locale]/[currency]/account/orders/[id]/cancellation/page.tsx +94 -5
  8. package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +9 -82
  9. package/app-template/src/app/[commerce]/[locale]/[currency]/orders/checkout/page.tsx +7 -4
  10. package/app-template/src/app/[commerce]/[locale]/[currency]/page.tsx +8 -0
  11. package/app-template/src/components/button.tsx +50 -35
  12. package/app-template/src/components/file-input.tsx +44 -2
  13. package/app-template/src/components/types/index.ts +4 -1
  14. package/app-template/src/components/widget/widget-placeholder.tsx +12 -0
  15. package/app-template/src/middleware.ts +1 -0
  16. package/app-template/src/settings.js +6 -1
  17. package/app-template/src/views/account/address-form.tsx +2 -2
  18. package/app-template/src/views/account/contact-form.tsx +3 -8
  19. package/app-template/src/views/account/orders/order-cancellation-item.tsx +4 -3
  20. package/app-template/src/views/basket/basket-content.tsx +106 -0
  21. package/app-template/src/views/basket/basket-item.tsx +16 -13
  22. package/app-template/src/views/basket/summary.tsx +10 -7
  23. package/app-template/src/views/login/index.tsx +28 -4
  24. package/app-template/src/views/register/index.tsx +30 -5
  25. package/package.json +1 -1
  26. package/app-template/sentry.edge.config.ts +0 -3
  27. package/app-template/sentry.server.config.ts +0 -3
@@ -8,7 +8,8 @@ export const OrderCancellationItem = ({
8
8
  item,
9
9
  value,
10
10
  onChange,
11
- selectOption
11
+ selectOption,
12
+ fileInput
12
13
  }) => {
13
14
  const { t } = useLocalization();
14
15
  const checkboxStatus =
@@ -38,7 +39,6 @@ export const OrderCancellationItem = ({
38
39
  <div className="flex flex-wrap justify-between border-gray border-b mb-4 pb-3">
39
40
  <div className="flex gap-3 mb-5 lg:mb-0">
40
41
  {checkboxStatus && (
41
- // TODO: Static image will change (TR)
42
42
  <Checkbox
43
43
  className="m-auto"
44
44
  data-testid="account-orders-return-checkbox"
@@ -80,7 +80,7 @@ export const OrderCancellationItem = ({
80
80
  </div>
81
81
  </div>
82
82
  </div>
83
- <div className="flex flex-wrap justify-between w-full items-start lg:items-center lg:w-auto">
83
+ <div className="flex flex-wrap justify-between w-full items-start lg:items-center lg:w-80 gap-4">
84
84
  <div className="w-full flex flex-col lg:items-center lg:flex-row">
85
85
  {item.active_cancellation_request?.easy_return?.code && (
86
86
  <div className="flex items-center">
@@ -93,6 +93,7 @@ export const OrderCancellationItem = ({
93
93
 
94
94
  {selectOption}
95
95
  </div>
96
+ <div>{fileInput}</div>
96
97
  </div>
97
98
  </div>
98
99
  );
@@ -0,0 +1,106 @@
1
+ 'use client';
2
+
3
+ import { useLocalization } from '@akinon/next/hooks';
4
+ import { Basket } from '@akinon/next/types';
5
+ import { Button, LoaderSpinner, Link } from '@theme/components';
6
+ import { BasketItem, Summary } from '@theme/views/basket';
7
+ import { ROUTES } from '@theme/routes';
8
+ import PluginModule, { Component } from '@akinon/next/components/plugin-module';
9
+ import { useEffect, useState } from 'react';
10
+ import { pushCartView } from '@theme/utils/gtm';
11
+
12
+ interface BasketContentProps {
13
+ initialBasket: Basket;
14
+ multiBasket: boolean;
15
+ }
16
+
17
+ export function BasketContent({
18
+ initialBasket,
19
+ multiBasket
20
+ }: BasketContentProps) {
21
+ const { t } = useLocalization();
22
+ const [basket, setBasket] = useState<Basket>(initialBasket);
23
+
24
+ useEffect(() => {
25
+ if (basket) {
26
+ const products = basket.basketitem_set.map((basketItem) => ({
27
+ ...basketItem.product
28
+ }));
29
+ pushCartView(products);
30
+ }
31
+ }, [basket]);
32
+
33
+ const handleBasketUpdate = (updatedBasket: Basket) => {
34
+ setBasket(updatedBasket);
35
+ };
36
+
37
+ if (!basket) {
38
+ return (
39
+ <div className="flex justify-center w-full">
40
+ <LoaderSpinner />
41
+ </div>
42
+ );
43
+ }
44
+
45
+ return (
46
+ <div className="max-w-screen-xl p-4 flex flex-col text-primary-800 lg:p-8 xl:flex-row xl:mx-auto">
47
+ {basket.basketitem_set && basket.basketitem_set.length > 0 ? (
48
+ <>
49
+ <div className="flex-1 xl:mr-16">
50
+ <div className="flex items-center justify-between py-2 border-b border-gray-200 lg:py-3">
51
+ <h2 className="text-xl lg:text-2xl font-light">
52
+ {t('basket.my_cart')}
53
+ </h2>
54
+ <Link
55
+ href={ROUTES.HOME}
56
+ className="text-xs hover:text-secondary-500"
57
+ >
58
+ {t('basket.back_to_shopping')}
59
+ </Link>
60
+ </div>
61
+ <ul>
62
+ {multiBasket ? (
63
+ <PluginModule
64
+ component={Component.MultiBasket}
65
+ props={{
66
+ BasketItem,
67
+ onBasketUpdate: handleBasketUpdate
68
+ }}
69
+ />
70
+ ) : (
71
+ basket.basketitem_set.map((basketItem, index) => (
72
+ <BasketItem
73
+ key={index}
74
+ basketItem={basketItem}
75
+ onBasketUpdate={handleBasketUpdate}
76
+ />
77
+ ))
78
+ )}
79
+ </ul>
80
+ </div>
81
+ <Summary basket={basket} onBasketUpdate={handleBasketUpdate} />
82
+ </>
83
+ ) : (
84
+ <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">
85
+ <h1
86
+ className="w-full text-xl font-light text-secondary text-center sm:text-2xl"
87
+ data-testid="basket-empty"
88
+ >
89
+ {t('basket.empty.title')}
90
+ </h1>
91
+
92
+ <div className="w-full text-sm text-black-800 text-center my-4 mb-2 sm:text-base">
93
+ <p>{t('basket.empty.content_first')}</p>
94
+ <p>{t('basket.empty.content_second')}.</p>
95
+ </div>
96
+
97
+ <Link href={ROUTES.HOME} passHref>
98
+ <Button className="px-10 mt-2" appearance="filled">
99
+ {t('basket.empty.button')}
100
+ </Button>
101
+ </Link>
102
+ </div>
103
+ )}
104
+ </div>
105
+ );
106
+ }
@@ -3,7 +3,7 @@ import {
3
3
  useUpdateQuantityMutation
4
4
  } from '@akinon/next/data/client/basket';
5
5
  import { useAppDispatch } from '@akinon/next/redux/hooks';
6
- import { BasketItem as BasketItemType } from '@akinon/next/types';
6
+ import { Basket, BasketItem as BasketItemType } from '@akinon/next/types';
7
7
  import { Price, Button, Icon, Modal, Select, Link } from '@theme/components';
8
8
  import { useState } from 'react';
9
9
  import { useAddFavoriteMutation } from '@akinon/next/data/client/wishlist';
@@ -19,11 +19,12 @@ import { pushRemoveFromCart } from '@theme/utils/gtm';
19
19
  interface Props {
20
20
  basketItem?: BasketItemType;
21
21
  namespace?: string;
22
+ onBasketUpdate?: (basket: Basket) => void;
22
23
  }
23
24
 
24
25
  export const BasketItem = (props: Props) => {
25
26
  const { t } = useLocalization();
26
- const { basketItem, namespace } = props;
27
+ const { basketItem, namespace, onBasketUpdate } = props;
27
28
  const [updateQuantityMutation] = useUpdateQuantityMutation();
28
29
  const dispatch = useAppDispatch();
29
30
  const [isRemoveBasketModalOpen, setRemoveBasketModalOpen] = useState(false);
@@ -49,19 +50,21 @@ export const BasketItem = (props: Props) => {
49
50
  requestParams.namespace = namespace;
50
51
  }
51
52
 
52
- await updateQuantityMutation(requestParams)
53
- .unwrap()
54
- .then((data) =>
55
- dispatch(
56
- basketApi.util.updateQueryData(
57
- 'getBasket',
58
- undefined,
59
- (draftBasket) => {
60
- Object.assign(draftBasket, data.basket);
61
- }
62
- )
53
+ try {
54
+ const response = await updateQuantityMutation(requestParams).unwrap();
55
+ dispatch(
56
+ basketApi.util.updateQueryData(
57
+ 'getBasket',
58
+ undefined,
59
+ (draftBasket) => {
60
+ Object.assign(draftBasket, response.basket);
61
+ }
63
62
  )
64
63
  );
64
+ onBasketUpdate?.(response.basket);
65
+ } catch (error) {
66
+ console.error('Error updating quantity:', error);
67
+ }
65
68
  };
66
69
 
67
70
  const deleteProduct = async (productPk?: number) => {
@@ -18,6 +18,7 @@ import clsx from 'clsx';
18
18
 
19
19
  interface Props {
20
20
  basket: Basket;
21
+ onBasketUpdate?: (basket: Basket) => void;
21
22
  }
22
23
 
23
24
  const voucherCodeFormSchema = (t) =>
@@ -27,7 +28,7 @@ const voucherCodeFormSchema = (t) =>
27
28
 
28
29
  export const Summary = (props: Props) => {
29
30
  const { t } = useLocalization();
30
- const { basket } = props;
31
+ const { basket, onBasketUpdate } = props;
31
32
  const router = useRouter();
32
33
  const {
33
34
  register,
@@ -53,7 +54,7 @@ export const Summary = (props: Props) => {
53
54
  const removeVoucherCode = () => {
54
55
  removeVoucherCodeMutation()
55
56
  .unwrap()
56
- .then((basket) =>
57
+ .then((basket) => {
57
58
  dispatch(
58
59
  basketApi.util.updateQueryData(
59
60
  'getBasket',
@@ -62,8 +63,9 @@ export const Summary = (props: Props) => {
62
63
  Object.assign(draftBasket, basket);
63
64
  }
64
65
  )
65
- )
66
- )
66
+ );
67
+ onBasketUpdate?.(basket);
68
+ })
67
69
  .catch((error: Error) => {
68
70
  setError('voucherCode', { message: error.data.non_field_errors });
69
71
  });
@@ -74,7 +76,7 @@ export const Summary = (props: Props) => {
74
76
  voucher_code: data.voucherCode
75
77
  })
76
78
  .unwrap()
77
- .then((basket) =>
79
+ .then((basket) => {
78
80
  dispatch(
79
81
  basketApi.util.updateQueryData(
80
82
  'getBasket',
@@ -83,8 +85,9 @@ export const Summary = (props: Props) => {
83
85
  Object.assign(draftBasket, basket);
84
86
  }
85
87
  )
86
- )
87
- )
88
+ );
89
+ onBasketUpdate?.(basket);
90
+ })
88
91
  .catch((error: Error) => {
89
92
  setError('voucherCode', { message: error.data.non_field_errors });
90
93
  });
@@ -103,10 +103,34 @@ export const Login = () => {
103
103
  )?.data as string[];
104
104
 
105
105
  fieldErrors?.forEach((item) => {
106
- setError(item.name as keyof LoginFormType, {
107
- type: 'custom',
108
- message: item.value.join(', ')
109
- });
106
+ let parsedValue: Record<string, string[]> | string[] = [];
107
+
108
+ if (typeof item.value === 'string') {
109
+ try {
110
+ parsedValue = JSON.parse(item.value);
111
+ } catch {
112
+ parsedValue = [item.value];
113
+ }
114
+ } else {
115
+ parsedValue = item.value;
116
+ }
117
+
118
+ if (Array.isArray(parsedValue)) {
119
+ setError(item.name as keyof LoginFormType, {
120
+ type: 'custom',
121
+ message: parsedValue.join(', '),
122
+ });
123
+ } else {
124
+ Object.keys(parsedValue).forEach((key) => {
125
+ const fieldName = key as keyof LoginFormType;
126
+ const errorMessages = parsedValue[key] as string[];
127
+
128
+ setError(fieldName, {
129
+ type: 'custom',
130
+ message: errorMessages.join(', '),
131
+ });
132
+ });
133
+ }
110
134
  });
111
135
 
112
136
  if (nonFieldErrors?.length) {
@@ -143,6 +143,7 @@ export const Register = () => {
143
143
  if (registerResponse.error) {
144
144
  const errors: AuthError[] = JSON.parse(registerResponse.error);
145
145
 
146
+
146
147
  if (errors.find((error) => error.type === 'captcha')) {
147
148
  if (await validateCaptcha()) {
148
149
  onSubmit(data);
@@ -164,10 +165,34 @@ export const Register = () => {
164
165
  )?.data as string[];
165
166
 
166
167
  fieldErrors?.forEach((item) => {
167
- setError(item.name as keyof RegisterFormType, {
168
- type: 'custom',
169
- message: item.value.join(', ')
170
- });
168
+ let parsedValue: Record<string, string[]> | string[] = [];
169
+
170
+ if (typeof item.value === 'string') {
171
+ try {
172
+ parsedValue = JSON.parse(item.value);
173
+ } catch {
174
+ parsedValue = [item.value];
175
+ }
176
+ } else {
177
+ parsedValue = item.value;
178
+ }
179
+
180
+ if (Array.isArray(parsedValue)) {
181
+ setError(item.name as keyof RegisterFormType, {
182
+ type: 'custom',
183
+ message: parsedValue.join(', '),
184
+ });
185
+ } else {
186
+ Object.keys(parsedValue).forEach((key) => {
187
+ const fieldName = key as keyof RegisterFormType;
188
+ const errorMessages = parsedValue[key] as string[];
189
+
190
+ setError(fieldName, {
191
+ type: 'custom',
192
+ message: errorMessages.join(', '),
193
+ });
194
+ });
195
+ }
171
196
  });
172
197
 
173
198
  if (nonFieldErrors?.length) {
@@ -303,7 +328,7 @@ export const Register = () => {
303
328
  labelStyle="floating"
304
329
  label={t('auth.register.form.phone.placeholder')}
305
330
  className="h-14"
306
- format={user_phone_format.replace(/\9/g, '#')}
331
+ format={user_phone_format.replace(/9/g, '#')}
307
332
  allowEmptyFormatting
308
333
  mask="_"
309
334
  control={control}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/projectzero",
3
- "version": "1.77.0",
3
+ "version": "1.78.0-rc.1",
4
4
  "private": false,
5
5
  "description": "CLI tool to manage your Project Zero Next project",
6
6
  "bin": {
@@ -1,3 +0,0 @@
1
- import { initSentry } from '@akinon/next/sentry';
2
-
3
- initSentry('Edge');
@@ -1,3 +0,0 @@
1
- import { initSentry } from '@akinon/next/sentry';
2
-
3
- initSentry('Server');