@akinon/next 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/bin/pz-check-dependencies.js +98 -0
- package/bin/pz-install-plugins.js +1 -1
- package/bin/pz-install-theme.js +23 -6
- package/bin/pz-postinstall.js +3 -2
- package/bin/pz-prebuild.js +2 -3
- package/bin/pz-predev.js +2 -3
- package/bin/run-script.js +44 -0
- package/components/icon.tsx +18 -0
- package/components/index.ts +1 -0
- package/components/lazy-component.tsx +33 -0
- package/components/loader-spinner.tsx +23 -0
- package/components/plugin-module.tsx +8 -3
- package/components/radio.tsx +18 -0
- package/components/react-portal.tsx +45 -0
- package/components/selected-payment-option-view.tsx +3 -0
- package/data/client/api.ts +51 -9
- package/data/client/b2b.ts +79 -5
- package/data/client/wishlist.ts +16 -7
- package/data/server/form.ts +22 -0
- package/data/server/index.ts +1 -0
- package/data/urls.ts +6 -1
- package/lib/cache.ts +2 -1
- package/middlewares/redirection-payment.ts +23 -6
- package/middlewares/three-d-redirection.ts +23 -1
- package/package.json +3 -2
- package/types/commerce/b2b.ts +58 -2
- package/types/commerce/form.ts +66 -0
- package/types/commerce/index.ts +1 -0
- package/types/index.ts +8 -0
- package/utils/index.ts +12 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
+
## 1.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ZERO-2314: Add endpoints for B2B Basket
|
|
8
|
+
- ZERO-2340: Add LazyComponent
|
|
9
|
+
- ZERO-2366: Add email parameter to addStockAlert query
|
|
10
|
+
- ZERO-2361: Add BKM Express to plugin module system
|
|
11
|
+
- ZERO-2365: Add dependency control script
|
|
12
|
+
- ZERO-2341: Add ReactPortal component
|
|
13
|
+
|
|
14
|
+
## 1.11.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- ZERO-2355: Add LoaderSpinner component
|
|
19
|
+
- ZERO-2305: Add endpoints for B2B Basket
|
|
20
|
+
- ZERO-2319: Show 3D & redirection payment errors
|
|
21
|
+
- ZERO-2353: Add Icon component
|
|
22
|
+
- ZERO-2357: Add Radio component
|
|
23
|
+
- ZERO-2307: Prevent multiple mutation calls
|
|
24
|
+
- ZERO-2240: Add endpoints and types for dynamic forms
|
|
25
|
+
|
|
3
26
|
## 1.10.0
|
|
4
27
|
|
|
5
28
|
### Minor Changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const semver = require('semver');
|
|
6
|
+
|
|
7
|
+
function checkDir() {
|
|
8
|
+
let currentDir = __dirname;
|
|
9
|
+
|
|
10
|
+
while (currentDir !== path.resolve(currentDir, '..')) {
|
|
11
|
+
if (
|
|
12
|
+
fs.existsSync(
|
|
13
|
+
path.join(currentDir, 'node_modules/@akinon/next/package.json')
|
|
14
|
+
)
|
|
15
|
+
) {
|
|
16
|
+
return currentDir;
|
|
17
|
+
}
|
|
18
|
+
currentDir = path.resolve(currentDir, '..');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return path.resolve(__dirname, '../../../../');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const BASE_DIR = checkDir();
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const akinonNextPackagePath = fs.existsSync(
|
|
28
|
+
path.join(BASE_DIR, 'packages/akinon-next/package.json')
|
|
29
|
+
)
|
|
30
|
+
? path.join(BASE_DIR, 'packages/akinon-next/package.json')
|
|
31
|
+
: path.join(BASE_DIR, 'node_modules/@akinon/next/package.json');
|
|
32
|
+
|
|
33
|
+
let akinonNextPackage = JSON.parse(
|
|
34
|
+
fs.readFileSync(akinonNextPackagePath, 'utf8')
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const projectZeroPwaPackagePath = path.join(BASE_DIR, 'package.json');
|
|
38
|
+
|
|
39
|
+
const projectZeroPwaPackage = JSON.parse(
|
|
40
|
+
fs.readFileSync(projectZeroPwaPackagePath, 'utf8')
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const { peerDependencies } = akinonNextPackage;
|
|
44
|
+
|
|
45
|
+
let hasErrors = false;
|
|
46
|
+
|
|
47
|
+
let errorMessages = [];
|
|
48
|
+
|
|
49
|
+
for (const dependency in peerDependencies) {
|
|
50
|
+
const requiredVersion = peerDependencies[dependency];
|
|
51
|
+
|
|
52
|
+
const installedVersion =
|
|
53
|
+
projectZeroPwaPackage.dependencies[dependency] ||
|
|
54
|
+
projectZeroPwaPackage.devDependencies[dependency];
|
|
55
|
+
|
|
56
|
+
if (!installedVersion) {
|
|
57
|
+
errorMessages.push(
|
|
58
|
+
`Dependency ${dependency} is missing in projectzeropwa.`
|
|
59
|
+
);
|
|
60
|
+
hasErrors = true;
|
|
61
|
+
} else {
|
|
62
|
+
const requiredSemver = semver.coerce(requiredVersion);
|
|
63
|
+
|
|
64
|
+
const installedSemver = semver.coerce(installedVersion);
|
|
65
|
+
|
|
66
|
+
if (!requiredSemver || !installedSemver) {
|
|
67
|
+
errorMessages.push(
|
|
68
|
+
`Invalid semver for ${dependency}: required ${requiredVersion}, found ${installedVersion}`
|
|
69
|
+
);
|
|
70
|
+
hasErrors = true;
|
|
71
|
+
} else if (
|
|
72
|
+
requiredSemver.major !== installedSemver.major ||
|
|
73
|
+
requiredSemver.minor !== installedSemver.minor
|
|
74
|
+
) {
|
|
75
|
+
errorMessages.push(
|
|
76
|
+
`Version mismatch for ${dependency}: expected ${requiredVersion} or higher but found ${installedVersion}`
|
|
77
|
+
);
|
|
78
|
+
hasErrors = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (hasErrors) {
|
|
84
|
+
console.error(
|
|
85
|
+
'\x1b[31mDependency errors found:\n' +
|
|
86
|
+
errorMessages.join('\n') +
|
|
87
|
+
'\x1b[0m'
|
|
88
|
+
);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(
|
|
93
|
+
'All dependencies are installed and compatible in projectzeropwa.'
|
|
94
|
+
);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('\x1b[31mError:', error.message + '\x1b[0m');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
package/bin/pz-install-theme.js
CHANGED
|
@@ -2,15 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const spawn = require('cross-spawn');
|
|
6
|
+
|
|
7
|
+
function findBaseDir() {
|
|
8
|
+
const insideNodeModules = __dirname.includes('node_modules');
|
|
9
|
+
|
|
10
|
+
if (insideNodeModules) {
|
|
11
|
+
return path.resolve(__dirname, '../../../../');
|
|
12
|
+
} else {
|
|
13
|
+
return path.resolve(__dirname, '../../../apps/projectzeropwa');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const BASE_DIR = findBaseDir();
|
|
5
18
|
|
|
6
|
-
const BASE_DIR =
|
|
7
|
-
path.resolve(__dirname, '../../../apps/projectzeropwa') || process.cwd();
|
|
8
19
|
const getFullPath = (relativePath) => path.join(BASE_DIR, relativePath);
|
|
9
20
|
|
|
10
|
-
|
|
21
|
+
let theme;
|
|
22
|
+
try {
|
|
23
|
+
theme = require(getFullPath('src/theme.js'));
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('Error loading theme.js:', error.message);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
11
28
|
|
|
12
29
|
try {
|
|
13
|
-
const spawn = require('cross-spawn');
|
|
14
30
|
const tsConfigPath = getFullPath('tsconfig.json');
|
|
15
31
|
|
|
16
32
|
if (!fs.existsSync(tsConfigPath)) {
|
|
@@ -31,11 +47,12 @@ try {
|
|
|
31
47
|
|
|
32
48
|
fs.writeFileSync(tsConfigPath, newContent);
|
|
33
49
|
|
|
34
|
-
if (fs.existsSync(getFullPath('
|
|
50
|
+
if (fs.existsSync(getFullPath('turbo.json'))) {
|
|
35
51
|
spawn.sync('turbo', ['clean'], {
|
|
36
52
|
stdio: 'inherit'
|
|
37
53
|
});
|
|
38
54
|
}
|
|
39
55
|
} catch (error) {
|
|
40
|
-
console.error('Error:', error.message);
|
|
56
|
+
console.error('Error modifying tsconfig.json:', error.message);
|
|
57
|
+
process.exit(1);
|
|
41
58
|
}
|
package/bin/pz-postinstall.js
CHANGED
package/bin/pz-prebuild.js
CHANGED
package/bin/pz-predev.js
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const spawn = require('cross-spawn');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
function runScript(scriptName) {
|
|
6
|
+
let currentPath;
|
|
7
|
+
|
|
8
|
+
const monorepoPath = path.join(
|
|
9
|
+
process.cwd(),
|
|
10
|
+
'../../packages/akinon-next/bin',
|
|
11
|
+
scriptName
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const standardRepoPath = path.join(
|
|
15
|
+
process.cwd(),
|
|
16
|
+
'node_modules/@akinon/next/bin',
|
|
17
|
+
scriptName
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (fs.existsSync(monorepoPath)) {
|
|
21
|
+
currentPath = monorepoPath;
|
|
22
|
+
} else if (fs.existsSync(standardRepoPath)) {
|
|
23
|
+
currentPath = standardRepoPath;
|
|
24
|
+
} else {
|
|
25
|
+
console.error(`Unable to find the ${scriptName} file`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const result = spawn.sync('node', [currentPath], { stdio: 'inherit' });
|
|
30
|
+
|
|
31
|
+
if (result.error) {
|
|
32
|
+
console.error(`Error executing ${scriptName}:`, result.error);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (result.status !== 0) {
|
|
37
|
+
console.error(
|
|
38
|
+
`Script ${scriptName} failed with exit code ${result.status}`
|
|
39
|
+
);
|
|
40
|
+
process.exit(result.status);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = runScript;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IconProps } from '../types/index';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
|
|
4
|
+
export const Icon = (props: IconProps) => {
|
|
5
|
+
const { name, size, className, ...rest } = props;
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<i
|
|
9
|
+
className={clsx(`flex pz-icon-${name}`, className)}
|
|
10
|
+
{...rest}
|
|
11
|
+
style={
|
|
12
|
+
size && {
|
|
13
|
+
fontSize: `${size}px`
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './react-portal';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
import { useInView } from 'react-intersection-observer';
|
|
6
|
+
|
|
7
|
+
interface LazyComponentProps {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function LazyComponent({
|
|
13
|
+
children,
|
|
14
|
+
className
|
|
15
|
+
}: LazyComponentProps) {
|
|
16
|
+
const [isInView, setIsInView] = useState(false);
|
|
17
|
+
|
|
18
|
+
const { ref, inView } = useInView({
|
|
19
|
+
threshold: 0
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (inView) {
|
|
24
|
+
setIsInView(true);
|
|
25
|
+
}
|
|
26
|
+
}, [inView]);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div ref={ref} className={clsx(className)}>
|
|
30
|
+
{isInView ? <>{children}</> : null}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { twMerge } from 'tailwind-merge';
|
|
2
|
+
|
|
3
|
+
type LoaderSpinnerProps = {
|
|
4
|
+
className?: string;
|
|
5
|
+
borderType?: 'solid' | 'dotted' | 'dashed';
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const LoaderSpinner: React.FC<LoaderSpinnerProps> = ({
|
|
9
|
+
borderType = 'solid',
|
|
10
|
+
className
|
|
11
|
+
}) => {
|
|
12
|
+
return (
|
|
13
|
+
<div className="w-full h-full flex justify-center items-center">
|
|
14
|
+
<div
|
|
15
|
+
className={twMerge(
|
|
16
|
+
'w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin',
|
|
17
|
+
`border-${borderType}`,
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -10,7 +10,8 @@ enum Plugin {
|
|
|
10
10
|
PayOnDelivery = 'pz-pay-on-delivery',
|
|
11
11
|
CheckoutGiftPack = 'pz-checkout-gift-pack',
|
|
12
12
|
GPay = 'pz-gpay',
|
|
13
|
-
Otp = 'pz-otp'
|
|
13
|
+
Otp = 'pz-otp',
|
|
14
|
+
BKMExpress = 'pz-bkm'
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export enum Component {
|
|
@@ -20,7 +21,8 @@ export enum Component {
|
|
|
20
21
|
PayOnDelivery = 'PayOnDelivery',
|
|
21
22
|
CheckoutGiftPack = 'CheckoutGiftPack',
|
|
22
23
|
GPay = 'GPayOption',
|
|
23
|
-
Otp = 'Otp'
|
|
24
|
+
Otp = 'Otp',
|
|
25
|
+
BKMExpress = 'BKMOption'
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const PluginComponents = new Map([
|
|
@@ -30,7 +32,8 @@ const PluginComponents = new Map([
|
|
|
30
32
|
[Plugin.PayOnDelivery, [Component.PayOnDelivery]],
|
|
31
33
|
[Plugin.CheckoutGiftPack, [Component.CheckoutGiftPack]],
|
|
32
34
|
[Plugin.GPay, [Component.GPay]],
|
|
33
|
-
[Plugin.Otp, [Component.Otp]]
|
|
35
|
+
[Plugin.Otp, [Component.Otp]],
|
|
36
|
+
[Plugin.BKMExpress, [Component.BKMExpress]]
|
|
34
37
|
]);
|
|
35
38
|
|
|
36
39
|
const getPlugin = (component: Component) => {
|
|
@@ -75,6 +78,8 @@ export default function PluginModule({
|
|
|
75
78
|
promise = import(`${'pz-gpay'}`);
|
|
76
79
|
} else if (plugin === Plugin.Otp) {
|
|
77
80
|
promise = import(`${'pz-otp'}`);
|
|
81
|
+
} else if (plugin === Plugin.BKMExpress) {
|
|
82
|
+
promise = import(`${'pz-bkm'}`);
|
|
78
83
|
}
|
|
79
84
|
} catch (error) {
|
|
80
85
|
logger.error(error);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { RadioProps } from '../types/index';
|
|
3
|
+
import { twMerge } from 'tailwind-merge';
|
|
4
|
+
|
|
5
|
+
const Radio = forwardRef<HTMLInputElement, RadioProps>((props, ref) => {
|
|
6
|
+
const { children, ...rest } = props;
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<label className={twMerge('flex items-center text-xs', props.className)}>
|
|
10
|
+
<input type="radio" {...rest} ref={ref} className="w-4 h-4" />
|
|
11
|
+
{children && <span className="text-xs ml-2">{children}</span>}
|
|
12
|
+
</label>
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
Radio.displayName = 'Radio';
|
|
17
|
+
|
|
18
|
+
export { Radio };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
|
|
4
|
+
function createWrapperAndAppendToBody(wrapperId: string) {
|
|
5
|
+
const wrapperElement = document.createElement('div');
|
|
6
|
+
wrapperElement.setAttribute('id', wrapperId);
|
|
7
|
+
document.body.appendChild(wrapperElement);
|
|
8
|
+
return wrapperElement;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
wrapperId: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const ReactPortal: React.FC<Props> = ({
|
|
17
|
+
children,
|
|
18
|
+
wrapperId = 'react-portal-wrapper'
|
|
19
|
+
}) => {
|
|
20
|
+
const [wrapperElement, setWrapperElement] = useState<HTMLElement | null>(
|
|
21
|
+
null
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
let element = document.getElementById(wrapperId) as HTMLElement;
|
|
26
|
+
let modalCreated = false;
|
|
27
|
+
|
|
28
|
+
if (!element) {
|
|
29
|
+
modalCreated = true;
|
|
30
|
+
element = createWrapperAndAppendToBody(wrapperId);
|
|
31
|
+
}
|
|
32
|
+
setWrapperElement(element);
|
|
33
|
+
|
|
34
|
+
return () => {
|
|
35
|
+
// delete the programatically created element if it was created
|
|
36
|
+
if (modalCreated && element.parentNode) {
|
|
37
|
+
element.parentNode.removeChild(element);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}, [wrapperId]);
|
|
41
|
+
|
|
42
|
+
if (wrapperElement === null) return null;
|
|
43
|
+
|
|
44
|
+
return createPortal(children, wrapperElement);
|
|
45
|
+
};
|
|
@@ -54,6 +54,9 @@ export default function SelectedPaymentOptionView() {
|
|
|
54
54
|
// else if (payment_option.payment_type === 'gpay') {
|
|
55
55
|
// promise = import(`views/checkout/steps/payment/options/gpay`);
|
|
56
56
|
// }
|
|
57
|
+
// else if (payment_option.payment_type === 'bkm_express') {
|
|
58
|
+
// promise = import(`views/checkout/steps/payment/options/bkm`);
|
|
59
|
+
// }
|
|
57
60
|
} catch (error) {}
|
|
58
61
|
|
|
59
62
|
return promise
|
package/data/client/api.ts
CHANGED
|
@@ -1,10 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createApi,
|
|
3
|
+
fetchBaseQuery,
|
|
4
|
+
retry,
|
|
5
|
+
BaseQueryFn,
|
|
6
|
+
FetchBaseQueryError,
|
|
7
|
+
FetchBaseQueryMeta,
|
|
8
|
+
FetchArgs,
|
|
9
|
+
BaseQueryApi
|
|
10
|
+
} from '@reduxjs/toolkit/query/react';
|
|
2
11
|
import settings from 'settings';
|
|
3
12
|
import { getCookie } from '../../utils';
|
|
13
|
+
import { RootState } from '@theme/redux/store';
|
|
4
14
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
15
|
+
interface CustomBaseQueryApi extends BaseQueryApi {
|
|
16
|
+
getState: () => RootState;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const customBaseQuery: BaseQueryFn<
|
|
20
|
+
string | FetchArgs,
|
|
21
|
+
unknown,
|
|
22
|
+
FetchBaseQueryError,
|
|
23
|
+
{},
|
|
24
|
+
FetchBaseQueryMeta
|
|
25
|
+
> = async (args, api: CustomBaseQueryApi, extraOptions) => {
|
|
26
|
+
const mutations = Object.entries(api.getState()?.api?.mutations ?? {}).map(
|
|
27
|
+
(x) => x[1]
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
api.type === 'mutation' &&
|
|
32
|
+
mutations.filter(
|
|
33
|
+
(m) => m.status === 'pending' && m.endpointName === api.endpoint
|
|
34
|
+
).length > 1
|
|
35
|
+
) {
|
|
36
|
+
api.abort('Mutation already in progress.');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const baseQuery = fetchBaseQuery({
|
|
8
40
|
prepareHeaders: async (headers) => {
|
|
9
41
|
const csrfCookie = getCookie('csrftoken');
|
|
10
42
|
const activeLocale = getCookie('pz-locale');
|
|
@@ -19,22 +51,32 @@ export const api = createApi({
|
|
|
19
51
|
if (csrfCookie) {
|
|
20
52
|
headers.set('x-csrftoken', `${csrfCookie}`);
|
|
21
53
|
}
|
|
22
|
-
|
|
23
54
|
return headers;
|
|
24
55
|
},
|
|
25
56
|
credentials: 'include'
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
|
|
57
|
+
});
|
|
58
|
+
try {
|
|
59
|
+
const result = await baseQuery(args, api, extraOptions);
|
|
60
|
+
return result;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return { error };
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const api = createApi({
|
|
67
|
+
reducerPath: 'api',
|
|
68
|
+
baseQuery: retry(customBaseQuery, { maxRetries: 3 }),
|
|
29
69
|
tagTypes: [
|
|
30
70
|
'Basket',
|
|
71
|
+
'BasketB2b',
|
|
72
|
+
'DraftsB2b',
|
|
31
73
|
'Product',
|
|
32
74
|
'Checkout',
|
|
33
75
|
'Favorite',
|
|
34
76
|
'Addresses',
|
|
35
77
|
'Profile'
|
|
36
78
|
],
|
|
37
|
-
endpoints: () => ({})
|
|
79
|
+
endpoints: () => ({})
|
|
38
80
|
});
|
|
39
81
|
|
|
40
82
|
export const {
|
package/data/client/b2b.ts
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
import { buildClientRequestUrl } from '../../utils';
|
|
2
2
|
import { api } from './api';
|
|
3
3
|
import { b2b } from '../urls';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Basket,
|
|
6
|
+
BasketParams,
|
|
7
|
+
BasketResponse,
|
|
8
|
+
Division,
|
|
9
|
+
DraftResponse,
|
|
10
|
+
GetResponse,
|
|
11
|
+
LoadBasketParams,
|
|
12
|
+
SaveBasketParams,
|
|
13
|
+
UpdateProductParams,
|
|
14
|
+
DeleteProductParams,
|
|
15
|
+
CreateQuotationParams
|
|
16
|
+
} from '../../types';
|
|
5
17
|
|
|
6
18
|
const b2bApi = api.injectEndpoints({
|
|
7
19
|
endpoints: (build) => ({
|
|
8
|
-
|
|
20
|
+
getBasketB2b: build.query<BasketResponse, void>({
|
|
9
21
|
query: () =>
|
|
10
22
|
buildClientRequestUrl(b2b.basket, {
|
|
11
23
|
contentType: 'application/json'
|
|
12
24
|
}),
|
|
13
|
-
|
|
14
|
-
providesTags: ['Basket']
|
|
25
|
+
providesTags: ['BasketB2b']
|
|
15
26
|
}),
|
|
16
27
|
getDivisions: build.query<GetResponse<Division>, void>({
|
|
17
28
|
query: () => buildClientRequestUrl(b2b.divisions)
|
|
@@ -25,8 +36,71 @@ const b2bApi = api.injectEndpoints({
|
|
|
25
36
|
body
|
|
26
37
|
})
|
|
27
38
|
}),
|
|
39
|
+
saveBasket: build.mutation<BasketResponse, SaveBasketParams>({
|
|
40
|
+
query: (body) => ({
|
|
41
|
+
url: buildClientRequestUrl(b2b.saveBasket, {
|
|
42
|
+
contentType: 'application/json'
|
|
43
|
+
}),
|
|
44
|
+
method: 'POST',
|
|
45
|
+
body
|
|
46
|
+
}),
|
|
47
|
+
invalidatesTags: ['BasketB2b', 'DraftsB2b']
|
|
48
|
+
}),
|
|
49
|
+
getDrafts: build.query<DraftResponse[], void>({
|
|
50
|
+
query: () => buildClientRequestUrl(b2b.draftBaskets),
|
|
51
|
+
providesTags: ['DraftsB2b']
|
|
52
|
+
}),
|
|
53
|
+
loadBasket: build.mutation<string, number>({
|
|
54
|
+
query: (id) => ({
|
|
55
|
+
url: buildClientRequestUrl(b2b.loadBasket(id), {
|
|
56
|
+
contentType: 'application/json'
|
|
57
|
+
}),
|
|
58
|
+
method: 'POST'
|
|
59
|
+
}),
|
|
60
|
+
invalidatesTags: ['BasketB2b']
|
|
61
|
+
}),
|
|
62
|
+
updateProduct: build.mutation<Basket, UpdateProductParams>({
|
|
63
|
+
query: (body) => ({
|
|
64
|
+
url: buildClientRequestUrl(b2b.basket, {
|
|
65
|
+
contentType: 'application/json'
|
|
66
|
+
}),
|
|
67
|
+
method: 'PUT',
|
|
68
|
+
body
|
|
69
|
+
}),
|
|
70
|
+
invalidatesTags: ['BasketB2b']
|
|
71
|
+
}),
|
|
72
|
+
deleteProduct: build.mutation<Basket, DeleteProductParams>({
|
|
73
|
+
query: (body) => ({
|
|
74
|
+
url: buildClientRequestUrl(b2b.basket, {
|
|
75
|
+
contentType: 'application/json'
|
|
76
|
+
}),
|
|
77
|
+
method: 'DELETE',
|
|
78
|
+
body
|
|
79
|
+
}),
|
|
80
|
+
invalidatesTags: ['BasketB2b']
|
|
81
|
+
}),
|
|
82
|
+
createQuotation: build.mutation<BasketResponse, CreateQuotationParams>({
|
|
83
|
+
query: (body) => ({
|
|
84
|
+
url: buildClientRequestUrl(b2b.myQuotations, {
|
|
85
|
+
contentType: 'application/json'
|
|
86
|
+
}),
|
|
87
|
+
method: 'POST',
|
|
88
|
+
body
|
|
89
|
+
}),
|
|
90
|
+
invalidatesTags: ['BasketB2b', 'DraftsB2b']
|
|
91
|
+
}),
|
|
28
92
|
}),
|
|
29
93
|
overrideExisting: true
|
|
30
94
|
});
|
|
31
95
|
|
|
32
|
-
export const {
|
|
96
|
+
export const {
|
|
97
|
+
useGetBasketB2bQuery,
|
|
98
|
+
useLazyGetDivisionsQuery,
|
|
99
|
+
useAddToBasketMutation,
|
|
100
|
+
useSaveBasketMutation,
|
|
101
|
+
useGetDraftsQuery,
|
|
102
|
+
useLoadBasketMutation,
|
|
103
|
+
useUpdateProductMutation,
|
|
104
|
+
useDeleteProductMutation,
|
|
105
|
+
useCreateQuotationMutation
|
|
106
|
+
} = b2bApi;
|
package/data/client/wishlist.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FavoriteItem } from '../../types';
|
|
2
2
|
import { buildClientRequestUrl } from '../../utils';
|
|
3
3
|
import { api } from './api';
|
|
4
|
-
import {
|
|
4
|
+
import { wishlist } from '../urls';
|
|
5
5
|
|
|
6
6
|
export type AddProductRequest = {
|
|
7
7
|
product: number;
|
|
@@ -28,6 +28,16 @@ interface RemoteFavoriteResponse {
|
|
|
28
28
|
success: boolean;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
interface AddStockAlertRequest {
|
|
32
|
+
productPk: number;
|
|
33
|
+
email?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface AddStockAlertResponse {
|
|
37
|
+
pk: number;
|
|
38
|
+
product: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
export const wishlistApi = api.injectEndpoints({
|
|
32
42
|
endpoints: (build) => ({
|
|
33
43
|
getFavorites: build.query<GetFavoritesResponse, GetFavoritesParams>({
|
|
@@ -54,16 +64,15 @@ export const wishlistApi = api.injectEndpoints({
|
|
|
54
64
|
}),
|
|
55
65
|
invalidatesTags: ['Favorite']
|
|
56
66
|
}),
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
query: (productPk: number) => ({
|
|
67
|
+
addStockAlert: build.mutation<AddStockAlertResponse, AddStockAlertRequest>({
|
|
68
|
+
query: ({ productPk, email }) => ({
|
|
60
69
|
url: buildClientRequestUrl(wishlist.addStockAlert, {
|
|
61
|
-
useFormData: true
|
|
62
|
-
contentType: 'application/json'
|
|
70
|
+
useFormData: true
|
|
63
71
|
}),
|
|
64
72
|
method: 'POST',
|
|
65
73
|
body: {
|
|
66
|
-
product: productPk
|
|
74
|
+
product: productPk,
|
|
75
|
+
...(email ? { email } : {})
|
|
67
76
|
}
|
|
68
77
|
})
|
|
69
78
|
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Cache, CacheKey } from "../../lib/cache";
|
|
2
|
+
import { FormType } from "../../types/commerce/form";
|
|
3
|
+
|
|
4
|
+
import appFetch from "../../utils/app-fetch";
|
|
5
|
+
import { form } from "../urls";
|
|
6
|
+
|
|
7
|
+
const getFormDataHandler = (pk: number) => {
|
|
8
|
+
return async function () {
|
|
9
|
+
const data = await appFetch<FormType>(form.getForm(pk), {
|
|
10
|
+
headers: {
|
|
11
|
+
Accept: 'application/json',
|
|
12
|
+
'Content-Type': 'application/json'
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return data;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const getFormData = ({ pk }: { pk: number }) => {
|
|
21
|
+
return Cache.wrap(CacheKey.Form(pk), getFormDataHandler(pk));
|
|
22
|
+
};
|
package/data/server/index.ts
CHANGED
package/data/urls.ts
CHANGED
|
@@ -169,6 +169,10 @@ export const widgets = {
|
|
|
169
169
|
getWidget: (slug: string) => `/widgets/${slug}/`
|
|
170
170
|
};
|
|
171
171
|
|
|
172
|
+
export const form = {
|
|
173
|
+
getForm: (pk: number) => `/forms/${pk}/generate/`,
|
|
174
|
+
};
|
|
175
|
+
|
|
172
176
|
const URLS = {
|
|
173
177
|
account,
|
|
174
178
|
address,
|
|
@@ -180,7 +184,8 @@ const URLS = {
|
|
|
180
184
|
product,
|
|
181
185
|
wishlist,
|
|
182
186
|
user,
|
|
183
|
-
widgets
|
|
187
|
+
widgets,
|
|
188
|
+
form
|
|
184
189
|
};
|
|
185
190
|
|
|
186
191
|
const UrlProxyHandler = {
|
package/lib/cache.ts
CHANGED
|
@@ -51,7 +51,8 @@ export const CacheKey = {
|
|
|
51
51
|
Menu: (depth: number, parent?: string) =>
|
|
52
52
|
`menu_${depth}${parent ? `_${parent}` : ''}`,
|
|
53
53
|
Seo: (url: string) => `seo_${url}`,
|
|
54
|
-
RootSeo: 'root_seo'
|
|
54
|
+
RootSeo: 'root_seo',
|
|
55
|
+
Form: (pk: number) => `form_${pk}`
|
|
55
56
|
};
|
|
56
57
|
|
|
57
58
|
export class Cache {
|
|
@@ -4,7 +4,6 @@ import Settings from 'settings';
|
|
|
4
4
|
import logger from '../utils/log';
|
|
5
5
|
import { PzNextRequest } from '.';
|
|
6
6
|
import { getUrlPathWithLocale } from '../utils/localization';
|
|
7
|
-
import { urlLocaleMatcherRegex } from '../utils';
|
|
8
7
|
|
|
9
8
|
const streamToString = async (stream: ReadableStream<Uint8Array> | null) => {
|
|
10
9
|
if (stream) {
|
|
@@ -33,10 +32,6 @@ const withRedirectionPayment =
|
|
|
33
32
|
(middleware: NextMiddleware) =>
|
|
34
33
|
async (req: PzNextRequest, event: NextFetchEvent) => {
|
|
35
34
|
const url = req.nextUrl.clone();
|
|
36
|
-
const pathnameWithoutLocale = url.pathname.replace(
|
|
37
|
-
urlLocaleMatcherRegex,
|
|
38
|
-
''
|
|
39
|
-
);
|
|
40
35
|
const searchParams = new URLSearchParams(url.search);
|
|
41
36
|
const ip = req.headers.get('x-forwarded-for') ?? '';
|
|
42
37
|
|
|
@@ -70,12 +65,34 @@ const withRedirectionPayment =
|
|
|
70
65
|
|
|
71
66
|
const response = await request.json();
|
|
72
67
|
|
|
73
|
-
const { context_list: contextList } = response;
|
|
68
|
+
const { context_list: contextList, errors } = response;
|
|
74
69
|
const redirectionContext = contextList?.find(
|
|
75
70
|
(context) => context.page_context?.redirect_url
|
|
76
71
|
);
|
|
77
72
|
const redirectUrl = redirectionContext?.page_context?.redirect_url;
|
|
78
73
|
|
|
74
|
+
if (errors && Object.keys(errors).length) {
|
|
75
|
+
logger.error('Error while completing redirection payment', {
|
|
76
|
+
middleware: 'redirection-payment',
|
|
77
|
+
errors,
|
|
78
|
+
requestHeaders,
|
|
79
|
+
ip
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return NextResponse.redirect(
|
|
83
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
84
|
+
'/orders/checkout/',
|
|
85
|
+
req.cookies.get('pz-locale')?.value
|
|
86
|
+
)}`,
|
|
87
|
+
{
|
|
88
|
+
status: 303,
|
|
89
|
+
headers: {
|
|
90
|
+
'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
79
96
|
logger.info('Order success page context list', {
|
|
80
97
|
middleware: 'redirection-payment',
|
|
81
98
|
contextList,
|
|
@@ -64,12 +64,34 @@ const withThreeDRedirection =
|
|
|
64
64
|
|
|
65
65
|
const response = await request.json();
|
|
66
66
|
|
|
67
|
-
const { context_list: contextList } = response;
|
|
67
|
+
const { context_list: contextList, errors } = response;
|
|
68
68
|
const redirectionContext = contextList?.find(
|
|
69
69
|
(context) => context.page_context?.redirect_url
|
|
70
70
|
);
|
|
71
71
|
const redirectUrl = redirectionContext?.page_context?.redirect_url;
|
|
72
72
|
|
|
73
|
+
if (errors && Object.keys(errors).length) {
|
|
74
|
+
logger.error('Error while completing 3D payment', {
|
|
75
|
+
middleware: 'three-d-redirection',
|
|
76
|
+
errors,
|
|
77
|
+
requestHeaders,
|
|
78
|
+
ip
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return NextResponse.redirect(
|
|
82
|
+
`${url.origin}${getUrlPathWithLocale(
|
|
83
|
+
'/orders/checkout/',
|
|
84
|
+
req.cookies.get('pz-locale')?.value
|
|
85
|
+
)}`,
|
|
86
|
+
{
|
|
87
|
+
status: 303,
|
|
88
|
+
headers: {
|
|
89
|
+
'Set-Cookie': `pz-pos-error=${JSON.stringify(errors)}; path=/;`
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
73
95
|
logger.info('Order success page context list', {
|
|
74
96
|
middleware: 'three-d-redirection',
|
|
75
97
|
contextList,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/next",
|
|
3
3
|
"description": "Core package for Project Zero Next",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.12.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"cross-spawn": "7.0.3",
|
|
19
19
|
"react-redux": "8.1.3",
|
|
20
20
|
"react-string-replace": "1.1.1",
|
|
21
|
-
"redis": "4.5.1"
|
|
21
|
+
"redis": "4.5.1",
|
|
22
|
+
"semver": "7.5.4"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
"@types/react-redux": "7.1.30"
|
package/types/commerce/b2b.ts
CHANGED
|
@@ -22,7 +22,11 @@ export type Division = {
|
|
|
22
22
|
extra_data: object;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
interface ProductB2b extends Product {
|
|
25
|
+
export interface ProductB2b extends Product {
|
|
26
|
+
sku: string;
|
|
27
|
+
name: string;
|
|
28
|
+
price: string;
|
|
29
|
+
base_code: string;
|
|
26
30
|
asorti: string;
|
|
27
31
|
category: string;
|
|
28
32
|
currency: string;
|
|
@@ -41,21 +45,73 @@ export type BasketResponse = {
|
|
|
41
45
|
total_amount: string;
|
|
42
46
|
price: string;
|
|
43
47
|
quantity: number;
|
|
44
|
-
divisions:
|
|
48
|
+
divisions: BasketItemDivision[];
|
|
45
49
|
product: ProductB2b;
|
|
50
|
+
product_remote_id: number;
|
|
46
51
|
}
|
|
47
52
|
];
|
|
48
53
|
};
|
|
49
54
|
|
|
55
|
+
export interface QuotationErrorType {
|
|
56
|
+
data: {
|
|
57
|
+
non_field_errors: string[];
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface BasketItemDivision {
|
|
62
|
+
id: number;
|
|
63
|
+
name: string;
|
|
64
|
+
erp_code: string;
|
|
65
|
+
quantity: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type BasketItemType = {
|
|
69
|
+
total_amount: string;
|
|
70
|
+
price: string;
|
|
71
|
+
quantity: number;
|
|
72
|
+
divisions: BasketItemDivision[];
|
|
73
|
+
product: ProductB2b;
|
|
74
|
+
product_remote_id: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
50
77
|
export type BasketParams = {
|
|
51
78
|
division: string;
|
|
52
79
|
product_remote_id: string;
|
|
53
80
|
quantity: string;
|
|
54
81
|
};
|
|
55
82
|
|
|
83
|
+
export type SaveBasketParams = {
|
|
84
|
+
name: string;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export type CreateQuotationParams = {
|
|
88
|
+
name: string;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type UpdateProductParams = {
|
|
92
|
+
product_remote_id: number;
|
|
93
|
+
division: number;
|
|
94
|
+
quantity: number
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type DeleteProductParams = {
|
|
98
|
+
product_remote_id: number;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type LoadBasketParams = {
|
|
102
|
+
id: number;
|
|
103
|
+
};
|
|
104
|
+
|
|
56
105
|
export interface GetResponse<T> {
|
|
57
106
|
count: number;
|
|
58
107
|
next: null;
|
|
59
108
|
previous: null;
|
|
60
109
|
results: T[];
|
|
61
110
|
}
|
|
111
|
+
|
|
112
|
+
export type DraftResponse = {
|
|
113
|
+
id: number;
|
|
114
|
+
name: string;
|
|
115
|
+
total_amount: number;
|
|
116
|
+
total_quantity: number;
|
|
117
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { InputHTMLAttributes, HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
export type Validator = {
|
|
4
|
+
regex: {
|
|
5
|
+
regex: string;
|
|
6
|
+
message: string;
|
|
7
|
+
inverse_match: boolean;
|
|
8
|
+
};
|
|
9
|
+
max_length: number;
|
|
10
|
+
required: boolean;
|
|
11
|
+
min_length: number;
|
|
12
|
+
max_value: number;
|
|
13
|
+
min_value: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type FormField = {
|
|
17
|
+
chosen: boolean;
|
|
18
|
+
input_type: string;
|
|
19
|
+
id: string;
|
|
20
|
+
key: string;
|
|
21
|
+
validators: Validator;
|
|
22
|
+
label: string;
|
|
23
|
+
order: number;
|
|
24
|
+
class?: string;
|
|
25
|
+
attributes?: object | null;
|
|
26
|
+
labelClass?: string;
|
|
27
|
+
wrapperClass?: string;
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
choices?: string[];
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type Schema = FormField[];
|
|
34
|
+
|
|
35
|
+
export type FormType = {
|
|
36
|
+
pk: number;
|
|
37
|
+
schema: Schema;
|
|
38
|
+
template: string;
|
|
39
|
+
is_active: boolean;
|
|
40
|
+
url: string;
|
|
41
|
+
name: string;
|
|
42
|
+
pretty_url: any;
|
|
43
|
+
created_date: string;
|
|
44
|
+
modified_date: string;
|
|
45
|
+
formprettyurl_set: any[];
|
|
46
|
+
translations: null | any;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type FieldPropertiesType = {
|
|
50
|
+
key?: string;
|
|
51
|
+
className?: string | Record<string, boolean>;
|
|
52
|
+
attributes?: InputHTMLAttributes<object> | HTMLAttributes<object>;
|
|
53
|
+
labelClassName?: string;
|
|
54
|
+
wrapperClassName?: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type AllFieldClassesType = {
|
|
58
|
+
className?: string | undefined;
|
|
59
|
+
labelClassName?: string | undefined;
|
|
60
|
+
wrapperClassName?: string | undefined;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type FormPropertiesType = {
|
|
64
|
+
actionUrl: string;
|
|
65
|
+
className?: string;
|
|
66
|
+
};
|
package/types/commerce/index.ts
CHANGED
package/types/index.ts
CHANGED
|
@@ -222,7 +222,15 @@ export interface RootLayoutProps<
|
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
export type RadioProps = React.HTMLProps<HTMLInputElement>;
|
|
226
|
+
|
|
225
227
|
export interface AuthError {
|
|
226
228
|
type: string;
|
|
227
229
|
data?: any;
|
|
228
230
|
}
|
|
231
|
+
|
|
232
|
+
export interface IconProps extends React.ComponentPropsWithRef<'i'> {
|
|
233
|
+
name: string;
|
|
234
|
+
size?: number;
|
|
235
|
+
className?: string;
|
|
236
|
+
}
|
package/utils/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ export function setCookie(name: string, val: string) {
|
|
|
32
32
|
export function removeCookie(name: string) {
|
|
33
33
|
const date = 'Thu, 01 Jan 1970 00:00:00 UTC';
|
|
34
34
|
|
|
35
|
-
document.cookie = name
|
|
35
|
+
document.cookie = `${name}=; expires=${date}; path=/;`;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -137,3 +137,14 @@ export const urlLocaleMatcherRegex = new RegExp(
|
|
|
137
137
|
.map((l) => l.value)
|
|
138
138
|
.join('|')})`
|
|
139
139
|
);
|
|
140
|
+
|
|
141
|
+
export const getPosError = () => {
|
|
142
|
+
const error = JSON.parse(getCookie('pz-pos-error') ?? '{}');
|
|
143
|
+
|
|
144
|
+
// delete 'pz-pos-error' cookie when refreshing or closing page
|
|
145
|
+
window.addEventListener('beforeunload', () => {
|
|
146
|
+
removeCookie('pz-pos-error');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return error;
|
|
150
|
+
};
|