@akinon/projectzero 1.98.0 → 1.99.0-rc.66
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 +233 -4
- package/app-template/.env.example +1 -0
- package/app-template/CHANGELOG.md +4978 -320
- package/app-template/README.md +25 -1
- package/app-template/package.json +19 -19
- package/app-template/public/locales/en/common.json +42 -1
- package/app-template/public/locales/tr/common.json +42 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +9 -82
- package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +17 -4
- package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +12 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +29 -11
- package/app-template/src/app/[commerce]/[locale]/[currency]/landing-page/[pk]/page.tsx +12 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +67 -0
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +28 -10
- package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +12 -1
- package/app-template/src/app/api/image-proxy/route.ts +1 -0
- package/app-template/src/app/api/similar-product-list/route.ts +1 -0
- package/app-template/src/app/api/similar-products/route.ts +1 -0
- package/app-template/src/assets/fonts/pz-icon.css +3 -0
- package/app-template/src/components/accordion.tsx +22 -19
- package/app-template/src/components/currency-select.tsx +1 -0
- package/app-template/src/components/file-input.tsx +27 -7
- package/app-template/src/components/input.tsx +2 -1
- package/app-template/src/components/modal.tsx +32 -16
- package/app-template/src/components/select.tsx +38 -26
- package/app-template/src/components/types/index.ts +25 -1
- package/app-template/src/hooks/index.ts +2 -0
- package/app-template/src/hooks/use-product-cart.ts +77 -0
- package/app-template/src/hooks/use-stock-alert.ts +74 -0
- package/app-template/src/plugins.js +3 -1
- package/app-template/src/settings.js +8 -2
- package/app-template/src/types/index.ts +74 -3
- package/app-template/src/utils/variant-validation.ts +41 -0
- package/app-template/src/views/account/address-form.tsx +8 -4
- package/app-template/src/views/account/content-header.tsx +2 -2
- package/app-template/src/views/basket/basket-content.tsx +106 -0
- package/app-template/src/views/basket/basket-item.tsx +16 -13
- package/app-template/src/views/basket/summary.tsx +10 -7
- package/app-template/src/views/guest-login/index.tsx +6 -1
- package/app-template/src/views/header/action-menu.tsx +1 -1
- package/app-template/src/views/header/search/index.tsx +17 -5
- package/app-template/src/views/login/index.tsx +11 -10
- package/app-template/src/views/otp-login/index.tsx +11 -6
- package/app-template/src/views/product/product-actions.tsx +165 -0
- package/app-template/src/views/product/product-info.tsx +61 -263
- package/app-template/src/views/product/product-share.tsx +56 -0
- package/app-template/src/views/product/product-variants.tsx +26 -0
- package/app-template/src/views/product/slider.tsx +86 -73
- package/app-template/src/views/register/index.tsx +15 -11
- package/commands/plugins.ts +63 -16
- package/dist/commands/plugins.js +57 -16
- package/package.json +1 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Trans } from '@akinon/next/components/trans';
|
|
3
|
+
import { VariantType } from '@akinon/next/types';
|
|
4
|
+
|
|
5
|
+
export const isVariantSelectionComplete = (variants: VariantType[]): boolean => {
|
|
6
|
+
return variants?.every((variant) =>
|
|
7
|
+
variant?.options.some((opt) => opt.is_selected)
|
|
8
|
+
);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const getUnselectedVariant = (variants: VariantType[]): VariantType | undefined => {
|
|
12
|
+
return variants.find((variant) =>
|
|
13
|
+
variant.options.every((opt) => !opt.is_selected)
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const createVariantErrorMessage = (unselectedVariant: VariantType) => {
|
|
18
|
+
const TransComponent = Trans as any;
|
|
19
|
+
return React.createElement(
|
|
20
|
+
TransComponent,
|
|
21
|
+
{
|
|
22
|
+
i18nKey: "product.please_select_variant",
|
|
23
|
+
components: {
|
|
24
|
+
VariantName: React.createElement('span', {}, unselectedVariant.attribute_name)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const validateVariantSelection = (variants: VariantType[]) => {
|
|
31
|
+
const unselectedVariant = getUnselectedVariant(variants);
|
|
32
|
+
|
|
33
|
+
if (unselectedVariant) {
|
|
34
|
+
return {
|
|
35
|
+
isValid: false,
|
|
36
|
+
errorMessage: createVariantErrorMessage(unselectedVariant)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { isValid: true, errorMessage: null };
|
|
41
|
+
};
|
|
@@ -259,23 +259,25 @@ export const AddressForm = (props: Props) => {
|
|
|
259
259
|
{/* TODO: Fix select and textarea components */}
|
|
260
260
|
|
|
261
261
|
<Select
|
|
262
|
-
className="w-full border-gray-500 text-sm
|
|
262
|
+
className="w-full border-gray-500 text-sm"
|
|
263
263
|
options={countryOptions}
|
|
264
264
|
{...register('country')}
|
|
265
265
|
error={errors.country}
|
|
266
266
|
data-testid="address-form-country"
|
|
267
267
|
label={t('account.address_book.form.country.title')}
|
|
268
|
+
labelClassName="mb-3"
|
|
268
269
|
required
|
|
269
270
|
/>
|
|
270
271
|
|
|
271
272
|
{city && (
|
|
272
273
|
<Select
|
|
273
|
-
className="w-full border-gray-500 text-sm
|
|
274
|
+
className="w-full border-gray-500 text-sm"
|
|
274
275
|
options={cityOptions}
|
|
275
276
|
{...register('city')}
|
|
276
277
|
error={errors.city}
|
|
277
278
|
data-testid="address-form-city"
|
|
278
279
|
label={t('account.address_book.form.province.title')}
|
|
280
|
+
labelClassName="mb-3"
|
|
279
281
|
required
|
|
280
282
|
/>
|
|
281
283
|
)}
|
|
@@ -283,24 +285,26 @@ export const AddressForm = (props: Props) => {
|
|
|
283
285
|
<div className="flex gap-4">
|
|
284
286
|
<div className="flex-1">
|
|
285
287
|
<Select
|
|
286
|
-
className="w-full border-gray-500 text-sm
|
|
288
|
+
className="w-full border-gray-500 text-sm"
|
|
287
289
|
options={townshipOptions}
|
|
288
290
|
{...register('township')}
|
|
289
291
|
error={errors.township}
|
|
290
292
|
data-testid="address-form-township"
|
|
291
293
|
label={t('account.address_book.form.township.title')}
|
|
294
|
+
labelClassName="mb-3"
|
|
292
295
|
required
|
|
293
296
|
/>
|
|
294
297
|
</div>
|
|
295
298
|
{district && (
|
|
296
299
|
<div className="flex-1">
|
|
297
300
|
<Select
|
|
298
|
-
className="w-full border-gray-500 text-sm
|
|
301
|
+
className="w-full border-gray-500 text-sm"
|
|
299
302
|
options={districtOptions}
|
|
300
303
|
{...register('district')}
|
|
301
304
|
error={errors.district}
|
|
302
305
|
data-testid="address-form-district"
|
|
303
306
|
label={t('account.address_book.form.district.title')}
|
|
307
|
+
labelClassName="mb-3"
|
|
304
308
|
required
|
|
305
309
|
/>
|
|
306
310
|
</div>
|
|
@@ -31,13 +31,13 @@ export const ContentHeader = (props: Props) => {
|
|
|
31
31
|
</h3>
|
|
32
32
|
<Select
|
|
33
33
|
onChange={handleChange}
|
|
34
|
-
className="w-full mb-4 md:mb-0 md:w-56
|
|
34
|
+
className="w-full mb-4 md:mb-0 md:w-56 text-xs"
|
|
35
35
|
options={orders}
|
|
36
36
|
data-testid="account-orders-header-select"
|
|
37
37
|
></Select>
|
|
38
38
|
<Button
|
|
39
39
|
className={clsx(
|
|
40
|
-
'w-full md:w-56',
|
|
40
|
+
'w-full md:w-56 md:ms-4',
|
|
41
41
|
isButtonDisabled &&
|
|
42
42
|
'hover:bg-black hover:text-white disabled:opacity-75'
|
|
43
43
|
)}
|
|
@@ -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
|
-
|
|
53
|
-
.unwrap()
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
(draftBasket)
|
|
60
|
-
|
|
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
|
});
|
|
@@ -51,9 +51,14 @@ const GuestLogin = () => {
|
|
|
51
51
|
).unwrap();
|
|
52
52
|
|
|
53
53
|
Object.keys(response?.errors || {}).forEach((key) => {
|
|
54
|
+
const errorValue = response?.errors[key];
|
|
55
|
+
const message = Array.isArray(errorValue)
|
|
56
|
+
? errorValue.join(', ')
|
|
57
|
+
: errorValue || '';
|
|
58
|
+
|
|
54
59
|
setError(key as keyof GuestLoginFormParams, {
|
|
55
60
|
type: 'custom',
|
|
56
|
-
message
|
|
61
|
+
message
|
|
57
62
|
});
|
|
58
63
|
});
|
|
59
64
|
} catch (error) {
|
|
@@ -4,11 +4,11 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
4
4
|
import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
|
|
5
5
|
import { closeSearch } from '@akinon/next/redux/reducers/header';
|
|
6
6
|
import clsx from 'clsx';
|
|
7
|
-
|
|
8
|
-
import { Icon } from '@theme/components';
|
|
7
|
+
import { Icon, Input } from '@theme/components';
|
|
9
8
|
import Results from './results';
|
|
10
9
|
import { ROUTES } from '@theme/routes';
|
|
11
10
|
import { useLocalization, useRouter } from '@akinon/next/hooks';
|
|
11
|
+
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
12
12
|
|
|
13
13
|
export default function Search() {
|
|
14
14
|
const { t } = useLocalization();
|
|
@@ -41,6 +41,14 @@ export default function Search() {
|
|
|
41
41
|
};
|
|
42
42
|
}, [isSearchOpen, dispatch]);
|
|
43
43
|
|
|
44
|
+
const handleSearchTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
45
|
+
setSearchText(e.target.value);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleCloseSearch = () => {
|
|
49
|
+
dispatch(closeSearch());
|
|
50
|
+
};
|
|
51
|
+
|
|
44
52
|
return (
|
|
45
53
|
<>
|
|
46
54
|
<div
|
|
@@ -66,9 +74,9 @@ export default function Search() {
|
|
|
66
74
|
{t('common.search.results_for')}
|
|
67
75
|
</span>
|
|
68
76
|
<div className="flex items-center">
|
|
69
|
-
<
|
|
77
|
+
<Input
|
|
70
78
|
value={searchText}
|
|
71
|
-
onChange={
|
|
79
|
+
onChange={handleSearchTextChange}
|
|
72
80
|
onKeyDown={(e) => {
|
|
73
81
|
if (e.key === 'Enter' && searchText.trim() !== '') {
|
|
74
82
|
router.push(`${ROUTES.LIST}/?search_text=${searchText}`);
|
|
@@ -78,14 +86,18 @@ export default function Search() {
|
|
|
78
86
|
placeholder={t('common.search.placeholder')}
|
|
79
87
|
ref={inputRef}
|
|
80
88
|
/>
|
|
89
|
+
|
|
90
|
+
<PluginModule component={Component.HeaderImageSearchFeature} />
|
|
91
|
+
|
|
81
92
|
<Icon
|
|
82
93
|
name="close"
|
|
83
94
|
size={14}
|
|
84
|
-
onClick={
|
|
95
|
+
onClick={handleCloseSearch}
|
|
85
96
|
className="cursor-pointer"
|
|
86
97
|
/>
|
|
87
98
|
</div>
|
|
88
99
|
</div>
|
|
100
|
+
|
|
89
101
|
<Results searchText={searchText} />
|
|
90
102
|
</div>
|
|
91
103
|
</div>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { signIn
|
|
3
|
+
import { signIn } from 'next-auth/react';
|
|
4
4
|
import { useSearchParams } from 'next/navigation';
|
|
5
5
|
import { useState } from 'react';
|
|
6
6
|
import { ROUTES } from '@theme/routes';
|
|
7
|
-
import { LoginFormType } from '@theme/types';
|
|
7
|
+
import { LoginFormType, FormType, PzSignInOptions } from '@theme/types';
|
|
8
8
|
import { Button, Input, Link } from '@theme/components';
|
|
9
9
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
|
10
10
|
import * as yup from 'yup';
|
|
@@ -79,8 +79,9 @@ export const Login = () => {
|
|
|
79
79
|
redirect: false,
|
|
80
80
|
callbackUrl: searchParams.get('callbackUrl') ?? '/',
|
|
81
81
|
captchaValidated,
|
|
82
|
-
...data
|
|
83
|
-
|
|
82
|
+
...data,
|
|
83
|
+
formType: FormType.login
|
|
84
|
+
} as PzSignInOptions);
|
|
84
85
|
|
|
85
86
|
if (loginResponse.error) {
|
|
86
87
|
const errors: AuthError[] = JSON.parse(loginResponse.error);
|
|
@@ -109,25 +110,25 @@ export const Login = () => {
|
|
|
109
110
|
try {
|
|
110
111
|
parsedValue = JSON.parse(item.value);
|
|
111
112
|
} catch {
|
|
112
|
-
parsedValue = [item.value];
|
|
113
|
+
parsedValue = [item.value];
|
|
113
114
|
}
|
|
114
115
|
} else {
|
|
115
|
-
parsedValue = item.value;
|
|
116
|
+
parsedValue = item.value;
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
if (Array.isArray(parsedValue)) {
|
|
119
120
|
setError(item.name as keyof LoginFormType, {
|
|
120
121
|
type: 'custom',
|
|
121
|
-
message: parsedValue.join(', ')
|
|
122
|
+
message: parsedValue.join(', ')
|
|
122
123
|
});
|
|
123
124
|
} else {
|
|
124
125
|
Object.keys(parsedValue).forEach((key) => {
|
|
125
126
|
const fieldName = key as keyof LoginFormType;
|
|
126
127
|
const errorMessages = parsedValue[key] as string[];
|
|
127
|
-
|
|
128
|
+
|
|
128
129
|
setError(fieldName, {
|
|
129
130
|
type: 'custom',
|
|
130
|
-
message: errorMessages.join(', ')
|
|
131
|
+
message: errorMessages.join(', ')
|
|
131
132
|
});
|
|
132
133
|
});
|
|
133
134
|
}
|
|
@@ -161,7 +162,7 @@ export const Login = () => {
|
|
|
161
162
|
method="post"
|
|
162
163
|
onSubmit={handleSubmit(onSubmit)}
|
|
163
164
|
>
|
|
164
|
-
<input type="hidden" value=
|
|
165
|
+
<input type="hidden" value={FormType.login} {...register('formType')} />
|
|
165
166
|
<input type="hidden" value={locale} {...register('locale')} />
|
|
166
167
|
|
|
167
168
|
<div className={clsx(errors.email ? 'mb-8' : 'mb-4')}>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { signIn
|
|
3
|
+
import { signIn } from 'next-auth/react';
|
|
4
4
|
import { useState } from 'react';
|
|
5
|
-
import { OtpLoginFormType } from '@theme/types';
|
|
5
|
+
import { OtpLoginFormType, FormType, PzSignInOptions } from '@theme/types';
|
|
6
6
|
import { Button, Input } from '@theme/components';
|
|
7
7
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
|
8
8
|
import * as yup from 'yup';
|
|
@@ -47,8 +47,9 @@ export const OtpLogin = () => {
|
|
|
47
47
|
const loginResponse = await signIn('default', {
|
|
48
48
|
redirect: false,
|
|
49
49
|
callbackUrl: '/',
|
|
50
|
-
...data
|
|
51
|
-
|
|
50
|
+
...data,
|
|
51
|
+
formType: FormType.otpLogin
|
|
52
|
+
} as PzSignInOptions);
|
|
52
53
|
|
|
53
54
|
if (loginResponse.error) {
|
|
54
55
|
const errors: AuthError[] = JSON.parse(loginResponse.error);
|
|
@@ -100,7 +101,11 @@ export const OtpLogin = () => {
|
|
|
100
101
|
{t('auth.login.title_gsm')}
|
|
101
102
|
</h2>
|
|
102
103
|
<form onSubmit={handleSubmit(onSubmit)}>
|
|
103
|
-
<input
|
|
104
|
+
<input
|
|
105
|
+
type="hidden"
|
|
106
|
+
value={FormType.otpLogin}
|
|
107
|
+
{...register('formType')}
|
|
108
|
+
/>
|
|
104
109
|
<input type="hidden" value={locale} {...register('locale')} />
|
|
105
110
|
|
|
106
111
|
<div className="mb-4">
|
|
@@ -109,7 +114,7 @@ export const OtpLogin = () => {
|
|
|
109
114
|
className="h-14"
|
|
110
115
|
label={t('auth.login.form.phone.placeholder')}
|
|
111
116
|
type="tel"
|
|
112
|
-
format={user_phone_format.replace(
|
|
117
|
+
format={user_phone_format.replace(/9/g, '#')}
|
|
113
118
|
mask="_"
|
|
114
119
|
allowEmptyFormatting={true}
|
|
115
120
|
control={control}
|