@akinon/projectzero 2.0.0-beta.12 → 2.0.0-beta.14
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 +102 -23
- package/app-template/.env.example +1 -0
- package/app-template/.github/instructions/account.instructions.md +749 -0
- package/app-template/.github/instructions/checkout.instructions.md +678 -0
- package/app-template/.github/instructions/default.instructions.md +279 -0
- package/app-template/.github/instructions/edge-cases.instructions.md +73 -0
- package/app-template/.github/instructions/routing.instructions.md +603 -0
- package/app-template/.github/instructions/settings.instructions.md +338 -0
- package/app-template/.gitignore +3 -0
- package/app-template/AGENTS.md +7 -0
- package/app-template/CHANGELOG.md +1387 -310
- package/app-template/Procfile +1 -1
- package/app-template/akinon.json +0 -3
- package/app-template/build.sh +10 -0
- package/app-template/docs/advanced-usage.md +101 -0
- package/app-template/docs/sentry-usage.md +35 -0
- package/app-template/next-env.d.ts +1 -0
- package/app-template/{next.config.ts → next.config.mjs} +6 -6
- package/app-template/package.json +58 -51
- package/app-template/postcss.config.mjs +1 -4
- package/app-template/public/locales/en/checkout.json +11 -0
- package/app-template/public/locales/en/common.json +50 -1
- package/app-template/public/locales/en/product.json +62 -1
- package/app-template/public/locales/tr/checkout.json +11 -0
- package/app-template/public/locales/tr/common.json +50 -1
- package/app-template/public/locales/tr/product.json +63 -0
- package/app-template/public/masterpass-javascript-sdk-web.min.js +1 -0
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/[...prettyurl]/page.tsx +9 -9
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/layout.tsx +2 -2
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/cancellation/page.tsx +6 -6
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/page.tsx +6 -6
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/page.tsx +1 -1
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/profile/page.tsx +2 -2
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/address/stores/page.tsx +2 -2
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/page.tsx +1 -1
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket/page.tsx +2 -2
- package/app-template/src/app/[pz]/category/[pk]/page.tsx +27 -0
- package/app-template/src/app/[pz]/flat-page/[pk]/page.tsx +23 -0
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/forms/[pk]/generate/page.tsx +2 -3
- package/app-template/src/app/[pz]/group-product/[pk]/page.tsx +93 -0
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/page.tsx +2 -4
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/layout.tsx +3 -10
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/page.tsx +2 -4
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/not-found.tsx +5 -7
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/page.tsx +6 -4
- package/app-template/src/app/[pz]/product/[pk]/page.tsx +102 -0
- package/app-template/src/app/[pz]/special-page/[pk]/page.tsx +35 -0
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/email-set-primary/[[...id]]/page.tsx +3 -4
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/registration/account-confirm-email/[[...id]]/page.tsx +3 -3
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/reset/[[...id]]/page.tsx +6 -12
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/[node]/route.ts +2 -2
- package/app-template/src/app/api/auth/[...nextauth]/route.ts +3 -0
- package/app-template/src/app/api/form/[...id]/route.ts +1 -7
- package/app-template/src/app/api/image-proxy/route.ts +1 -0
- package/app-template/src/app/api/product-categories/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/app/api/virtual-try-on/limited-categories/route.ts +1 -0
- package/app-template/src/app/api/virtual-try-on/route.ts +1 -0
- package/app-template/src/assets/globals.scss +4 -133
- package/app-template/src/auth.ts +3 -0
- package/app-template/src/components/__tests__/badge.test.tsx +2 -2
- package/app-template/src/components/__tests__/link.test.tsx +2 -0
- package/app-template/src/components/accordion.tsx +23 -20
- package/app-template/src/components/button.tsx +1 -1
- package/app-template/src/components/carousel-core.tsx +4 -11
- package/app-template/src/components/checkbox.tsx +1 -1
- 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/generate-form-fields.tsx +49 -10
- package/app-template/src/components/input.tsx +11 -5
- package/app-template/src/components/modal.tsx +32 -16
- package/app-template/src/components/pagination.tsx +1 -0
- package/app-template/src/components/price.tsx +1 -1
- package/app-template/src/components/pwa-tags.tsx +1 -0
- package/app-template/src/components/select.tsx +39 -27
- package/app-template/src/components/shimmer.tsx +1 -1
- package/app-template/src/components/types/index.ts +25 -1
- package/app-template/src/hooks/use-fav-button.tsx +4 -8
- 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 +12 -2
- package/app-template/src/redux/middlewares/category.ts +5 -4
- package/app-template/src/redux/store.ts +21 -1
- package/app-template/src/routes/index.ts +2 -1
- package/app-template/src/settings.js +3 -1
- package/app-template/src/types/index.ts +74 -3
- package/app-template/src/types/next-auth.d.ts +2 -2
- 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/contact-form.tsx +2 -2
- package/app-template/src/views/account/content-header.tsx +4 -3
- package/app-template/src/views/account/faq/faq-tabs.tsx +8 -2
- package/app-template/src/views/account/order.tsx +1 -1
- package/app-template/src/views/account/orders/order-cancellation-item.tsx +1 -1
- package/app-template/src/views/anonymous-tracking/order-detail/index.tsx +1 -1
- package/app-template/src/views/basket/basket-item.tsx +6 -1
- package/app-template/src/views/basket/summary.tsx +16 -0
- package/app-template/src/views/breadcrumb.tsx +2 -2
- package/app-template/src/views/category/category-info.tsx +2 -1
- package/app-template/src/views/category/filters/index.tsx +1 -1
- package/app-template/src/views/checkout/auth.tsx +1 -1
- package/app-template/src/views/checkout/layout/header.tsx +1 -1
- package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +22 -6
- package/app-template/src/views/checkout/steps/payment/options/funds-transfer.tsx +25 -5
- package/app-template/src/views/checkout/steps/payment/options/loyalty.tsx +21 -2
- package/app-template/src/views/checkout/steps/payment/options/redirection.tsx +22 -4
- package/app-template/src/views/checkout/steps/payment/options/store-credit.tsx +121 -0
- package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +4 -4
- package/app-template/src/views/checkout/steps/shipping/address-box.tsx +3 -3
- package/app-template/src/views/checkout/steps/shipping/addresses.tsx +1 -1
- package/app-template/src/views/checkout/summary.tsx +12 -2
- package/app-template/src/views/find-in-store/index.tsx +2 -2
- package/app-template/src/views/header/action-menu.tsx +2 -6
- package/app-template/src/views/header/band.tsx +2 -2
- package/app-template/src/views/header/index.tsx +1 -1
- package/app-template/src/views/header/mini-basket.tsx +2 -2
- package/app-template/src/views/header/mobile-menu.tsx +6 -6
- package/app-template/src/views/header/navbar.tsx +1 -1
- package/app-template/src/views/header/pwa-back-button.tsx +1 -1
- package/app-template/src/views/header/search/index.tsx +13 -3
- package/app-template/src/views/header/search/results.tsx +1 -1
- package/app-template/src/views/header/user-menu.tsx +1 -3
- package/app-template/src/views/login/index.tsx +14 -13
- package/app-template/src/views/otp-login/index.tsx +11 -6
- package/app-template/src/views/product/layout.tsx +15 -1
- package/app-template/src/views/product/product-actions.tsx +165 -0
- package/app-template/src/views/product/product-info.tsx +69 -261
- 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 +22 -1
- package/app-template/src/views/product-pointer-banner-item.tsx +1 -1
- package/app-template/src/views/register/index.tsx +17 -21
- package/app-template/src/views/sales-contract-modal/index.tsx +17 -17
- package/app-template/src/widgets/footer-info.tsx +1 -1
- package/app-template/src/widgets/footer-menu.tsx +7 -3
- package/app-template/src/widgets/footer-subscription/index.tsx +1 -1
- package/app-template/src/widgets/home-stories-eng.tsx +43 -35
- package/app-template/tailwind.config.js +129 -1
- package/app-template/tsconfig.json +29 -11
- package/codemods/migrate-segments/index.js +591 -0
- package/commands/plugins.ts +62 -14
- package/dist/commands/plugins.js +62 -14
- package/package.json +1 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +0 -22
- package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +0 -20
- package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +0 -74
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +0 -84
- package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +0 -27
- package/app-template/src/pages/api/auth/[...nextauth].ts +0 -3
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/address/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-email/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/change-password/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/contact/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/coupons/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/email-verification/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/faq/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/favourite-products/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/my-quotations/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/[id]/layout.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/account/orders/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/anonymous-tracking/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/auth/oauth-login/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/basket-b2b/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/category/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/client-root.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/contact-us/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/error.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/flat-page/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/group-product/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/landing-page/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/list/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/checkout/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/orders/completed/[token]/layout.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/special-page/[pk]/loading.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/template.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/users/password/reset/page.tsx +0 -0
- /package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/xml-sitemap/route.ts +0 -0
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
3
|
import { FileInputProps } from '@theme/components/types';
|
|
4
|
-
import clsx from 'clsx';
|
|
5
4
|
import { useLocalization } from '@akinon/next/hooks';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
6
|
|
|
7
7
|
export const FileInput = forwardRef<HTMLInputElement, FileInputProps>(
|
|
8
|
-
function FileInput(
|
|
8
|
+
function FileInput(
|
|
9
|
+
{
|
|
10
|
+
buttonClassName,
|
|
11
|
+
onChange,
|
|
12
|
+
fileClassName,
|
|
13
|
+
fileNameWrapperClassName,
|
|
14
|
+
fileInputClassName,
|
|
15
|
+
...props
|
|
16
|
+
},
|
|
17
|
+
ref
|
|
18
|
+
) {
|
|
9
19
|
const { t } = useLocalization();
|
|
10
20
|
const [fileNames, setFileNames] = useState<string[]>([]);
|
|
11
21
|
|
|
@@ -24,24 +34,34 @@ export const FileInput = forwardRef<HTMLInputElement, FileInputProps>(
|
|
|
24
34
|
type="file"
|
|
25
35
|
{...props}
|
|
26
36
|
ref={ref}
|
|
27
|
-
className=
|
|
37
|
+
className={twMerge(
|
|
38
|
+
'absolute inset-0 w-full h-full opacity-0 cursor-pointer',
|
|
39
|
+
fileInputClassName
|
|
40
|
+
)}
|
|
28
41
|
onChange={handleFileChange}
|
|
29
42
|
/>
|
|
30
43
|
<button
|
|
31
44
|
type="button"
|
|
32
|
-
className={
|
|
45
|
+
className={twMerge(
|
|
46
|
+
'bg-primary text-white py-2 px-4 text-sm',
|
|
47
|
+
buttonClassName
|
|
48
|
+
)}
|
|
33
49
|
>
|
|
34
50
|
{t('common.file_input.select_file')}
|
|
35
51
|
</button>
|
|
36
|
-
<div
|
|
52
|
+
<div
|
|
53
|
+
className={twMerge('mt-1 text-gray-500', fileNameWrapperClassName)}
|
|
54
|
+
>
|
|
37
55
|
{fileNames.length > 0 ? (
|
|
38
|
-
<ul className=
|
|
56
|
+
<ul className={twMerge('list-disc pl-4 text-xs', fileClassName)}>
|
|
39
57
|
{fileNames.map((name, index) => (
|
|
40
58
|
<li key={index}>{name}</li>
|
|
41
59
|
))}
|
|
42
60
|
</ul>
|
|
43
61
|
) : (
|
|
44
|
-
<span className=
|
|
62
|
+
<span className={twMerge('text-xs', fileClassName)}>
|
|
63
|
+
{t('common.file_input.no_file')}
|
|
64
|
+
</span>
|
|
45
65
|
)}
|
|
46
66
|
</div>
|
|
47
67
|
</div>
|
|
@@ -7,6 +7,7 @@ import { useForm } from 'react-hook-form';
|
|
|
7
7
|
import { yupResolver } from '@hookform/resolvers/yup';
|
|
8
8
|
import * as yup from 'yup';
|
|
9
9
|
import DynamicForm from './dynamic-form';
|
|
10
|
+
|
|
10
11
|
import {
|
|
11
12
|
AllFieldClassesType,
|
|
12
13
|
FieldPropertiesType,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
FormPropertiesType,
|
|
15
16
|
Schema
|
|
16
17
|
} from '@akinon/next/types';
|
|
18
|
+
import { useLocalization } from '@akinon/next/hooks';
|
|
17
19
|
|
|
18
20
|
export function GenerateFormFields({
|
|
19
21
|
schema,
|
|
@@ -28,8 +30,14 @@ export function GenerateFormFields({
|
|
|
28
30
|
formProperties: FormPropertiesType;
|
|
29
31
|
submitButtonText: string;
|
|
30
32
|
}) {
|
|
33
|
+
const { t } = useLocalization();
|
|
31
34
|
const [fields, setFields] = useState([]);
|
|
32
35
|
const [loading, setIsLoading] = useState(true);
|
|
36
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
37
|
+
const [submitStatus, setSubmitStatus] = useState<
|
|
38
|
+
'idle' | 'success' | 'error'
|
|
39
|
+
>('idle');
|
|
40
|
+
const [submitMessage, setSubmitMessage] = useState('');
|
|
33
41
|
|
|
34
42
|
const generateValidationSchema = () => {
|
|
35
43
|
const schemaObject = {};
|
|
@@ -80,6 +88,7 @@ export function GenerateFormFields({
|
|
|
80
88
|
const {
|
|
81
89
|
handleSubmit,
|
|
82
90
|
register,
|
|
91
|
+
reset,
|
|
83
92
|
formState: { errors }
|
|
84
93
|
} = useForm<FormField>({
|
|
85
94
|
resolver: yupResolver(generateValidationSchema())
|
|
@@ -108,6 +117,10 @@ export function GenerateFormFields({
|
|
|
108
117
|
}, [schema, fieldProperties]);
|
|
109
118
|
|
|
110
119
|
const onSubmit = async (data) => {
|
|
120
|
+
setIsSubmitting(true);
|
|
121
|
+
setSubmitStatus('idle');
|
|
122
|
+
setSubmitMessage('');
|
|
123
|
+
|
|
111
124
|
try {
|
|
112
125
|
const formData = new FormData();
|
|
113
126
|
|
|
@@ -115,12 +128,25 @@ export function GenerateFormFields({
|
|
|
115
128
|
formData.append(key, data[key]);
|
|
116
129
|
});
|
|
117
130
|
|
|
118
|
-
fetch(formProperties.actionUrl, {
|
|
131
|
+
const response = await fetch(formProperties.actionUrl, {
|
|
119
132
|
method: 'POST',
|
|
120
133
|
body: formData
|
|
121
134
|
});
|
|
135
|
+
|
|
136
|
+
if (response.ok) {
|
|
137
|
+
setSubmitStatus('success');
|
|
138
|
+
setSubmitMessage(t('common.forms.success'));
|
|
139
|
+
reset();
|
|
140
|
+
} else {
|
|
141
|
+
setSubmitStatus('error');
|
|
142
|
+
setSubmitMessage(t('common.forms.error'));
|
|
143
|
+
}
|
|
122
144
|
} catch (error) {
|
|
123
|
-
console.error('
|
|
145
|
+
console.error(t('common.forms.submit_error'), error);
|
|
146
|
+
setSubmitStatus('error');
|
|
147
|
+
setSubmitMessage(t('common.forms.error'));
|
|
148
|
+
} finally {
|
|
149
|
+
setIsSubmitting(false);
|
|
124
150
|
}
|
|
125
151
|
};
|
|
126
152
|
|
|
@@ -157,7 +183,7 @@ export function GenerateFormFields({
|
|
|
157
183
|
className={twMerge(allFieldClasses?.className, field.class)}
|
|
158
184
|
name={field.key}
|
|
159
185
|
{...field.attributes}
|
|
160
|
-
error={errors[field.key]}
|
|
186
|
+
error={errors[field.key] as any}
|
|
161
187
|
{...register(field.key)}
|
|
162
188
|
/>
|
|
163
189
|
</div>
|
|
@@ -187,7 +213,7 @@ export function GenerateFormFields({
|
|
|
187
213
|
className={twMerge(allFieldClasses?.className, field?.class)}
|
|
188
214
|
name={field.key}
|
|
189
215
|
{...field.attributes}
|
|
190
|
-
error={errors[field.key]}
|
|
216
|
+
error={errors[field.key] as any}
|
|
191
217
|
{...register(field.key, { valueAsNumber: true })}
|
|
192
218
|
/>
|
|
193
219
|
</div>
|
|
@@ -220,7 +246,7 @@ export function GenerateFormFields({
|
|
|
220
246
|
/>
|
|
221
247
|
{errors[field.key] && (
|
|
222
248
|
<span className="mt-1 text-sm text-error">
|
|
223
|
-
{errors[field.key].message}
|
|
249
|
+
{String(errors[field.key].message)}
|
|
224
250
|
</span>
|
|
225
251
|
)}
|
|
226
252
|
</div>
|
|
@@ -251,7 +277,7 @@ export function GenerateFormFields({
|
|
|
251
277
|
className={twMerge(allFieldClasses?.className, field?.class)}
|
|
252
278
|
name={field.key}
|
|
253
279
|
{...field.attributes}
|
|
254
|
-
error={errors[field.key]}
|
|
280
|
+
error={errors[field.key] as any}
|
|
255
281
|
{...register(field.key)}
|
|
256
282
|
/>
|
|
257
283
|
</div>
|
|
@@ -285,7 +311,7 @@ export function GenerateFormFields({
|
|
|
285
311
|
label: choice
|
|
286
312
|
}))}
|
|
287
313
|
{...field.attributes}
|
|
288
|
-
error={errors[field.key]}
|
|
314
|
+
error={errors[field.key] as any}
|
|
289
315
|
{...register(field.key)}
|
|
290
316
|
/>
|
|
291
317
|
</div>
|
|
@@ -316,7 +342,7 @@ export function GenerateFormFields({
|
|
|
316
342
|
className={twMerge(allFieldClasses?.className, field?.class)}
|
|
317
343
|
name={field.key}
|
|
318
344
|
{...field.attributes}
|
|
319
|
-
error={errors[field.key]}
|
|
345
|
+
error={errors[field.key] as any}
|
|
320
346
|
{...register(field.key)}
|
|
321
347
|
/>
|
|
322
348
|
</div>
|
|
@@ -337,9 +363,22 @@ export function GenerateFormFields({
|
|
|
337
363
|
<LoaderSpinner />
|
|
338
364
|
) : (
|
|
339
365
|
<>
|
|
366
|
+
{submitStatus === 'success' && (
|
|
367
|
+
<div className="mb-4 p-3 bg-green-100 border border-green-400 text-green-700 rounded">
|
|
368
|
+
{submitMessage}
|
|
369
|
+
</div>
|
|
370
|
+
)}
|
|
371
|
+
|
|
372
|
+
{submitStatus === 'error' && (
|
|
373
|
+
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
|
374
|
+
{submitMessage}
|
|
375
|
+
</div>
|
|
376
|
+
)}
|
|
377
|
+
|
|
340
378
|
{fields.map((field: FormField) => generateField(field))}
|
|
341
|
-
|
|
342
|
-
|
|
379
|
+
|
|
380
|
+
<Button type="submit" className="w-full" disabled={isSubmitting}>
|
|
381
|
+
{isSubmitting ? t('common.forms.sending') : submitButtonText}
|
|
343
382
|
</Button>
|
|
344
383
|
</>
|
|
345
384
|
)}
|
|
@@ -39,8 +39,8 @@ export const Input = forwardRef<
|
|
|
39
39
|
const hasFloatingLabel = label && labelStyle === 'floating';
|
|
40
40
|
const inputClass = twMerge(
|
|
41
41
|
clsx(
|
|
42
|
-
'text-xs border px-2.5 h-10 placeholder:text-gray-600 peer
|
|
43
|
-
'focus-visible:outline-
|
|
42
|
+
'text-xs border px-2.5 h-10 placeholder:text-gray-600 peer',
|
|
43
|
+
'focus-visible:outline-none', // disable outline on focus
|
|
44
44
|
error
|
|
45
45
|
? 'border-error focus:border-error'
|
|
46
46
|
: 'border-gray-500 hover:border-black focus:border-black'
|
|
@@ -48,7 +48,13 @@ export const Input = forwardRef<
|
|
|
48
48
|
props.className
|
|
49
49
|
);
|
|
50
50
|
|
|
51
|
-
const inputProps:
|
|
51
|
+
const inputProps: {
|
|
52
|
+
id?: string;
|
|
53
|
+
ref?: Ref<HTMLInputElement>;
|
|
54
|
+
className?: string;
|
|
55
|
+
onFocus?: () => void;
|
|
56
|
+
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
|
|
57
|
+
} = {
|
|
52
58
|
id,
|
|
53
59
|
ref,
|
|
54
60
|
className: inputClass,
|
|
@@ -68,7 +74,7 @@ export const Input = forwardRef<
|
|
|
68
74
|
className={twMerge(
|
|
69
75
|
'text-xs text-gray-800 transition-all',
|
|
70
76
|
clsx({
|
|
71
|
-
'absolute left-2.5 pointer-events-none transform flex items-center h-full top-0
|
|
77
|
+
'absolute left-2.5 pointer-events-none transform flex items-center h-full !top-0 peer-placeholder-shown:-translate-y-2 peer-placeholder-shown:bg-white peer-placeholder-shown:inline-flex peer-placeholder-shown:h-auto':
|
|
72
78
|
hasFloatingLabel,
|
|
73
79
|
'mb-2': !hasFloatingLabel,
|
|
74
80
|
'-translate-y-2 bg-white inline-flex h-auto':
|
|
@@ -112,7 +118,7 @@ export const Input = forwardRef<
|
|
|
112
118
|
)}
|
|
113
119
|
</div>
|
|
114
120
|
{error && (
|
|
115
|
-
<span className="mt-1 text-sm text-error">{error.message}</span>
|
|
121
|
+
<span className="mt-1 text-sm text-error">{String(error.message)}</span>
|
|
116
122
|
)}
|
|
117
123
|
</div>
|
|
118
124
|
);
|
|
@@ -4,16 +4,7 @@ import ReactPortal from './react-portal';
|
|
|
4
4
|
import { Icon } from './icon';
|
|
5
5
|
import { twMerge } from 'tailwind-merge';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
|
-
|
|
8
|
-
export interface ModalProps {
|
|
9
|
-
portalId: string;
|
|
10
|
-
children?: React.ReactNode;
|
|
11
|
-
open?: boolean;
|
|
12
|
-
setOpen?: (open: boolean) => void;
|
|
13
|
-
title?: React.ReactNode;
|
|
14
|
-
showCloseButton?: React.ReactNode;
|
|
15
|
-
className?: string;
|
|
16
|
-
}
|
|
7
|
+
import { ModalProps } from '@theme/types';
|
|
17
8
|
|
|
18
9
|
export const Modal = (props: ModalProps) => {
|
|
19
10
|
const {
|
|
@@ -23,7 +14,14 @@ export const Modal = (props: ModalProps) => {
|
|
|
23
14
|
setOpen,
|
|
24
15
|
title = '',
|
|
25
16
|
showCloseButton = true,
|
|
26
|
-
className
|
|
17
|
+
className,
|
|
18
|
+
overlayClassName,
|
|
19
|
+
headerWrapperClassName,
|
|
20
|
+
titleClassName,
|
|
21
|
+
closeButtonClassName,
|
|
22
|
+
iconName = 'close',
|
|
23
|
+
iconSize = 16,
|
|
24
|
+
iconClassName
|
|
27
25
|
} = props;
|
|
28
26
|
|
|
29
27
|
useEffect(() => {
|
|
@@ -38,7 +36,12 @@ export const Modal = (props: ModalProps) => {
|
|
|
38
36
|
|
|
39
37
|
return (
|
|
40
38
|
<ReactPortal wrapperId={portalId}>
|
|
41
|
-
<div
|
|
39
|
+
<div
|
|
40
|
+
className={twMerge(
|
|
41
|
+
'fixed top-0 left-0 w-screen h-screen bg-primary bg-opacity-60 z-50',
|
|
42
|
+
overlayClassName
|
|
43
|
+
)}
|
|
44
|
+
/>
|
|
42
45
|
<section
|
|
43
46
|
className={twMerge(
|
|
44
47
|
'fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 bg-white',
|
|
@@ -46,15 +49,28 @@ export const Modal = (props: ModalProps) => {
|
|
|
46
49
|
)}
|
|
47
50
|
>
|
|
48
51
|
{(showCloseButton || title) && (
|
|
49
|
-
<div
|
|
50
|
-
|
|
52
|
+
<div
|
|
53
|
+
className={twMerge(
|
|
54
|
+
'flex px-6 py-4 border-b border-gray-400',
|
|
55
|
+
headerWrapperClassName
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{title && (
|
|
59
|
+
<h3 className={twMerge('text-lg font-light', titleClassName)}>
|
|
60
|
+
{title}
|
|
61
|
+
</h3>
|
|
62
|
+
)}
|
|
51
63
|
{showCloseButton && (
|
|
52
64
|
<button
|
|
53
65
|
type="button"
|
|
54
66
|
onClick={() => setOpen(false)}
|
|
55
|
-
className=
|
|
67
|
+
className={twMerge('ml-auto', closeButtonClassName)}
|
|
56
68
|
>
|
|
57
|
-
<Icon
|
|
69
|
+
<Icon
|
|
70
|
+
name={iconName}
|
|
71
|
+
size={iconSize}
|
|
72
|
+
className={iconClassName}
|
|
73
|
+
/>
|
|
58
74
|
</button>
|
|
59
75
|
)}
|
|
60
76
|
</div>
|
|
@@ -56,7 +56,7 @@ export const Price = (props: NumericFormatProps & PriceProps) => {
|
|
|
56
56
|
|
|
57
57
|
const currentCurrencyDecimalScale = Settings.localization.currencies.find(
|
|
58
58
|
(currency) => currency.code === currencyCode_
|
|
59
|
-
)
|
|
59
|
+
)?.decimalScale;
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
62
|
<NumericFormat
|
|
@@ -14,14 +14,18 @@ const Select = forwardRef<HTMLSelectElement, SelectProps>((props, ref) => {
|
|
|
14
14
|
error,
|
|
15
15
|
label,
|
|
16
16
|
required = false,
|
|
17
|
+
labelClassName,
|
|
17
18
|
...rest
|
|
18
19
|
} = props;
|
|
19
20
|
|
|
20
21
|
return (
|
|
21
22
|
<label
|
|
22
|
-
className={
|
|
23
|
-
'
|
|
24
|
-
|
|
23
|
+
className={twMerge(
|
|
24
|
+
clsx('flex flex-col relative text-xs text-gray-800', {
|
|
25
|
+
'pl-7': icon
|
|
26
|
+
}),
|
|
27
|
+
labelClassName
|
|
28
|
+
)}
|
|
25
29
|
>
|
|
26
30
|
{icon && (
|
|
27
31
|
<Icon
|
|
@@ -32,34 +36,42 @@ const Select = forwardRef<HTMLSelectElement, SelectProps>((props, ref) => {
|
|
|
32
36
|
)}
|
|
33
37
|
|
|
34
38
|
{label && (
|
|
35
|
-
<span className=
|
|
39
|
+
<span className={twMerge('mb-1', labelClassName)}>
|
|
36
40
|
{label} {required && <span className="text-secondary">*</span>}
|
|
37
41
|
</span>
|
|
38
42
|
)}
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
<div className="relative">
|
|
44
|
+
<select
|
|
45
|
+
{...rest}
|
|
46
|
+
ref={ref}
|
|
47
|
+
className={twMerge(
|
|
48
|
+
clsx(
|
|
49
|
+
'cursor-pointer truncate h-10 w-40 px-2.5 shrink-0 outline-none',
|
|
50
|
+
!borderless &&
|
|
51
|
+
'border border-gray-200 transition-all duration-150 hover:border-primary',
|
|
52
|
+
'appearance-none bg-transparent'
|
|
53
|
+
),
|
|
54
|
+
className
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
{options?.map((option) => (
|
|
58
|
+
<option
|
|
59
|
+
key={option.value}
|
|
60
|
+
value={option.value}
|
|
61
|
+
className={option.class}
|
|
62
|
+
>
|
|
63
|
+
{option.label}
|
|
64
|
+
</option>
|
|
65
|
+
))}
|
|
66
|
+
</select>
|
|
67
|
+
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
|
|
68
|
+
<svg className="h-4 w-4 fill-current" viewBox="0 0 20 20">
|
|
69
|
+
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" />
|
|
70
|
+
</svg>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
61
73
|
{error && (
|
|
62
|
-
<span className="mt-1 text-sm text-error">{error.message}</span>
|
|
74
|
+
<span className="mt-1 text-sm text-error">{String(error.message)}</span>
|
|
63
75
|
)}
|
|
64
76
|
</label>
|
|
65
77
|
);
|
|
@@ -12,7 +12,7 @@ export const Shimmer: React.FC<ShimmerProps> = ({ className }) => {
|
|
|
12
12
|
>
|
|
13
13
|
<div
|
|
14
14
|
className={twMerge(
|
|
15
|
-
'w-full h-full bg-black
|
|
15
|
+
'w-full h-full bg-black bg-opacity-20 shadow-[0 0 30px 30px rgba(255,255,255,0.2)]',
|
|
16
16
|
className
|
|
17
17
|
)}
|
|
18
18
|
></div>
|
|
@@ -29,7 +29,13 @@ export interface PaginationProps {
|
|
|
29
29
|
isLoading?: boolean;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export
|
|
32
|
+
export interface FileInputProps extends React.HTMLProps<HTMLInputElement> {
|
|
33
|
+
fileClassName?: string;
|
|
34
|
+
fileNameWrapperClassName?: string;
|
|
35
|
+
fileInputClassName?: string;
|
|
36
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
37
|
+
buttonClassName?: string;
|
|
38
|
+
}
|
|
33
39
|
|
|
34
40
|
export type RadioProps = React.HTMLProps<HTMLInputElement>;
|
|
35
41
|
|
|
@@ -58,6 +64,7 @@ export interface SelectProps extends React.HTMLProps<HTMLSelectElement> {
|
|
|
58
64
|
iconSize?: number;
|
|
59
65
|
error?: FieldError | undefined;
|
|
60
66
|
required?: boolean;
|
|
67
|
+
labelClassName?: string;
|
|
61
68
|
}
|
|
62
69
|
export interface IconProps extends React.ComponentPropsWithRef<'i'> {
|
|
63
70
|
name: string;
|
|
@@ -91,3 +98,20 @@ export interface BadgeProps {
|
|
|
91
98
|
children: ReactNode;
|
|
92
99
|
className?: string;
|
|
93
100
|
}
|
|
101
|
+
|
|
102
|
+
export type AccordionProps = {
|
|
103
|
+
isCollapse?: boolean;
|
|
104
|
+
collapseClassName?: string;
|
|
105
|
+
title?: string;
|
|
106
|
+
subTitle?: string;
|
|
107
|
+
icons?: string[];
|
|
108
|
+
iconSize?: number;
|
|
109
|
+
iconColor?: string;
|
|
110
|
+
children?: ReactNode;
|
|
111
|
+
headerClassName?: string;
|
|
112
|
+
className?: string;
|
|
113
|
+
titleClassName?: string;
|
|
114
|
+
subTitleClassName?: string;
|
|
115
|
+
dataTestId?: string;
|
|
116
|
+
contentClassName?: string;
|
|
117
|
+
};
|
|
@@ -35,7 +35,7 @@ const useFavButton = (productPk: number) => {
|
|
|
35
35
|
[favorites, productPk]
|
|
36
36
|
);
|
|
37
37
|
|
|
38
|
-
const
|
|
38
|
+
const isActive = Boolean(favoriteItem);
|
|
39
39
|
const [isPushed, setIsPushed] = useState<boolean>(false);
|
|
40
40
|
|
|
41
41
|
const [addFavorite] = useAddFavoriteMutation();
|
|
@@ -55,20 +55,16 @@ const useFavButton = (productPk: number) => {
|
|
|
55
55
|
}, [favoriteItem, productPk, addFavorite, removeFavorite]);
|
|
56
56
|
|
|
57
57
|
useEffect(() => {
|
|
58
|
-
|
|
59
|
-
}, [favoriteItem]);
|
|
60
|
-
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
if (favoriteItem && !isActive && isPushed) {
|
|
63
|
-
setIsActive(true);
|
|
58
|
+
if (favoriteItem && isPushed) {
|
|
64
59
|
pushAddToWishlist({
|
|
65
60
|
base_code: favoriteItem?.product?.base_code,
|
|
66
61
|
name: favoriteItem?.product?.name,
|
|
67
62
|
price: favoriteItem?.product?.price,
|
|
68
63
|
currency_type: favoriteItem?.product?.currency_type
|
|
69
64
|
});
|
|
65
|
+
setIsPushed(false);
|
|
70
66
|
}
|
|
71
|
-
}, [favoriteItem,
|
|
67
|
+
}, [favoriteItem, isPushed]);
|
|
72
68
|
|
|
73
69
|
const FavButton = useMemo(() => {
|
|
74
70
|
const View = (props: FavButtonProps) => (
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useAddProductToBasket } from './index';
|
|
3
|
+
import { pushAddToCart } from '@theme/utils/gtm';
|
|
4
|
+
import { validateVariantSelection } from '../utils/variant-validation';
|
|
5
|
+
import { VariantType } from '@akinon/next/types';
|
|
6
|
+
|
|
7
|
+
interface Product {
|
|
8
|
+
pk: number;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface UseProductCartProps {
|
|
13
|
+
product: Product;
|
|
14
|
+
variants: VariantType[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface AddToCartError {
|
|
18
|
+
data?: {
|
|
19
|
+
non_field_errors?: string[];
|
|
20
|
+
[key: string]: string[];
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const useProductCart = ({ product, variants }: UseProductCartProps) => {
|
|
25
|
+
const [productError, setProductError] = useState<React.ReactNode | null>(null);
|
|
26
|
+
const [addProduct, { isLoading: isAddToCartLoading }] = useAddProductToBasket();
|
|
27
|
+
|
|
28
|
+
const formatError = (error: AddToCartError) => {
|
|
29
|
+
if (error?.data?.non_field_errors) {
|
|
30
|
+
return error.data.non_field_errors;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (error?.data) {
|
|
34
|
+
return Object.keys(error.data).map(
|
|
35
|
+
(key) => `${key}: ${error.data[key].join(', ')}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return 'An error occurred';
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const addProductToCart = async () => {
|
|
43
|
+
const validation = validateVariantSelection(variants);
|
|
44
|
+
|
|
45
|
+
if (!validation.isValid) {
|
|
46
|
+
setProductError(validation.errorMessage);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
await addProduct({
|
|
52
|
+
product: product.pk,
|
|
53
|
+
quantity: 1,
|
|
54
|
+
attributes: {}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
pushAddToCart(product);
|
|
58
|
+
setProductError(null);
|
|
59
|
+
return true;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const formattedError = formatError(error as AddToCartError);
|
|
62
|
+
setProductError(formattedError);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const clearProductError = () => {
|
|
68
|
+
setProductError(null);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
addProductToCart,
|
|
73
|
+
productError,
|
|
74
|
+
clearProductError,
|
|
75
|
+
isAddToCartLoading
|
|
76
|
+
};
|
|
77
|
+
};
|