@akinon/projectzero 1.99.0-rc.69 → 1.99.0-snapshot-ZERO-3640-20250919140314
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 +5 -238
- package/app-template/.env.example +0 -1
- package/app-template/CHANGELOG.md +304 -5041
- package/app-template/README.md +1 -25
- package/app-template/package.json +19 -21
- package/app-template/public/locales/en/common.json +1 -42
- package/app-template/public/locales/tr/common.json +1 -42
- package/app-template/src/app/[commerce]/[locale]/[currency]/basket/page.tsx +82 -9
- package/app-template/src/app/[commerce]/[locale]/[currency]/category/[pk]/page.tsx +4 -17
- package/app-template/src/app/[commerce]/[locale]/[currency]/flat-page/[pk]/page.tsx +1 -12
- package/app-template/src/app/[commerce]/[locale]/[currency]/group-product/[pk]/page.tsx +11 -29
- package/app-template/src/app/[commerce]/[locale]/[currency]/landing-page/[pk]/page.tsx +1 -12
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/page.tsx +10 -28
- package/app-template/src/app/[commerce]/[locale]/[currency]/special-page/[pk]/page.tsx +1 -12
- package/app-template/src/assets/fonts/pz-icon.css +0 -3
- package/app-template/src/components/__tests__/link.test.tsx +0 -2
- package/app-template/src/components/accordion.tsx +19 -22
- package/app-template/src/components/currency-select.tsx +0 -1
- package/app-template/src/components/file-input.tsx +7 -27
- package/app-template/src/components/input.tsx +2 -9
- package/app-template/src/components/modal.tsx +16 -32
- package/app-template/src/components/pagination.tsx +0 -1
- package/app-template/src/components/select.tsx +26 -38
- package/app-template/src/components/types/index.ts +1 -25
- package/app-template/src/hooks/index.ts +0 -2
- package/app-template/src/plugins.js +1 -3
- package/app-template/src/settings.js +2 -8
- package/app-template/src/types/index.ts +3 -74
- package/app-template/src/views/account/address-form.tsx +4 -8
- package/app-template/src/views/account/contact-form.tsx +1 -1
- package/app-template/src/views/account/content-header.tsx +2 -2
- package/app-template/src/views/account/faq/faq-tabs.tsx +2 -8
- package/app-template/src/views/basket/basket-item.tsx +14 -22
- package/app-template/src/views/basket/summary.tsx +7 -10
- package/app-template/src/views/breadcrumb.tsx +2 -2
- package/app-template/src/views/category/category-info.tsx +0 -1
- package/app-template/src/views/category/filters/index.tsx +1 -1
- package/app-template/src/views/guest-login/index.tsx +1 -6
- package/app-template/src/views/header/action-menu.tsx +1 -1
- package/app-template/src/views/header/search/index.tsx +5 -17
- package/app-template/src/views/login/index.tsx +10 -11
- package/app-template/src/views/otp-login/index.tsx +6 -11
- package/app-template/src/views/product/product-info.tsx +263 -62
- package/app-template/src/views/product/slider.tsx +73 -86
- package/app-template/src/views/register/index.tsx +11 -15
- package/app-template/src/widgets/footer-menu.tsx +2 -6
- package/commands/plugins.ts +16 -63
- package/dist/commands/plugins.js +16 -57
- package/package.json +1 -1
- package/app-template/.github/instructions/account.instructions.md +0 -749
- package/app-template/.github/instructions/edge-cases.instructions.md +0 -73
- package/app-template/src/app/[commerce]/[locale]/[currency]/product/[pk]/loading.tsx +0 -67
- package/app-template/src/app/api/image-proxy/route.ts +0 -1
- package/app-template/src/app/api/similar-product-list/route.ts +0 -1
- package/app-template/src/app/api/similar-products/route.ts +0 -1
- package/app-template/src/hooks/use-product-cart.ts +0 -77
- package/app-template/src/hooks/use-stock-alert.ts +0 -74
- package/app-template/src/utils/variant-validation.ts +0 -41
- package/app-template/src/views/basket/basket-content.tsx +0 -106
- package/app-template/src/views/product/product-actions.tsx +0 -165
- package/app-template/src/views/product/product-share.tsx +0 -56
- package/app-template/src/views/product/product-variants.tsx +0 -26
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
applyTo: '**'
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Edge Cases & Troubleshooting Instructions
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
This document contains common edge cases, bugs, and troubleshooting steps encountered in Project Zero Next.js development. These are real-world issues that have caused development delays and should be checked first when similar problems occur.
|
|
10
|
-
|
|
11
|
-
## Checkout Flow Edge Cases
|
|
12
|
-
|
|
13
|
-
### 1. Order Selection Page Request Timing
|
|
14
|
-
|
|
15
|
-
**Problem:** `orderselectionpage` request should NOT be called after `walletselectionpage` or `walletpaymentpage` requests.
|
|
16
|
-
|
|
17
|
-
This causes checkout flow to break after wallet payment steps, order selection shows incorrect state, and payment flow gets reset unexpectedly. After `walletselectionpage` or `walletpaymentpage` requests are made, `orderselectionpage` request should not be called. Always check the sequence of checkout API calls.
|
|
18
|
-
|
|
19
|
-
### 2. Guest Login with URL Parameters
|
|
20
|
-
|
|
21
|
-
**Problem:** Adding `?page=IndexPage` to checkout URLs breaks guest login functionality.
|
|
22
|
-
|
|
23
|
-
Guest login fails on checkout URLs with `?page=IndexPage` but works fine without the parameter. This parameter interferes with authentication middleware routing. Avoid adding unnecessary URL parameters to checkout flows.
|
|
24
|
-
|
|
25
|
-
### 3. Agreement Error After 3D Payment Redirect
|
|
26
|
-
|
|
27
|
-
**Problem:** "You must accept agreement to continue" error appears after 3D payment or redirections, but the issue is NOT related to agreements.
|
|
28
|
-
|
|
29
|
-
Agreement error occurs after successful 3D payment and checkout flow breaks after external redirections. The error message is misleading - actually `preOrder` data gets reset/lost during payment redirections. Don't assume agreement errors are actually about agreements.
|
|
30
|
-
|
|
31
|
-
### 4. Address Visibility with fetchCheckout
|
|
32
|
-
|
|
33
|
-
**Problem:** When `fetchCheckout` request includes `page=IndexPage` parameter, addresses don't show in checkout and the entire flow breaks.
|
|
34
|
-
|
|
35
|
-
Addresses are missing from checkout page, shipping options don't load, and checkout flow is completely broken. Avoid adding unnecessary parameters to core checkout requests and validate that all checkout data loads properly.
|
|
36
|
-
|
|
37
|
-
## Plugin & Configuration Edge Cases
|
|
38
|
-
|
|
39
|
-
### 5. Attribute-Based Shipping Options Typo
|
|
40
|
-
|
|
41
|
-
**Problem:** `attribute_based_shipping_options` was incorrectly entered as `atribute_based` (missing 't') in brand configurations.
|
|
42
|
-
|
|
43
|
-
Shipping options don't work for specific brands and debugging is difficult because the setting exists but is misspelled. This can cause days of debugging. Always double-check configuration property names.
|
|
44
|
-
|
|
45
|
-
### 6. Apple Pay Plugin Configuration
|
|
46
|
-
|
|
47
|
-
**Problem:** Apple Pay can work as both `confirmation` and `wallet` payment types, but for the plugin to work properly, it should NOT be configured as `confirmation` type.
|
|
48
|
-
|
|
49
|
-
Apple Pay flow breaks in certain scenarios, payment confirmation issues occur, and plugin conflicts with checkout flow. Always configure Apple Pay as wallet type.
|
|
50
|
-
|
|
51
|
-
## Build & Development Edge Cases
|
|
52
|
-
|
|
53
|
-
### 7. Build Failures Due to yarn.lock Corruption
|
|
54
|
-
|
|
55
|
-
**Problem:** Build fails with apparent TypeScript errors in `@akinon/next` components, but the real issue is a corrupted `yarn.lock` file.
|
|
56
|
-
|
|
57
|
-
Build fails with nonsensical type errors, components from `@akinon/next` are flagged with errors, and multiple versions of the same package exist in lock file.
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
# Check for duplicate packages in yarn.lock
|
|
61
|
-
yarn list --pattern "@akinon/next" --depth=0
|
|
62
|
-
|
|
63
|
-
# If multiple versions exist, clean up
|
|
64
|
-
rm yarn.lock
|
|
65
|
-
rm -rf node_modules
|
|
66
|
-
yarn install
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Regularly check `yarn.lock` for duplicate dependencies and use `yarn dedupe` after adding new packages.
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
**Remember:** Edge cases often have misleading error messages. Always investigate the root cause rather than fixing symptoms. The most time-consuming bugs are usually simple configuration or timing issues.
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { Skeleton, SkeletonWrapper } from 'components';
|
|
2
|
-
|
|
3
|
-
export default function Loading() {
|
|
4
|
-
return (
|
|
5
|
-
<div className="container mx-auto">
|
|
6
|
-
<div className="max-w-5xl mx-auto my-5 px-7">
|
|
7
|
-
<SkeletonWrapper className="md:mb-7">
|
|
8
|
-
<Skeleton className="w-[17.25rem] h-4 lg:w-64" />
|
|
9
|
-
</SkeletonWrapper>
|
|
10
|
-
</div>
|
|
11
|
-
<div className="grid max-w-5xl grid-cols-2 lg:gap-8 mx-auto px-7">
|
|
12
|
-
<div className="col-span-2 mb-7 md:mb-0 lg:col-span-1">
|
|
13
|
-
<div className="flex gap-1">
|
|
14
|
-
<SkeletonWrapper className="hidden md:block md:mb-7">
|
|
15
|
-
{Array(5)
|
|
16
|
-
.fill(null)
|
|
17
|
-
.map((_, index) => (
|
|
18
|
-
<Skeleton key={index} className="w-20 h-24 mb-2" />
|
|
19
|
-
))}
|
|
20
|
-
</SkeletonWrapper>
|
|
21
|
-
|
|
22
|
-
<div className="flex-1">
|
|
23
|
-
<SkeletonWrapper className="md:mb-7">
|
|
24
|
-
<Skeleton className="w-full h-[30.375rem] md:h-[36.375rem]" />
|
|
25
|
-
</SkeletonWrapper>
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
<div className="flex flex-col items-center col-span-2 lg:col-span-1">
|
|
30
|
-
<div className="w-full">
|
|
31
|
-
<SkeletonWrapper className="w-full md:mb-7 flex justify-center items-center">
|
|
32
|
-
<Skeleton className="w-96 h-16 mb-9" />
|
|
33
|
-
<Skeleton className="hidden w-36 h-14 mb-9 md:block" />
|
|
34
|
-
|
|
35
|
-
<div className="flex flex-col justify-center items-center mb-9">
|
|
36
|
-
<Skeleton className="w-36 h-4 mb-2" />
|
|
37
|
-
<div className="flex items-center gap-2">
|
|
38
|
-
{Array(3)
|
|
39
|
-
.fill(null)
|
|
40
|
-
.map((_, index) => (
|
|
41
|
-
<Skeleton key={index} className="w-24 h-10" />
|
|
42
|
-
))}
|
|
43
|
-
</div>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
<div className="flex flex-col justify-center items-center mb-4">
|
|
47
|
-
<Skeleton className="w-36 h-4 mb-2" />
|
|
48
|
-
<div className="flex items-center gap-2">
|
|
49
|
-
{Array(5)
|
|
50
|
-
.fill(null)
|
|
51
|
-
.map((_, index) => (
|
|
52
|
-
<Skeleton key={index} className="w-11 h-11" />
|
|
53
|
-
))}
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
<Skeleton className="hidden w-full h-14 mb-6 md:block" />
|
|
58
|
-
<Skeleton className="w-40 h-10 mb-9 md:w-72" />
|
|
59
|
-
<Skeleton className="w-24 h-10 mb-7" />
|
|
60
|
-
<Skeleton className="w-full h-36" />
|
|
61
|
-
</SkeletonWrapper>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from '@akinon/next/api/image-proxy';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from '@akinon/next/api/similar-product-list';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from '@akinon/next/api/similar-products';
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import { useAddProductToBasket } from './index';
|
|
3
|
-
import { pushAddToCart } from '@theme/utils/gtm';
|
|
4
|
-
import { validateVariantSelection } from '../utils/variant-validation';
|
|
5
|
-
import { VariantType } from '@akinon/next/types';
|
|
6
|
-
|
|
7
|
-
interface Product {
|
|
8
|
-
pk: number;
|
|
9
|
-
[key: string]: any;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface UseProductCartProps {
|
|
13
|
-
product: Product;
|
|
14
|
-
variants: VariantType[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface AddToCartError {
|
|
18
|
-
data?: {
|
|
19
|
-
non_field_errors?: string[];
|
|
20
|
-
[key: string]: string[];
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const useProductCart = ({ product, variants }: UseProductCartProps) => {
|
|
25
|
-
const [productError, setProductError] = useState<React.ReactNode | null>(null);
|
|
26
|
-
const [addProduct, { isLoading: isAddToCartLoading }] = useAddProductToBasket();
|
|
27
|
-
|
|
28
|
-
const formatError = (error: AddToCartError) => {
|
|
29
|
-
if (error?.data?.non_field_errors) {
|
|
30
|
-
return error.data.non_field_errors;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (error?.data) {
|
|
34
|
-
return Object.keys(error.data).map(
|
|
35
|
-
(key) => `${key}: ${error.data[key].join(', ')}`
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return 'An error occurred';
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const addProductToCart = async () => {
|
|
43
|
-
const validation = validateVariantSelection(variants);
|
|
44
|
-
|
|
45
|
-
if (!validation.isValid) {
|
|
46
|
-
setProductError(validation.errorMessage);
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
await addProduct({
|
|
52
|
-
product: product.pk,
|
|
53
|
-
quantity: 1,
|
|
54
|
-
attributes: {}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
pushAddToCart(product);
|
|
58
|
-
setProductError(null);
|
|
59
|
-
return true;
|
|
60
|
-
} catch (error) {
|
|
61
|
-
const formattedError = formatError(error as AddToCartError);
|
|
62
|
-
setProductError(formattedError);
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const clearProductError = () => {
|
|
68
|
-
setProductError(null);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
addProductToCart,
|
|
73
|
-
productError,
|
|
74
|
-
clearProductError,
|
|
75
|
-
isAddToCartLoading
|
|
76
|
-
};
|
|
77
|
-
};
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { useAddStockAlertMutation } from '@akinon/next/data/client/wishlist';
|
|
3
|
-
import { Trans } from '@akinon/next/components/trans';
|
|
4
|
-
import { useLocalization } from '@akinon/next/hooks';
|
|
5
|
-
|
|
6
|
-
interface UseStockAlertProps {
|
|
7
|
-
productPk: number;
|
|
8
|
-
userEmail?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const useStockAlert = ({ productPk, userEmail }: UseStockAlertProps) => {
|
|
12
|
-
const { t } = useLocalization();
|
|
13
|
-
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
14
|
-
const [stockAlertResponseMessage, setStockAlertResponseMessage] = useState<React.ReactNode | null>(null);
|
|
15
|
-
const [productError, setProductError] = useState<React.ReactNode | null>(null);
|
|
16
|
-
|
|
17
|
-
const [addStockAlert, { isLoading: isAddToStockAlertLoading }] = useAddStockAlertMutation();
|
|
18
|
-
|
|
19
|
-
const handleSuccess = () => {
|
|
20
|
-
setStockAlertResponseMessage(React.createElement(
|
|
21
|
-
Trans,
|
|
22
|
-
{
|
|
23
|
-
i18nKey: "product.stock_alert.success_description",
|
|
24
|
-
components: {
|
|
25
|
-
Email: React.createElement('span', {}, userEmail)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
));
|
|
29
|
-
setIsModalOpen(true);
|
|
30
|
-
setProductError(null);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const handleError = (err: any) => {
|
|
34
|
-
if (err.status !== 401) {
|
|
35
|
-
setStockAlertResponseMessage(
|
|
36
|
-
t('product.stock_alert.error_description').toString()
|
|
37
|
-
);
|
|
38
|
-
setIsModalOpen(true);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const addProductToStockAlertList = async () => {
|
|
43
|
-
try {
|
|
44
|
-
await addStockAlert({
|
|
45
|
-
productPk,
|
|
46
|
-
email: userEmail
|
|
47
|
-
})
|
|
48
|
-
.unwrap()
|
|
49
|
-
.then(handleSuccess)
|
|
50
|
-
.catch(handleError);
|
|
51
|
-
} catch (error: any) {
|
|
52
|
-
setProductError(error?.data?.non_field_errors || null);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const closeModal = () => {
|
|
57
|
-
setIsModalOpen(false);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const clearError = () => {
|
|
61
|
-
setProductError(null);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
addProductToStockAlertList,
|
|
66
|
-
isModalOpen,
|
|
67
|
-
setIsModalOpen,
|
|
68
|
-
stockAlertResponseMessage,
|
|
69
|
-
productError,
|
|
70
|
-
isAddToStockAlertLoading,
|
|
71
|
-
closeModal,
|
|
72
|
-
clearError
|
|
73
|
-
};
|
|
74
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Trans } from '@akinon/next/components/trans';
|
|
3
|
-
import { VariantType } from '@akinon/next/types';
|
|
4
|
-
|
|
5
|
-
export const isVariantSelectionComplete = (variants: VariantType[]): boolean => {
|
|
6
|
-
return variants?.every((variant) =>
|
|
7
|
-
variant?.options.some((opt) => opt.is_selected)
|
|
8
|
-
);
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const getUnselectedVariant = (variants: VariantType[]): VariantType | undefined => {
|
|
12
|
-
return variants.find((variant) =>
|
|
13
|
-
variant.options.every((opt) => !opt.is_selected)
|
|
14
|
-
);
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const createVariantErrorMessage = (unselectedVariant: VariantType) => {
|
|
18
|
-
const TransComponent = Trans as any;
|
|
19
|
-
return React.createElement(
|
|
20
|
-
TransComponent,
|
|
21
|
-
{
|
|
22
|
-
i18nKey: "product.please_select_variant",
|
|
23
|
-
components: {
|
|
24
|
-
VariantName: React.createElement('span', {}, unselectedVariant.attribute_name)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const validateVariantSelection = (variants: VariantType[]) => {
|
|
31
|
-
const unselectedVariant = getUnselectedVariant(variants);
|
|
32
|
-
|
|
33
|
-
if (unselectedVariant) {
|
|
34
|
-
return {
|
|
35
|
-
isValid: false,
|
|
36
|
-
errorMessage: createVariantErrorMessage(unselectedVariant)
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return { isValid: true, errorMessage: null };
|
|
41
|
-
};
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useLocalization } from '@akinon/next/hooks';
|
|
4
|
-
import { Basket } from '@akinon/next/types';
|
|
5
|
-
import { Button, LoaderSpinner, Link } from '@theme/components';
|
|
6
|
-
import { BasketItem, Summary } from '@theme/views/basket';
|
|
7
|
-
import { ROUTES } from '@theme/routes';
|
|
8
|
-
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
9
|
-
import { useEffect, useState } from 'react';
|
|
10
|
-
import { pushCartView } from '@theme/utils/gtm';
|
|
11
|
-
|
|
12
|
-
interface BasketContentProps {
|
|
13
|
-
initialBasket: Basket;
|
|
14
|
-
multiBasket: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function BasketContent({
|
|
18
|
-
initialBasket,
|
|
19
|
-
multiBasket
|
|
20
|
-
}: BasketContentProps) {
|
|
21
|
-
const { t } = useLocalization();
|
|
22
|
-
const [basket, setBasket] = useState<Basket>(initialBasket);
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (basket) {
|
|
26
|
-
const products = basket.basketitem_set.map((basketItem) => ({
|
|
27
|
-
...basketItem.product
|
|
28
|
-
}));
|
|
29
|
-
pushCartView(products);
|
|
30
|
-
}
|
|
31
|
-
}, [basket]);
|
|
32
|
-
|
|
33
|
-
const handleBasketUpdate = (updatedBasket: Basket) => {
|
|
34
|
-
setBasket(updatedBasket);
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
if (!basket) {
|
|
38
|
-
return (
|
|
39
|
-
<div className="flex justify-center w-full">
|
|
40
|
-
<LoaderSpinner />
|
|
41
|
-
</div>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<div className="max-w-screen-xl p-4 flex flex-col text-primary-800 lg:p-8 xl:flex-row xl:mx-auto">
|
|
47
|
-
{basket.basketitem_set && basket.basketitem_set.length > 0 ? (
|
|
48
|
-
<>
|
|
49
|
-
<div className="flex-1 xl:mr-16">
|
|
50
|
-
<div className="flex items-center justify-between py-2 border-b border-gray-200 lg:py-3">
|
|
51
|
-
<h2 className="text-xl lg:text-2xl font-light">
|
|
52
|
-
{t('basket.my_cart')}
|
|
53
|
-
</h2>
|
|
54
|
-
<Link
|
|
55
|
-
href={ROUTES.HOME}
|
|
56
|
-
className="text-xs hover:text-secondary-500"
|
|
57
|
-
>
|
|
58
|
-
{t('basket.back_to_shopping')}
|
|
59
|
-
</Link>
|
|
60
|
-
</div>
|
|
61
|
-
<ul>
|
|
62
|
-
{multiBasket ? (
|
|
63
|
-
<PluginModule
|
|
64
|
-
component={Component.MultiBasket}
|
|
65
|
-
props={{
|
|
66
|
-
BasketItem,
|
|
67
|
-
onBasketUpdate: handleBasketUpdate
|
|
68
|
-
}}
|
|
69
|
-
/>
|
|
70
|
-
) : (
|
|
71
|
-
basket.basketitem_set.map((basketItem, index) => (
|
|
72
|
-
<BasketItem
|
|
73
|
-
key={index}
|
|
74
|
-
basketItem={basketItem}
|
|
75
|
-
onBasketUpdate={handleBasketUpdate}
|
|
76
|
-
/>
|
|
77
|
-
))
|
|
78
|
-
)}
|
|
79
|
-
</ul>
|
|
80
|
-
</div>
|
|
81
|
-
<Summary basket={basket} onBasketUpdate={handleBasketUpdate} />
|
|
82
|
-
</>
|
|
83
|
-
) : (
|
|
84
|
-
<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">
|
|
85
|
-
<h1
|
|
86
|
-
className="w-full text-xl font-light text-secondary text-center sm:text-2xl"
|
|
87
|
-
data-testid="basket-empty"
|
|
88
|
-
>
|
|
89
|
-
{t('basket.empty.title')}
|
|
90
|
-
</h1>
|
|
91
|
-
|
|
92
|
-
<div className="w-full text-sm text-black-800 text-center my-4 mb-2 sm:text-base">
|
|
93
|
-
<p>{t('basket.empty.content_first')}</p>
|
|
94
|
-
<p>{t('basket.empty.content_second')}.</p>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
<Link href={ROUTES.HOME} passHref>
|
|
98
|
-
<Button className="px-10 mt-2" appearance="filled">
|
|
99
|
-
{t('basket.empty.button')}
|
|
100
|
-
</Button>
|
|
101
|
-
</Link>
|
|
102
|
-
</div>
|
|
103
|
-
)}
|
|
104
|
-
</div>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import clsx from 'clsx';
|
|
3
|
-
import { Button, Icon, Modal } from '@theme/components';
|
|
4
|
-
import { useLocalization } from '@akinon/next/hooks';
|
|
5
|
-
import PluginModule, { Component } from '@akinon/next/components/plugin-module';
|
|
6
|
-
import { validateVariantSelection } from '../../utils/variant-validation';
|
|
7
|
-
import { VariantType } from '@akinon/next/types';
|
|
8
|
-
|
|
9
|
-
interface Product {
|
|
10
|
-
pk: number;
|
|
11
|
-
name: string;
|
|
12
|
-
[key: string]: any;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface ProductActionsProps {
|
|
16
|
-
product: Product;
|
|
17
|
-
variants: VariantType[];
|
|
18
|
-
inStock: boolean;
|
|
19
|
-
isVariantLoading: boolean;
|
|
20
|
-
onAddToCart: () => void;
|
|
21
|
-
onAddToStockAlert: () => void;
|
|
22
|
-
onClearError: () => void;
|
|
23
|
-
isAddToCartLoading: boolean;
|
|
24
|
-
isAddToStockAlertLoading: boolean;
|
|
25
|
-
productError: React.ReactNode | null;
|
|
26
|
-
isModalOpen: boolean;
|
|
27
|
-
stockAlertResponseMessage: React.ReactNode | null;
|
|
28
|
-
onCloseModal: () => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const ProductActions: React.FC<ProductActionsProps> = ({
|
|
32
|
-
product,
|
|
33
|
-
variants,
|
|
34
|
-
inStock,
|
|
35
|
-
isVariantLoading,
|
|
36
|
-
onAddToCart,
|
|
37
|
-
onAddToStockAlert,
|
|
38
|
-
onClearError,
|
|
39
|
-
isAddToCartLoading,
|
|
40
|
-
isAddToStockAlertLoading,
|
|
41
|
-
productError,
|
|
42
|
-
isModalOpen,
|
|
43
|
-
stockAlertResponseMessage,
|
|
44
|
-
onCloseModal
|
|
45
|
-
}) => {
|
|
46
|
-
const { t } = useLocalization();
|
|
47
|
-
|
|
48
|
-
const checkoutProviderProps = {
|
|
49
|
-
product,
|
|
50
|
-
clearBasket: true,
|
|
51
|
-
addBeforeClick: () => validateVariantSelection(variants).isValid,
|
|
52
|
-
openMiniBasket: false,
|
|
53
|
-
className: clsx([
|
|
54
|
-
'py-2.5',
|
|
55
|
-
'bg-black',
|
|
56
|
-
'relative',
|
|
57
|
-
'hover:bg-black',
|
|
58
|
-
'before:content-[""]',
|
|
59
|
-
'before:w-6',
|
|
60
|
-
'before:h-6',
|
|
61
|
-
'before:bg-white',
|
|
62
|
-
'before:absolute',
|
|
63
|
-
'before:rounded-r-[18px]',
|
|
64
|
-
'before:left-0',
|
|
65
|
-
'after:content-[""]',
|
|
66
|
-
'after:absolute',
|
|
67
|
-
'after:w-3',
|
|
68
|
-
'after:h-3',
|
|
69
|
-
'after:bg-[#d02c2f]',
|
|
70
|
-
'after:rounded-xl',
|
|
71
|
-
'after:left-1'
|
|
72
|
-
]),
|
|
73
|
-
onError: (error: any) => {
|
|
74
|
-
const formattedError = error?.data?.non_field_errors ||
|
|
75
|
-
Object.keys(error?.data || {}).map(
|
|
76
|
-
(key) => `${key}: ${error?.data[key].join(', ')}`
|
|
77
|
-
);
|
|
78
|
-
// This would need to be handled by parent component
|
|
79
|
-
console.error('Checkout error:', formattedError);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const handleMainActionClick = () => {
|
|
84
|
-
onClearError();
|
|
85
|
-
|
|
86
|
-
if (inStock) {
|
|
87
|
-
onAddToCart();
|
|
88
|
-
} else {
|
|
89
|
-
onAddToStockAlert();
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<>
|
|
95
|
-
{productError && (
|
|
96
|
-
<div className="mt-4 text-xs text-center text-error">
|
|
97
|
-
{productError}
|
|
98
|
-
</div>
|
|
99
|
-
)}
|
|
100
|
-
|
|
101
|
-
<Button
|
|
102
|
-
disabled={isAddToCartLoading || isAddToStockAlertLoading || isVariantLoading}
|
|
103
|
-
className={clsx(
|
|
104
|
-
'fixed bottom-0 right-0 w-1/2 h-14 z-[20] flex items-center justify-center fill-primary-foreground',
|
|
105
|
-
'hover:fill-primary sm:relative sm:w-full sm:mt-3 sm:font-semibold sm:h-12'
|
|
106
|
-
)}
|
|
107
|
-
onClick={handleMainActionClick}
|
|
108
|
-
data-testid="product-add-to-cart"
|
|
109
|
-
>
|
|
110
|
-
{isVariantLoading ? (
|
|
111
|
-
<Icon
|
|
112
|
-
name="spinner"
|
|
113
|
-
size={20}
|
|
114
|
-
className="animate-spin mr-4 fill-primary"
|
|
115
|
-
/>
|
|
116
|
-
) : inStock ? (
|
|
117
|
-
<span>{t('product.add_to_cart')}</span>
|
|
118
|
-
) : (
|
|
119
|
-
<>
|
|
120
|
-
<Icon name="bell" size={20} className="mr-4" />
|
|
121
|
-
<span>{t('product.add_stock_alert')}</span>
|
|
122
|
-
</>
|
|
123
|
-
)}
|
|
124
|
-
</Button>
|
|
125
|
-
|
|
126
|
-
<PluginModule
|
|
127
|
-
component={Component.AkifastCheckoutButton}
|
|
128
|
-
props={{
|
|
129
|
-
...checkoutProviderProps,
|
|
130
|
-
isPdp: true
|
|
131
|
-
}}
|
|
132
|
-
/>
|
|
133
|
-
|
|
134
|
-
<PluginModule
|
|
135
|
-
component={Component.OneClickCheckoutButtons}
|
|
136
|
-
props={checkoutProviderProps}
|
|
137
|
-
/>
|
|
138
|
-
|
|
139
|
-
<Modal
|
|
140
|
-
portalId="stock-alert-modal"
|
|
141
|
-
open={isModalOpen}
|
|
142
|
-
setOpen={onCloseModal}
|
|
143
|
-
showCloseButton={false}
|
|
144
|
-
className="w-5/6 md:max-w-md"
|
|
145
|
-
>
|
|
146
|
-
<div className="flex flex-col items-center justify-center gap-4 px-6 py-9">
|
|
147
|
-
<Icon name="bell" size={48} />
|
|
148
|
-
<h2 className="text-xl font-semibold">
|
|
149
|
-
{t('product.stock_alert.title')}
|
|
150
|
-
</h2>
|
|
151
|
-
<div className="max-w-40 text-xs text-center leading-4">
|
|
152
|
-
<p>{stockAlertResponseMessage}</p>
|
|
153
|
-
</div>
|
|
154
|
-
<Button
|
|
155
|
-
onClick={onCloseModal}
|
|
156
|
-
appearance="outlined"
|
|
157
|
-
className="font-semibold px-10 h-12"
|
|
158
|
-
>
|
|
159
|
-
{t('product.stock_alert.close_button')}
|
|
160
|
-
</Button>
|
|
161
|
-
</div>
|
|
162
|
-
</Modal>
|
|
163
|
-
</>
|
|
164
|
-
);
|
|
165
|
-
};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import Share from '@theme/views/share';
|
|
3
|
-
import { useLocalization } from '@akinon/next/hooks';
|
|
4
|
-
|
|
5
|
-
interface ProductShareProps {
|
|
6
|
-
productName: string;
|
|
7
|
-
className?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const ProductShare: React.FC<ProductShareProps> = ({
|
|
11
|
-
productName,
|
|
12
|
-
className
|
|
13
|
-
}) => {
|
|
14
|
-
const { t } = useLocalization();
|
|
15
|
-
const [currentUrl, setCurrentUrl] = useState<string | null>(null);
|
|
16
|
-
|
|
17
|
-
useEffect(() => {
|
|
18
|
-
setCurrentUrl(window.location.href);
|
|
19
|
-
}, []);
|
|
20
|
-
|
|
21
|
-
if (!currentUrl) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const shareItems = [
|
|
26
|
-
{
|
|
27
|
-
href: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
|
|
28
|
-
currentUrl
|
|
29
|
-
)}`,
|
|
30
|
-
iconName: 'facebook' as const,
|
|
31
|
-
iconSize: 22
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
href: `https://twitter.com/intent/tweet?text=${encodeURIComponent(
|
|
35
|
-
currentUrl
|
|
36
|
-
)}`,
|
|
37
|
-
iconName: 'twitter' as const,
|
|
38
|
-
iconSize: 22
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
href: `https://api.whatsapp.com/send?text=${productName}%20${encodeURIComponent(
|
|
42
|
-
currentUrl
|
|
43
|
-
)}`,
|
|
44
|
-
iconName: 'whatsapp' as const,
|
|
45
|
-
iconSize: 22
|
|
46
|
-
}
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<Share
|
|
51
|
-
className={className}
|
|
52
|
-
buttonText={t('product.share')}
|
|
53
|
-
items={shareItems}
|
|
54
|
-
/>
|
|
55
|
-
);
|
|
56
|
-
};
|