@akinon/projectzero 1.48.0-rc.5 → 1.48.0
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 +1 -59
- package/README.md +2 -3
- package/app-template/.gitignore +0 -2
- package/app-template/.lintstagedrc.js +4 -5
- package/app-template/CHANGELOG.md +48 -1406
- package/app-template/docs/basic-setup.md +1 -1
- package/app-template/docs/plugins.md +7 -7
- package/app-template/package.json +20 -22
- package/app-template/public/locales/en/account.json +4 -4
- package/app-template/public/locales/tr/account.json +1 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/account/address/page.tsx +1 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/account/coupons/page.tsx +4 -4
- package/app-template/src/app/[commerce]/[locale]/[currency]/account/profile/page.tsx +0 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +2 -5
- package/app-template/src/app/[commerce]/[locale]/[currency]/orders/completed/[token]/page.tsx +8 -12
- package/app-template/src/components/checkbox.tsx +2 -2
- package/app-template/src/components/input.tsx +7 -19
- package/app-template/src/components/pagination.tsx +18 -13
- package/app-template/src/components/price.tsx +4 -9
- package/app-template/src/redux/reducers/category.ts +1 -7
- package/app-template/src/settings.js +1 -6
- package/app-template/src/views/account/address-card.tsx +2 -2
- package/app-template/src/views/account/address-form.tsx +7 -22
- package/app-template/src/views/account/contact-form.tsx +6 -23
- package/app-template/src/views/account/favorite-item.tsx +2 -2
- package/app-template/src/views/account/favourite-products/favourite-products-list.tsx +1 -5
- package/app-template/src/views/breadcrumb.tsx +1 -4
- package/app-template/src/views/category/category-active-filters.tsx +6 -16
- package/app-template/src/views/category/category-info.tsx +17 -31
- package/app-template/src/views/category/filters/index.tsx +108 -16
- package/app-template/src/views/category/layout.tsx +3 -5
- package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +4 -33
- package/app-template/src/views/checkout/steps/payment/options/redirection.tsx +37 -43
- package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +3 -19
- package/app-template/src/views/checkout/steps/shipping/address-box.tsx +2 -2
- package/app-template/src/views/checkout/steps/shipping/addresses.tsx +1 -1
- package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +37 -230
- package/app-template/src/views/find-in-store/index.tsx +3 -2
- package/app-template/src/views/header/mobile-menu.tsx +8 -25
- package/app-template/src/views/product/product-info.tsx +2 -0
- package/app-template/tsconfig.json +4 -14
- package/app-template/yarn.lock +1953 -1824
- package/commands/create.ts +5 -29
- package/dist/commands/create.js +2 -25
- package/package.json +2 -2
- package/app-template/package-lock.json +0 -29303
- package/app-template/src/app/[commerce]/[locale]/[currency]/[...prettyurl]/page.tsx +0 -8
- package/app-template/src/views/category/filters/filter-item.tsx +0 -163
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
Button,
|
|
4
4
|
FileInput,
|
|
5
5
|
Input,
|
|
6
|
-
Link,
|
|
7
6
|
LoaderSpinner,
|
|
8
|
-
Select
|
|
7
|
+
Select,
|
|
8
|
+
Link
|
|
9
9
|
} from '@theme/components';
|
|
10
10
|
import { useSession } from 'next-auth/react';
|
|
11
11
|
import { useEffect, useState } from 'react';
|
|
@@ -45,8 +45,7 @@ const contactFormSchema = (t) =>
|
|
|
45
45
|
.when('subject', {
|
|
46
46
|
is: (value) => value === '2',
|
|
47
47
|
then: yup.string().required(t('account.contact.form.error.required'))
|
|
48
|
-
})
|
|
49
|
-
file: yup.mixed()
|
|
48
|
+
})
|
|
50
49
|
});
|
|
51
50
|
|
|
52
51
|
const ContactForm = () => {
|
|
@@ -111,18 +110,8 @@ const ContactForm = () => {
|
|
|
111
110
|
resolver: yupResolver(contactFormSchema(t))
|
|
112
111
|
});
|
|
113
112
|
|
|
114
|
-
const onSubmit: SubmitHandler<ContactFormType> = (data
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Object.keys(data ?? {}).forEach((key) => {
|
|
118
|
-
if (key === 'file' && data[key]) {
|
|
119
|
-
formData.append(key, data[key][0]);
|
|
120
|
-
} else if (data[key]) {
|
|
121
|
-
formData.append(key, data[key]);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
sendContact(formData);
|
|
113
|
+
const onSubmit: SubmitHandler<ContactFormType> = (data) => {
|
|
114
|
+
sendContact(data);
|
|
126
115
|
};
|
|
127
116
|
|
|
128
117
|
const handleChange = (e) => {
|
|
@@ -245,7 +234,6 @@ const ContactForm = () => {
|
|
|
245
234
|
className="border-gray-500 border w-full text-xs p-2.5 focus-visible:outline-none focus:border-black hover:border-black"
|
|
246
235
|
rows={7}
|
|
247
236
|
name="message"
|
|
248
|
-
// @ts-expect-error -- awaiting fix:
|
|
249
237
|
{...register('message')}
|
|
250
238
|
/>
|
|
251
239
|
{errors.message && (
|
|
@@ -254,12 +242,7 @@ const ContactForm = () => {
|
|
|
254
242
|
<label className="text-xs text-gray-800 mb-2 block">
|
|
255
243
|
{t('account.contact.form.file.title')}
|
|
256
244
|
</label>
|
|
257
|
-
<FileInput
|
|
258
|
-
name="file"
|
|
259
|
-
title="file"
|
|
260
|
-
className="w-full mb-5"
|
|
261
|
-
{...register('file')}
|
|
262
|
-
/>
|
|
245
|
+
<FileInput className="w-full mb-5" title="test" />
|
|
263
246
|
<Button type="submit" className="w-full font-medium">
|
|
264
247
|
{t('account.contact.form.submit_button')}
|
|
265
248
|
</Button>
|
|
@@ -93,8 +93,8 @@ export const FavoriteItem = (props: Props) => {
|
|
|
93
93
|
className={clsx(
|
|
94
94
|
'absolute top-4 right-4 cursor-pointer',
|
|
95
95
|
isRemoveFavoriteLoading
|
|
96
|
-
? 'hover:cursor-wait'
|
|
97
|
-
: '
|
|
96
|
+
? 'pointer-events-none hover:cursor-wait' // TODO: Cursors not working fix!
|
|
97
|
+
: 'hove:cursor-pointer'
|
|
98
98
|
)}
|
|
99
99
|
data-testid="favorites-remove"
|
|
100
100
|
/>
|
|
@@ -30,11 +30,7 @@ const FavoriteProductsList = () => {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
if (isLoading || isFetching) {
|
|
33
|
-
return
|
|
34
|
-
<div className="flex items-center justify-center h-80">
|
|
35
|
-
<LoaderSpinner />
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
33
|
+
return <LoaderSpinner />; // TODO: Fix loader spinner position
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
return (
|
|
@@ -5,7 +5,6 @@ import { Icon, Link } from '@theme/components';
|
|
|
5
5
|
import { ROUTES } from '@theme/routes';
|
|
6
6
|
import { useLocalization } from '@akinon/next/hooks';
|
|
7
7
|
import { BreadcrumbResultType } from '@akinon/next/types';
|
|
8
|
-
import { capitalize } from '@akinon/next/utils';
|
|
9
8
|
|
|
10
9
|
export interface BreadcrumbProps {
|
|
11
10
|
breadcrumbList?: BreadcrumbResultType[];
|
|
@@ -27,9 +26,7 @@ export default function Breadcrumb(props: BreadcrumbProps) {
|
|
|
27
26
|
<div className="flex items-center gap-3 text-xs leading-4 text-gray-950">
|
|
28
27
|
{list.map((item, index) => (
|
|
29
28
|
<Fragment key={index}>
|
|
30
|
-
<Link href={item.url}>
|
|
31
|
-
{capitalize(item.text.toLocaleLowerCase())}
|
|
32
|
-
</Link>
|
|
29
|
+
<Link href={item.url}>{item.text}</Link>
|
|
33
30
|
{index !== list.length - 1 && <Icon name="chevron-end" size={8} />}
|
|
34
31
|
</Fragment>
|
|
35
32
|
))}
|
|
@@ -22,28 +22,17 @@ const CategoryActiveFilters = () => {
|
|
|
22
22
|
const handleRemoveFilter = ({ facet, choice }) => {
|
|
23
23
|
if (facet.widget_type === WIDGET_TYPE.category) {
|
|
24
24
|
dispatch(removeCategoryFacet({ facet, choice }));
|
|
25
|
-
|
|
26
|
-
dispatch(toggleFacet({ facet, choice }));
|
|
25
|
+
return;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
urlSearchParams.delete(facet.search_key);
|
|
31
|
-
router.replace(pathname + '?' + urlSearchParams.toString());
|
|
28
|
+
dispatch(toggleFacet({ facet, choice }));
|
|
32
29
|
};
|
|
33
30
|
|
|
34
31
|
const url = useMemo(() => {
|
|
35
|
-
const facetSearchParams =
|
|
36
|
-
|
|
32
|
+
const facetSearchParams =
|
|
33
|
+
convertFacetSearchParams(selectedFacets).toString();
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
if (facetSearchParams.has(key)) {
|
|
40
|
-
urlSearchParams.delete(key);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
for (const [key, value] of Array.from(facetSearchParams.entries())) {
|
|
45
|
-
urlSearchParams.append(key, value);
|
|
46
|
-
}
|
|
35
|
+
const urlSearchParams = new URLSearchParams(facetSearchParams);
|
|
47
36
|
|
|
48
37
|
const searchText = searchParams.get('search_text');
|
|
49
38
|
const page = searchParams.get('page');
|
|
@@ -64,6 +53,7 @@ const CategoryActiveFilters = () => {
|
|
|
64
53
|
|
|
65
54
|
useEffect(() => {
|
|
66
55
|
router.push(url);
|
|
56
|
+
|
|
67
57
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
68
58
|
}, [url]);
|
|
69
59
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useEffect, useMemo } from 'react';
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
5
|
import { useSearchParams } from 'next/navigation';
|
|
6
6
|
import { CategoryHeader } from './category-header';
|
|
@@ -8,14 +8,13 @@ import { Filters } from './filters';
|
|
|
8
8
|
import { Pagination } from '@theme/components';
|
|
9
9
|
import { ProductItem } from '@theme/views/product-item';
|
|
10
10
|
import { GetCategoryResponse } from '@akinon/next/types';
|
|
11
|
-
import { useAppDispatch
|
|
12
|
-
import { setFacets
|
|
11
|
+
import { useAppDispatch } from '@akinon/next/redux/hooks';
|
|
12
|
+
import { setFacets } from '@theme/redux/reducers/category';
|
|
13
13
|
import CategoryActiveFilters from '@theme/views/category/category-active-filters';
|
|
14
14
|
import { useLocalization } from '@akinon/next/hooks';
|
|
15
15
|
import { Link, LoaderSpinner } from '@akinon/next/components';
|
|
16
16
|
import { ROUTES } from '@theme/routes';
|
|
17
17
|
import { useRouter } from '@akinon/next/hooks';
|
|
18
|
-
import { RootState } from '@theme/redux/store';
|
|
19
18
|
|
|
20
19
|
interface ListPageProps {
|
|
21
20
|
data: GetCategoryResponse;
|
|
@@ -23,16 +22,13 @@ interface ListPageProps {
|
|
|
23
22
|
|
|
24
23
|
export default function ListPage(props: ListPageProps) {
|
|
25
24
|
const { data } = props;
|
|
26
|
-
const
|
|
27
|
-
const isMenuOpen = useAppSelector(
|
|
28
|
-
(state: RootState) => state.category.isMenuOpen
|
|
29
|
-
);
|
|
25
|
+
const [isMenuOpen, setIsMenuOpen] = useState(false); // TODO: Move to redux
|
|
30
26
|
|
|
31
27
|
const searchParams = useSearchParams();
|
|
32
28
|
const router = useRouter();
|
|
33
29
|
|
|
34
30
|
const layoutSize = useMemo(
|
|
35
|
-
() =>
|
|
31
|
+
() => searchParams.get('layout') ?? 3,
|
|
36
32
|
[searchParams]
|
|
37
33
|
);
|
|
38
34
|
|
|
@@ -41,24 +37,16 @@ export default function ListPage(props: ListPageProps) {
|
|
|
41
37
|
[searchParams]
|
|
42
38
|
);
|
|
43
39
|
|
|
44
|
-
const itemDimensions = useMemo(() => {
|
|
45
|
-
switch (layoutSize) {
|
|
46
|
-
case 2:
|
|
47
|
-
return { width: 510, height: 765 };
|
|
48
|
-
case 3:
|
|
49
|
-
default:
|
|
50
|
-
return { width: 340, height: 510 };
|
|
51
|
-
}
|
|
52
|
-
}, [layoutSize]);
|
|
53
|
-
|
|
54
40
|
useEffect(() => {
|
|
55
41
|
if (page > 1 && data.products?.length === 0) {
|
|
56
42
|
const newUrl = new URL(window.location.href);
|
|
43
|
+
|
|
57
44
|
newUrl.searchParams.delete('page');
|
|
58
45
|
router.push(newUrl.pathname + newUrl.search, undefined);
|
|
59
46
|
}
|
|
60
|
-
}, [searchParams, data.products, page]);
|
|
47
|
+
}, [searchParams, data.products, page]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
61
48
|
|
|
49
|
+
const dispatch = useAppDispatch();
|
|
62
50
|
const { t } = useLocalization();
|
|
63
51
|
|
|
64
52
|
useEffect(() => {
|
|
@@ -70,12 +58,9 @@ export default function ListPage(props: ListPageProps) {
|
|
|
70
58
|
<>
|
|
71
59
|
<div className="container px-4 mx-auto lg:px-0 lg:my-4">
|
|
72
60
|
<div className="grid grid-cols-[19rem_1fr]">
|
|
73
|
-
<Filters
|
|
74
|
-
isMenuOpen={isMenuOpen}
|
|
75
|
-
setIsMenuOpen={(open) => dispatch(setMenuOpen(open))}
|
|
76
|
-
/>
|
|
61
|
+
<Filters isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} />
|
|
77
62
|
<div
|
|
78
|
-
onClick={() =>
|
|
63
|
+
onClick={() => setIsMenuOpen(false)}
|
|
79
64
|
className={clsx(
|
|
80
65
|
'transition-opacity duration-300 ease-linear lg:hidden',
|
|
81
66
|
isMenuOpen
|
|
@@ -86,7 +71,7 @@ export default function ListPage(props: ListPageProps) {
|
|
|
86
71
|
<div className="flex flex-col items-center lg:items-stretch col-span-2 lg:col-span-1">
|
|
87
72
|
<CategoryHeader
|
|
88
73
|
totalCount={data.pagination?.total_count}
|
|
89
|
-
setMenuStatus={() =>
|
|
74
|
+
setMenuStatus={() => setIsMenuOpen(true)}
|
|
90
75
|
sortOptions={data.sorters}
|
|
91
76
|
/>
|
|
92
77
|
<div className="hidden lg:block">
|
|
@@ -106,17 +91,18 @@ export default function ListPage(props: ListPageProps) {
|
|
|
106
91
|
|
|
107
92
|
<div
|
|
108
93
|
className={clsx('grid gap-x-4 gap-y-12 grid-cols-2', {
|
|
109
|
-
'md:grid-cols-3': layoutSize === 3,
|
|
110
|
-
'lg:grid-cols-2': layoutSize === 2,
|
|
111
|
-
'lg:grid-cols-3': layoutSize === 3
|
|
94
|
+
'md:grid-cols-3': Number(layoutSize) === 3,
|
|
95
|
+
'lg:grid-cols-2': Number(layoutSize) === 2,
|
|
96
|
+
'lg:grid-cols-3': Number(layoutSize) === 3
|
|
112
97
|
})}
|
|
113
98
|
>
|
|
114
99
|
{data.products.map((product, index) => (
|
|
115
100
|
<ProductItem
|
|
116
101
|
key={product.pk}
|
|
117
102
|
product={product}
|
|
118
|
-
|
|
119
|
-
|
|
103
|
+
// TODO: Find a better way to handle layout size
|
|
104
|
+
width={340}
|
|
105
|
+
height={510}
|
|
120
106
|
index={index}
|
|
121
107
|
/>
|
|
122
108
|
))}
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { WIDGET_TYPE } from '@theme/types';
|
|
3
4
|
import clsx from 'clsx';
|
|
4
|
-
|
|
5
|
-
import {
|
|
5
|
+
|
|
6
|
+
import { Accordion, Button, Checkbox, Icon, Radio } from '@theme/components';
|
|
7
|
+
import { SizeFilter } from './size-filter';
|
|
8
|
+
|
|
9
|
+
import { useLocalization, useRouter } from '@akinon/next/hooks';
|
|
10
|
+
import { Facet, FacetChoice } from '@akinon/next/types';
|
|
6
11
|
import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
|
|
7
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
resetSelectedFacets,
|
|
14
|
+
toggleFacet
|
|
15
|
+
} from '@theme/redux/reducers/category';
|
|
8
16
|
import CategoryActiveFilters from '@theme/views/category/category-active-filters';
|
|
9
|
-
import { useMemo
|
|
10
|
-
import {
|
|
17
|
+
import { useMemo } from 'react';
|
|
18
|
+
import { commonProductAttributes } from '@theme/settings';
|
|
19
|
+
|
|
20
|
+
const COMPONENT_TYPES = {
|
|
21
|
+
[WIDGET_TYPE.category]: Radio,
|
|
22
|
+
[WIDGET_TYPE.multiselect]: Checkbox
|
|
23
|
+
};
|
|
11
24
|
|
|
12
25
|
interface Props {
|
|
13
26
|
isMenuOpen: boolean;
|
|
@@ -15,16 +28,37 @@ interface Props {
|
|
|
15
28
|
}
|
|
16
29
|
|
|
17
30
|
export const Filters = (props: Props) => {
|
|
31
|
+
const router = useRouter();
|
|
18
32
|
const facets = useAppSelector((state) => state.category.facets);
|
|
19
33
|
const dispatch = useAppDispatch();
|
|
20
34
|
const { t } = useLocalization();
|
|
21
35
|
const { isMenuOpen, setIsMenuOpen } = props;
|
|
22
36
|
|
|
23
|
-
const
|
|
37
|
+
const handleSelectFilter = ({
|
|
38
|
+
facet,
|
|
39
|
+
choice
|
|
40
|
+
}: {
|
|
41
|
+
facet: Facet;
|
|
42
|
+
choice: FacetChoice;
|
|
43
|
+
}) => {
|
|
44
|
+
if (facet.key === 'category_ids') {
|
|
45
|
+
router.push(choice.url);
|
|
46
|
+
} else {
|
|
47
|
+
dispatch(
|
|
48
|
+
toggleFacet({
|
|
49
|
+
facet,
|
|
50
|
+
choice
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
24
55
|
|
|
25
56
|
const haveFilter = useMemo(() => {
|
|
26
|
-
return
|
|
27
|
-
|
|
57
|
+
return (
|
|
58
|
+
facets.filter(
|
|
59
|
+
(facet) =>
|
|
60
|
+
facet.data.choices.filter((choice) => choice.is_selected).length > 0
|
|
61
|
+
).length > 0
|
|
28
62
|
);
|
|
29
63
|
}, [facets]);
|
|
30
64
|
|
|
@@ -32,6 +66,10 @@ export const Filters = (props: Props) => {
|
|
|
32
66
|
dispatch(resetSelectedFacets());
|
|
33
67
|
};
|
|
34
68
|
|
|
69
|
+
const sizeKey = commonProductAttributes.find(
|
|
70
|
+
(item) => item.translationKey === 'size'
|
|
71
|
+
).key;
|
|
72
|
+
|
|
35
73
|
return (
|
|
36
74
|
<div
|
|
37
75
|
className={clsx(
|
|
@@ -49,22 +87,76 @@ export const Filters = (props: Props) => {
|
|
|
49
87
|
<span className="text-sm">1 {t('category.filters.results')}</span>
|
|
50
88
|
<span>{t('category.filters.ready_to_wear')}</span>
|
|
51
89
|
</div>
|
|
52
|
-
|
|
53
90
|
{facets.map((facet) => {
|
|
91
|
+
let Component = null;
|
|
92
|
+
const choices = [...facet.data.choices];
|
|
93
|
+
|
|
94
|
+
if (facet.key === sizeKey) {
|
|
95
|
+
// If it's a size facet, use the custom size filter component
|
|
96
|
+
Component = SizeFilter;
|
|
97
|
+
|
|
98
|
+
const order = ['xs', 's', 'm', 'l', 'xl'];
|
|
99
|
+
choices.sort((a, b) => {
|
|
100
|
+
return (
|
|
101
|
+
order.indexOf(a.label.toLowerCase()) -
|
|
102
|
+
order.indexOf(b.label.toLowerCase())
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
Component =
|
|
107
|
+
COMPONENT_TYPES[facet.widget_type] ||
|
|
108
|
+
COMPONENT_TYPES[WIDGET_TYPE.category];
|
|
109
|
+
}
|
|
110
|
+
|
|
54
111
|
return (
|
|
55
|
-
<
|
|
112
|
+
<Accordion
|
|
56
113
|
key={facet.key}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
114
|
+
title={facet.name}
|
|
115
|
+
isCollapse={choices.some((choice) => choice.is_selected)}
|
|
116
|
+
dataTestId={`filter-${facet.name}`}
|
|
117
|
+
>
|
|
118
|
+
<div
|
|
119
|
+
className={clsx(
|
|
120
|
+
'flex gap-4 flex-wrap',
|
|
121
|
+
facet.key === sizeKey ? 'flex-row' : 'flex-col' // TODO: This condition must be refactor to a better way
|
|
122
|
+
)}
|
|
123
|
+
>
|
|
124
|
+
{choices.map((choice, index) => (
|
|
125
|
+
<Component // TODO: This dynamic component can be a hook or higher order component so it props can be standardized
|
|
126
|
+
key={choice.label}
|
|
127
|
+
data={choice}
|
|
128
|
+
name={facet.key}
|
|
129
|
+
onChange={() => {
|
|
130
|
+
if (facet.key !== sizeKey) {
|
|
131
|
+
// TODO: This condition must be refactor to a better way
|
|
132
|
+
handleSelectFilter({ facet, choice });
|
|
133
|
+
}
|
|
134
|
+
}}
|
|
135
|
+
onClick={() => {
|
|
136
|
+
if (facet.key === sizeKey) {
|
|
137
|
+
// TODO: This condition must be refactor to a better way
|
|
138
|
+
handleSelectFilter({ facet, choice });
|
|
139
|
+
}
|
|
140
|
+
}}
|
|
141
|
+
checked={choice.is_selected}
|
|
142
|
+
data-testid={`${choice.label.trim()}`}
|
|
143
|
+
>
|
|
144
|
+
{choice.label} (
|
|
145
|
+
<span
|
|
146
|
+
data-testid={`filter-count-${facet.name.toLowerCase()}-${index}`}
|
|
147
|
+
>
|
|
148
|
+
{choice.quantity}
|
|
149
|
+
</span>
|
|
150
|
+
)
|
|
151
|
+
</Component>
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
</Accordion>
|
|
61
155
|
);
|
|
62
156
|
})}
|
|
63
|
-
|
|
64
157
|
<div className="lg:hidden">
|
|
65
158
|
<CategoryActiveFilters />
|
|
66
159
|
</div>
|
|
67
|
-
|
|
68
160
|
{haveFilter && (
|
|
69
161
|
<div className="lg:hidden">
|
|
70
162
|
<Button
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import {
|
|
2
|
+
import { GetCategoryResponse } from '@akinon/next/types';
|
|
3
3
|
import Breadcrumb from '@theme/views/breadcrumb';
|
|
4
4
|
import { CategoryBanner } from '@theme/views/category/category-banner';
|
|
5
5
|
import ListPage from '@theme/views/category/category-info';
|
|
6
6
|
|
|
7
7
|
export default async function Layout({
|
|
8
8
|
data,
|
|
9
|
-
children
|
|
10
|
-
breadcrumbData
|
|
9
|
+
children
|
|
11
10
|
}: {
|
|
12
11
|
data: GetCategoryResponse;
|
|
13
12
|
children?: React.ReactNode;
|
|
14
|
-
breadcrumbData?: BreadcrumbResultType[];
|
|
15
13
|
}) {
|
|
16
14
|
return (
|
|
17
15
|
<>
|
|
@@ -30,7 +28,7 @@ export default async function Layout({
|
|
|
30
28
|
'lg:absolute lg:inset-x-0 z-10 container lg:my-4 mx-auto'
|
|
31
29
|
)}
|
|
32
30
|
>
|
|
33
|
-
<Breadcrumb
|
|
31
|
+
<Breadcrumb />
|
|
34
32
|
</div>
|
|
35
33
|
<CategoryBanner {...data.category?.attributes?.category_banner} />
|
|
36
34
|
</div>
|
|
@@ -23,29 +23,13 @@ import { PaymentOption } from '@akinon/next/types';
|
|
|
23
23
|
const creditCardFormSchema = (
|
|
24
24
|
t,
|
|
25
25
|
payment_option: PaymentOption,
|
|
26
|
-
isMasterpassDirectPurchase?: boolean
|
|
27
|
-
isMasterpassCvcRequired?: boolean
|
|
26
|
+
isMasterpassDirectPurchase?: boolean
|
|
28
27
|
) => {
|
|
29
28
|
if (
|
|
30
29
|
payment_option?.payment_type === 'masterpass' &&
|
|
31
30
|
isMasterpassDirectPurchase === false
|
|
32
31
|
) {
|
|
33
32
|
return yup.object().shape({
|
|
34
|
-
card_cvv: yup
|
|
35
|
-
.string()
|
|
36
|
-
.transform((value: string) => value.replace(/_/g, '').replace(/ /g, ''))
|
|
37
|
-
.when('*', (_, schema) => {
|
|
38
|
-
if (isMasterpassCvcRequired) {
|
|
39
|
-
return schema
|
|
40
|
-
.length(
|
|
41
|
-
3,
|
|
42
|
-
t('checkout.payment.credit_card.form.error.cvv_length')
|
|
43
|
-
)
|
|
44
|
-
.required(t('checkout.payment.credit_card.form.error.required'));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return schema;
|
|
48
|
-
}),
|
|
49
33
|
agreement: yup
|
|
50
34
|
.boolean()
|
|
51
35
|
.oneOf([true], t('checkout.payment.credit_card.form.error.required'))
|
|
@@ -103,16 +87,10 @@ const CheckoutCreditCard = () => {
|
|
|
103
87
|
control,
|
|
104
88
|
formState: { errors },
|
|
105
89
|
setError,
|
|
106
|
-
getValues
|
|
107
|
-
clearErrors
|
|
90
|
+
getValues
|
|
108
91
|
} = useForm<CreditCardForm>({
|
|
109
92
|
resolver: yupResolver(
|
|
110
|
-
creditCardFormSchema(
|
|
111
|
-
t,
|
|
112
|
-
payment_option,
|
|
113
|
-
masterpass?.isDirectPurchase,
|
|
114
|
-
masterpass?.cvcRequired
|
|
115
|
-
)
|
|
93
|
+
creditCardFormSchema(t, payment_option, masterpass?.isDirectPurchase)
|
|
116
94
|
)
|
|
117
95
|
});
|
|
118
96
|
const [months, setMonths] = useState([]);
|
|
@@ -339,14 +317,7 @@ const CheckoutCreditCard = () => {
|
|
|
339
317
|
<PluginModule
|
|
340
318
|
component={Component.MasterpassCardList}
|
|
341
319
|
props={{
|
|
342
|
-
className: 'p-10'
|
|
343
|
-
form: {
|
|
344
|
-
control,
|
|
345
|
-
register,
|
|
346
|
-
errors,
|
|
347
|
-
setFormValue,
|
|
348
|
-
clearErrors
|
|
349
|
-
}
|
|
320
|
+
className: 'p-10'
|
|
350
321
|
}}
|
|
351
322
|
/>
|
|
352
323
|
|
|
@@ -9,7 +9,6 @@ import { twMerge } from 'tailwind-merge';
|
|
|
9
9
|
import * as yup from 'yup';
|
|
10
10
|
import { useEffect, useState } from 'react';
|
|
11
11
|
import { getPosError } from '@akinon/next/utils';
|
|
12
|
-
import { useMessageListener } from '@akinon/next/hooks';
|
|
13
12
|
|
|
14
13
|
interface FormValues {
|
|
15
14
|
agreement: boolean;
|
|
@@ -26,6 +25,7 @@ const formSchema = () =>
|
|
|
26
25
|
export default function RedirectionPayment() {
|
|
27
26
|
const { payment_option } = useAppSelector((state) => state.checkout.preOrder);
|
|
28
27
|
const [formError, setFormError] = useState(null);
|
|
28
|
+
|
|
29
29
|
const {
|
|
30
30
|
register,
|
|
31
31
|
handleSubmit,
|
|
@@ -34,12 +34,11 @@ export default function RedirectionPayment() {
|
|
|
34
34
|
resolver: yupResolver(formSchema())
|
|
35
35
|
});
|
|
36
36
|
const [completeRedirectionPayment] = useCompleteRedirectionPaymentMutation();
|
|
37
|
+
|
|
37
38
|
const onSubmit = async () => {
|
|
38
39
|
completeRedirectionPayment();
|
|
39
40
|
};
|
|
40
41
|
|
|
41
|
-
useMessageListener();
|
|
42
|
-
|
|
43
42
|
useEffect(() => {
|
|
44
43
|
const posErrors = getPosError();
|
|
45
44
|
|
|
@@ -49,49 +48,44 @@ export default function RedirectionPayment() {
|
|
|
49
48
|
}, []);
|
|
50
49
|
|
|
51
50
|
return (
|
|
52
|
-
<
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
>
|
|
57
|
-
<h1 className="text-2xl font-bold px-4 md:px-0">
|
|
58
|
-
Pay With {payment_option.name}
|
|
59
|
-
</h1>
|
|
51
|
+
<form onSubmit={handleSubmit(onSubmit)} className="lg-5 space-y-5 lg:p-10">
|
|
52
|
+
<h1 className="text-2xl font-bold px-4 md:px-0">
|
|
53
|
+
Pay With {payment_option.name}
|
|
54
|
+
</h1>
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
<p className="px-4 md:px-0">
|
|
57
|
+
You can quickly and easily pay and complete your order with{' '}
|
|
58
|
+
{payment_option.name}.
|
|
59
|
+
</p>
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
61
|
+
<Checkbox
|
|
62
|
+
className="px-4 md:px-0"
|
|
63
|
+
{...register('agreement')}
|
|
64
|
+
error={errors.agreement}
|
|
65
|
+
>
|
|
66
|
+
Check here to indicate that you have read and agree to the all terms.
|
|
67
|
+
</Checkbox>
|
|
73
68
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
69
|
+
{formError?.non_field_errors && (
|
|
70
|
+
<div
|
|
71
|
+
className="w-full text-xs text-start px-1 mt-3 text-error"
|
|
72
|
+
data-testid="checkout-form-error"
|
|
73
|
+
>
|
|
74
|
+
{formError.non_field_errors}
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
{formError?.status && (
|
|
78
|
+
<div
|
|
79
|
+
className="w-full text-xs text-start px-1 mt-3 text-error"
|
|
80
|
+
data-testid="checkout-form-error"
|
|
81
|
+
>
|
|
82
|
+
{formError.status}
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
90
85
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
</div>
|
|
86
|
+
<Button className={twMerge('w-full md:w-36 px-4 md:px-0')}>
|
|
87
|
+
{payment_option.name}
|
|
88
|
+
</Button>
|
|
89
|
+
</form>
|
|
96
90
|
);
|
|
97
91
|
}
|