@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.
- package/CHANGELOG.md +9 -0
- package/app-template/.gitignore +2 -0
- package/app-template/CHANGELOG.md +2655 -124
- package/app-template/package.json +18 -18
- package/app-template/public/locales/en/common.json +4 -0
- package/app-template/public/locales/tr/common.json +4 -0
- package/app-template/src/app/[commerce]/[locale]/[currency]/account/orders/[id]/cancellation/page.tsx +94 -5
- package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +9 -82
- package/app-template/src/app/[commerce]/[locale]/[currency]/orders/checkout/page.tsx +7 -4
- package/app-template/src/app/[commerce]/[locale]/[currency]/page.tsx +8 -0
- package/app-template/src/components/button.tsx +50 -35
- package/app-template/src/components/file-input.tsx +44 -2
- package/app-template/src/components/types/index.ts +4 -1
- package/app-template/src/components/widget/widget-placeholder.tsx +12 -0
- package/app-template/src/middleware.ts +1 -0
- package/app-template/src/settings.js +6 -1
- package/app-template/src/views/account/address-form.tsx +2 -2
- package/app-template/src/views/account/contact-form.tsx +3 -8
- package/app-template/src/views/account/orders/order-cancellation-item.tsx +4 -3
- 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/login/index.tsx +28 -4
- package/app-template/src/views/register/index.tsx +30 -5
- package/package.json +1 -1
- package/app-template/sentry.edge.config.ts +0 -3
- package/app-template/sentry.server.config.ts +0 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projectzeronext",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.78.0-rc.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"scripts": {
|
|
@@ -22,25 +22,25 @@
|
|
|
22
22
|
"prestart": "pz-prestart"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@akinon/next": "1.
|
|
26
|
-
"@akinon/pz-akifast": "1.
|
|
27
|
-
"@akinon/pz-b2b": "1.
|
|
28
|
-
"@akinon/pz-basket-gift-pack": "1.
|
|
29
|
-
"@akinon/pz-bkm": "1.
|
|
30
|
-
"@akinon/pz-checkout-gift-pack": "1.
|
|
31
|
-
"@akinon/pz-click-collect": "1.
|
|
32
|
-
"@akinon/pz-credit-payment": "1.
|
|
33
|
-
"@akinon/pz-gpay": "1.
|
|
34
|
-
"@akinon/pz-masterpass": "1.
|
|
35
|
-
"@akinon/pz-one-click-checkout": "1.
|
|
36
|
-
"@akinon/pz-otp": "1.
|
|
37
|
-
"@akinon/pz-pay-on-delivery": "1.
|
|
38
|
-
"@akinon/pz-saved-card": "1.
|
|
39
|
-
"@akinon/pz-tabby-extension": "1.
|
|
25
|
+
"@akinon/next": "1.78.0-rc.1",
|
|
26
|
+
"@akinon/pz-akifast": "1.78.0-rc.1",
|
|
27
|
+
"@akinon/pz-b2b": "1.78.0-rc.1",
|
|
28
|
+
"@akinon/pz-basket-gift-pack": "1.78.0-rc.1",
|
|
29
|
+
"@akinon/pz-bkm": "1.78.0-rc.1",
|
|
30
|
+
"@akinon/pz-checkout-gift-pack": "1.78.0-rc.1",
|
|
31
|
+
"@akinon/pz-click-collect": "1.78.0-rc.1",
|
|
32
|
+
"@akinon/pz-credit-payment": "1.78.0-rc.1",
|
|
33
|
+
"@akinon/pz-gpay": "1.78.0-rc.1",
|
|
34
|
+
"@akinon/pz-masterpass": "1.78.0-rc.1",
|
|
35
|
+
"@akinon/pz-one-click-checkout": "1.78.0-rc.1",
|
|
36
|
+
"@akinon/pz-otp": "1.78.0-rc.1",
|
|
37
|
+
"@akinon/pz-pay-on-delivery": "1.78.0-rc.1",
|
|
38
|
+
"@akinon/pz-saved-card": "1.78.0-rc.1",
|
|
39
|
+
"@akinon/pz-tabby-extension": "1.78.0-rc.1",
|
|
40
40
|
"@hookform/resolvers": "2.9.0",
|
|
41
41
|
"@next/third-parties": "14.1.0",
|
|
42
42
|
"@react-google-maps/api": "2.17.1",
|
|
43
|
-
"@sentry/nextjs": "
|
|
43
|
+
"@sentry/nextjs": "8.35.0",
|
|
44
44
|
"dayjs": "1.11.5",
|
|
45
45
|
"lossless-json": "2.0.5",
|
|
46
46
|
"next": "14.2.5",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"yup": "0.32.11"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@akinon/eslint-plugin-projectzero": "1.
|
|
64
|
+
"@akinon/eslint-plugin-projectzero": "1.78.0-rc.1",
|
|
65
65
|
"@semantic-release/changelog": "6.0.2",
|
|
66
66
|
"@semantic-release/exec": "6.0.3",
|
|
67
67
|
"@semantic-release/git": "10.0.1",
|
|
@@ -8,14 +8,15 @@ import {
|
|
|
8
8
|
useGetOrderQuery,
|
|
9
9
|
useGetCancellationReasonsQuery
|
|
10
10
|
} from '@akinon/next/data/client/account';
|
|
11
|
-
import { AccountOrderCancellation } from '@akinon/next/types';
|
|
11
|
+
import type { AccountOrderCancellation } from '@akinon/next/types';
|
|
12
12
|
import {
|
|
13
13
|
Button,
|
|
14
14
|
Checkbox,
|
|
15
15
|
Select,
|
|
16
16
|
Modal,
|
|
17
17
|
LoaderSpinner,
|
|
18
|
-
Link
|
|
18
|
+
Link,
|
|
19
|
+
FileInput
|
|
19
20
|
} from '@theme/components';
|
|
20
21
|
import { useState } from 'react';
|
|
21
22
|
import { OrderDetailHeader } from '@theme/views/account/orders/order-detail-header';
|
|
@@ -39,6 +40,7 @@ const AccountOrderCancellation = ({ params }) => {
|
|
|
39
40
|
} = useForm<AccountOrderCancellation>({
|
|
40
41
|
resolver: yupResolver(accountOrderCancellationSchema)
|
|
41
42
|
});
|
|
43
|
+
|
|
42
44
|
const { data: cancellationReasons, isSuccess: cancellationReasonsSuccess } =
|
|
43
45
|
useGetCancellationReasonsQuery();
|
|
44
46
|
|
|
@@ -56,6 +58,9 @@ const AccountOrderCancellation = ({ params }) => {
|
|
|
56
58
|
const watchAllFields = watch();
|
|
57
59
|
const cancelItemsLength = watchAllFields?.cancel_order_items?.length;
|
|
58
60
|
const [cancelOrder] = useCancelOrderMutation();
|
|
61
|
+
const [files, setFiles] = useState<
|
|
62
|
+
{ itemId: string; image: string; description: string }[]
|
|
63
|
+
>([]);
|
|
59
64
|
|
|
60
65
|
const modalHandleClick = () => {
|
|
61
66
|
setIsModalOpen(false);
|
|
@@ -112,8 +117,68 @@ const AccountOrderCancellation = ({ params }) => {
|
|
|
112
117
|
]);
|
|
113
118
|
};
|
|
114
119
|
|
|
120
|
+
const handleFileChange = async (
|
|
121
|
+
e: React.ChangeEvent<HTMLInputElement>,
|
|
122
|
+
itemId: string,
|
|
123
|
+
description: string
|
|
124
|
+
) => {
|
|
125
|
+
const selectedFiles = Array.from(e.target.files || []);
|
|
126
|
+
|
|
127
|
+
const base64Files = await Promise.all(
|
|
128
|
+
selectedFiles.map((file) => {
|
|
129
|
+
return new Promise<string>((resolve, reject) => {
|
|
130
|
+
const reader = new FileReader();
|
|
131
|
+
reader.onload = () => resolve(reader.result as string);
|
|
132
|
+
reader.onerror = reject;
|
|
133
|
+
reader.readAsDataURL(file);
|
|
134
|
+
});
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const validFiles = base64Files.filter((file) =>
|
|
139
|
+
/^data:image\/(jpeg|png|jpg);base64,.+/.test(file)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const formattedFiles = validFiles.map((file) => ({
|
|
143
|
+
itemId,
|
|
144
|
+
image: file,
|
|
145
|
+
description
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
setFiles((prevFiles) => [
|
|
149
|
+
...prevFiles.filter((f) => f.itemId !== itemId),
|
|
150
|
+
...formattedFiles
|
|
151
|
+
]);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const fileInputCondition = (item, description: string) => {
|
|
155
|
+
if (item.is_refundable && !item.active_cancellation_request) {
|
|
156
|
+
return (
|
|
157
|
+
<FileInput
|
|
158
|
+
name="files"
|
|
159
|
+
title="files"
|
|
160
|
+
multiple
|
|
161
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
162
|
+
handleFileChange(e, item.id, description)
|
|
163
|
+
}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
115
169
|
const onSubmit: SubmitHandler<AccountOrderCancellation> = (orderItems) => {
|
|
116
|
-
|
|
170
|
+
const mergedData = {
|
|
171
|
+
...orderItems,
|
|
172
|
+
cancel_order_items: orderItems.cancel_order_items.map((orderItem) => ({
|
|
173
|
+
...orderItem,
|
|
174
|
+
...(files.length > 0 && {
|
|
175
|
+
cancellation_request_image_set: files.filter(
|
|
176
|
+
(file) => file.itemId === orderItem.order_item
|
|
177
|
+
)
|
|
178
|
+
})
|
|
179
|
+
}))
|
|
180
|
+
};
|
|
181
|
+
cancelOrder({ id: order.number, ...mergedData })
|
|
117
182
|
.unwrap()
|
|
118
183
|
.then(() => {
|
|
119
184
|
setResponseMessage({
|
|
@@ -122,10 +187,33 @@ const AccountOrderCancellation = ({ params }) => {
|
|
|
122
187
|
});
|
|
123
188
|
setIsModalOpen(true);
|
|
124
189
|
})
|
|
125
|
-
.catch(() => {
|
|
190
|
+
.catch((err) => {
|
|
191
|
+
console.error('Err', err);
|
|
192
|
+
|
|
193
|
+
const errorMessages = new Set();
|
|
194
|
+
|
|
195
|
+
if (err?.data?.cancel_order_items) {
|
|
196
|
+
err.data.cancel_order_items.forEach((item) => {
|
|
197
|
+
if (item.cancellation_request_image_set) {
|
|
198
|
+
item.cancellation_request_image_set.forEach((error) => {
|
|
199
|
+
if (typeof error === 'string') {
|
|
200
|
+
errorMessages.add(error);
|
|
201
|
+
} else if (typeof error === 'object' && error?.image) {
|
|
202
|
+
error.image.forEach((msg) => errorMessages.add(msg));
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const errorContent =
|
|
210
|
+
errorMessages.size > 0
|
|
211
|
+
? Array.from(errorMessages).join('\n')
|
|
212
|
+
: t('account.my_orders.return.error.description').toString();
|
|
213
|
+
|
|
126
214
|
setResponseMessage({
|
|
127
215
|
title: t('account.my_orders.return.error.title').toString(),
|
|
128
|
-
content:
|
|
216
|
+
content: errorContent
|
|
129
217
|
});
|
|
130
218
|
setIsModalOpen(true);
|
|
131
219
|
});
|
|
@@ -180,6 +268,7 @@ const AccountOrderCancellation = ({ params }) => {
|
|
|
180
268
|
onChange={onChange}
|
|
181
269
|
value={value}
|
|
182
270
|
selectOption={selectOption}
|
|
271
|
+
fileInput={fileInputCondition(item, item.product.name)}
|
|
183
272
|
/>
|
|
184
273
|
);
|
|
185
274
|
}}
|
|
@@ -1,87 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { useEffect } from 'react';
|
|
4
|
-
import { ROUTES } from '@theme/routes';
|
|
5
|
-
import { useGetBasketQuery } from '@akinon/next/data/client/basket';
|
|
6
|
-
import { pushCartView } from '@theme/utils/gtm';
|
|
7
|
-
import { Button, LoaderSpinner, Link } from '@theme/components';
|
|
8
|
-
import { BasketItem, Summary } from '@theme/views/basket';
|
|
9
|
-
import { useLocalization } from '@akinon/next/hooks';
|
|
10
|
-
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
1
|
+
import { BasketContent } from '@theme/views/basket/basket-content';
|
|
2
|
+
import { getBasketData } from '@akinon/next/data/server/basket';
|
|
11
3
|
import settings from '@theme/settings';
|
|
12
4
|
|
|
13
|
-
export default function Page() {
|
|
14
|
-
const {
|
|
15
|
-
const { t } = useLocalization();
|
|
16
|
-
const multiBasket = settings.plugins?.multiBasket ?? false;
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (isSuccess) {
|
|
20
|
-
const products = basket.basketitem_set.map((basketItem) => ({
|
|
21
|
-
...basketItem.product
|
|
22
|
-
}));
|
|
23
|
-
pushCartView(products);
|
|
24
|
-
}
|
|
25
|
-
}, [basket, isSuccess]);
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div className="max-w-screen-xl p-4 flex flex-col text-primary-800 lg:p-8 xl:flex-row xl:mx-auto">
|
|
29
|
-
{isLoading && (
|
|
30
|
-
<div className="flex justify-center w-full">
|
|
31
|
-
<LoaderSpinner />
|
|
32
|
-
</div>
|
|
33
|
-
)}
|
|
34
|
-
{isSuccess &&
|
|
35
|
-
(basket && basket.basketitem_set && basket.basketitem_set.length > 0 ? (
|
|
36
|
-
<>
|
|
37
|
-
<div className="flex-1 xl:mr-16">
|
|
38
|
-
<div className="flex items-center justify-between py-2 border-b border-gray-200 lg:py-3">
|
|
39
|
-
<h2 className="text-xl lg:text-2xl font-light">
|
|
40
|
-
{t('basket.my_cart')}
|
|
41
|
-
</h2>
|
|
42
|
-
<Link
|
|
43
|
-
href={ROUTES.HOME}
|
|
44
|
-
className="text-xs hover:text-secondary-500"
|
|
45
|
-
>
|
|
46
|
-
{t('basket.back_to_shopping')}
|
|
47
|
-
</Link>
|
|
48
|
-
</div>
|
|
49
|
-
<ul>
|
|
50
|
-
{multiBasket ? (
|
|
51
|
-
<PluginModule
|
|
52
|
-
component={Component.MultiBasket}
|
|
53
|
-
props={{ BasketItem }}
|
|
54
|
-
/>
|
|
55
|
-
) : (
|
|
56
|
-
basket.basketitem_set.map((basketItem, index) => (
|
|
57
|
-
<BasketItem basketItem={basketItem} key={index} />
|
|
58
|
-
))
|
|
59
|
-
)}
|
|
60
|
-
</ul>
|
|
61
|
-
</div>
|
|
62
|
-
<Summary basket={basket} />
|
|
63
|
-
</>
|
|
64
|
-
) : (
|
|
65
|
-
<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">
|
|
66
|
-
<h1
|
|
67
|
-
className="w-full text-xl font-light text-secondary text-center sm:text-2xl"
|
|
68
|
-
data-testid="basket-empty"
|
|
69
|
-
>
|
|
70
|
-
{t('basket.empty.title')}
|
|
71
|
-
</h1>
|
|
5
|
+
export default async function Page() {
|
|
6
|
+
const { basket } = await getBasketData();
|
|
72
7
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
8
|
+
const multiBasket: boolean =
|
|
9
|
+
typeof settings.plugins?.multiBasket === 'boolean'
|
|
10
|
+
? settings.plugins.multiBasket
|
|
11
|
+
: false;
|
|
77
12
|
|
|
78
|
-
|
|
79
|
-
<Button className="px-10 mt-2" appearance="filled">
|
|
80
|
-
{t('basket.empty.button')}
|
|
81
|
-
</Button>
|
|
82
|
-
</Link>
|
|
83
|
-
</div>
|
|
84
|
-
))}
|
|
85
|
-
</div>
|
|
86
|
-
);
|
|
13
|
+
return <BasketContent initialBasket={basket} multiBasket={multiBasket} />;
|
|
87
14
|
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from '@akinon/next/redux/reducers/checkout';
|
|
12
12
|
import { RootState } from '@theme/redux/store';
|
|
13
13
|
import { ROUTES } from '@theme/routes';
|
|
14
|
-
import { useFetchCheckoutQuery } from '@akinon/next/data/client/checkout';
|
|
14
|
+
import { useFetchCheckoutQuery, useResetCheckoutStateQuery } from '@akinon/next/data/client/checkout';
|
|
15
15
|
import { Button, LoaderSpinner } from '@theme/components';
|
|
16
16
|
import { pushAddPaymentInfo, pushAddShippingInfo } from '@theme/utils/gtm';
|
|
17
17
|
import { CheckoutStep } from '@akinon/next/types';
|
|
@@ -25,6 +25,8 @@ const Checkout = () => {
|
|
|
25
25
|
(state: RootState) => state.checkout
|
|
26
26
|
);
|
|
27
27
|
|
|
28
|
+
const { data: indexData, isLoading: isResetStateLoading } = useResetCheckoutStateQuery(null);
|
|
29
|
+
|
|
28
30
|
const {
|
|
29
31
|
data: checkoutData,
|
|
30
32
|
isFetching,
|
|
@@ -32,7 +34,8 @@ const Checkout = () => {
|
|
|
32
34
|
isSuccess,
|
|
33
35
|
refetch: refetchCheckout
|
|
34
36
|
} = useFetchCheckoutQuery(null, {
|
|
35
|
-
refetchOnMountOrArgChange: true
|
|
37
|
+
refetchOnMountOrArgChange: true,
|
|
38
|
+
skip: isResetStateLoading || !indexData
|
|
36
39
|
});
|
|
37
40
|
const initialStepChanged = useRef<boolean>(false);
|
|
38
41
|
const router = useRouter();
|
|
@@ -94,10 +97,10 @@ const Checkout = () => {
|
|
|
94
97
|
);
|
|
95
98
|
}
|
|
96
99
|
|
|
97
|
-
if (isFetching || isError) {
|
|
100
|
+
if (isResetStateLoading || isFetching || isError) {
|
|
98
101
|
return (
|
|
99
102
|
<div className="flex flex-col items-center justify-center h-80">
|
|
100
|
-
{isFetching ? (
|
|
103
|
+
{isResetStateLoading || isFetching ? (
|
|
101
104
|
<LoaderSpinner />
|
|
102
105
|
) : (
|
|
103
106
|
<>
|
|
@@ -4,6 +4,7 @@ import { HOME_WIDGETS } from '@theme/widgets';
|
|
|
4
4
|
import { getWidgetData } from '@akinon/next/data/server';
|
|
5
5
|
import { withSegmentDefaults } from '@akinon/next/hocs/server';
|
|
6
6
|
import LazyComponent from '@akinon/next/components/lazy-component';
|
|
7
|
+
import WidgetPlaceholder from '@theme/components/widget/widget-placeholder';
|
|
7
8
|
|
|
8
9
|
type HomeWidgetOrderType = {
|
|
9
10
|
widget_order: Array<{
|
|
@@ -16,6 +17,13 @@ export const dynamic = 'force-static';
|
|
|
16
17
|
export const revalidate = 600;
|
|
17
18
|
|
|
18
19
|
async function Page() {
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<WidgetPlaceholder slug="home-widget-order" />
|
|
23
|
+
<WidgetPlaceholder slug="home-widget-order-2" />
|
|
24
|
+
</>
|
|
25
|
+
);
|
|
26
|
+
|
|
19
27
|
const data = await getWidgetData<HomeWidgetOrderType>({
|
|
20
28
|
slug: 'home-widget-order'
|
|
21
29
|
});
|
|
@@ -1,46 +1,61 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { Link } from '@theme/components';
|
|
3
4
|
import { ButtonProps } from '@theme/components/types';
|
|
4
5
|
import clsx from 'clsx';
|
|
5
6
|
import { twMerge } from 'tailwind-merge';
|
|
6
7
|
|
|
7
8
|
export const Button = (props: ButtonProps) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
9
|
+
const {
|
|
10
|
+
appearance = 'filled',
|
|
11
|
+
size = 'md',
|
|
12
|
+
href,
|
|
13
|
+
target,
|
|
14
|
+
children,
|
|
15
|
+
className,
|
|
16
|
+
...rest
|
|
17
|
+
} = props;
|
|
18
|
+
|
|
19
|
+
const variants = {
|
|
20
|
+
filled:
|
|
21
|
+
'bg-primary text-primary-foreground border border-primary hover:bg-white hover:border-primary hover:text-primary',
|
|
22
|
+
outlined:
|
|
23
|
+
'bg-transparent text-primary hover:bg-primary hover:text-primary-foreground',
|
|
24
|
+
ghost:
|
|
25
|
+
'bg-transparent border-transparent text-primary hover:bg-primary hover:text-primary-foreground',
|
|
26
|
+
link: 'px-0 h-auto underline underline-offset-2'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const sizes = {
|
|
30
|
+
sm: 'h-8',
|
|
31
|
+
md: 'h-10',
|
|
32
|
+
lg: 'h-12',
|
|
33
|
+
xl: 'h-14'
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const buttonClasses = twMerge(
|
|
37
|
+
clsx(
|
|
38
|
+
'px-4 text-xs transition-all duration-200',
|
|
39
|
+
'inline-flex gap-2 justify-center items-center',
|
|
40
|
+
variants[appearance],
|
|
41
|
+
sizes[size],
|
|
42
|
+
className
|
|
43
|
+
),
|
|
44
|
+
className
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return props.href ? (
|
|
48
|
+
<Link
|
|
49
|
+
prefetch={false}
|
|
50
|
+
target={target}
|
|
51
|
+
href={href}
|
|
52
|
+
className={buttonClasses}
|
|
42
53
|
>
|
|
43
|
-
{
|
|
54
|
+
{children}
|
|
55
|
+
</Link>
|
|
56
|
+
) : (
|
|
57
|
+
<button {...rest} className={buttonClasses}>
|
|
58
|
+
{children}
|
|
44
59
|
</button>
|
|
45
60
|
);
|
|
46
61
|
};
|
|
@@ -1,8 +1,50 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
1
2
|
import { forwardRef } from 'react';
|
|
2
3
|
import { FileInputProps } from '@theme/components/types';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { useLocalization } from '@akinon/next/hooks';
|
|
3
6
|
|
|
4
7
|
export const FileInput = forwardRef<HTMLInputElement, FileInputProps>(
|
|
5
|
-
function
|
|
6
|
-
|
|
8
|
+
function FileInput({ className, onChange, ...props }, ref) {
|
|
9
|
+
const { t } = useLocalization();
|
|
10
|
+
const [fileNames, setFileNames] = useState<string[]>([]);
|
|
11
|
+
|
|
12
|
+
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
13
|
+
const files = Array.from(event.target.files || []);
|
|
14
|
+
setFileNames(files.map((file) => file.name));
|
|
15
|
+
|
|
16
|
+
if (onChange) {
|
|
17
|
+
onChange(event);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="relative">
|
|
23
|
+
<input
|
|
24
|
+
type="file"
|
|
25
|
+
{...props}
|
|
26
|
+
ref={ref}
|
|
27
|
+
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
|
28
|
+
onChange={handleFileChange}
|
|
29
|
+
/>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
className={clsx('bg-primary text-white py-2 px-4 text-sm', className)}
|
|
33
|
+
>
|
|
34
|
+
{t('common.file_input.select_file')}
|
|
35
|
+
</button>
|
|
36
|
+
<div className="mt-1 text-gray-500">
|
|
37
|
+
{fileNames.length > 0 ? (
|
|
38
|
+
<ul className="list-disc pl-4 text-xs">
|
|
39
|
+
{fileNames.map((name, index) => (
|
|
40
|
+
<li key={index}>{name}</li>
|
|
41
|
+
))}
|
|
42
|
+
</ul>
|
|
43
|
+
) : (
|
|
44
|
+
<span className="text-xs">{t('common.file_input.no_file')}</span>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
7
49
|
}
|
|
8
50
|
);
|
|
@@ -4,7 +4,10 @@ import { UsePaginationType } from '@akinon/next/hooks/use-pagination';
|
|
|
4
4
|
|
|
5
5
|
export interface ButtonProps
|
|
6
6
|
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
7
|
-
appearance?: 'filled' | 'outlined' | 'ghost';
|
|
7
|
+
appearance?: 'filled' | 'outlined' | 'ghost' | 'link' | string;
|
|
8
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
9
|
+
href?: string;
|
|
10
|
+
target?: '_blank' | '_self' | '_parent' | '_top';
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export interface PaginationProps {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import { DynamicWidgetContainer } from '../dynamic-widget-renderer';
|
|
3
|
+
|
|
4
|
+
export default async function WidgetPlaceholder({ slug }: { slug: string }) {
|
|
5
|
+
return (
|
|
6
|
+
<>
|
|
7
|
+
<DynamicWidgetContainer />
|
|
8
|
+
</>
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
return <div>WidgetPlaceholder</div>;
|
|
12
|
+
}
|
|
@@ -9,6 +9,7 @@ import { NextMiddleware, NextResponse } from 'next/server';
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
export const config = {
|
|
12
|
+
// For .xml.gz type sitemap urls, update '/(.*sitemap\\.xml)' with this regex '/(.*sitemap\\.xml|sitemap/.*.xml(?:.gz)?)/',
|
|
12
13
|
matcher: [
|
|
13
14
|
'/((?!api|_next|[\\w-\\/*]+\\.\\w+).*)',
|
|
14
15
|
'/(.*sitemap\\.xml)',
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
const { LocaleUrlStrategy } = require('@akinon/next/localization');
|
|
2
2
|
const { ROUTES } = require('@theme/routes');
|
|
3
3
|
|
|
4
|
+
/* IMPORTANT *
|
|
5
|
+
* In order to use one locale in the locales array and hide the default locale in the URL, you need to set the localeUrlStrategy to LocaleUrlStrategy.ShowAllLocales.
|
|
6
|
+
*/
|
|
7
|
+
|
|
4
8
|
const commerceUrl = encodeURI(process.env.SERVICE_BACKEND_URL ?? 'default');
|
|
5
9
|
|
|
6
10
|
/** @type {import('@akinon/next/types').Settings} */
|
|
@@ -14,6 +18,7 @@ module.exports = {
|
|
|
14
18
|
{ translationKey: 'size', key: 'size' }
|
|
15
19
|
],
|
|
16
20
|
localization: {
|
|
21
|
+
// If there is one locale in the locales array, the default locale will be hidden in the URL.
|
|
17
22
|
locales: [
|
|
18
23
|
{
|
|
19
24
|
label: 'EN',
|
|
@@ -41,7 +46,7 @@ module.exports = {
|
|
|
41
46
|
}
|
|
42
47
|
],
|
|
43
48
|
defaultLocaleValue: 'en',
|
|
44
|
-
localeUrlStrategy: LocaleUrlStrategy.HideDefaultLocale,
|
|
49
|
+
localeUrlStrategy: LocaleUrlStrategy.HideDefaultLocale, // If there is one locale in the locales array, the default locale will be hidden in the URL and localeUrlStrategy should be set to LocaleUrlStrategy.ShowAllLocales.
|
|
45
50
|
redirectToDefaultLocale: true,
|
|
46
51
|
defaultCurrencyCode: 'usd'
|
|
47
52
|
},
|
|
@@ -247,7 +247,7 @@ export const AddressForm = (props: Props) => {
|
|
|
247
247
|
/>
|
|
248
248
|
<Input
|
|
249
249
|
label={t('account.address_book.form.phone.placeholder')}
|
|
250
|
-
format={config.user_phone_format.replaceAll(
|
|
250
|
+
format={config.user_phone_format.replaceAll(/9/g, '#')}
|
|
251
251
|
mask="_"
|
|
252
252
|
allowEmptyFormatting={true}
|
|
253
253
|
control={control}
|
|
@@ -339,7 +339,7 @@ export const AddressForm = (props: Props) => {
|
|
|
339
339
|
error={errors.postcode}
|
|
340
340
|
data-testid="address-form-post-code"
|
|
341
341
|
required
|
|
342
|
-
format={config.user_post_code_format.replaceAll(
|
|
342
|
+
format={config.user_post_code_format.replaceAll(/9/g, '#')}
|
|
343
343
|
control={control}
|
|
344
344
|
mask="_"
|
|
345
345
|
allowEmptyFormatting
|
|
@@ -192,7 +192,7 @@ const ContactForm = () => {
|
|
|
192
192
|
label={t('account.contact.form.phone.placeholder')}
|
|
193
193
|
type="tel"
|
|
194
194
|
className="mb-1"
|
|
195
|
-
format={user_phone_format.replace(
|
|
195
|
+
format={user_phone_format.replace(/9/g, '#')}
|
|
196
196
|
mask="_"
|
|
197
197
|
allowEmptyFormatting={true}
|
|
198
198
|
control={control}
|
|
@@ -255,13 +255,8 @@ const ContactForm = () => {
|
|
|
255
255
|
<label className="text-xs text-gray-800 mb-2 block">
|
|
256
256
|
{t('account.contact.form.file.title')}
|
|
257
257
|
</label>
|
|
258
|
-
<FileInput
|
|
259
|
-
|
|
260
|
-
title="file"
|
|
261
|
-
className="w-full mb-5"
|
|
262
|
-
{...register('file')}
|
|
263
|
-
/>
|
|
264
|
-
<Button type="submit" className="w-full font-medium">
|
|
258
|
+
<FileInput name="file" title="file" {...register('file')} />
|
|
259
|
+
<Button type="submit" className="w-full font-medium mt-4">
|
|
265
260
|
{t('account.contact.form.submit_button')}
|
|
266
261
|
</Button>
|
|
267
262
|
</form>
|