@akinon/projectzero 2.0.0-beta.20 → 2.0.0-beta.22
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 +14 -0
- package/app-template/CHANGELOG.md +170 -0
- package/app-template/next.config.mjs +0 -1
- package/app-template/package.json +31 -30
- package/app-template/src/app/[pz]/[...prettyurl]/page.tsx +2 -2
- package/app-template/src/app/[pz]/account/layout.tsx +2 -1
- package/app-template/src/app/{[commerce]/[locale]/[currency] → [pz]}/blog/[slug]/page.tsx +4 -2
- package/app-template/src/app/[pz]/category/[pk]/page.tsx +11 -1
- package/app-template/src/app/[pz]/group-product/[pk]/page.tsx +2 -2
- package/app-template/src/app/[pz]/layout.tsx +3 -1
- package/app-template/src/app/[pz]/list/page.tsx +11 -1
- package/app-template/src/app/[pz]/page.tsx +13 -35
- package/app-template/src/app/[pz]/pages/[slug]/page.tsx +19 -0
- package/app-template/src/app/[pz]/product/[pk]/page.tsx +2 -2
- package/app-template/src/app/api/barcode-search/route.ts +1 -1
- package/app-template/src/app/api/cache/route.ts +1 -1
- package/app-template/src/app/api/image-proxy/route.ts +1 -1
- package/app-template/src/app/api/logout/route.ts +1 -1
- package/app-template/src/app/api/product-categories/route.ts +1 -1
- package/app-template/src/app/api/similar-product-list/route.ts +1 -1
- package/app-template/src/app/api/similar-products/route.ts +1 -1
- package/app-template/src/app/api/virtual-try-on/route.ts +1 -1
- package/app-template/src/app/api/web-vitals/route.ts +1 -1
- package/app-template/src/components/quantity-selector.tsx +16 -4
- package/app-template/src/components/widget-content.tsx +3 -3
- package/app-template/src/routes/index.ts +6 -6
- package/app-template/src/utils/__tests__/theme-page-context.test.ts +145 -0
- package/app-template/src/utils/theme-page-context.ts +309 -0
- package/app-template/src/views/basket/basket-item.tsx +107 -691
- package/app-template/src/views/basket/index.ts +0 -2
- package/app-template/src/views/basket/summary.tsx +75 -496
- package/app-template/src/views/breadcrumb.tsx +38 -13
- package/app-template/src/views/category/category-header.tsx +66 -289
- package/app-template/src/views/category/category-info.tsx +24 -173
- package/app-template/src/views/category/filters/index.tsx +48 -208
- package/app-template/src/views/category/layout.tsx +5 -7
- package/app-template/src/views/checkout/index.tsx +0 -5
- package/app-template/src/views/checkout/steps/payment/index.tsx +2 -5
- package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +1 -72
- package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +40 -171
- package/app-template/src/views/checkout/steps/shipping/address-box.tsx +12 -74
- package/app-template/src/views/checkout/steps/shipping/addresses.tsx +45 -128
- package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +27 -232
- package/app-template/src/views/checkout/summary.tsx +29 -303
- package/app-template/src/views/footer.tsx +13 -415
- package/app-template/src/views/guest-login/index.tsx +1 -1
- package/app-template/src/views/header/action-menu.tsx +45 -277
- package/app-template/src/views/header/band.tsx +21 -6
- package/app-template/src/views/header/index.tsx +47 -109
- package/app-template/src/views/header/mini-basket.tsx +45 -820
- package/app-template/src/views/header/navbar.tsx +111 -178
- package/app-template/src/views/header/search/index.tsx +32 -71
- package/app-template/src/views/header/search/results.tsx +65 -127
- package/app-template/src/views/product/accordion-wrapper.tsx +43 -135
- package/app-template/src/views/product/index.ts +1 -1
- package/app-template/src/views/product/layout.tsx +7 -2
- package/app-template/src/views/product/misc-buttons.tsx +25 -339
- package/app-template/src/views/product/product-actions.tsx +8 -137
- package/app-template/src/views/product/product-info.tsx +31 -69
- package/app-template/src/views/product/product-share.tsx +8 -11
- package/app-template/src/views/product/slider.tsx +79 -117
- package/app-template/src/views/product-item/index.tsx +46 -119
- package/app-template/src/widgets/footer-social.tsx +16 -47
- package/app-template/src/widgets/footer-subscription/index.tsx +17 -183
- package/codemods/migrate-auth-v5/index.js +339 -0
- package/codemods/migrate-auth-v5/transform.js +86 -0
- package/dist/commands/plugins.js +23 -2
- package/package.json +1 -1
- package/app-template/src/app/[commerce]/[locale]/[currency]/pages/[slug]/page.tsx +0 -15
- package/app-template/src/views/basket/basket-summary-context.tsx +0 -560
- package/app-template/src/views/basket/designer-context.tsx +0 -617
- package/app-template/src/views/breadcrumb/breadcrumb-client.tsx +0 -190
- package/app-template/src/views/breadcrumb/breadcrumb-registrar.tsx +0 -286
- package/app-template/src/views/breadcrumb/constants.ts +0 -15
- package/app-template/src/views/breadcrumb/index.tsx +0 -127
- package/app-template/src/views/category/native-widget-context.tsx +0 -257
- package/app-template/src/views/category/product-list-registrar.tsx +0 -665
- package/app-template/src/views/checkout/checkout-address-registrar.tsx +0 -254
- package/app-template/src/views/checkout/checkout-buttons-registrar.tsx +0 -183
- package/app-template/src/views/checkout/checkout-delivery-method-registrar.tsx +0 -259
- package/app-template/src/views/checkout/checkout-payment-options-registrar.tsx +0 -253
- package/app-template/src/views/checkout/checkout-summary-registrar.tsx +0 -183
- package/app-template/src/views/checkout/constants.ts +0 -5
- package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +0 -15
- package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +0 -18
- package/app-template/src/views/footer/footer-app-banner-context.tsx +0 -326
- package/app-template/src/views/footer/footer-bottom-context.tsx +0 -215
- package/app-template/src/views/footer/footer-bottom-wrapper.tsx +0 -74
- package/app-template/src/views/footer/footer-layout-constants.ts +0 -35
- package/app-template/src/views/footer/footer-layout-registrar.tsx +0 -342
- package/app-template/src/views/footer/footer-layout-switcher.tsx +0 -110
- package/app-template/src/views/footer/footer-menu-context.tsx +0 -211
- package/app-template/src/views/footer/footer-native-widgets.tsx +0 -60
- package/app-template/src/views/footer/footer-social-context.tsx +0 -254
- package/app-template/src/views/footer/footer-subscription-context.tsx +0 -210
- package/app-template/src/views/footer/footer-utils.ts +0 -43
- package/app-template/src/views/footer/footer-value-props-context.tsx +0 -326
- package/app-template/src/views/footer/logo-settings.ts +0 -183
- package/app-template/src/views/footer/native-widget-config.ts +0 -262
- package/app-template/src/views/footer/subscription-settings.ts +0 -122
- package/app-template/src/views/footer/use-footer-logo.ts +0 -162
- package/app-template/src/views/header/designer-context.tsx +0 -261
- package/app-template/src/views/header/header-announcement-registrar.tsx +0 -267
- package/app-template/src/views/header/header-client-wrapper.tsx +0 -496
- package/app-template/src/views/header/header-content.tsx +0 -1026
- package/app-template/src/views/header/header-currency-registrar.tsx +0 -348
- package/app-template/src/views/header/header-icons-context.tsx +0 -262
- package/app-template/src/views/header/header-language-registrar.tsx +0 -348
- package/app-template/src/views/header/header-layout-context.tsx +0 -143
- package/app-template/src/views/header/header-layout-registrar.tsx +0 -658
- package/app-template/src/views/header/header-logo-context.tsx +0 -228
- package/app-template/src/views/header/header-logo.tsx +0 -118
- package/app-template/src/views/header/header-mini-basket-context.tsx +0 -524
- package/app-template/src/views/header/header-search-registrar.tsx +0 -511
- package/app-template/src/views/header/header-text-slider-registrar.tsx +0 -382
- package/app-template/src/views/header/inline-search.tsx +0 -262
- package/app-template/src/views/header/navbar-menu-context.tsx +0 -219
- package/app-template/src/views/header/search/search-input.tsx +0 -61
- package/app-template/src/views/header/server-settings-parser.ts +0 -1105
- package/app-template/src/views/header/use-header-icons.ts +0 -241
- package/app-template/src/views/header/use-header-logo.ts +0 -213
- package/app-template/src/views/header/use-navbar-menu.ts +0 -179
- package/app-template/src/views/product/accordion-section.tsx +0 -61
- package/app-template/src/views/product/custom-button-group.tsx +0 -69
- package/app-template/src/views/product/favorites-button-section.tsx +0 -69
- package/app-template/src/views/product/find-in-store-section.tsx +0 -60
- package/app-template/src/views/product/product-info-section.tsx +0 -140
- package/app-template/src/views/product/quantity-section.tsx +0 -73
- package/app-template/src/views/product/sale-tag.tsx +0 -10
- package/app-template/src/views/product/share-section.tsx +0 -357
- package/app-template/src/views/product/variants-section.tsx +0 -126
- package/app-template/src/views/product-detail/constants.ts +0 -272
- package/app-template/src/views/product-detail/index.ts +0 -10
- package/app-template/src/views/product-detail/product-detail-registrar.tsx +0 -616
- package/app-template/src/widgets/footer-app-banner.tsx +0 -444
- package/app-template/src/widgets/footer-bottom.tsx +0 -127
- package/app-template/src/widgets/footer-menu-compact.tsx +0 -238
- package/app-template/src/widgets/footer-menu-two.tsx +0 -298
- package/app-template/src/widgets/footer-social-client.tsx +0 -251
- package/app-template/src/widgets/footer-value-props.tsx +0 -201
|
@@ -1,64 +1,24 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import clsx from 'clsx';
|
|
3
4
|
import React, { useEffect, useState } from 'react';
|
|
5
|
+
import { PriceWrapper } from '@theme/views/product';
|
|
4
6
|
import { ProductPageProps } from './layout';
|
|
7
|
+
import MiscButtons from './misc-buttons';
|
|
5
8
|
import { pushProductViewed } from '@theme/utils/gtm';
|
|
6
9
|
import { useSession } from 'next-auth/react';
|
|
7
10
|
import { isVariantSelectionComplete } from '../../utils/variant-validation';
|
|
8
11
|
import { useProductCart } from '../../hooks/use-product-cart';
|
|
9
12
|
import { useStockAlert } from '../../hooks/use-stock-alert';
|
|
13
|
+
import { ProductVariants } from './product-variants';
|
|
10
14
|
import { ProductActions } from './product-actions';
|
|
11
|
-
import {
|
|
12
|
-
import { VariantsSection } from './variants-section';
|
|
13
|
-
import { ProductInfoSection } from './product-info-section';
|
|
14
|
-
import ProductShare from './product-share';
|
|
15
|
-
import MiscButtons from './misc-buttons';
|
|
15
|
+
import { ProductShare } from './product-share';
|
|
16
16
|
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
17
|
-
import {
|
|
18
|
-
ProductDetailRegistrar,
|
|
19
|
-
AddToCartStyles,
|
|
20
|
-
QuantityStyles,
|
|
21
|
-
QuantityProperties,
|
|
22
|
-
VariantsStyles,
|
|
23
|
-
ProductInfoStyles,
|
|
24
|
-
ShareStyles,
|
|
25
|
-
ShareProperties,
|
|
26
|
-
AccordionStyles,
|
|
27
|
-
FavoritesButtonStyles,
|
|
28
|
-
FavoritesButtonProperties,
|
|
29
|
-
FindInStoreStyles,
|
|
30
|
-
FindInStoreProperties
|
|
31
|
-
} from '@theme/views/product-detail';
|
|
32
|
-
import { AccordionWrapper } from '@theme/views/product';
|
|
33
|
-
|
|
34
|
-
export interface ProductDetailSettings {
|
|
35
|
-
addToCartStyles?: AddToCartStyles;
|
|
36
|
-
quantityStyles?: QuantityStyles;
|
|
37
|
-
quantityProperties?: QuantityProperties;
|
|
38
|
-
variantsStyles?: VariantsStyles;
|
|
39
|
-
productInfoStyles?: ProductInfoStyles;
|
|
40
|
-
shareStyles?: ShareStyles;
|
|
41
|
-
shareProperties?: ShareProperties;
|
|
42
|
-
accordionStyles?: AccordionStyles;
|
|
43
|
-
favoritesButtonStyles?: FavoritesButtonStyles;
|
|
44
|
-
favoritesButtonProperties?: FavoritesButtonProperties;
|
|
45
|
-
findInStoreStyles?: FindInStoreStyles;
|
|
46
|
-
findInStoreProperties?: FindInStoreProperties;
|
|
47
|
-
}
|
|
48
17
|
|
|
49
|
-
|
|
50
|
-
productDetailSettings?: ProductDetailSettings;
|
|
51
|
-
deliveryReturn?: any;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export default function ProductInfo({
|
|
55
|
-
data,
|
|
56
|
-
productDetailSettings,
|
|
57
|
-
deliveryReturn
|
|
58
|
-
}: ProductInfoProps) {
|
|
18
|
+
export default function ProductInfo({ data }: ProductPageProps) {
|
|
59
19
|
const { data: session } = useSession();
|
|
60
20
|
const [isVariantLoading, setIsVariantLoading] = useState(false);
|
|
61
|
-
|
|
21
|
+
|
|
62
22
|
const inStock = data.selected_variant !== null || data.product.in_stock;
|
|
63
23
|
|
|
64
24
|
const {
|
|
@@ -112,26 +72,30 @@ export default function ProductInfo({
|
|
|
112
72
|
};
|
|
113
73
|
|
|
114
74
|
return (
|
|
115
|
-
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
75
|
+
<>
|
|
76
|
+
<div
|
|
77
|
+
className={clsx(
|
|
78
|
+
'fixed bottom-0 left-0 w-1/2 h-14 z-[20] bg-white mt-0 border-t border-gray-500 items-center justify-center',
|
|
79
|
+
'sm:relative sm:flex sm:items-center sm:mt-5 sm:border-none'
|
|
80
|
+
)}
|
|
81
|
+
>
|
|
82
|
+
<PriceWrapper
|
|
83
|
+
price={data.product.price}
|
|
84
|
+
retailPrice={data.product.retail_price}
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<ProductVariants
|
|
119
89
|
variants={data.variants}
|
|
120
90
|
onVariantChange={handleVariantChange}
|
|
121
91
|
/>
|
|
122
92
|
|
|
123
|
-
<QuantitySection
|
|
124
|
-
quantity={quantity}
|
|
125
|
-
onChange={setQuantity}
|
|
126
|
-
disabled={!inStock || isVariantLoading}
|
|
127
|
-
/>
|
|
128
|
-
|
|
129
93
|
<ProductActions
|
|
130
94
|
product={data.product}
|
|
131
95
|
variants={data.variants}
|
|
132
96
|
inStock={inStock}
|
|
133
97
|
isVariantLoading={isVariantLoading}
|
|
134
|
-
onAddToCart={
|
|
98
|
+
onAddToCart={addProductToCart}
|
|
135
99
|
onAddToStockAlert={addProductToStockAlertList}
|
|
136
100
|
onClearError={clearProductError}
|
|
137
101
|
isAddToCartLoading={isAddToCartLoading}
|
|
@@ -142,16 +106,6 @@ export default function ProductInfo({
|
|
|
142
106
|
onCloseModal={closeModal}
|
|
143
107
|
/>
|
|
144
108
|
|
|
145
|
-
<ProductShare productName={data.product.name} />
|
|
146
|
-
|
|
147
|
-
<MiscButtons
|
|
148
|
-
productName={data.product.name}
|
|
149
|
-
productPk={data.product.pk}
|
|
150
|
-
variants={data.variants}
|
|
151
|
-
/>
|
|
152
|
-
|
|
153
|
-
<AccordionWrapper data={data} deliveryReturn={deliveryReturn} />
|
|
154
|
-
|
|
155
109
|
<PluginModule
|
|
156
110
|
component={Component.VirtualTryOnPlugin}
|
|
157
111
|
props={{
|
|
@@ -159,6 +113,14 @@ export default function ProductInfo({
|
|
|
159
113
|
className: 'hidden sm:flex'
|
|
160
114
|
}}
|
|
161
115
|
/>
|
|
162
|
-
|
|
116
|
+
|
|
117
|
+
<MiscButtons
|
|
118
|
+
productName={data.product.name}
|
|
119
|
+
productPk={data.product.pk}
|
|
120
|
+
variants={data.variants}
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
<ProductShare productName={data.product.name} className="my-2 sm:mb-4" />
|
|
124
|
+
</>
|
|
163
125
|
);
|
|
164
126
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect } from 'react';
|
|
4
|
-
import
|
|
4
|
+
import Share from '@theme/views/share';
|
|
5
5
|
import { useLocalization } from '@akinon/next/hooks';
|
|
6
6
|
|
|
7
7
|
interface ProductShareProps {
|
|
@@ -9,7 +9,7 @@ interface ProductShareProps {
|
|
|
9
9
|
className?: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const ProductShare: React.FC<ProductShareProps> = ({
|
|
12
|
+
export const ProductShare: React.FC<ProductShareProps> = ({
|
|
13
13
|
productName,
|
|
14
14
|
className
|
|
15
15
|
}) => {
|
|
@@ -30,32 +30,29 @@ const ProductShare: React.FC<ProductShareProps> = ({
|
|
|
30
30
|
currentUrl
|
|
31
31
|
)}`,
|
|
32
32
|
iconName: 'facebook' as const,
|
|
33
|
-
iconSize:
|
|
33
|
+
iconSize: 22
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
href: `https://twitter.com/intent/tweet?text=${encodeURIComponent(
|
|
37
37
|
currentUrl
|
|
38
38
|
)}`,
|
|
39
39
|
iconName: 'twitter' as const,
|
|
40
|
-
iconSize:
|
|
40
|
+
iconSize: 22
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
href: `https://api.whatsapp.com/send?text=${productName}%20${encodeURIComponent(
|
|
44
44
|
currentUrl
|
|
45
45
|
)}`,
|
|
46
46
|
iconName: 'whatsapp' as const,
|
|
47
|
-
iconSize:
|
|
47
|
+
iconSize: 22
|
|
48
48
|
}
|
|
49
49
|
];
|
|
50
50
|
|
|
51
51
|
return (
|
|
52
|
-
<
|
|
53
|
-
|
|
52
|
+
<Share
|
|
53
|
+
className={className}
|
|
54
54
|
buttonText={t('product.share')}
|
|
55
55
|
items={shareItems}
|
|
56
|
-
className={className}
|
|
57
56
|
/>
|
|
58
57
|
);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
export default ProductShare;
|
|
58
|
+
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React, {
|
|
3
|
+
import React, { useState, useRef } from 'react';
|
|
4
4
|
import { CarouselCore } from '@theme/components/carousel-core';
|
|
5
|
+
import { Icon } from '@theme/components';
|
|
5
6
|
import { Product } from '@akinon/next/types';
|
|
6
7
|
import { Image } from '@akinon/next/components/image';
|
|
7
|
-
import
|
|
8
|
-
import
|
|
8
|
+
import useFavButton from '../../hooks/use-fav-button';
|
|
9
|
+
import { twMerge } from 'tailwind-merge';
|
|
9
10
|
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
10
11
|
|
|
11
12
|
type ProductSliderItem = {
|
|
@@ -13,32 +14,81 @@ type ProductSliderItem = {
|
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export default function ProductInfoSlider({ product }: ProductSliderItem) {
|
|
17
|
+
const { FavButton } = useFavButton(product.pk);
|
|
16
18
|
const carouselRef = useRef(null);
|
|
17
|
-
const [zoomedImage, setZoomedImage] = useState<string | null>(null);
|
|
18
|
-
const handleOpenZoom = useCallback((src: string) => setZoomedImage(src), []);
|
|
19
|
-
const handleCloseZoom = useCallback(() => setZoomedImage(null), []);
|
|
20
|
-
const productImages = product?.productimage_set ?? [];
|
|
21
19
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
22
20
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
const goToPrev = () => {
|
|
22
|
+
const newIndex =
|
|
23
|
+
activeIndex === 0
|
|
24
|
+
? product?.productimage_set?.length - 1
|
|
25
|
+
: activeIndex - 1;
|
|
26
|
+
setActiveIndex(newIndex);
|
|
27
|
+
carouselRef.current?.previous();
|
|
28
|
+
};
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
const goToNext = () => {
|
|
31
|
+
const newIndex =
|
|
32
|
+
activeIndex === product?.productimage_set?.length - 1
|
|
33
|
+
? 0
|
|
34
|
+
: activeIndex + 1;
|
|
35
|
+
setActiveIndex(newIndex);
|
|
36
|
+
carouselRef.current?.next();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const handleThumbnailClick = (index) => {
|
|
40
|
+
setActiveIndex(index);
|
|
41
|
+
carouselRef.current?.goToSlide(index);
|
|
42
|
+
};
|
|
35
43
|
|
|
36
44
|
return (
|
|
37
|
-
|
|
38
|
-
<div className="
|
|
39
|
-
<div className="
|
|
40
|
-
<
|
|
45
|
+
<div className="lg:grid lg:grid-cols-6">
|
|
46
|
+
<div className="lg:col-span-1">
|
|
47
|
+
<div className="flex flex-col items-center justify-center md:mr-[6px]">
|
|
48
|
+
<button
|
|
49
|
+
onClick={goToPrev}
|
|
50
|
+
className={twMerge(
|
|
51
|
+
'hidden justify-center p-2 mb-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
52
|
+
[activeIndex === 0 && 'cursor-not-allowed opacity-45']
|
|
53
|
+
)}
|
|
54
|
+
disabled={activeIndex === 0}
|
|
55
|
+
>
|
|
56
|
+
<Icon name="chevron-up" size={15} className="fill-[#000000]" />
|
|
57
|
+
</button>
|
|
58
|
+
<div className="hidden flex-col items-center overflow-scroll w-[80px] max-h-[620px] lg:block">
|
|
59
|
+
{product?.productimage_set?.map((item, index) => (
|
|
60
|
+
<Image
|
|
61
|
+
key={index}
|
|
62
|
+
src={item.image}
|
|
63
|
+
alt={`Thumbnail ${index}`}
|
|
64
|
+
width={80}
|
|
65
|
+
height={128}
|
|
66
|
+
className={twMerge('cursor-pointer', [
|
|
67
|
+
activeIndex === index && 'border-2 border-primary'
|
|
68
|
+
])}
|
|
69
|
+
onClick={() => handleThumbnailClick(index)}
|
|
70
|
+
/>
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
<button
|
|
74
|
+
onClick={goToNext}
|
|
75
|
+
className={twMerge(
|
|
76
|
+
'hidden justify-center p-2 mt-3 border border-gray-100 rounded-full cursor-pointer lg:block',
|
|
77
|
+
[
|
|
78
|
+
activeIndex === product.productimage_set.length - 1 &&
|
|
79
|
+
'cursor-not-allowed opacity-45'
|
|
80
|
+
]
|
|
81
|
+
)}
|
|
82
|
+
disabled={activeIndex === product.productimage_set.length - 1}
|
|
83
|
+
>
|
|
84
|
+
<Icon name="chevron-down" size={15} className="fill-[#000000]" />
|
|
85
|
+
</button>
|
|
41
86
|
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div className="relative lg:col-span-5">
|
|
90
|
+
<FavButton className="absolute right-8 top-6 z-[20] sm:hidden" />
|
|
91
|
+
|
|
42
92
|
<PluginModule
|
|
43
93
|
component={Component.ProductImageSearchFeature}
|
|
44
94
|
props={{
|
|
@@ -46,8 +96,7 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
|
|
|
46
96
|
activeIndex,
|
|
47
97
|
showResetButton: true,
|
|
48
98
|
enableTextSearch: true,
|
|
49
|
-
isEnabled: true
|
|
50
|
-
className: 'right-2 top-6 absolute z-[30] w-auto px-4 text-xs mt-0'
|
|
99
|
+
isEnabled: true
|
|
51
100
|
}}
|
|
52
101
|
/>
|
|
53
102
|
|
|
@@ -59,6 +108,7 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
|
|
|
59
108
|
'sm:hidden absolute bottom-[70px] right-[5px] z-[30] w-auto px-4 text-xs mt-0'
|
|
60
109
|
}}
|
|
61
110
|
/>
|
|
111
|
+
|
|
62
112
|
<CarouselCore
|
|
63
113
|
responsive={{
|
|
64
114
|
all: {
|
|
@@ -67,117 +117,29 @@ export default function ProductInfoSlider({ product }: ProductSliderItem) {
|
|
|
67
117
|
}
|
|
68
118
|
}}
|
|
69
119
|
arrows={false}
|
|
70
|
-
swipeable
|
|
120
|
+
swipeable={true}
|
|
71
121
|
ref={carouselRef}
|
|
72
122
|
afterChange={(previousSlide, { currentSlide }) => {
|
|
73
123
|
setActiveIndex(currentSlide);
|
|
74
124
|
}}
|
|
75
|
-
containerAspectRatio={{ mobile:
|
|
76
|
-
renderButtonGroupOutside
|
|
77
|
-
customButtonGroup={<CustomButtonGroup />}
|
|
125
|
+
containerAspectRatio={{ mobile: 520 / 798, desktop: 484 / 726 }}
|
|
78
126
|
>
|
|
79
|
-
{
|
|
127
|
+
{product?.productimage_set?.map((item, i) => (
|
|
80
128
|
<Image
|
|
81
129
|
key={i}
|
|
82
130
|
src={item.image}
|
|
83
131
|
alt={product.name}
|
|
84
132
|
draggable={false}
|
|
85
|
-
aspectRatio={
|
|
133
|
+
aspectRatio={484 / 726}
|
|
86
134
|
sizes="(min-width: 425px) 512px,
|
|
87
135
|
(min-width: 601px) 576px,
|
|
88
136
|
(min-width: 768px) 336px,
|
|
89
137
|
(min-width: 1024px) 484px, 368px"
|
|
90
138
|
fill
|
|
91
|
-
className="cursor-zoom-in"
|
|
92
|
-
onClick={() => handleOpenZoom(item.image)}
|
|
93
139
|
/>
|
|
94
140
|
))}
|
|
95
141
|
</CarouselCore>
|
|
96
142
|
</div>
|
|
97
|
-
|
|
98
|
-
{firstImage && (
|
|
99
|
-
<div className="relative w-full overflow-hidden bg-gray-50 aspect-square">
|
|
100
|
-
<div className="absolute left-6 top-6 z-[20] w-8 h-8 flex items-center justify-center rounded-full bg-white border border-gray-30">
|
|
101
|
-
<Icon name="zoom" size={11} className="text-primary" />
|
|
102
|
-
</div>
|
|
103
|
-
|
|
104
|
-
<Image
|
|
105
|
-
src={firstImage.image}
|
|
106
|
-
alt={`${product.name} 1`}
|
|
107
|
-
draggable={false}
|
|
108
|
-
fill
|
|
109
|
-
aspectRatio={1}
|
|
110
|
-
sizes="(min-width: 1024px) 50vw, 90vw"
|
|
111
|
-
imageClassName="object-cover cursor-zoom-in"
|
|
112
|
-
onClick={() => handleOpenZoom(firstImage.image)}
|
|
113
|
-
/>
|
|
114
|
-
</div>
|
|
115
|
-
)}
|
|
116
|
-
{smallImagePairs.map((pair, pairIndex) => (
|
|
117
|
-
<div key={pairIndex} className="grid grid-cols-2 gap-2.5">
|
|
118
|
-
{pair.map((item, itemIndex) => (
|
|
119
|
-
<div
|
|
120
|
-
key={itemIndex}
|
|
121
|
-
className="relative w-full overflow-hidden bg-gray-50 aspect-square"
|
|
122
|
-
>
|
|
123
|
-
<Image
|
|
124
|
-
src={item.image}
|
|
125
|
-
alt={`${product.name} ${pairIndex * 2 + itemIndex + 2}`}
|
|
126
|
-
draggable={false}
|
|
127
|
-
fill
|
|
128
|
-
aspectRatio={1}
|
|
129
|
-
sizes="(min-width: 1024px) 25vw, 45vw"
|
|
130
|
-
imageClassName="object-cover cursor-zoom-in"
|
|
131
|
-
onClick={() => handleOpenZoom(item.image)}
|
|
132
|
-
/>
|
|
133
|
-
</div>
|
|
134
|
-
))}
|
|
135
|
-
</div>
|
|
136
|
-
))}
|
|
137
|
-
{trailingLargeImage && (
|
|
138
|
-
<div className="relative w-full overflow-hidden bg-gray-50 aspect-square">
|
|
139
|
-
<Image
|
|
140
|
-
src={trailingLargeImage.image}
|
|
141
|
-
alt={`${product.name} ${productImages.length}`}
|
|
142
|
-
draggable={false}
|
|
143
|
-
fill
|
|
144
|
-
aspectRatio={1}
|
|
145
|
-
sizes="(min-width: 1024px) 50vw, 90vw"
|
|
146
|
-
imageClassName="object-cover cursor-zoom-in"
|
|
147
|
-
onClick={() => handleOpenZoom(trailingLargeImage.image)}
|
|
148
|
-
/>
|
|
149
|
-
</div>
|
|
150
|
-
)}
|
|
151
|
-
</div>
|
|
152
|
-
{zoomedImage && (
|
|
153
|
-
<div
|
|
154
|
-
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4"
|
|
155
|
-
onClick={handleCloseZoom}
|
|
156
|
-
>
|
|
157
|
-
<div
|
|
158
|
-
className="relative w-full max-w-3xl aspect-square"
|
|
159
|
-
onClick={(event) => event.stopPropagation()}
|
|
160
|
-
>
|
|
161
|
-
<Button
|
|
162
|
-
type="button"
|
|
163
|
-
aria-label="Close zoomed image"
|
|
164
|
-
className="absolute right-3 top-3 z-10 rounded-full bg-white/90 px-3 py-1 text-sm font-medium text-gray-900 shadow cursor-pointer"
|
|
165
|
-
onClick={handleCloseZoom}
|
|
166
|
-
>
|
|
167
|
-
×
|
|
168
|
-
</Button>
|
|
169
|
-
<Image
|
|
170
|
-
src={zoomedImage}
|
|
171
|
-
alt={`${product.name} zoomed`}
|
|
172
|
-
draggable={false}
|
|
173
|
-
fill
|
|
174
|
-
aspectRatio={1}
|
|
175
|
-
sizes="100vw"
|
|
176
|
-
imageClassName="object-contain"
|
|
177
|
-
/>
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
)}
|
|
181
|
-
</>
|
|
143
|
+
</div>
|
|
182
144
|
);
|
|
183
145
|
}
|
|
@@ -10,9 +10,6 @@ import useFavButton from '../../hooks/use-fav-button';
|
|
|
10
10
|
import { Product } from '@akinon/next/types';
|
|
11
11
|
import { Image } from '@akinon/next/components/image';
|
|
12
12
|
import { Price, Link } from '@theme/components';
|
|
13
|
-
import { SaleTag } from '../product/sale-tag';
|
|
14
|
-
import { useProductList } from '../category/product-list-registrar';
|
|
15
|
-
import { useNativeWidget } from '../category/native-widget-context';
|
|
16
13
|
|
|
17
14
|
interface Props {
|
|
18
15
|
product: Product;
|
|
@@ -22,42 +19,17 @@ interface Props {
|
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
export const ProductItem = (props: Props) => {
|
|
22
|
+
// TODO: Static image will change (TR)
|
|
25
23
|
const { product, width, height, index } = props;
|
|
26
24
|
const [viewed, setViewed] = useState(false);
|
|
27
25
|
const { FavButton } = useFavButton(product.pk);
|
|
28
26
|
const { ref, inView } = useInView();
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
const {
|
|
32
|
-
productItemProperties: registrarProps,
|
|
33
|
-
productItemStyles: registrarStyles
|
|
34
|
-
} = useProductList();
|
|
35
|
-
|
|
36
|
-
// Get product-item properties from NativeWidgetProvider
|
|
37
|
-
const {
|
|
38
|
-
productItemProperties: widgetProps,
|
|
39
|
-
productItemStyles: widgetStyles
|
|
40
|
-
} = useNativeWidget();
|
|
41
|
-
|
|
42
|
-
// Designer mode uses registrar props, saved widget uses widget props
|
|
43
|
-
const isDesigner = typeof window !== 'undefined' && window.parent !== window;
|
|
44
|
-
const properties = isDesigner
|
|
45
|
-
? { ...widgetProps, ...registrarProps }
|
|
46
|
-
: widgetProps;
|
|
47
|
-
const styles = isDesigner
|
|
48
|
-
? { ...widgetStyles, ...registrarStyles }
|
|
49
|
-
: widgetStyles;
|
|
50
|
-
|
|
51
|
-
const showFavButton = properties['show-fav-button'] !== false;
|
|
52
|
-
const favButtonPosition =
|
|
53
|
-
(properties['fav-button-position'] as string) || 'top-right';
|
|
54
|
-
|
|
55
|
-
const image_url = product.productimage_set?.[0]?.image;
|
|
28
|
+
const image_url = product.productimage_set[0]?.image;
|
|
56
29
|
const absolute_url = product.absolute_url;
|
|
57
30
|
const product_name = product.name;
|
|
58
31
|
const retail_price = product.retail_price;
|
|
59
32
|
const price = product.price;
|
|
60
|
-
const isOnSale = parseFloat(String(retail_price)) > parseFloat(String(price));
|
|
61
33
|
|
|
62
34
|
useEffect(() => {
|
|
63
35
|
if (!viewed && inView) {
|
|
@@ -66,103 +38,58 @@ export const ProductItem = (props: Props) => {
|
|
|
66
38
|
}
|
|
67
39
|
}, [inView, viewed, product]);
|
|
68
40
|
|
|
69
|
-
// Position classes
|
|
70
|
-
const positionClasses = {
|
|
71
|
-
'top-left': 'top-2.5 left-2.5',
|
|
72
|
-
'top-right': 'top-2.5 right-2.5',
|
|
73
|
-
'bottom-left': 'bottom-2.5 left-2.5',
|
|
74
|
-
'bottom-right': 'bottom-2.5 right-2.5'
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const favButtonStyles: React.CSSProperties = {
|
|
78
|
-
width: (styles['fav-button-size'] as string) || '32px',
|
|
79
|
-
height: (styles['fav-button-size'] as string) || '32px',
|
|
80
|
-
backgroundColor:
|
|
81
|
-
(styles['fav-button-bg-color'] as string) || 'rgba(255, 255, 255, 0.8)',
|
|
82
|
-
color: (styles['fav-button-icon-color'] as string) || '#000000',
|
|
83
|
-
borderRadius: (styles['fav-button-border-radius'] as string) || '50%'
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const productNameStyles: React.CSSProperties = {
|
|
87
|
-
fontSize: (styles['product-name-font-size'] as string) || '14px',
|
|
88
|
-
color: (styles['product-name-color'] as string) || '#1a1a1a',
|
|
89
|
-
fontWeight: (styles['product-name-font-weight'] as string) || '400',
|
|
90
|
-
display: '-webkit-box',
|
|
91
|
-
WebkitLineClamp: parseInt(
|
|
92
|
-
(styles['product-name-line-clamp'] as string) || '2',
|
|
93
|
-
10
|
|
94
|
-
),
|
|
95
|
-
WebkitBoxOrient: 'vertical' as const,
|
|
96
|
-
overflow: 'hidden'
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const priceStyles: React.CSSProperties = {
|
|
100
|
-
fontSize: (styles['price-font-size'] as string) || '16px',
|
|
101
|
-
color: (styles['price-color'] as string) || '#1a1a1a',
|
|
102
|
-
fontWeight: (styles['price-font-weight'] as string) || '400'
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const oldPriceStyles: React.CSSProperties = {
|
|
106
|
-
fontSize: (styles['old-price-font-size'] as string) || '14px',
|
|
107
|
-
color: (styles['old-price-color'] as string) || '#6b7280'
|
|
108
|
-
};
|
|
109
|
-
|
|
110
41
|
return (
|
|
111
42
|
<div
|
|
112
|
-
className="flex flex-col
|
|
43
|
+
className="text-sm text-left flex flex-col justify-between"
|
|
113
44
|
data-testid="product-box"
|
|
114
45
|
ref={ref}
|
|
115
|
-
data-section-id="product-item-section"
|
|
116
46
|
>
|
|
117
|
-
<div className="relative mb-
|
|
47
|
+
<div className="relative mb-3 h-full">
|
|
118
48
|
<Link href={absolute_url} onClick={() => pushProductClicked(product)}>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
width
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
49
|
+
{image_url ? (
|
|
50
|
+
<Image
|
|
51
|
+
fill
|
|
52
|
+
loading="lazy"
|
|
53
|
+
src={image_url}
|
|
54
|
+
alt={product_name}
|
|
55
|
+
aspectRatio={width / height}
|
|
56
|
+
sizes="
|
|
57
|
+
(max-width: 768px) 50vw,
|
|
58
|
+
(max-width: 1024px) 30vw,
|
|
59
|
+
33vw"
|
|
60
|
+
crop="center"
|
|
61
|
+
/>
|
|
62
|
+
) : (
|
|
63
|
+
<Image
|
|
64
|
+
className="h-full"
|
|
65
|
+
src="/noimage.jpg"
|
|
66
|
+
fill
|
|
67
|
+
aspectRatio={width / height}
|
|
68
|
+
sizes="100vw"
|
|
69
|
+
alt={product_name}
|
|
70
|
+
imageClassName="object-cover"
|
|
71
|
+
/>
|
|
72
|
+
)}
|
|
131
73
|
</Link>
|
|
132
|
-
|
|
133
|
-
{showFavButton && (
|
|
134
|
-
<FavButton
|
|
135
|
-
className={`absolute ${positionClasses[favButtonPosition]} hover:opacity-100 transition-all flex items-center justify-center z-10`}
|
|
136
|
-
style={favButtonStyles}
|
|
137
|
-
/>
|
|
138
|
-
)}
|
|
139
|
-
|
|
140
|
-
{isOnSale && (
|
|
141
|
-
<div className="absolute bottom-4 left-2 z-10">
|
|
142
|
-
<SaleTag />
|
|
143
|
-
</div>
|
|
144
|
-
)}
|
|
74
|
+
<FavButton className="absolute top-4 right-4" />
|
|
145
75
|
</div>
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
/>
|
|
164
|
-
)}
|
|
165
|
-
<Price value={price} data-testid="product-price" style={priceStyles} />
|
|
76
|
+
<div>
|
|
77
|
+
<Link
|
|
78
|
+
href={absolute_url}
|
|
79
|
+
data-testid={`${product_name}-${index}`}
|
|
80
|
+
onClick={() => pushProductClicked(product)}
|
|
81
|
+
>
|
|
82
|
+
{product_name}
|
|
83
|
+
</Link>
|
|
84
|
+
<div className="font-semibold">
|
|
85
|
+
{parseFloat(retail_price) > parseFloat(price) && (
|
|
86
|
+
<Price
|
|
87
|
+
value={retail_price}
|
|
88
|
+
className="font-normal line-through mr-3"
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
<Price value={price} data-testid="product-price" />
|
|
92
|
+
</div>
|
|
166
93
|
</div>
|
|
167
94
|
</div>
|
|
168
95
|
);
|