@akinon/projectzero 1.99.0-rc.70 → 1.99.0-snapshot-ZERO-3640-20250919140314
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 +5 -240
- package/app-template/.env.example +0 -1
- package/app-template/CHANGELOG.md +300 -5065
- package/app-template/README.md +1 -25
- package/app-template/package.json +19 -21
- package/app-template/public/locales/en/common.json +1 -48
- package/app-template/public/locales/tr/common.json +1 -48
- package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +82 -9
- package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +4 -17
- package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +1 -12
- package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +11 -29
- package/app-template/src/app/[commerce]/[locale]/[currency]/landing-page/[pk]/page.tsx +1 -12
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +10 -28
- package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +1 -12
- package/app-template/src/app/api/form/[...id]/route.ts +7 -1
- package/app-template/src/assets/fonts/pz-icon.css +0 -3
- package/app-template/src/components/__tests__/link.test.tsx +0 -2
- package/app-template/src/components/accordion.tsx +19 -22
- package/app-template/src/components/currency-select.tsx +0 -1
- package/app-template/src/components/file-input.tsx +7 -27
- package/app-template/src/components/generate-form-fields.tsx +4 -43
- package/app-template/src/components/input.tsx +2 -9
- package/app-template/src/components/modal.tsx +16 -32
- package/app-template/src/components/pagination.tsx +0 -1
- package/app-template/src/components/select.tsx +26 -38
- package/app-template/src/components/types/index.ts +1 -25
- package/app-template/src/hooks/index.ts +0 -2
- package/app-template/src/plugins.js +1 -3
- package/app-template/src/settings.js +2 -8
- package/app-template/src/types/index.ts +3 -74
- package/app-template/src/views/account/address-form.tsx +4 -8
- package/app-template/src/views/account/contact-form.tsx +1 -1
- package/app-template/src/views/account/content-header.tsx +2 -2
- package/app-template/src/views/account/faq/faq-tabs.tsx +2 -8
- package/app-template/src/views/basket/basket-item.tsx +14 -22
- package/app-template/src/views/basket/summary.tsx +7 -10
- package/app-template/src/views/breadcrumb.tsx +2 -2
- package/app-template/src/views/category/category-info.tsx +0 -1
- package/app-template/src/views/category/filters/index.tsx +1 -1
- package/app-template/src/views/guest-login/index.tsx +1 -6
- package/app-template/src/views/header/action-menu.tsx +1 -1
- package/app-template/src/views/header/search/index.tsx +5 -17
- package/app-template/src/views/login/index.tsx +10 -11
- package/app-template/src/views/otp-login/index.tsx +6 -11
- package/app-template/src/views/product/product-info.tsx +263 -62
- package/app-template/src/views/product/slider.tsx +73 -86
- package/app-template/src/views/register/index.tsx +11 -15
- package/app-template/src/widgets/footer-menu.tsx +2 -6
- package/commands/plugins.ts +16 -63
- package/dist/commands/plugins.js +16 -57
- package/package.json +1 -1
- package/app-template/.github/instructions/account.instructions.md +0 -749
- package/app-template/.github/instructions/edge-cases.instructions.md +0 -73
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +0 -67
- package/app-template/src/app/api/image-proxy/route.ts +0 -1
- package/app-template/src/app/api/similar-product-list/route.ts +0 -1
- package/app-template/src/app/api/similar-products/route.ts +0 -1
- package/app-template/src/hooks/use-product-cart.ts +0 -77
- package/app-template/src/hooks/use-stock-alert.ts +0 -74
- package/app-template/src/utils/variant-validation.ts +0 -41
- package/app-template/src/views/basket/basket-content.tsx +0 -106
- package/app-template/src/views/product/product-actions.tsx +0 -165
- package/app-template/src/views/product/product-share.tsx +0 -56
- package/app-template/src/views/product/product-variants.tsx +0 -26
|
@@ -1,73 +1,177 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import clsx from 'clsx';
|
|
4
|
+
import { Button, Icon, Modal } from '@theme/components';
|
|
5
|
+
import { useAddProductToBasket } from '../../hooks';
|
|
4
6
|
import React, { useEffect, useState } from 'react';
|
|
5
|
-
import {
|
|
7
|
+
import { useAddStockAlertMutation } from '@akinon/next/data/client/wishlist';
|
|
8
|
+
import { pushAddToCart, pushProductViewed } from '@theme/utils/gtm';
|
|
9
|
+
import { PriceWrapper, Variant } from '@theme/views/product';
|
|
10
|
+
import Share from '@theme/views/share';
|
|
6
11
|
import { ProductPageProps } from './layout';
|
|
7
12
|
import MiscButtons from './misc-buttons';
|
|
8
|
-
import {
|
|
13
|
+
import { useLocalization } from '@akinon/next/hooks';
|
|
14
|
+
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
15
|
+
import { Trans } from '@akinon/next/components/trans';
|
|
9
16
|
import { useSession } from 'next-auth/react';
|
|
10
|
-
import { isVariantSelectionComplete } from '../../utils/variant-validation';
|
|
11
|
-
import { useProductCart } from '../../hooks/use-product-cart';
|
|
12
|
-
import { useStockAlert } from '../../hooks/use-stock-alert';
|
|
13
|
-
import { ProductVariants } from './product-variants';
|
|
14
|
-
import { ProductActions } from './product-actions';
|
|
15
|
-
import { ProductShare } from './product-share';
|
|
16
17
|
|
|
17
18
|
export default function ProductInfo({ data }: ProductPageProps) {
|
|
19
|
+
const { t } = useLocalization();
|
|
18
20
|
const { data: session } = useSession();
|
|
21
|
+
const [currentUrl, setCurrentUrl] = useState(null);
|
|
22
|
+
const [productError, setProductError] = useState(null);
|
|
23
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
24
|
+
const [stockAlertResponseMessage, setStockAlertResponseMessage] =
|
|
25
|
+
useState(null);
|
|
19
26
|
const [isVariantLoading, setIsVariantLoading] = useState(false);
|
|
20
27
|
|
|
28
|
+
const [addProduct, { isLoading: isAddToCartLoading }] =
|
|
29
|
+
useAddProductToBasket();
|
|
30
|
+
const [addStockAlert, { isLoading: isAddToStockAlertLoading }] =
|
|
31
|
+
useAddStockAlertMutation();
|
|
21
32
|
const inStock = data.selected_variant !== null || data.product.in_stock;
|
|
22
33
|
|
|
23
|
-
const {
|
|
24
|
-
addProductToCart,
|
|
25
|
-
productError: cartError,
|
|
26
|
-
clearProductError: clearCartError,
|
|
27
|
-
isAddToCartLoading
|
|
28
|
-
} = useProductCart({
|
|
29
|
-
product: data.product,
|
|
30
|
-
variants: data.variants
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
const {
|
|
34
|
-
addProductToStockAlertList,
|
|
35
|
-
isModalOpen,
|
|
36
|
-
stockAlertResponseMessage,
|
|
37
|
-
productError: stockError,
|
|
38
|
-
isAddToStockAlertLoading,
|
|
39
|
-
closeModal,
|
|
40
|
-
clearError: clearStockError
|
|
41
|
-
} = useStockAlert({
|
|
42
|
-
productPk: data.product.pk,
|
|
43
|
-
userEmail: session?.user?.email
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const productError = cartError || stockError;
|
|
47
|
-
const clearProductError = () => {
|
|
48
|
-
clearCartError();
|
|
49
|
-
clearStockError();
|
|
50
|
-
};
|
|
51
|
-
|
|
52
34
|
useEffect(() => {
|
|
53
|
-
isVariantSelectionComplete(
|
|
35
|
+
isVariantSelectionComplete() && setIsVariantLoading(false);
|
|
36
|
+
|
|
54
37
|
!inStock && setIsVariantLoading(false);
|
|
55
|
-
}, [data
|
|
38
|
+
}, [data]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
56
39
|
|
|
57
40
|
useEffect(() => {
|
|
58
41
|
if (isVariantLoading) {
|
|
59
|
-
|
|
42
|
+
setProductError(null);
|
|
60
43
|
}
|
|
61
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
62
44
|
}, [isVariantLoading]);
|
|
63
45
|
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
setCurrentUrl(window.location.href);
|
|
48
|
+
}, [currentUrl]);
|
|
49
|
+
|
|
64
50
|
useEffect(() => {
|
|
65
51
|
pushProductViewed(data?.product);
|
|
66
52
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
67
53
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
54
|
+
const addProductToCart = async () => {
|
|
55
|
+
if (!variantsSelectionCheck()) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
await addProduct({
|
|
61
|
+
product: data.product.pk,
|
|
62
|
+
quantity: 1,
|
|
63
|
+
attributes: {}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
pushAddToCart(data?.product);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
setProductError(
|
|
69
|
+
error?.data?.non_field_errors ||
|
|
70
|
+
Object.keys(error?.data).map(
|
|
71
|
+
(key) => `${key}: ${error?.data[key].join(', ')}`
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const variantsSelectionCheck = () => {
|
|
78
|
+
const unselectedVariant = data.variants.find((variant) =>
|
|
79
|
+
variant.options.every((opt) => !opt.is_selected)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (unselectedVariant) {
|
|
83
|
+
setProductError(() => (
|
|
84
|
+
<Trans
|
|
85
|
+
i18nKey="product.please_select_variant"
|
|
86
|
+
components={{
|
|
87
|
+
VariantName: <span>{unselectedVariant.attribute_name}</span>
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
));
|
|
91
|
+
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return true;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const isVariantSelectionComplete = () => {
|
|
99
|
+
return data?.variants.every((variant) =>
|
|
100
|
+
variant?.options.some((opt) => opt.is_selected)
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const addProductToStockAlertList = async () => {
|
|
105
|
+
try {
|
|
106
|
+
await addStockAlert({
|
|
107
|
+
productPk: data.product.pk,
|
|
108
|
+
email: session?.user?.email
|
|
109
|
+
})
|
|
110
|
+
.unwrap()
|
|
111
|
+
.then(handleSuccess)
|
|
112
|
+
.catch((err) => handleError(err));
|
|
113
|
+
} catch (error) {
|
|
114
|
+
setProductError(error?.data?.non_field_errors || null);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const handleModalClick = () => {
|
|
119
|
+
setIsModalOpen(false);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const handleSuccess = () => {
|
|
123
|
+
setStockAlertResponseMessage(() => (
|
|
124
|
+
<Trans
|
|
125
|
+
i18nKey="product.stock_alert.success_description"
|
|
126
|
+
components={{
|
|
127
|
+
Email: <span>{session?.user?.email}</span>
|
|
128
|
+
}}
|
|
129
|
+
/>
|
|
130
|
+
));
|
|
131
|
+
setIsModalOpen(true);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const handleError = (err) => {
|
|
135
|
+
if (err.status !== 401) {
|
|
136
|
+
setStockAlertResponseMessage(
|
|
137
|
+
t('product.stock_alert.error_description').toString()
|
|
138
|
+
);
|
|
139
|
+
setIsModalOpen(true);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const checkoutProviderProps = {
|
|
144
|
+
product: data.product,
|
|
145
|
+
clearBasket: true,
|
|
146
|
+
addBeforeClick: variantsSelectionCheck,
|
|
147
|
+
openMiniBasket: false,
|
|
148
|
+
className: clsx([
|
|
149
|
+
'py-2.5',
|
|
150
|
+
'bg-black',
|
|
151
|
+
'relative',
|
|
152
|
+
'hover:bg-black',
|
|
153
|
+
'before:content-[""]',
|
|
154
|
+
'before:w-6',
|
|
155
|
+
'before:h-6',
|
|
156
|
+
'before:bg-white',
|
|
157
|
+
'before:absolute',
|
|
158
|
+
'before:rounded-r-[18px]',
|
|
159
|
+
'before:left-0',
|
|
160
|
+
'after:content-[""]',
|
|
161
|
+
'after:absolute',
|
|
162
|
+
'after:w-3',
|
|
163
|
+
'after:h-3',
|
|
164
|
+
'after:bg-[#d02c2f]',
|
|
165
|
+
'after:rounded-xl',
|
|
166
|
+
'after:left-1'
|
|
167
|
+
]),
|
|
168
|
+
onError: (error) =>
|
|
169
|
+
setProductError(
|
|
170
|
+
error?.data?.non_field_errors ||
|
|
171
|
+
Object.keys(error?.data).map(
|
|
172
|
+
(key) => `${key}: ${error?.data[key].join(', ')}`
|
|
173
|
+
)
|
|
174
|
+
)
|
|
71
175
|
};
|
|
72
176
|
|
|
73
177
|
return (
|
|
@@ -83,26 +187,72 @@ export default function ProductInfo({ data }: ProductPageProps) {
|
|
|
83
187
|
retailPrice={data.product.retail_price}
|
|
84
188
|
/>
|
|
85
189
|
</div>
|
|
190
|
+
<div className="flex flex-col">
|
|
191
|
+
{data.variants.map((variant) => (
|
|
192
|
+
<Variant
|
|
193
|
+
key={variant.attribute_key}
|
|
194
|
+
{...variant}
|
|
195
|
+
className="items-center mt-8"
|
|
196
|
+
onChange={() => {
|
|
197
|
+
setProductError(null);
|
|
198
|
+
setIsVariantLoading(true);
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
))}
|
|
202
|
+
</div>
|
|
86
203
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
204
|
+
{productError && (
|
|
205
|
+
<div className="mt-4 text-xs text-center text-error">
|
|
206
|
+
{productError}
|
|
207
|
+
</div>
|
|
208
|
+
)}
|
|
209
|
+
|
|
210
|
+
<Button
|
|
211
|
+
disabled={
|
|
212
|
+
isAddToCartLoading || isAddToStockAlertLoading || isVariantLoading
|
|
213
|
+
}
|
|
214
|
+
className={clsx(
|
|
215
|
+
'fixed bottom-0 right-0 w-1/2 h-14 z-[20] flex items-center justify-center fill-primary-foreground',
|
|
216
|
+
'hover:fill-primary sm:relative sm:w-full sm:mt-3 sm:font-semibold sm:h-12'
|
|
217
|
+
)}
|
|
218
|
+
onClick={() => {
|
|
219
|
+
setProductError(null);
|
|
220
|
+
|
|
221
|
+
if (inStock) {
|
|
222
|
+
addProductToCart();
|
|
223
|
+
} else {
|
|
224
|
+
addProductToStockAlertList();
|
|
225
|
+
}
|
|
226
|
+
}}
|
|
227
|
+
data-testid="product-add-to-cart"
|
|
228
|
+
>
|
|
229
|
+
{isVariantLoading ? (
|
|
230
|
+
<Icon
|
|
231
|
+
name="spinner"
|
|
232
|
+
size={20}
|
|
233
|
+
className="animate-spin mr-4 fill-primary"
|
|
234
|
+
/>
|
|
235
|
+
) : inStock ? (
|
|
236
|
+
<span>{t('product.add_to_cart')}</span>
|
|
237
|
+
) : (
|
|
238
|
+
<>
|
|
239
|
+
<Icon name="bell" size={20} className="mr-4" />
|
|
240
|
+
<span>{t('product.add_stock_alert')}</span>
|
|
241
|
+
</>
|
|
242
|
+
)}
|
|
243
|
+
</Button>
|
|
244
|
+
|
|
245
|
+
<PluginModule
|
|
246
|
+
component={Component.AkifastCheckoutButton}
|
|
247
|
+
props={{
|
|
248
|
+
...checkoutProviderProps,
|
|
249
|
+
isPdp: true
|
|
250
|
+
}}
|
|
90
251
|
/>
|
|
91
252
|
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
inStock={inStock}
|
|
96
|
-
isVariantLoading={isVariantLoading}
|
|
97
|
-
onAddToCart={addProductToCart}
|
|
98
|
-
onAddToStockAlert={addProductToStockAlertList}
|
|
99
|
-
onClearError={clearProductError}
|
|
100
|
-
isAddToCartLoading={isAddToCartLoading}
|
|
101
|
-
isAddToStockAlertLoading={isAddToStockAlertLoading}
|
|
102
|
-
productError={productError}
|
|
103
|
-
isModalOpen={isModalOpen}
|
|
104
|
-
stockAlertResponseMessage={stockAlertResponseMessage}
|
|
105
|
-
onCloseModal={closeModal}
|
|
253
|
+
<PluginModule
|
|
254
|
+
component={Component.OneClickCheckoutButtons}
|
|
255
|
+
props={checkoutProviderProps}
|
|
106
256
|
/>
|
|
107
257
|
|
|
108
258
|
<MiscButtons
|
|
@@ -111,7 +261,58 @@ export default function ProductInfo({ data }: ProductPageProps) {
|
|
|
111
261
|
variants={data.variants}
|
|
112
262
|
/>
|
|
113
263
|
|
|
114
|
-
<
|
|
264
|
+
<Share
|
|
265
|
+
className="my-2 sm:mb-4"
|
|
266
|
+
buttonText={t('product.share')}
|
|
267
|
+
items={[
|
|
268
|
+
{
|
|
269
|
+
href: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
|
|
270
|
+
currentUrl
|
|
271
|
+
)}`,
|
|
272
|
+
iconName: 'facebook',
|
|
273
|
+
iconSize: 22
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
href: `https://twitter.com/intent/tweet?text=${encodeURIComponent(
|
|
277
|
+
currentUrl
|
|
278
|
+
)}`,
|
|
279
|
+
iconName: 'twitter',
|
|
280
|
+
iconSize: 22
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
href: `https://api.whatsapp.com/send?text=${
|
|
284
|
+
data.product.name
|
|
285
|
+
}%20${encodeURIComponent(currentUrl)}`,
|
|
286
|
+
iconName: 'whatsapp',
|
|
287
|
+
iconSize: 22
|
|
288
|
+
}
|
|
289
|
+
]}
|
|
290
|
+
/>
|
|
291
|
+
|
|
292
|
+
<Modal
|
|
293
|
+
portalId="stock-alert-modal"
|
|
294
|
+
open={isModalOpen}
|
|
295
|
+
setOpen={setIsModalOpen}
|
|
296
|
+
showCloseButton={false}
|
|
297
|
+
className="w-5/6 md:max-w-md"
|
|
298
|
+
>
|
|
299
|
+
<div className="flex flex-col items-center justify-center gap-4 px-6 py-9">
|
|
300
|
+
<Icon name="bell" size={48} />
|
|
301
|
+
<h2 className="text-xl font-semibold">
|
|
302
|
+
{t('product.stock_alert.title')}
|
|
303
|
+
</h2>
|
|
304
|
+
<div className="max-w-40 text-xs text-center leading-4">
|
|
305
|
+
<p>{stockAlertResponseMessage}</p>
|
|
306
|
+
</div>
|
|
307
|
+
<Button
|
|
308
|
+
onClick={handleModalClick}
|
|
309
|
+
appearance="outlined"
|
|
310
|
+
className="font-semibold px-10 h-12"
|
|
311
|
+
>
|
|
312
|
+
{t('product.stock_alert.close_button')}
|
|
313
|
+
</Button>
|
|
314
|
+
</div>
|
|
315
|
+
</Modal>
|
|
115
316
|
</>
|
|
116
317
|
);
|
|
117
318
|
}
|
|
@@ -7,7 +7,6 @@ import { Product } from '@akinon/next/types';
|
|
|
7
7
|
import { Image } from '@akinon/next/components/image';
|
|
8
8
|
import useFavButton from '../../hooks/use-fav-button';
|
|
9
9
|
import { twMerge } from 'tailwind-merge';
|
|
10
|
-
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
11
10
|
|
|
12
11
|
type ProductSliderItem = {
|
|
13
12
|
product: Product;
|
|
@@ -36,102 +35,90 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
|
|
|
36
35
|
carouselRef.current?.next();
|
|
37
36
|
};
|
|
38
37
|
|
|
39
|
-
const handleThumbnailClick = (index
|
|
38
|
+
const handleThumbnailClick = (index) => {
|
|
40
39
|
setActiveIndex(index);
|
|
41
40
|
carouselRef.current?.goToSlide(index);
|
|
42
41
|
};
|
|
43
42
|
|
|
44
43
|
return (
|
|
45
|
-
|
|
46
|
-
<div className="lg:
|
|
47
|
-
<div className="
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
disabled={activeIndex === 0}
|
|
56
|
-
>
|
|
57
|
-
<Icon name="chevron-up" size={15} className="fill-[#000000]" />
|
|
58
|
-
</button>
|
|
59
|
-
<div className="hidden flex-col items-center overflow-scroll w-[80px] max-h-[620px] lg:block">
|
|
60
|
-
{product?.productimage_set?.map((item, index) => (
|
|
61
|
-
<Image
|
|
62
|
-
key={index}
|
|
63
|
-
src={item.image}
|
|
64
|
-
alt={`Thumbnail ${index}`}
|
|
65
|
-
width={80}
|
|
66
|
-
height={128}
|
|
67
|
-
aspectRatio={80 / 128}
|
|
68
|
-
className={twMerge('cursor-pointer', [
|
|
69
|
-
activeIndex === index && 'border-2 border-primary'
|
|
70
|
-
])}
|
|
71
|
-
onClick={() => handleThumbnailClick(index)}
|
|
72
|
-
/>
|
|
73
|
-
))}
|
|
74
|
-
</div>
|
|
75
|
-
<button
|
|
76
|
-
onClick={goToNext}
|
|
77
|
-
className={twMerge(
|
|
78
|
-
'hidden justify-center p-2 mt-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
79
|
-
[
|
|
80
|
-
activeIndex === product.productimage_set.length - 1 &&
|
|
81
|
-
'cursor-not-allowed opacity-45'
|
|
82
|
-
]
|
|
83
|
-
)}
|
|
84
|
-
disabled={activeIndex === product.productimage_set.length - 1}
|
|
85
|
-
>
|
|
86
|
-
<Icon name="chevron-down" size={15} className="fill-[#000000]" />
|
|
87
|
-
</button>
|
|
88
|
-
</div>
|
|
89
|
-
</div>
|
|
90
|
-
|
|
91
|
-
<div className="relative lg:col-span-5">
|
|
92
|
-
<FavButton className="absolute right-8 top-6 z-[20] sm:hidden" />
|
|
93
|
-
|
|
94
|
-
<PluginModule
|
|
95
|
-
component={Component.ProductImageSearchFeature}
|
|
96
|
-
props={{
|
|
97
|
-
product,
|
|
98
|
-
activeIndex,
|
|
99
|
-
showResetButton: true
|
|
100
|
-
}}
|
|
101
|
-
/>
|
|
102
|
-
|
|
103
|
-
<CarouselCore
|
|
104
|
-
responsive={{
|
|
105
|
-
all: {
|
|
106
|
-
breakpoint: { max: 5000, min: 0 },
|
|
107
|
-
items: 1
|
|
108
|
-
}
|
|
109
|
-
}}
|
|
110
|
-
arrows={false}
|
|
111
|
-
swipeable={true}
|
|
112
|
-
ref={carouselRef}
|
|
113
|
-
afterChange={(previousSlide, { currentSlide }) => {
|
|
114
|
-
setActiveIndex(currentSlide);
|
|
115
|
-
}}
|
|
116
|
-
containerAspectRatio={{ mobile: 520 / 798, desktop: 484 / 726 }}
|
|
44
|
+
<div className="lg:grid lg:grid-cols-6">
|
|
45
|
+
<div className="lg:col-span-1">
|
|
46
|
+
<div className="flex flex-col items-center justify-center md:mr-[6px]">
|
|
47
|
+
<button
|
|
48
|
+
onClick={goToPrev}
|
|
49
|
+
className={twMerge(
|
|
50
|
+
'hidden justify-center p-2 mb-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
51
|
+
[activeIndex === 0 && 'cursor-not-allowed opacity-45']
|
|
52
|
+
)}
|
|
53
|
+
disabled={activeIndex === 0}
|
|
117
54
|
>
|
|
118
|
-
{
|
|
55
|
+
<Icon name="chevron-up" size={15} className="fill-[#000000]" />
|
|
56
|
+
</button>
|
|
57
|
+
<div className="hidden flex-col items-center overflow-scroll w-[80px] max-h-[620px] lg:block">
|
|
58
|
+
{product?.productimage_set?.map((item, index) => (
|
|
119
59
|
<Image
|
|
120
|
-
key={
|
|
60
|
+
key={index}
|
|
121
61
|
src={item.image}
|
|
122
|
-
alt={
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
fill
|
|
62
|
+
alt={`Thumbnail ${index}`}
|
|
63
|
+
width={80}
|
|
64
|
+
height={128}
|
|
65
|
+
className={twMerge('cursor-pointer', [
|
|
66
|
+
activeIndex === index && 'border-2 border-primary'
|
|
67
|
+
])}
|
|
68
|
+
onClick={() => handleThumbnailClick(index)}
|
|
130
69
|
/>
|
|
131
70
|
))}
|
|
132
|
-
</
|
|
71
|
+
</div>
|
|
72
|
+
<button
|
|
73
|
+
onClick={goToNext}
|
|
74
|
+
className={twMerge(
|
|
75
|
+
'hidden justify-center p-2 mt-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
76
|
+
[
|
|
77
|
+
activeIndex === product.productimage_set.length - 1 &&
|
|
78
|
+
'cursor-not-allowed opacity-45'
|
|
79
|
+
]
|
|
80
|
+
)}
|
|
81
|
+
disabled={activeIndex === product.productimage_set.length - 1}
|
|
82
|
+
>
|
|
83
|
+
<Icon name="chevron-down" size={15} className="fill-[#000000]" />
|
|
84
|
+
</button>
|
|
133
85
|
</div>
|
|
134
86
|
</div>
|
|
135
|
-
|
|
87
|
+
|
|
88
|
+
<div className="relative lg:col-span-5">
|
|
89
|
+
<FavButton className="absolute right-8 top-6 z-[20] sm:hidden" />
|
|
90
|
+
|
|
91
|
+
<CarouselCore
|
|
92
|
+
responsive={{
|
|
93
|
+
all: {
|
|
94
|
+
breakpoint: { max: 5000, min: 0 },
|
|
95
|
+
items: 1
|
|
96
|
+
}
|
|
97
|
+
}}
|
|
98
|
+
arrows={false}
|
|
99
|
+
swipeable={true}
|
|
100
|
+
ref={carouselRef}
|
|
101
|
+
afterChange={(previousSlide, { currentSlide }) => {
|
|
102
|
+
setActiveIndex(currentSlide);
|
|
103
|
+
}}
|
|
104
|
+
containerAspectRatio={{ mobile: 520 / 798, desktop: 484 / 726 }}
|
|
105
|
+
>
|
|
106
|
+
{product?.productimage_set?.map((item, i) => (
|
|
107
|
+
<Image
|
|
108
|
+
key={i}
|
|
109
|
+
src={item.image}
|
|
110
|
+
alt={product.name}
|
|
111
|
+
draggable={false}
|
|
112
|
+
aspectRatio={484 / 726}
|
|
113
|
+
sizes="(min-width: 425px) 512px,
|
|
114
|
+
(min-width: 601px) 576px,
|
|
115
|
+
(min-width: 768px) 336px,
|
|
116
|
+
(min-width: 1024px) 484px, 368px"
|
|
117
|
+
fill
|
|
118
|
+
/>
|
|
119
|
+
))}
|
|
120
|
+
</CarouselCore>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
136
123
|
);
|
|
137
124
|
}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { yupResolver } from '@hookform/resolvers/yup';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
|
-
import { signIn } from 'next-auth/react';
|
|
5
|
+
import { signIn, SignInOptions } from 'next-auth/react';
|
|
6
6
|
import { useState } from 'react';
|
|
7
7
|
import { SubmitHandler, useForm } from 'react-hook-form';
|
|
8
8
|
import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
|
|
9
|
-
import { RegisterFormType
|
|
9
|
+
import { RegisterFormType } from '@theme/types';
|
|
10
10
|
import { Button, Checkbox, Icon, Input, Modal } from '@theme/components';
|
|
11
11
|
import * as yup from 'yup';
|
|
12
12
|
import { useCaptcha, useLocalization, useRouter } from '@akinon/next/hooks';
|
|
@@ -125,9 +125,8 @@ export const Register = () => {
|
|
|
125
125
|
redirect: false,
|
|
126
126
|
callbackUrl: '/',
|
|
127
127
|
captchaValidated,
|
|
128
|
-
...data
|
|
129
|
-
|
|
130
|
-
} as PzSignInOptions);
|
|
128
|
+
...data
|
|
129
|
+
} as SignInOptions);
|
|
131
130
|
};
|
|
132
131
|
|
|
133
132
|
const onSubmit: SubmitHandler<RegisterFormType> = async (data) => {
|
|
@@ -144,6 +143,7 @@ export const Register = () => {
|
|
|
144
143
|
if (registerResponse.error) {
|
|
145
144
|
const errors: AuthError[] = JSON.parse(registerResponse.error);
|
|
146
145
|
|
|
146
|
+
|
|
147
147
|
if (errors.find((error) => error.type === 'captcha')) {
|
|
148
148
|
if (await validateCaptcha()) {
|
|
149
149
|
onSubmit(data);
|
|
@@ -171,25 +171,25 @@ export const Register = () => {
|
|
|
171
171
|
try {
|
|
172
172
|
parsedValue = JSON.parse(item.value);
|
|
173
173
|
} catch {
|
|
174
|
-
parsedValue = [item.value];
|
|
174
|
+
parsedValue = [item.value];
|
|
175
175
|
}
|
|
176
176
|
} else {
|
|
177
|
-
parsedValue = item.value;
|
|
177
|
+
parsedValue = item.value;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
if (Array.isArray(parsedValue)) {
|
|
181
181
|
setError(item.name as keyof RegisterFormType, {
|
|
182
182
|
type: 'custom',
|
|
183
|
-
message: parsedValue.join(', ')
|
|
183
|
+
message: parsedValue.join(', '),
|
|
184
184
|
});
|
|
185
185
|
} else {
|
|
186
186
|
Object.keys(parsedValue).forEach((key) => {
|
|
187
187
|
const fieldName = key as keyof RegisterFormType;
|
|
188
188
|
const errorMessages = parsedValue[key] as string[];
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
setError(fieldName, {
|
|
191
191
|
type: 'custom',
|
|
192
|
-
message: errorMessages.join(', ')
|
|
192
|
+
message: errorMessages.join(', '),
|
|
193
193
|
});
|
|
194
194
|
});
|
|
195
195
|
}
|
|
@@ -247,11 +247,7 @@ export const Register = () => {
|
|
|
247
247
|
</p>
|
|
248
248
|
|
|
249
249
|
<form onSubmit={handleSubmit(onSubmit)} className="flex flex-col gap-4">
|
|
250
|
-
<input
|
|
251
|
-
type="hidden"
|
|
252
|
-
value={FormType.register}
|
|
253
|
-
{...register('formType')}
|
|
254
|
-
/>
|
|
250
|
+
<input type="hidden" value="register" {...register('formType')} />
|
|
255
251
|
<input type="hidden" value={locale} {...register('locale')} />
|
|
256
252
|
|
|
257
253
|
<div className={clsx({ 'mb-4': errors.email })}>
|
|
@@ -2,7 +2,6 @@ import 'server-only';
|
|
|
2
2
|
|
|
3
3
|
import { Link, Accordion } from '@theme/components';
|
|
4
4
|
import { getWidgetData } from '@akinon/next/data/server';
|
|
5
|
-
import { ServerVariables } from '@akinon/next/utils/server-variables';
|
|
6
5
|
|
|
7
6
|
type SideItem = {
|
|
8
7
|
value: string;
|
|
@@ -48,7 +47,6 @@ type FooterMenuType = {
|
|
|
48
47
|
|
|
49
48
|
export default async function FooterMenu() {
|
|
50
49
|
const data = await getWidgetData<FooterMenuType>({ slug: 'footer-menu' });
|
|
51
|
-
const { locale } = ServerVariables;
|
|
52
50
|
|
|
53
51
|
return (
|
|
54
52
|
<div className="flex-1">
|
|
@@ -74,7 +72,7 @@ export default async function FooterMenu() {
|
|
|
74
72
|
: '_self'
|
|
75
73
|
}
|
|
76
74
|
data-testid={`footer-categories-${item?.value?.name
|
|
77
|
-
?.toLocaleLowerCase(
|
|
75
|
+
?.toLocaleLowerCase()
|
|
78
76
|
.split(' ')
|
|
79
77
|
.join('')}`}
|
|
80
78
|
>
|
|
@@ -98,9 +96,7 @@ export default async function FooterMenu() {
|
|
|
98
96
|
? '_blank'
|
|
99
97
|
: '_self'
|
|
100
98
|
}
|
|
101
|
-
data-testid={`footer-categories-${item?.value?.name?.toLocaleLowerCase(
|
|
102
|
-
locale
|
|
103
|
-
)}`}
|
|
99
|
+
data-testid={`footer-categories-${item?.value?.name?.toLocaleLowerCase()}`}
|
|
104
100
|
>
|
|
105
101
|
{item?.value?.name}
|
|
106
102
|
</Link>
|