@akinon/next 2.0.6-rc.1 → 2.0.6
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 -27
- package/api/auth.ts +84 -24
- package/api/client.ts +32 -0
- package/bin/pz-generate-routes.js +1 -4
- package/components/client-root.tsx +2 -0
- package/components/plugin-module.tsx +9 -3
- package/components/toast.tsx +258 -0
- package/data/client/checkout.ts +1 -0
- package/data/server/category.ts +2 -14
- package/data/server/list.ts +1 -13
- package/data/server/product.ts +0 -10
- package/data/server/special-page.ts +1 -14
- package/data/server/widget.ts +1 -14
- package/data/urls.ts +1 -5
- package/hooks/index.ts +1 -0
- package/hooks/use-captcha.tsx +1 -1
- package/hooks/use-toast.ts +56 -0
- package/instrumentation/index.ts +1 -0
- package/instrumentation/node.ts +224 -2
- package/lib/fixture-manager.ts +146 -0
- package/middlewares/default.ts +1 -2
- package/middlewares/masterpass-rest-callback.ts +147 -34
- package/package.json +6 -7
- package/plugins.d.ts +0 -10
- package/plugins.js +0 -1
- package/redux/actions.ts +1 -0
- package/redux/middlewares/checkout.ts +3 -45
- package/redux/middlewares/pre-order/installment-option.ts +1 -9
- package/redux/reducers/index.ts +2 -0
- package/redux/reducers/toast.ts +70 -0
- package/types/commerce/flatpage.ts +7 -0
- package/types/index.ts +0 -7
- package/utils/app-fetch.ts +27 -0
- package/utils/format-error-message.ts +7 -0
- package/utils/index.ts +8 -1
- package/with-pz-config.js +3 -3
- package/utils/payload-optimizer.ts +0 -481
package/CHANGELOG.md
CHANGED
|
@@ -1,35 +1,13 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
-
## 2.0.6
|
|
3
|
+
## 2.0.6
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
### Patch Changes
|
|
12
|
-
|
|
13
|
-
- 0cf9ea23: BRDG-16491: Prevent redirect when iframe payment is active
|
|
14
|
-
- 324f97d5: ZERO-4219: replace masterpass-rest-complete with masterpass-rest-callback
|
|
15
|
-
- ZERO-4160: Enhance oauth-login middleware with improved request handling and logging
|
|
16
|
-
- b55acb768: ZERO-2577: Fix pagination bug and update usePagination hook and ensure pagination controls rendering correctly
|
|
17
|
-
- 760258c1: ZERO-4160: Enhance oauth-login middleware to handle fetch errors and improve response handling
|
|
18
|
-
- 143be2b9: ZERO-3457: Crop styles are customizable and logic improved for rendering similar products modal
|
|
19
|
-
- 7889b08f: ZERO-4276: Enhance route generation by adding .env loading and custom skip segments support
|
|
20
|
-
- 9f8cd3bc5: ZERO-3449: AI Search Active Filters & Crop Style changes have been implemented
|
|
21
|
-
- bfafa3f4: ZERO-4160: Refactor oauth-login middleware to use fetchCommerce for API calls and improve cookie handling
|
|
22
|
-
- 57d7eb30: ZERO-4276: Refactor route generation logic by removing environment loading and simplifying skip segments handling
|
|
23
|
-
- d99a6a7d: ZERO-3457_1: Fixed the settings prop and made sure everything is customizable.
|
|
24
|
-
- 9db81a71: ZERO-4365: Remove brand `@theme/*` alias imports from library packages
|
|
25
|
-
- 591e345e: ZERO-3855: Enhance credit card payment handling in checkout middlewares
|
|
26
|
-
- 4de5303c5: ZERO-2504: add cookie filter to api client request
|
|
27
|
-
- 95b139dc: ZERO-3795: Remove duplicate entry for SavedCard in PluginComponents map
|
|
28
|
-
- 1d00f2d0: BRDG-16664: Set secure flag for CSRF token cookies in useCaptcha and default middleware
|
|
29
|
-
- 4ac7b2a1: ZERO-4219: fix masterpass-rest callback route format and double-encoded error cookie
|
|
30
|
-
- 4998a963: ZERO-4168: Add server-side payload optimization
|
|
31
|
-
- 3909d322: Edit the duplicate Plugin.SimilarProducts in the plugin-module.
|
|
32
|
-
- e18836b2: ZERO-4160: Restore scope in Sentry addon configuration in akinon.json
|
|
7
|
+
- 89deabe5: ZERO-4392: Remove deprecated dependencies and update package versions in yarn.lock
|
|
8
|
+
- 1a345c47: ZERO-4234: Add Global Toast Notification System
|
|
9
|
+
- 1f1ae44e: ZERO-4230: Add Commerce API mock/replay mode for development
|
|
10
|
+
- 8d8fefbe: ZERO-4398: Enhance SEO metadata generation for flat pages and update FlatPage interface to include localized URLs
|
|
33
11
|
|
|
34
12
|
## 2.0.5
|
|
35
13
|
|
package/api/auth.ts
CHANGED
|
@@ -154,18 +154,49 @@ const getDefaultAuthConfig = () => {
|
|
|
154
154
|
userIp
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
157
|
+
const existingSessionId = cookieStore.get('osessionid')?.value;
|
|
158
|
+
|
|
159
|
+
if (existingSessionId) {
|
|
160
|
+
const checkCurrentUser = await getCurrentUser(
|
|
161
|
+
existingSessionId,
|
|
162
|
+
cookieStore.get('pz-currency')?.value ?? ''
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (checkCurrentUser?.pk) {
|
|
166
|
+
const sessionCookie = reqHeaders
|
|
167
|
+
.get('cookie')
|
|
168
|
+
?.match(/osessionid=\w+/)?.[0]
|
|
169
|
+
.replace(/osessionid=/, '');
|
|
170
|
+
if (sessionCookie) {
|
|
171
|
+
reqHeaders.set('cookie', sessionCookie);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
// Stale session cookie — remove from headers and clear in browser
|
|
175
|
+
const currentCookies = reqHeaders.get('cookie') || '';
|
|
176
|
+
const cleanedCookies = currentCookies
|
|
177
|
+
.split(';')
|
|
178
|
+
.filter((c) => !c.trim().startsWith('osessionid='))
|
|
179
|
+
.join(';')
|
|
180
|
+
.trim();
|
|
181
|
+
reqHeaders.set('cookie', cleanedCookies);
|
|
182
|
+
|
|
183
|
+
const { localeUrlStrategy } = Settings.localization;
|
|
184
|
+
const fallbackHost =
|
|
185
|
+
headerStore.get('x-forwarded-host') ||
|
|
186
|
+
headerStore.get('host');
|
|
187
|
+
const hostname =
|
|
188
|
+
process.env.NEXT_PUBLIC_URL || `https://${fallbackHost}`;
|
|
189
|
+
const rootHostname =
|
|
190
|
+
localeUrlStrategy === LocaleUrlStrategy.Subdomain
|
|
191
|
+
? getRootHostname(hostname)
|
|
192
|
+
: null;
|
|
193
|
+
const expireOptions = {
|
|
194
|
+
path: '/',
|
|
195
|
+
maxAge: 0,
|
|
196
|
+
...(rootHostname ? { domain: rootHostname } : {})
|
|
197
|
+
};
|
|
198
|
+
cookieStore.set('osessionid', '', expireOptions);
|
|
199
|
+
cookieStore.set('sessionid', '', expireOptions);
|
|
169
200
|
}
|
|
170
201
|
}
|
|
171
202
|
|
|
@@ -393,18 +424,47 @@ const defaultNextAuthOptionsV4 = (req: any, res: any) => {
|
|
|
393
424
|
userIp
|
|
394
425
|
});
|
|
395
426
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
427
|
+
if (req.cookies['osessionid']) {
|
|
428
|
+
const checkCurrentUser = await getCurrentUser(
|
|
429
|
+
req.cookies['osessionid'],
|
|
430
|
+
req.cookies['pz-currency'] ?? ''
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
if (checkCurrentUser?.pk) {
|
|
434
|
+
const sessionCookie = reqHeaders
|
|
435
|
+
.get('cookie')
|
|
436
|
+
?.match(/osessionid=\w+/)?.[0]
|
|
437
|
+
.replace(/osessionid=/, '');
|
|
438
|
+
if (sessionCookie) {
|
|
439
|
+
reqHeaders.set('cookie', sessionCookie);
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
// Stale session cookie — remove from headers and clear in browser
|
|
443
|
+
const currentCookies = reqHeaders.get('cookie') || '';
|
|
444
|
+
const cleanedCookies = currentCookies
|
|
445
|
+
.split(';')
|
|
446
|
+
.filter((c) => !c.trim().startsWith('osessionid='))
|
|
447
|
+
.join(';')
|
|
448
|
+
.trim();
|
|
449
|
+
reqHeaders.set('cookie', cleanedCookies);
|
|
450
|
+
|
|
451
|
+
const { localeUrlStrategy } = Settings.localization;
|
|
452
|
+
const fallbackHost =
|
|
453
|
+
req.headers['x-forwarded-host']?.toString() ||
|
|
454
|
+
req.headers.host?.toString();
|
|
455
|
+
const hostname =
|
|
456
|
+
process.env.NEXT_PUBLIC_URL || `https://${fallbackHost}`;
|
|
457
|
+
const rootHostname =
|
|
458
|
+
localeUrlStrategy === LocaleUrlStrategy.Subdomain
|
|
459
|
+
? getRootHostname(hostname)
|
|
460
|
+
: null;
|
|
461
|
+
const domainOption = rootHostname
|
|
462
|
+
? ` Domain=${rootHostname};`
|
|
463
|
+
: '';
|
|
464
|
+
res.setHeader('Set-Cookie', [
|
|
465
|
+
`osessionid=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT;${domainOption}`,
|
|
466
|
+
`sessionid=; Path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT;${domainOption}`
|
|
467
|
+
]);
|
|
408
468
|
}
|
|
409
469
|
}
|
|
410
470
|
|
package/api/client.ts
CHANGED
|
@@ -7,6 +7,7 @@ import cookieParser from 'set-cookie-parser';
|
|
|
7
7
|
import { cookies } from 'next/headers';
|
|
8
8
|
import getRootHostname from '../utils/get-root-hostname';
|
|
9
9
|
import { LocaleUrlStrategy } from '../localization';
|
|
10
|
+
import { fixtureManager, MockMode } from '../lib/fixture-manager';
|
|
10
11
|
|
|
11
12
|
interface RouteParams {
|
|
12
13
|
params: {
|
|
@@ -156,6 +157,28 @@ async function proxyRequest(...args) {
|
|
|
156
157
|
url += `?${urlSearchParams.toString()}`;
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
const mockMode = process.env.PZ_MOCK;
|
|
161
|
+
const fixtureBody = req.method !== 'GET' ? fetchOptions.body : undefined;
|
|
162
|
+
|
|
163
|
+
// Replay mode: serve from fixtures
|
|
164
|
+
if (mockMode === MockMode.REPLAY) {
|
|
165
|
+
const { found, fixture } = await fixtureManager.read(req.method, slug, fixtureBody);
|
|
166
|
+
|
|
167
|
+
if (found) {
|
|
168
|
+
return NextResponse.json(
|
|
169
|
+
options.responseType === 'text'
|
|
170
|
+
? { result: fixture.response.body }
|
|
171
|
+
: fixture.response.body,
|
|
172
|
+
{ status: fixture.response.status }
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return NextResponse.json(
|
|
177
|
+
{ error: 'No fixture recorded for this endpoint' },
|
|
178
|
+
{ status: 404 }
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
159
182
|
try {
|
|
160
183
|
const request = await fetch(url, fetchOptions);
|
|
161
184
|
|
|
@@ -181,6 +204,15 @@ async function proxyRequest(...args) {
|
|
|
181
204
|
);
|
|
182
205
|
}
|
|
183
206
|
|
|
207
|
+
// Record mode: save response to fixtures
|
|
208
|
+
if (mockMode === MockMode.RECORD) {
|
|
209
|
+
await fixtureManager.write(req.method, slug, fixtureBody, {
|
|
210
|
+
status: request.status,
|
|
211
|
+
headers: fixtureManager.extractHeaders(request.headers),
|
|
212
|
+
body: response
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
184
216
|
const setCookieHeaders = request.headers.getSetCookie();
|
|
185
217
|
const exceptCookieKeys = ['pz-locale', 'pz-currency'];
|
|
186
218
|
|
|
@@ -6,7 +6,6 @@ const findBaseDir = require('../utils/find-base-dir');
|
|
|
6
6
|
|
|
7
7
|
const generateRoutes = () => {
|
|
8
8
|
const baseDir = findBaseDir();
|
|
9
|
-
|
|
10
9
|
const srcDir = path.join(baseDir, 'src');
|
|
11
10
|
const appDir = path.join(srcDir, 'app');
|
|
12
11
|
|
|
@@ -35,10 +34,8 @@ const generateRoutes = () => {
|
|
|
35
34
|
'[segment]',
|
|
36
35
|
'[url]',
|
|
37
36
|
'[theme]',
|
|
38
|
-
'[member_type]'
|
|
39
|
-
'[clienttype]'
|
|
37
|
+
'[member_type]'
|
|
40
38
|
];
|
|
41
|
-
|
|
42
39
|
const skipCatchAllRoutes = ['[...prettyurl]', '[...not_found]'];
|
|
43
40
|
|
|
44
41
|
const walkDirectory = (dir, basePath = '') => {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '../redux/reducers/widget';
|
|
18
18
|
import { LoggerPopup } from './logger-popup';
|
|
19
19
|
import { LoggerProvider } from '../hooks/use-logger-context';
|
|
20
|
+
import { ToastContainer } from './toast';
|
|
20
21
|
import * as Sentry from '@sentry/nextjs';
|
|
21
22
|
import { initSentry } from '../sentry';
|
|
22
23
|
|
|
@@ -150,6 +151,7 @@ export default function ClientRoot({
|
|
|
150
151
|
<LoggerProvider>
|
|
151
152
|
{children}
|
|
152
153
|
<LoggerPopup />
|
|
154
|
+
<ToastContainer />
|
|
153
155
|
</LoggerProvider>
|
|
154
156
|
);
|
|
155
157
|
}
|
|
@@ -27,7 +27,8 @@ enum Plugin {
|
|
|
27
27
|
MasterpassRest = 'pz-masterpass-rest',
|
|
28
28
|
SimilarProducts = 'pz-similar-products',
|
|
29
29
|
Haso = 'pz-haso',
|
|
30
|
-
GooglePay = 'pz-google-pay'
|
|
30
|
+
GooglePay = 'pz-google-pay',
|
|
31
|
+
ListHoverImage = 'pz-list-hover-image'
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export enum Component {
|
|
@@ -68,7 +69,8 @@ export enum Component {
|
|
|
68
69
|
ImageSearchButton = 'ImageSearchButton',
|
|
69
70
|
HeaderImageSearchFeature = 'HeaderImageSearchFeature',
|
|
70
71
|
HasoPaymentGateway = 'HasoPaymentGateway',
|
|
71
|
-
GooglePay = 'GooglePay'
|
|
72
|
+
GooglePay = 'GooglePay',
|
|
73
|
+
ListHoverImage = 'ListHoverImage'
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
const PluginComponents = new Map([
|
|
@@ -114,6 +116,7 @@ const PluginComponents = new Map([
|
|
|
114
116
|
]
|
|
115
117
|
],
|
|
116
118
|
[Plugin.SavedCard, [Component.SavedCard, Component.IyzicoSavedCard]],
|
|
119
|
+
[Plugin.SavedCard, [Component.SavedCard]],
|
|
117
120
|
[Plugin.FlowPayment, [Component.FlowPayment]],
|
|
118
121
|
[
|
|
119
122
|
Plugin.VirtualTryOn,
|
|
@@ -122,7 +125,8 @@ const PluginComponents = new Map([
|
|
|
122
125
|
[Plugin.Hepsipay, [Component.Hepsipay]],
|
|
123
126
|
[Plugin.MasterpassRest, [Component.MasterpassRest]],
|
|
124
127
|
[Plugin.Haso, [Component.HasoPaymentGateway]],
|
|
125
|
-
[Plugin.GooglePay, [Component.GooglePay]]
|
|
128
|
+
[Plugin.GooglePay, [Component.GooglePay]],
|
|
129
|
+
[Plugin.ListHoverImage, [Component.ListHoverImage]]
|
|
126
130
|
]);
|
|
127
131
|
|
|
128
132
|
const getPlugin = (component: Component) => {
|
|
@@ -201,6 +205,8 @@ export default function PluginModule({
|
|
|
201
205
|
promise = import(`${'@akinon/pz-haso'}`);
|
|
202
206
|
} else if (plugin === Plugin.GooglePay) {
|
|
203
207
|
promise = import(`${'@akinon/pz-google-pay'}`);
|
|
208
|
+
} else if (plugin === Plugin.ListHoverImage) {
|
|
209
|
+
promise = import(`${'@akinon/pz-list-hover-image'}`);
|
|
204
210
|
}
|
|
205
211
|
} catch (error) {
|
|
206
212
|
logger.error(error);
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
|
4
|
+
import { createPortal } from 'react-dom'
|
|
5
|
+
import { twMerge } from 'tailwind-merge'
|
|
6
|
+
import { useAppDispatch, useAppSelector } from '../redux/hooks'
|
|
7
|
+
import {
|
|
8
|
+
removeToast,
|
|
9
|
+
Toast as ToastType,
|
|
10
|
+
ToastPosition
|
|
11
|
+
} from '../redux/reducers/toast'
|
|
12
|
+
import { Icon } from './icon'
|
|
13
|
+
|
|
14
|
+
type TypeStyles = {
|
|
15
|
+
container?: string
|
|
16
|
+
icon?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ToastClasses {
|
|
20
|
+
root?: string
|
|
21
|
+
item?: string
|
|
22
|
+
icon?: string
|
|
23
|
+
message?: string
|
|
24
|
+
action?: string
|
|
25
|
+
dismiss?: string
|
|
26
|
+
animateIn?: string
|
|
27
|
+
animateOut?: string
|
|
28
|
+
types?: Partial<Record<ToastType['type'], TypeStyles>>
|
|
29
|
+
positions?: Partial<Record<ToastPosition, string>>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ToastContainerProps {
|
|
33
|
+
classes?: ToastClasses
|
|
34
|
+
icons?: Partial<Record<ToastType['type'], string>>
|
|
35
|
+
customRender?: (props: {
|
|
36
|
+
toast: ToastType
|
|
37
|
+
onDismiss: () => void
|
|
38
|
+
}) => React.ReactNode
|
|
39
|
+
onAction?: (actionId: string, toast: ToastType) => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const builtInTypeStyles: Record<ToastType['type'], TypeStyles> = {
|
|
43
|
+
success: {
|
|
44
|
+
container: 'border-l-4 border-l-success bg-white',
|
|
45
|
+
icon: 'text-success'
|
|
46
|
+
},
|
|
47
|
+
error: {
|
|
48
|
+
container: 'border-l-4 border-l-error bg-white',
|
|
49
|
+
icon: 'text-error'
|
|
50
|
+
},
|
|
51
|
+
warning: {
|
|
52
|
+
container: 'border-l-4 border-l-[#e89a0c] bg-white',
|
|
53
|
+
icon: 'text-[#e89a0c]'
|
|
54
|
+
},
|
|
55
|
+
info: {
|
|
56
|
+
container: 'border-l-4 border-l-primary bg-white',
|
|
57
|
+
icon: 'text-primary'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const builtInIcons: Record<ToastType['type'], string> = {
|
|
62
|
+
success: 'check',
|
|
63
|
+
error: 'close',
|
|
64
|
+
warning: 'info',
|
|
65
|
+
info: 'info'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const builtInPositions: Record<ToastPosition, string> = {
|
|
69
|
+
'top-right': 'fixed top-4 right-4',
|
|
70
|
+
'top-left': 'fixed top-4 left-4',
|
|
71
|
+
'top-center': 'fixed top-4 left-1/2 -translate-x-1/2',
|
|
72
|
+
'bottom-right': 'fixed bottom-4 right-4',
|
|
73
|
+
'bottom-left': 'fixed bottom-4 left-4',
|
|
74
|
+
'bottom-center': 'fixed bottom-4 left-1/2 -translate-x-1/2'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const ToastItem = React.memo(function ToastItem({
|
|
78
|
+
toast,
|
|
79
|
+
classes,
|
|
80
|
+
icons,
|
|
81
|
+
customRender,
|
|
82
|
+
onAction
|
|
83
|
+
}: {
|
|
84
|
+
toast: ToastType
|
|
85
|
+
classes: ToastClasses
|
|
86
|
+
icons: Record<ToastType['type'], string>
|
|
87
|
+
customRender?: ToastContainerProps['customRender']
|
|
88
|
+
onAction?: ToastContainerProps['onAction']
|
|
89
|
+
}) {
|
|
90
|
+
const dispatch = useAppDispatch()
|
|
91
|
+
const [exiting, setExiting] = useState(false)
|
|
92
|
+
const timerRef = useRef<ReturnType<typeof setTimeout>>(null)
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (toast.duration > 0) {
|
|
96
|
+
timerRef.current = setTimeout(() => {
|
|
97
|
+
setExiting(true)
|
|
98
|
+
}, toast.duration)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return () => {
|
|
102
|
+
if (timerRef.current) clearTimeout(timerRef.current)
|
|
103
|
+
}
|
|
104
|
+
}, [toast.duration])
|
|
105
|
+
|
|
106
|
+
const handleAnimationEnd = () => {
|
|
107
|
+
if (exiting) {
|
|
108
|
+
dispatch(removeToast(toast.id))
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const handleDismiss = () => {
|
|
113
|
+
if (timerRef.current) clearTimeout(timerRef.current)
|
|
114
|
+
setExiting(true)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const animIn = classes.animateIn ?? 'animate-toast-in'
|
|
118
|
+
const animOut = classes.animateOut ?? 'animate-toast-out'
|
|
119
|
+
|
|
120
|
+
if (customRender) {
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
className={twMerge(
|
|
124
|
+
'pointer-events-auto',
|
|
125
|
+
exiting ? animOut : animIn
|
|
126
|
+
)}
|
|
127
|
+
onAnimationEnd={handleAnimationEnd}
|
|
128
|
+
>
|
|
129
|
+
{customRender({ toast, onDismiss: handleDismiss })}
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const typeStyle = classes.types?.[toast.type] ?? builtInTypeStyles[toast.type]
|
|
135
|
+
const iconName = toast.icon ?? icons[toast.type]
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<div
|
|
139
|
+
role="alert"
|
|
140
|
+
className={twMerge(
|
|
141
|
+
'flex items-center gap-3 px-4 py-3 rounded shadow-lg min-w-[300px] max-w-[420px]',
|
|
142
|
+
'pointer-events-auto',
|
|
143
|
+
exiting ? animOut : animIn,
|
|
144
|
+
typeStyle.container,
|
|
145
|
+
classes.item,
|
|
146
|
+
toast.className
|
|
147
|
+
)}
|
|
148
|
+
onAnimationEnd={handleAnimationEnd}
|
|
149
|
+
>
|
|
150
|
+
<Icon
|
|
151
|
+
name={iconName}
|
|
152
|
+
size={18}
|
|
153
|
+
className={twMerge('shrink-0', typeStyle.icon, classes.icon)}
|
|
154
|
+
/>
|
|
155
|
+
<p
|
|
156
|
+
className={twMerge(
|
|
157
|
+
'flex-1 text-sm text-primary leading-snug',
|
|
158
|
+
classes.message
|
|
159
|
+
)}
|
|
160
|
+
>
|
|
161
|
+
{toast.message}
|
|
162
|
+
</p>
|
|
163
|
+
{toast.action && onAction && (
|
|
164
|
+
<button
|
|
165
|
+
type="button"
|
|
166
|
+
onClick={() => onAction(toast.action.actionId, toast)}
|
|
167
|
+
className={twMerge(
|
|
168
|
+
'shrink-0 text-xs font-semibold text-primary underline hover:no-underline transition-colors',
|
|
169
|
+
classes.action
|
|
170
|
+
)}
|
|
171
|
+
>
|
|
172
|
+
{toast.action.label}
|
|
173
|
+
</button>
|
|
174
|
+
)}
|
|
175
|
+
{toast.dismissible !== false && (
|
|
176
|
+
<button
|
|
177
|
+
type="button"
|
|
178
|
+
onClick={handleDismiss}
|
|
179
|
+
className={twMerge(
|
|
180
|
+
'shrink-0 p-1 text-gray-600 hover:text-primary transition-colors',
|
|
181
|
+
classes.dismiss
|
|
182
|
+
)}
|
|
183
|
+
aria-label="Dismiss"
|
|
184
|
+
>
|
|
185
|
+
<Icon name="close" size={12} />
|
|
186
|
+
</button>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
const EMPTY_CLASSES: ToastClasses = {}
|
|
193
|
+
|
|
194
|
+
export function ToastContainer({
|
|
195
|
+
classes = EMPTY_CLASSES,
|
|
196
|
+
icons,
|
|
197
|
+
customRender,
|
|
198
|
+
onAction
|
|
199
|
+
}: ToastContainerProps = {}) {
|
|
200
|
+
const toasts = useAppSelector((state) => state.toast.toasts)
|
|
201
|
+
const [mounted, setMounted] = useState(false)
|
|
202
|
+
|
|
203
|
+
const mergedIcons = useMemo(
|
|
204
|
+
() => ({ ...builtInIcons, ...icons }),
|
|
205
|
+
[icons]
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
const posStyles = useMemo(
|
|
209
|
+
() => ({ ...builtInPositions, ...classes.positions }),
|
|
210
|
+
[classes.positions]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
const grouped = useMemo(
|
|
214
|
+
() =>
|
|
215
|
+
toasts.reduce<Record<string, ToastType[]>>((acc, toast) => {
|
|
216
|
+
const pos = toast.position ?? 'top-right'
|
|
217
|
+
if (!acc[pos]) acc[pos] = []
|
|
218
|
+
acc[pos].push(toast)
|
|
219
|
+
return acc
|
|
220
|
+
}, {}),
|
|
221
|
+
[toasts]
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
setMounted(true)
|
|
226
|
+
}, [])
|
|
227
|
+
|
|
228
|
+
if (!mounted || toasts.length === 0) return null
|
|
229
|
+
|
|
230
|
+
return createPortal(
|
|
231
|
+
<>
|
|
232
|
+
{Object.entries(grouped).map(([position, items]) => (
|
|
233
|
+
<div
|
|
234
|
+
key={position}
|
|
235
|
+
aria-live="polite"
|
|
236
|
+
aria-label="Notifications"
|
|
237
|
+
className={twMerge(
|
|
238
|
+
'z-[9999] flex flex-col gap-3 pointer-events-none',
|
|
239
|
+
posStyles[position as ToastPosition],
|
|
240
|
+
classes.root
|
|
241
|
+
)}
|
|
242
|
+
>
|
|
243
|
+
{items.map((toast) => (
|
|
244
|
+
<ToastItem
|
|
245
|
+
key={toast.id}
|
|
246
|
+
toast={toast}
|
|
247
|
+
classes={classes}
|
|
248
|
+
icons={mergedIcons}
|
|
249
|
+
customRender={customRender}
|
|
250
|
+
onAction={onAction}
|
|
251
|
+
/>
|
|
252
|
+
))}
|
|
253
|
+
</div>
|
|
254
|
+
))}
|
|
255
|
+
</>,
|
|
256
|
+
document.body
|
|
257
|
+
)
|
|
258
|
+
}
|
package/data/client/checkout.ts
CHANGED
|
@@ -738,6 +738,7 @@ export const checkoutApi = api.injectEndpoints({
|
|
|
738
738
|
},
|
|
739
739
|
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
|
|
740
740
|
dispatch(setPaymentStepBusy(true));
|
|
741
|
+
dispatch(setCardType(arg));
|
|
741
742
|
await queryFulfilled;
|
|
742
743
|
dispatch(setPaymentStepBusy(false));
|
|
743
744
|
}
|
package/data/server/category.ts
CHANGED
|
@@ -7,8 +7,6 @@ import { parse } from 'lossless-json';
|
|
|
7
7
|
import logger from '../../utils/log';
|
|
8
8
|
import { headers as nHeaders } from 'next/headers';
|
|
9
9
|
import { ServerVariables } from '../../utils/server-variables';
|
|
10
|
-
import { optimizeCategoryResponse } from '../../utils/payload-optimizer';
|
|
11
|
-
import settings from 'settings';
|
|
12
10
|
|
|
13
11
|
function getCategoryDataHandler(
|
|
14
12
|
pk: number,
|
|
@@ -82,7 +80,7 @@ function getCategoryDataHandler(
|
|
|
82
80
|
};
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
export const getCategoryData =
|
|
83
|
+
export const getCategoryData = ({
|
|
86
84
|
pk,
|
|
87
85
|
searchParams,
|
|
88
86
|
headers,
|
|
@@ -95,7 +93,7 @@ export const getCategoryData = async ({
|
|
|
95
93
|
searchParams?: SearchParams;
|
|
96
94
|
headers?: Record<string, string>;
|
|
97
95
|
}) => {
|
|
98
|
-
|
|
96
|
+
return Cache.wrap(
|
|
99
97
|
CacheKey.Category(pk, searchParams, headers),
|
|
100
98
|
locale,
|
|
101
99
|
getCategoryDataHandler(pk, locale, currency, searchParams, headers),
|
|
@@ -104,16 +102,6 @@ export const getCategoryData = async ({
|
|
|
104
102
|
compressed: true
|
|
105
103
|
}
|
|
106
104
|
);
|
|
107
|
-
|
|
108
|
-
if (settings.payloadOptimization?.enabled && result?.data) {
|
|
109
|
-
try {
|
|
110
|
-
return { ...result, data: optimizeCategoryResponse(result.data, settings.payloadOptimization) };
|
|
111
|
-
} catch (e) {
|
|
112
|
-
logger.error('Payload optimization failed for category', { pk, error: (e as Error).message });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return result;
|
|
117
105
|
};
|
|
118
106
|
|
|
119
107
|
function getCategoryBySlugDataHandler(
|
package/data/server/list.ts
CHANGED
|
@@ -6,8 +6,6 @@ import appFetch, { FetchResponseType } from '../../utils/app-fetch';
|
|
|
6
6
|
import { parse } from 'lossless-json';
|
|
7
7
|
import logger from '../../utils/log';
|
|
8
8
|
import { ServerVariables } from '../../utils/server-variables';
|
|
9
|
-
import { optimizeCategoryResponse } from '../../utils/payload-optimizer';
|
|
10
|
-
import settings from 'settings';
|
|
11
9
|
|
|
12
10
|
const getListDataHandler = (
|
|
13
11
|
locale,
|
|
@@ -68,7 +66,7 @@ export const getListData = async ({
|
|
|
68
66
|
searchParams: SearchParams;
|
|
69
67
|
headers?: Record<string, string>;
|
|
70
68
|
}) => {
|
|
71
|
-
|
|
69
|
+
return Cache.wrap(
|
|
72
70
|
CacheKey.List(searchParams, headers),
|
|
73
71
|
locale,
|
|
74
72
|
getListDataHandler(locale, currency, searchParams, headers),
|
|
@@ -77,14 +75,4 @@ export const getListData = async ({
|
|
|
77
75
|
compressed: true
|
|
78
76
|
}
|
|
79
77
|
);
|
|
80
|
-
|
|
81
|
-
if (settings.payloadOptimization?.enabled && result) {
|
|
82
|
-
try {
|
|
83
|
-
return optimizeCategoryResponse(result, settings.payloadOptimization);
|
|
84
|
-
} catch (e) {
|
|
85
|
-
logger.error('Payload optimization failed for list', { error: (e as Error).message });
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return result;
|
|
90
78
|
};
|
package/data/server/product.ts
CHANGED
|
@@ -4,8 +4,6 @@ import { ProductCategoryResult, ProductResult, SearchParams } from '../../types'
|
|
|
4
4
|
import appFetch from '../../utils/app-fetch';
|
|
5
5
|
import { ServerVariables } from '../../utils/server-variables';
|
|
6
6
|
import logger from '../../utils/log';
|
|
7
|
-
import { optimizeProductResponse } from '../../utils/payload-optimizer';
|
|
8
|
-
import settings from 'settings';
|
|
9
7
|
|
|
10
8
|
type GetProduct = {
|
|
11
9
|
pk: number | string;
|
|
@@ -165,13 +163,5 @@ export const getProductData = async ({
|
|
|
165
163
|
throw error;
|
|
166
164
|
}
|
|
167
165
|
|
|
168
|
-
if (settings.payloadOptimization?.enabled && result?.data) {
|
|
169
|
-
try {
|
|
170
|
-
return { ...result, data: optimizeProductResponse(result.data, settings.payloadOptimization) };
|
|
171
|
-
} catch (e) {
|
|
172
|
-
logger.error('Payload optimization failed for product', { pk, error: (e as Error).message });
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
166
|
return result;
|
|
177
167
|
};
|