@akinon/next 1.92.0-rc.9 → 1.92.0-snapshot-ZERO-3449-20250618101111
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 +33 -1180
- package/api/similar-product-list.ts +63 -0
- package/api/similar-products.ts +109 -0
- package/components/accordion.tsx +5 -20
- package/components/file-input.tsx +3 -65
- package/components/input.tsx +0 -2
- package/components/link.tsx +12 -16
- package/components/modal.tsx +16 -32
- package/components/plugin-module.tsx +3 -13
- package/components/selected-payment-option-view.tsx +0 -11
- package/data/client/similar-products.ts +122 -0
- package/data/urls.ts +5 -1
- package/hocs/server/with-segment-defaults.tsx +2 -5
- package/hooks/index.ts +2 -0
- package/hooks/use-image-cropper.ts +160 -0
- package/hooks/use-similar-products.ts +720 -0
- package/instrumentation/node.ts +13 -15
- package/lib/cache.ts +0 -2
- package/middlewares/complete-gpay.ts +1 -2
- package/middlewares/complete-masterpass.ts +1 -2
- package/middlewares/default.ts +184 -196
- package/middlewares/index.ts +1 -3
- package/middlewares/redirection-payment.ts +1 -2
- package/middlewares/saved-card-redirection.ts +1 -2
- package/middlewares/three-d-redirection.ts +1 -2
- package/middlewares/url-redirection.ts +14 -8
- package/package.json +3 -3
- package/plugins.d.ts +0 -2
- package/plugins.js +1 -3
- package/redux/middlewares/checkout.ts +2 -15
- package/redux/reducers/checkout.ts +1 -9
- package/sentry/index.ts +17 -54
- package/types/commerce/order.ts +0 -1
- package/types/index.ts +73 -26
- package/utils/app-fetch.ts +2 -2
- package/utils/image-validation.ts +303 -0
- package/utils/redirect.ts +3 -5
- package/with-pz-config.js +5 -1
- package/data/server/basket.ts +0 -72
- package/hooks/use-loyalty-availability.ts +0 -21
- package/middlewares/wallet-complete-redirection.ts +0 -179
- package/utils/redirect-ignore.ts +0 -35
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import Settings from 'settings';
|
|
3
|
+
|
|
4
|
+
export async function GET(request: NextRequest) {
|
|
5
|
+
try {
|
|
6
|
+
const { searchParams } = new URL(request.url);
|
|
7
|
+
const dynamicFilter = request.headers.get('x-search-dynamic-filter');
|
|
8
|
+
const dynamicExclude = request.headers.get('x-search-dynamic-exclude');
|
|
9
|
+
|
|
10
|
+
if (!dynamicFilter && !dynamicExclude) {
|
|
11
|
+
return NextResponse.json(
|
|
12
|
+
{
|
|
13
|
+
error:
|
|
14
|
+
'Missing x-search-dynamic-filter or x-search-dynamic-exclude header'
|
|
15
|
+
},
|
|
16
|
+
{ status: 400 }
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (Settings.commerceUrl === 'default') {
|
|
21
|
+
return NextResponse.json(
|
|
22
|
+
{ error: 'Commerce URL is not configured' },
|
|
23
|
+
{ status: 500 }
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const queryString = searchParams.toString();
|
|
28
|
+
const apiUrl = `${Settings.commerceUrl}/list${
|
|
29
|
+
queryString ? `?${queryString}` : ''
|
|
30
|
+
}`;
|
|
31
|
+
|
|
32
|
+
const headers: Record<string, string> = {
|
|
33
|
+
Accept: 'application/json',
|
|
34
|
+
'Content-Type': 'application/json'
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
if (dynamicFilter) {
|
|
38
|
+
headers['x-search-dynamic-filter'] = dynamicFilter;
|
|
39
|
+
} else if (dynamicExclude) {
|
|
40
|
+
headers['x-search-dynamic-exclude'] = dynamicExclude;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const response = await fetch(apiUrl, {
|
|
44
|
+
method: 'GET',
|
|
45
|
+
headers
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
return NextResponse.json(
|
|
50
|
+
{ error: `API request failed with status: ${response.status}` },
|
|
51
|
+
{ status: response.status }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
return NextResponse.json(data);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return NextResponse.json(
|
|
59
|
+
{ error: (error as Error).message },
|
|
60
|
+
{ status: 500 }
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import Settings from 'settings';
|
|
3
|
+
|
|
4
|
+
const IMAGE_SEARCH_API_URL = Settings.commerceUrl + '/image-search/';
|
|
5
|
+
|
|
6
|
+
const errorResponse = (message: string, status: number) => {
|
|
7
|
+
return NextResponse.json({ error: message }, { status });
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export async function GET(request: Request) {
|
|
11
|
+
const { searchParams } = new URL(request.url);
|
|
12
|
+
const limit = searchParams.get('limit') || '20';
|
|
13
|
+
const url = searchParams.get('url');
|
|
14
|
+
const excludedProductIds = searchParams.get('excluded_product_ids');
|
|
15
|
+
|
|
16
|
+
if (!url) {
|
|
17
|
+
return errorResponse('URL parameter is required', 400);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (Settings.commerceUrl === 'default') {
|
|
21
|
+
return errorResponse('Commerce URL is not configured', 500);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const apiParams = new URLSearchParams();
|
|
25
|
+
apiParams.append('limit', limit);
|
|
26
|
+
apiParams.append('url', url);
|
|
27
|
+
|
|
28
|
+
if (excludedProductIds) {
|
|
29
|
+
apiParams.append('excluded_product_ids', excludedProductIds);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch(apiUrl, {
|
|
36
|
+
method: 'GET',
|
|
37
|
+
headers: {
|
|
38
|
+
Accept: 'application/json'
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
return errorResponse(
|
|
44
|
+
`API request failed with status: ${response.status}`,
|
|
45
|
+
response.status
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const responseText = await response.text();
|
|
50
|
+
|
|
51
|
+
return NextResponse.json(JSON.parse(responseText));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return errorResponse((error as Error).message, 500);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function POST(request: Request) {
|
|
58
|
+
const { searchParams } = new URL(request.url);
|
|
59
|
+
const limit = searchParams.get('limit') || '20';
|
|
60
|
+
|
|
61
|
+
if (Settings.commerceUrl === 'default') {
|
|
62
|
+
return errorResponse('Commerce URL is not configured', 500);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let requestBody;
|
|
66
|
+
try {
|
|
67
|
+
requestBody = await request.json();
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return errorResponse('Invalid JSON in request body', 400);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!requestBody.image) {
|
|
73
|
+
return errorResponse('Image data is required in request body', 400);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const apiParams = new URLSearchParams();
|
|
77
|
+
apiParams.append('limit', limit);
|
|
78
|
+
|
|
79
|
+
const apiUrl = `${IMAGE_SEARCH_API_URL}?${apiParams.toString()}`;
|
|
80
|
+
|
|
81
|
+
const bodyData: any = { image: requestBody.image };
|
|
82
|
+
|
|
83
|
+
if (requestBody.excluded_product_ids) {
|
|
84
|
+
bodyData.excluded_product_ids = requestBody.excluded_product_ids;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetch(apiUrl, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: {
|
|
91
|
+
'Content-Type': 'application/json',
|
|
92
|
+
Accept: 'application/json'
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(bodyData)
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
return errorResponse(
|
|
99
|
+
`API request failed with status: ${response.status}`,
|
|
100
|
+
response.status
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const responseData = await response.json();
|
|
105
|
+
return NextResponse.json(responseData);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return errorResponse((error as Error).message, 500);
|
|
108
|
+
}
|
|
109
|
+
}
|
package/components/accordion.tsx
CHANGED
|
@@ -7,19 +7,15 @@ import { AccordionProps } from '../types';
|
|
|
7
7
|
|
|
8
8
|
export const Accordion = ({
|
|
9
9
|
isCollapse = false,
|
|
10
|
-
collapseClassName,
|
|
11
10
|
title,
|
|
12
11
|
subTitle,
|
|
13
12
|
icons = ['chevron-up', 'chevron-down'],
|
|
14
13
|
iconSize = 16,
|
|
15
14
|
iconColor = 'fill-[#000000]',
|
|
16
15
|
children,
|
|
17
|
-
headerClassName,
|
|
18
16
|
className,
|
|
19
17
|
titleClassName,
|
|
20
|
-
|
|
21
|
-
dataTestId,
|
|
22
|
-
contentClassName
|
|
18
|
+
dataTestId
|
|
23
19
|
}: AccordionProps) => {
|
|
24
20
|
const [collapse, setCollapse] = useState(isCollapse);
|
|
25
21
|
|
|
@@ -31,22 +27,15 @@ export const Accordion = ({
|
|
|
31
27
|
)}
|
|
32
28
|
>
|
|
33
29
|
<div
|
|
34
|
-
className=
|
|
35
|
-
'flex items-center justify-between cursor-pointer',
|
|
36
|
-
headerClassName
|
|
37
|
-
)}
|
|
30
|
+
className="flex items-center justify-between cursor-pointer"
|
|
38
31
|
onClick={() => setCollapse(!collapse)}
|
|
39
32
|
data-testid={dataTestId}
|
|
40
33
|
>
|
|
41
|
-
<div className=
|
|
34
|
+
<div className="flex flex-col">
|
|
42
35
|
{title && (
|
|
43
36
|
<h3 className={twMerge('text-sm', titleClassName)}>{title}</h3>
|
|
44
37
|
)}
|
|
45
|
-
{subTitle &&
|
|
46
|
-
<h4 className={twMerge('text-xs text-gray-700', subTitleClassName)}>
|
|
47
|
-
{subTitle}
|
|
48
|
-
</h4>
|
|
49
|
-
)}
|
|
38
|
+
{subTitle && <h4 className="text-xs text-gray-700">{subTitle}</h4>}
|
|
50
39
|
</div>
|
|
51
40
|
|
|
52
41
|
{icons && (
|
|
@@ -57,11 +46,7 @@ export const Accordion = ({
|
|
|
57
46
|
/>
|
|
58
47
|
)}
|
|
59
48
|
</div>
|
|
60
|
-
{collapse &&
|
|
61
|
-
<div className={twMerge('mt-3 text-sm', collapseClassName)}>
|
|
62
|
-
{children}
|
|
63
|
-
</div>
|
|
64
|
-
)}
|
|
49
|
+
{collapse && <div className="mt-3 text-sm">{children}</div>}
|
|
65
50
|
</div>
|
|
66
51
|
);
|
|
67
52
|
};
|
|
@@ -1,70 +1,8 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
1
|
import { forwardRef } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import { twMerge } from 'tailwind-merge';
|
|
5
|
-
import { FileInputProps } from '../types';
|
|
2
|
+
import { FileInputProps } from '../types/index';
|
|
6
3
|
|
|
7
4
|
export const FileInput = forwardRef<HTMLInputElement, FileInputProps>(
|
|
8
|
-
function
|
|
9
|
-
{
|
|
10
|
-
buttonClassName,
|
|
11
|
-
onChange,
|
|
12
|
-
fileClassName,
|
|
13
|
-
fileNameWrapperClassName,
|
|
14
|
-
fileInputClassName,
|
|
15
|
-
...props
|
|
16
|
-
},
|
|
17
|
-
ref
|
|
18
|
-
) {
|
|
19
|
-
const { t } = useLocalization();
|
|
20
|
-
const [fileNames, setFileNames] = useState<string[]>([]);
|
|
21
|
-
|
|
22
|
-
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
23
|
-
const files = Array.from(event.target.files || []);
|
|
24
|
-
setFileNames(files.map((file) => file.name));
|
|
25
|
-
|
|
26
|
-
if (onChange) {
|
|
27
|
-
onChange(event);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div className="relative">
|
|
33
|
-
<input
|
|
34
|
-
type="file"
|
|
35
|
-
{...props}
|
|
36
|
-
ref={ref}
|
|
37
|
-
className={twMerge(
|
|
38
|
-
'absolute inset-0 w-full h-full opacity-0 cursor-pointer',
|
|
39
|
-
fileInputClassName
|
|
40
|
-
)}
|
|
41
|
-
onChange={handleFileChange}
|
|
42
|
-
/>
|
|
43
|
-
<button
|
|
44
|
-
type="button"
|
|
45
|
-
className={twMerge(
|
|
46
|
-
'bg-primary text-white py-2 px-4 text-sm',
|
|
47
|
-
buttonClassName
|
|
48
|
-
)}
|
|
49
|
-
>
|
|
50
|
-
{t('common.file_input.select_file')}
|
|
51
|
-
</button>
|
|
52
|
-
<div
|
|
53
|
-
className={twMerge('mt-1 text-gray-500', fileNameWrapperClassName)}
|
|
54
|
-
>
|
|
55
|
-
{fileNames.length > 0 ? (
|
|
56
|
-
<ul className={twMerge('list-disc pl-4 text-xs', fileClassName)}>
|
|
57
|
-
{fileNames.map((name, index) => (
|
|
58
|
-
<li key={index}>{name}</li>
|
|
59
|
-
))}
|
|
60
|
-
</ul>
|
|
61
|
-
) : (
|
|
62
|
-
<span className={twMerge('text-xs', fileClassName)}>
|
|
63
|
-
{t('common.file_input.no_file')}
|
|
64
|
-
</span>
|
|
65
|
-
)}
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
5
|
+
function fileInput(props, ref) {
|
|
6
|
+
return <input type="file" {...props} ref={ref} />;
|
|
69
7
|
}
|
|
70
8
|
);
|
package/components/input.tsx
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
2
|
import { forwardRef, FocusEvent, useState, Ref } from 'react';
|
|
3
3
|
import { Controller } from 'react-hook-form';
|
|
4
|
-
|
|
5
|
-
// @ts-ignore
|
|
6
4
|
import { PatternFormat, PatternFormatProps } from 'react-number-format';
|
|
7
5
|
import { InputProps } from '../types';
|
|
8
6
|
import { twMerge } from 'tailwind-merge';
|
package/components/link.tsx
CHANGED
|
@@ -10,9 +10,7 @@ type LinkProps = Omit<
|
|
|
10
10
|
React.AnchorHTMLAttributes<HTMLAnchorElement>,
|
|
11
11
|
keyof NextLinkProps
|
|
12
12
|
> &
|
|
13
|
-
NextLinkProps
|
|
14
|
-
href: string;
|
|
15
|
-
};
|
|
13
|
+
NextLinkProps;
|
|
16
14
|
|
|
17
15
|
export const Link = ({ children, href, ...rest }: LinkProps) => {
|
|
18
16
|
const { locale, defaultLocaleValue, localeUrlStrategy } = useLocalization();
|
|
@@ -28,21 +26,19 @@ export const Link = ({ children, href, ...rest }: LinkProps) => {
|
|
|
28
26
|
return href;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return hrefWithLocale;
|
|
42
|
-
}
|
|
29
|
+
const pathnameWithoutLocale = href.replace(urlLocaleMatcherRegex, '');
|
|
30
|
+
const hrefWithLocale = `/${locale}${pathnameWithoutLocale}`;
|
|
31
|
+
|
|
32
|
+
if (localeUrlStrategy === LocaleUrlStrategy.ShowAllLocales) {
|
|
33
|
+
return hrefWithLocale;
|
|
34
|
+
} else if (
|
|
35
|
+
localeUrlStrategy === LocaleUrlStrategy.HideDefaultLocale &&
|
|
36
|
+
locale !== defaultLocaleValue
|
|
37
|
+
) {
|
|
38
|
+
return hrefWithLocale;
|
|
43
39
|
}
|
|
44
40
|
|
|
45
|
-
return href;
|
|
41
|
+
return href || '#';
|
|
46
42
|
}, [href, defaultLocaleValue, locale, localeUrlStrategy]);
|
|
47
43
|
|
|
48
44
|
return (
|
package/components/modal.tsx
CHANGED
|
@@ -4,7 +4,16 @@ import { ReactPortal } from './react-portal';
|
|
|
4
4
|
import { Icon } from './icon';
|
|
5
5
|
import { twMerge } from 'tailwind-merge';
|
|
6
6
|
import { useEffect } from 'react';
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
export interface ModalProps {
|
|
9
|
+
portalId: string;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
open?: boolean;
|
|
12
|
+
setOpen?: (open: boolean) => void;
|
|
13
|
+
title?: React.ReactNode;
|
|
14
|
+
showCloseButton?: React.ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
8
17
|
|
|
9
18
|
export const Modal = (props: ModalProps) => {
|
|
10
19
|
const {
|
|
@@ -14,14 +23,7 @@ export const Modal = (props: ModalProps) => {
|
|
|
14
23
|
setOpen,
|
|
15
24
|
title = '',
|
|
16
25
|
showCloseButton = true,
|
|
17
|
-
className
|
|
18
|
-
overlayClassName,
|
|
19
|
-
headerWrapperClassName,
|
|
20
|
-
titleClassName,
|
|
21
|
-
closeButtonClassName,
|
|
22
|
-
iconName = 'close',
|
|
23
|
-
iconSize = 16,
|
|
24
|
-
iconClassName
|
|
26
|
+
className
|
|
25
27
|
} = props;
|
|
26
28
|
|
|
27
29
|
useEffect(() => {
|
|
@@ -36,12 +38,7 @@ export const Modal = (props: ModalProps) => {
|
|
|
36
38
|
|
|
37
39
|
return (
|
|
38
40
|
<ReactPortal wrapperId={portalId}>
|
|
39
|
-
<div
|
|
40
|
-
className={twMerge(
|
|
41
|
-
'fixed top-0 left-0 w-screen h-screen bg-primary bg-opacity-60 z-50',
|
|
42
|
-
overlayClassName
|
|
43
|
-
)}
|
|
44
|
-
/>
|
|
41
|
+
<div className="fixed top-0 left-0 w-screen h-screen bg-primary bg-opacity-60 z-50" />
|
|
45
42
|
<section
|
|
46
43
|
className={twMerge(
|
|
47
44
|
'fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 bg-white',
|
|
@@ -49,28 +46,15 @@ export const Modal = (props: ModalProps) => {
|
|
|
49
46
|
)}
|
|
50
47
|
>
|
|
51
48
|
{(showCloseButton || title) && (
|
|
52
|
-
<div
|
|
53
|
-
className={
|
|
54
|
-
'flex px-6 py-4 border-b border-gray-400',
|
|
55
|
-
headerWrapperClassName
|
|
56
|
-
)}
|
|
57
|
-
>
|
|
58
|
-
{title && (
|
|
59
|
-
<h3 className={twMerge('text-lg font-light', titleClassName)}>
|
|
60
|
-
{title}
|
|
61
|
-
</h3>
|
|
62
|
-
)}
|
|
49
|
+
<div className="flex px-6 py-4 border-b border-gray-400">
|
|
50
|
+
{title && <h3 className="text-lg font-light">{title}</h3>}
|
|
63
51
|
{showCloseButton && (
|
|
64
52
|
<button
|
|
65
53
|
type="button"
|
|
66
54
|
onClick={() => setOpen(false)}
|
|
67
|
-
className=
|
|
55
|
+
className="ml-auto"
|
|
68
56
|
>
|
|
69
|
-
<Icon
|
|
70
|
-
name={iconName}
|
|
71
|
-
size={iconSize}
|
|
72
|
-
className={iconClassName}
|
|
73
|
-
/>
|
|
57
|
+
<Icon name="close" size={16} />
|
|
74
58
|
</button>
|
|
75
59
|
)}
|
|
76
60
|
</div>
|
|
@@ -20,9 +20,7 @@ enum Plugin {
|
|
|
20
20
|
B2B = 'pz-b2b',
|
|
21
21
|
Akifast = 'pz-akifast',
|
|
22
22
|
MultiBasket = 'pz-multi-basket',
|
|
23
|
-
SavedCard = 'pz-saved-card'
|
|
24
|
-
Hepsipay = 'pz-hepsipay',
|
|
25
|
-
FlowPayment = 'pz-flow-payment'
|
|
23
|
+
SavedCard = 'pz-saved-card'
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
export enum Component {
|
|
@@ -47,9 +45,7 @@ export enum Component {
|
|
|
47
45
|
AkifastQuickLoginButton = 'QuickLoginButton',
|
|
48
46
|
AkifastCheckoutButton = 'CheckoutButton',
|
|
49
47
|
MultiBasket = 'MultiBasket',
|
|
50
|
-
SavedCard = 'SavedCardOption'
|
|
51
|
-
Hepsipay = 'Hepsipay',
|
|
52
|
-
FlowPayment = 'FlowPayment'
|
|
48
|
+
SavedCard = 'SavedCardOption'
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
const PluginComponents = new Map([
|
|
@@ -82,9 +78,7 @@ const PluginComponents = new Map([
|
|
|
82
78
|
[Component.AkifastQuickLoginButton, Component.AkifastCheckoutButton]
|
|
83
79
|
],
|
|
84
80
|
[Plugin.MultiBasket, [Component.MultiBasket]],
|
|
85
|
-
[Plugin.SavedCard, [Component.SavedCard]]
|
|
86
|
-
[Plugin.Hepsipay, [Component.Hepsipay]],
|
|
87
|
-
[Plugin.FlowPayment, [Component.FlowPayment]]
|
|
81
|
+
[Plugin.SavedCard, [Component.SavedCard]]
|
|
88
82
|
]);
|
|
89
83
|
|
|
90
84
|
const getPlugin = (component: Component) => {
|
|
@@ -149,10 +143,6 @@ export default function PluginModule({
|
|
|
149
143
|
promise = import(`${'@akinon/pz-multi-basket'}`);
|
|
150
144
|
} else if (plugin === Plugin.SavedCard) {
|
|
151
145
|
promise = import(`${'@akinon/pz-saved-card'}`);
|
|
152
|
-
} else if (plugin === Plugin.Hepsipay) {
|
|
153
|
-
promise = import(`${'@akinon/pz-hepsipay'}`);
|
|
154
|
-
} else if (plugin === Plugin.FlowPayment) {
|
|
155
|
-
promise = import(`${'@akinon/pz-flow-payment'}`);
|
|
156
146
|
}
|
|
157
147
|
} catch (error) {
|
|
158
148
|
logger.error(error);
|
|
@@ -62,17 +62,6 @@ export default function SelectedPaymentOptionView({
|
|
|
62
62
|
: fallbackView;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
if (
|
|
66
|
-
payment_option?.payment_type === 'wallet' &&
|
|
67
|
-
wallet_method === 'checkout_flow'
|
|
68
|
-
) {
|
|
69
|
-
const mod = await import('@akinon/pz-flow-payment');
|
|
70
|
-
|
|
71
|
-
return typeof mod?.default === 'function'
|
|
72
|
-
? mod.default
|
|
73
|
-
: fallbackView;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
65
|
const view = paymentTypeToView[payment_option?.payment_type] || null;
|
|
77
66
|
|
|
78
67
|
if (view) {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { api } from './api';
|
|
2
|
+
import { product } from '../urls';
|
|
3
|
+
import {
|
|
4
|
+
Product,
|
|
5
|
+
Facet,
|
|
6
|
+
FacetChoice,
|
|
7
|
+
SortOption,
|
|
8
|
+
Pagination
|
|
9
|
+
} from '@akinon/next/types/commerce';
|
|
10
|
+
|
|
11
|
+
export interface SimilarProductsResponse {
|
|
12
|
+
product_ids?: number[];
|
|
13
|
+
productIds?: number[];
|
|
14
|
+
similar_products?: Array<{ product_id: number }>;
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SimilarProductsListResponse {
|
|
19
|
+
pagination: Pagination;
|
|
20
|
+
facets: Facet[];
|
|
21
|
+
sorters: SortOption[];
|
|
22
|
+
search_text: string | null;
|
|
23
|
+
products: Product[];
|
|
24
|
+
[key: string]: any;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const similarProductsApi = api.injectEndpoints({
|
|
28
|
+
endpoints: (build) => ({
|
|
29
|
+
getSimilarProductsByUrl: build.query<
|
|
30
|
+
SimilarProductsResponse,
|
|
31
|
+
{ url: string; limit?: number; excluded_product_ids?: number[] }
|
|
32
|
+
>({
|
|
33
|
+
query: ({ url, limit = 20, excluded_product_ids }) => {
|
|
34
|
+
const params = new URLSearchParams();
|
|
35
|
+
params.append('limit', String(limit));
|
|
36
|
+
params.append('url', url);
|
|
37
|
+
|
|
38
|
+
if (excluded_product_ids && excluded_product_ids.length > 0) {
|
|
39
|
+
params.append('excluded_product_ids', excluded_product_ids.join(','));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
url: `/api${product.similarProducts(params.toString())}`,
|
|
44
|
+
method: 'GET',
|
|
45
|
+
headers: {
|
|
46
|
+
Accept: 'application/json'
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}),
|
|
51
|
+
|
|
52
|
+
getSimilarProductsByImage: build.mutation<
|
|
53
|
+
SimilarProductsResponse,
|
|
54
|
+
{ image: string; limit?: number; excluded_product_ids?: number[] }
|
|
55
|
+
>({
|
|
56
|
+
query: ({ image, limit = 20, excluded_product_ids }) => {
|
|
57
|
+
const params = new URLSearchParams();
|
|
58
|
+
params.append('limit', String(limit));
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
url: `/api${product.similarProducts(params.toString())}`,
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
Accept: 'application/json'
|
|
66
|
+
},
|
|
67
|
+
body: JSON.stringify({ image, excluded_product_ids })
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}),
|
|
71
|
+
|
|
72
|
+
getSimilarProductsList: build.query<
|
|
73
|
+
SimilarProductsListResponse,
|
|
74
|
+
{
|
|
75
|
+
filter: string;
|
|
76
|
+
searchParams?: Record<string, string>;
|
|
77
|
+
isExclude?: boolean;
|
|
78
|
+
}
|
|
79
|
+
>({
|
|
80
|
+
query: ({ filter, searchParams = {}, isExclude = false }) => {
|
|
81
|
+
const params = new URLSearchParams(searchParams);
|
|
82
|
+
const queryString = params.toString();
|
|
83
|
+
|
|
84
|
+
const headerName = isExclude
|
|
85
|
+
? 'x-search-dynamic-exclude'
|
|
86
|
+
: 'x-search-dynamic-filter';
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
url: `/api${product.similarProductsList(queryString)}`,
|
|
90
|
+
headers: {
|
|
91
|
+
[headerName]: filter
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
serializeQueryArgs: ({ queryArgs }) => {
|
|
96
|
+
const { filter, searchParams = {}, isExclude = false } = queryArgs;
|
|
97
|
+
const sortedParams = Object.keys(searchParams)
|
|
98
|
+
.sort()
|
|
99
|
+
.reduce((acc, key) => {
|
|
100
|
+
acc[key] = searchParams[key];
|
|
101
|
+
return acc;
|
|
102
|
+
}, {} as Record<string, string>);
|
|
103
|
+
|
|
104
|
+
return JSON.stringify({
|
|
105
|
+
filter,
|
|
106
|
+
searchParams: sortedParams,
|
|
107
|
+
isExclude
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
}),
|
|
112
|
+
overrideExisting: true
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
export const {
|
|
116
|
+
useGetSimilarProductsByUrlQuery,
|
|
117
|
+
useGetSimilarProductsByImageMutation,
|
|
118
|
+
useGetSimilarProductsListQuery,
|
|
119
|
+
useLazyGetSimilarProductsListQuery
|
|
120
|
+
} = similarProductsApi;
|
|
121
|
+
|
|
122
|
+
export type { Product, Facet, FacetChoice, SortOption, Pagination };
|
package/data/urls.ts
CHANGED
|
@@ -182,7 +182,11 @@ export const product = {
|
|
|
182
182
|
breadcrumbUrl: (menuitemmodel: string) =>
|
|
183
183
|
`/menus/generate_breadcrumb/?item=${menuitemmodel}&generator_name=menu_item`,
|
|
184
184
|
bundleProduct: (productPk: string, queryString: string) =>
|
|
185
|
-
`/bundle-product/${productPk}/?${queryString}
|
|
185
|
+
`/bundle-product/${productPk}/?${queryString}`,
|
|
186
|
+
similarProducts: (params?: string) =>
|
|
187
|
+
`/similar-products${params ? `?${params}` : ''}`,
|
|
188
|
+
similarProductsList: (params?: string) =>
|
|
189
|
+
`/similar-product-list${params ? `?${params}` : ''}`
|
|
186
190
|
};
|
|
187
191
|
|
|
188
192
|
export const wishlist = {
|
|
@@ -72,13 +72,10 @@ const addRootLayoutProps = async (componentProps: RootLayoutProps) => {
|
|
|
72
72
|
const checkRedisVariables = () => {
|
|
73
73
|
const requiredVariableValues = [
|
|
74
74
|
process.env.CACHE_HOST,
|
|
75
|
-
process.env.CACHE_PORT
|
|
75
|
+
process.env.CACHE_PORT,
|
|
76
|
+
process.env.CACHE_SECRET
|
|
76
77
|
];
|
|
77
78
|
|
|
78
|
-
if (!settings.usePrettyUrlRoute) {
|
|
79
|
-
requiredVariableValues.push(process.env.CACHE_SECRET);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
79
|
if (
|
|
83
80
|
!requiredVariableValues.every((v) => v) &&
|
|
84
81
|
process.env.NODE_ENV === 'production'
|
package/hooks/index.ts
CHANGED