@akinon/projectzero 1.107.0 → 1.108.0-rc.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +233 -4
- package/app-template/.env.example +1 -0
- package/app-template/CHANGELOG.md +5697 -1063
- package/app-template/README.md +25 -1
- package/app-template/package.json +21 -20
- package/app-template/public/locales/en/common.json +42 -1
- package/app-template/public/locales/tr/common.json +42 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +9 -82
- package/app-template/src/app/[commerce]/[locale]/[currency]/landing-page/[pk]/page.tsx +12 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +67 -0
- package/app-template/src/app/api/image-proxy/route.ts +1 -0
- package/app-template/src/app/api/similar-product-list/route.ts +1 -0
- package/app-template/src/app/api/similar-products/route.ts +1 -0
- package/app-template/src/assets/fonts/pz-icon.css +3 -0
- package/app-template/src/components/input.tsx +2 -1
- package/app-template/src/hooks/index.ts +2 -0
- package/app-template/src/settings.js +6 -1
- package/app-template/src/views/basket/basket-content.tsx +106 -0
- package/app-template/src/views/basket/basket-item.tsx +16 -13
- package/app-template/src/views/basket/summary.tsx +10 -7
- package/app-template/src/views/guest-login/index.tsx +6 -1
- package/app-template/src/views/header/search/index.tsx +17 -5
- package/app-template/src/views/product/slider.tsx +86 -73
- package/commands/plugins.ts +29 -2
- package/dist/commands/plugins.js +23 -2
- package/package.json +1 -1
|
@@ -18,6 +18,7 @@ import clsx from 'clsx';
|
|
|
18
18
|
|
|
19
19
|
interface Props {
|
|
20
20
|
basket: Basket;
|
|
21
|
+
onBasketUpdate?: (basket: Basket) => void;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const voucherCodeFormSchema = (t) =>
|
|
@@ -27,7 +28,7 @@ const voucherCodeFormSchema = (t) =>
|
|
|
27
28
|
|
|
28
29
|
export const Summary = (props: Props) => {
|
|
29
30
|
const { t } = useLocalization();
|
|
30
|
-
const { basket } = props;
|
|
31
|
+
const { basket, onBasketUpdate } = props;
|
|
31
32
|
const router = useRouter();
|
|
32
33
|
const {
|
|
33
34
|
register,
|
|
@@ -53,7 +54,7 @@ export const Summary = (props: Props) => {
|
|
|
53
54
|
const removeVoucherCode = () => {
|
|
54
55
|
removeVoucherCodeMutation()
|
|
55
56
|
.unwrap()
|
|
56
|
-
.then((basket) =>
|
|
57
|
+
.then((basket) => {
|
|
57
58
|
dispatch(
|
|
58
59
|
basketApi.util.updateQueryData(
|
|
59
60
|
'getBasket',
|
|
@@ -62,8 +63,9 @@ export const Summary = (props: Props) => {
|
|
|
62
63
|
Object.assign(draftBasket, basket);
|
|
63
64
|
}
|
|
64
65
|
)
|
|
65
|
-
)
|
|
66
|
-
|
|
66
|
+
);
|
|
67
|
+
onBasketUpdate?.(basket);
|
|
68
|
+
})
|
|
67
69
|
.catch((error: Error) => {
|
|
68
70
|
setError('voucherCode', { message: error.data.non_field_errors });
|
|
69
71
|
});
|
|
@@ -74,7 +76,7 @@ export const Summary = (props: Props) => {
|
|
|
74
76
|
voucher_code: data.voucherCode
|
|
75
77
|
})
|
|
76
78
|
.unwrap()
|
|
77
|
-
.then((basket) =>
|
|
79
|
+
.then((basket) => {
|
|
78
80
|
dispatch(
|
|
79
81
|
basketApi.util.updateQueryData(
|
|
80
82
|
'getBasket',
|
|
@@ -83,8 +85,9 @@ export const Summary = (props: Props) => {
|
|
|
83
85
|
Object.assign(draftBasket, basket);
|
|
84
86
|
}
|
|
85
87
|
)
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
+
);
|
|
89
|
+
onBasketUpdate?.(basket);
|
|
90
|
+
})
|
|
88
91
|
.catch((error: Error) => {
|
|
89
92
|
setError('voucherCode', { message: error.data.non_field_errors });
|
|
90
93
|
});
|
|
@@ -51,9 +51,14 @@ const GuestLogin = () => {
|
|
|
51
51
|
).unwrap();
|
|
52
52
|
|
|
53
53
|
Object.keys(response?.errors || {}).forEach((key) => {
|
|
54
|
+
const errorValue = response?.errors[key];
|
|
55
|
+
const message = Array.isArray(errorValue)
|
|
56
|
+
? errorValue.join(', ')
|
|
57
|
+
: errorValue || '';
|
|
58
|
+
|
|
54
59
|
setError(key as keyof GuestLoginFormParams, {
|
|
55
60
|
type: 'custom',
|
|
56
|
-
message
|
|
61
|
+
message
|
|
57
62
|
});
|
|
58
63
|
});
|
|
59
64
|
} catch (error) {
|
|
@@ -4,11 +4,11 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
4
4
|
import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
|
|
5
5
|
import { closeSearch } from '@akinon/next/redux/reducers/header';
|
|
6
6
|
import clsx from 'clsx';
|
|
7
|
-
|
|
8
|
-
import { Icon } from '@theme/components';
|
|
7
|
+
import { Icon, Input } from '@theme/components';
|
|
9
8
|
import Results from './results';
|
|
10
9
|
import { ROUTES } from '@theme/routes';
|
|
11
10
|
import { useLocalization, useRouter } from '@akinon/next/hooks';
|
|
11
|
+
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
12
12
|
|
|
13
13
|
export default function Search() {
|
|
14
14
|
const { t } = useLocalization();
|
|
@@ -41,6 +41,14 @@ export default function Search() {
|
|
|
41
41
|
};
|
|
42
42
|
}, [isSearchOpen, dispatch]);
|
|
43
43
|
|
|
44
|
+
const handleSearchTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
45
|
+
setSearchText(e.target.value);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handleCloseSearch = () => {
|
|
49
|
+
dispatch(closeSearch());
|
|
50
|
+
};
|
|
51
|
+
|
|
44
52
|
return (
|
|
45
53
|
<>
|
|
46
54
|
<div
|
|
@@ -66,9 +74,9 @@ export default function Search() {
|
|
|
66
74
|
{t('common.search.results_for')}
|
|
67
75
|
</span>
|
|
68
76
|
<div className="flex items-center">
|
|
69
|
-
<
|
|
77
|
+
<Input
|
|
70
78
|
value={searchText}
|
|
71
|
-
onChange={
|
|
79
|
+
onChange={handleSearchTextChange}
|
|
72
80
|
onKeyDown={(e) => {
|
|
73
81
|
if (e.key === 'Enter' && searchText.trim() !== '') {
|
|
74
82
|
router.push(`${ROUTES.LIST}/?search_text=${searchText}`);
|
|
@@ -78,14 +86,18 @@ export default function Search() {
|
|
|
78
86
|
placeholder={t('common.search.placeholder')}
|
|
79
87
|
ref={inputRef}
|
|
80
88
|
/>
|
|
89
|
+
|
|
90
|
+
<PluginModule component={Component.HeaderImageSearchFeature} />
|
|
91
|
+
|
|
81
92
|
<Icon
|
|
82
93
|
name="close"
|
|
83
94
|
size={14}
|
|
84
|
-
onClick={
|
|
95
|
+
onClick={handleCloseSearch}
|
|
85
96
|
className="cursor-pointer"
|
|
86
97
|
/>
|
|
87
98
|
</div>
|
|
88
99
|
</div>
|
|
100
|
+
|
|
89
101
|
<Results searchText={searchText} />
|
|
90
102
|
</div>
|
|
91
103
|
</div>
|
|
@@ -7,6 +7,7 @@ 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';
|
|
10
11
|
|
|
11
12
|
type ProductSliderItem = {
|
|
12
13
|
product: Product;
|
|
@@ -35,90 +36,102 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
|
|
|
35
36
|
carouselRef.current?.next();
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
const handleThumbnailClick = (index) => {
|
|
39
|
+
const handleThumbnailClick = (index: number) => {
|
|
39
40
|
setActiveIndex(index);
|
|
40
41
|
carouselRef.current?.goToSlide(index);
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
return (
|
|
44
|
-
|
|
45
|
-
<div className="lg:
|
|
46
|
-
<div className="
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
<>
|
|
46
|
+
<div className="lg:grid lg:grid-cols-6">
|
|
47
|
+
<div className="lg:col-span-1">
|
|
48
|
+
<div className="flex flex-col items-center justify-center md:mr-[6px]">
|
|
49
|
+
<button
|
|
50
|
+
onClick={goToPrev}
|
|
51
|
+
className={twMerge(
|
|
52
|
+
'hidden justify-center p-2 mb-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
53
|
+
[activeIndex === 0 && 'cursor-not-allowed opacity-45']
|
|
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 }}
|
|
54
117
|
>
|
|
55
|
-
|
|
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) => (
|
|
118
|
+
{product?.productimage_set?.map((item, i) => (
|
|
59
119
|
<Image
|
|
60
|
-
key={
|
|
120
|
+
key={i}
|
|
61
121
|
src={item.image}
|
|
62
|
-
alt={
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
122
|
+
alt={product?.name || 'Product image'}
|
|
123
|
+
draggable={false}
|
|
124
|
+
aspectRatio={484 / 726}
|
|
125
|
+
sizes="(min-width: 425px) 512px,
|
|
126
|
+
(min-width: 601px) 576px,
|
|
127
|
+
(min-width: 768px) 336px,
|
|
128
|
+
(min-width: 1024px) 484px, 368px"
|
|
129
|
+
fill
|
|
69
130
|
/>
|
|
70
131
|
))}
|
|
71
|
-
</
|
|
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>
|
|
132
|
+
</CarouselCore>
|
|
85
133
|
</div>
|
|
86
134
|
</div>
|
|
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>
|
|
135
|
+
</>
|
|
123
136
|
);
|
|
124
137
|
}
|
package/commands/plugins.ts
CHANGED
|
@@ -29,7 +29,14 @@ async function checkVersion(pkg: PackageJson) {
|
|
|
29
29
|
|
|
30
30
|
if (!semver.satisfies(pkg.dependencies['@akinon/next'], latestVersion)) {
|
|
31
31
|
console.warn(
|
|
32
|
-
`\x1b[
|
|
32
|
+
`\x1b[43m Warning: The "${packageName}" package is currently at`,
|
|
33
|
+
`\x1b[41m version ${pkg.dependencies['@akinon/next']}`,
|
|
34
|
+
`\x1b[43m Please upgrade it to the latest version (${latestVersion}) to ensure plugin compatibility.`,
|
|
35
|
+
'\x1b[0m\n'
|
|
36
|
+
);
|
|
37
|
+
} else {
|
|
38
|
+
console.log(
|
|
39
|
+
`\x1b[42m Info: The package "${packageName}" is currently in the current version (${latestVersion}).`,
|
|
33
40
|
'\x1b[0m\n'
|
|
34
41
|
);
|
|
35
42
|
}
|
|
@@ -54,7 +61,27 @@ export default async () => {
|
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
|
|
57
|
-
|
|
64
|
+
function findPackageJson(): PackageJson {
|
|
65
|
+
const packageJsonPaths = [
|
|
66
|
+
path.resolve(rootDir, './package.json'),
|
|
67
|
+
path.resolve(rootDir, './apps/projectzeronext/package.json')
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
71
|
+
try {
|
|
72
|
+
const pkg = require(packageJsonPath);
|
|
73
|
+
if (pkg.dependencies['@akinon/next']) {
|
|
74
|
+
return pkg;
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
throw new Error('Could not find package.json with @akinon/next dependency');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const pkg = findPackageJson();
|
|
58
85
|
await checkVersion(pkg);
|
|
59
86
|
|
|
60
87
|
const pluginsFilePath = findPluginsFilePath();
|
package/dist/commands/plugins.js
CHANGED
|
@@ -50,7 +50,10 @@ function checkVersion(pkg) {
|
|
|
50
50
|
const pkgInfo = (yield response.json());
|
|
51
51
|
const latestVersion = pkgInfo['dist-tags'].latest;
|
|
52
52
|
if (!semver_1.default.satisfies(pkg.dependencies['@akinon/next'], latestVersion)) {
|
|
53
|
-
console.warn(`\x1b[
|
|
53
|
+
console.warn(`\x1b[43m Warning: The "${packageName}" package is currently at`, `\x1b[41m version ${pkg.dependencies['@akinon/next']}`, `\x1b[43m Please upgrade it to the latest version (${latestVersion}) to ensure plugin compatibility.`, '\x1b[0m\n');
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log(`\x1b[42m Info: The package "${packageName}" is currently in the current version (${latestVersion}).`, '\x1b[0m\n');
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
catch (error) {
|
|
@@ -72,7 +75,25 @@ exports.default = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
72
75
|
throw new Error('plugins.js was not found in either of the expected locations.');
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
|
-
|
|
78
|
+
function findPackageJson() {
|
|
79
|
+
const packageJsonPaths = [
|
|
80
|
+
path_1.default.resolve(rootDir, './package.json'),
|
|
81
|
+
path_1.default.resolve(rootDir, './apps/projectzeronext/package.json')
|
|
82
|
+
];
|
|
83
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
84
|
+
try {
|
|
85
|
+
const pkg = require(packageJsonPath);
|
|
86
|
+
if (pkg.dependencies['@akinon/next']) {
|
|
87
|
+
return pkg;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
throw new Error('Could not find package.json with @akinon/next dependency');
|
|
95
|
+
}
|
|
96
|
+
const pkg = findPackageJson();
|
|
76
97
|
yield checkVersion(pkg);
|
|
77
98
|
const pluginsFilePath = findPluginsFilePath();
|
|
78
99
|
let installedPlugins = [];
|