@akinon/next 2.0.0-beta.16 → 2.0.0-beta.18
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 +22 -0
- package/api/barcode-search.ts +59 -0
- package/bin/pz-generate-routes.js +11 -1
- package/components/client-root.tsx +12 -2
- package/components/index.ts +1 -0
- package/components/logger-popup.tsx +213 -0
- package/components/plugin-module.tsx +2 -1
- package/data/client/account.ts +5 -1
- package/data/client/basket.ts +39 -0
- package/data/client/checkout.ts +207 -26
- package/hooks/index.ts +2 -0
- package/hooks/use-logger-context.tsx +114 -0
- package/hooks/use-logger.ts +92 -0
- package/middlewares/bfcache-headers.ts +18 -0
- package/middlewares/default.ts +8 -6
- package/middlewares/index.ts +3 -1
- package/middlewares/oauth-login.ts +200 -57
- package/package.json +2 -2
- package/redux/middlewares/checkout.ts +9 -0
- package/redux/reducers/checkout.ts +6 -0
- package/types/commerce/checkout.ts +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @akinon/next
|
|
2
2
|
|
|
3
|
+
## 2.0.0-beta.18
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 929374c5: ZERO-3278: refactor variable names for better readability and consistency
|
|
8
|
+
- 71f8011d: ZERO-3271: remove code repetition in logger functions using closures
|
|
9
|
+
- 9be2c081: ZERO-3243: Improve basket update query handling with optimistic updates
|
|
10
|
+
- bd431e36: ZERO-3278: improve checkout validation error messages for better user guidance
|
|
11
|
+
- 54eac86b: ZERO-3271: add development logger system
|
|
12
|
+
|
|
13
|
+
## 2.0.0-beta.17
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- 8218dafa: ZERO-4160: Refactor oauth-login middleware to use fetch for login and callback responses
|
|
18
|
+
- 2a8ddcf6: ZERO-4111: Add total balance field to store credit localization and update related components
|
|
19
|
+
- 8a7fd0f4: ZERO-4065: Add '[segment]' to skipSegments in route generation
|
|
20
|
+
- 36143125: ZERO-3987: Add barcode scanner functionality with modal and button
|
|
21
|
+
- f7e0f646: ZERO-4032: Add bfcache-headers middleware, integrate it into the default chain, and introduce a new environment variable for control.
|
|
22
|
+
- 94a86fcc: ZERO-4065: Expand skipSegments array to include additional segments for route generation
|
|
23
|
+
- bcaad120: ZERO-4158: Add logging for OAuth login and callback redirects
|
|
24
|
+
|
|
3
25
|
## 2.0.0-beta.16
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
|
@@ -0,0 +1,59 @@
|
|
|
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 barcode = searchParams.get('search_text');
|
|
8
|
+
|
|
9
|
+
if (!barcode) {
|
|
10
|
+
return NextResponse.json(
|
|
11
|
+
{ error: 'Missing search_text parameter (barcode)' },
|
|
12
|
+
{ status: 400 }
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (Settings.commerceUrl === 'default') {
|
|
17
|
+
return NextResponse.json(
|
|
18
|
+
{ error: 'Commerce URL is not configured' },
|
|
19
|
+
{ status: 500 }
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const queryParams = new URLSearchParams();
|
|
24
|
+
queryParams.append('search_text', barcode);
|
|
25
|
+
|
|
26
|
+
searchParams.forEach((value, key) => {
|
|
27
|
+
if (key !== 'search_text') {
|
|
28
|
+
queryParams.append(key, value);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const apiUrl = `${Settings.commerceUrl}/list/?${queryParams.toString()}`;
|
|
33
|
+
|
|
34
|
+
const headers: Record<string, string> = {
|
|
35
|
+
Accept: 'application/json',
|
|
36
|
+
'Content-Type': 'application/json'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const response = await fetch(apiUrl, {
|
|
40
|
+
method: 'GET',
|
|
41
|
+
headers
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
return NextResponse.json(
|
|
46
|
+
{ error: `API request failed with status: ${response.status}` },
|
|
47
|
+
{ status: response.status }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const data = await response.json();
|
|
52
|
+
return NextResponse.json(data);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return NextResponse.json(
|
|
55
|
+
{ error: (error as Error).message },
|
|
56
|
+
{ status: 500 }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -25,7 +25,17 @@ const generateRoutes = () => {
|
|
|
25
25
|
const routes = [];
|
|
26
26
|
const excludedDirs = ['api', 'pz-not-found'];
|
|
27
27
|
|
|
28
|
-
const skipSegments = [
|
|
28
|
+
const skipSegments = [
|
|
29
|
+
'[pz]',
|
|
30
|
+
'[commerce]',
|
|
31
|
+
'[locale]',
|
|
32
|
+
'[currency]',
|
|
33
|
+
'[session]',
|
|
34
|
+
'[segment]',
|
|
35
|
+
'[url]',
|
|
36
|
+
'[theme]',
|
|
37
|
+
'[member_type]'
|
|
38
|
+
];
|
|
29
39
|
const skipCatchAllRoutes = ['[...prettyurl]', '[...not_found]'];
|
|
30
40
|
|
|
31
41
|
const walkDirectory = (dir, basePath = '') => {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import React from 'react';
|
|
3
4
|
import { useMobileIframeHandler } from '../hooks';
|
|
5
|
+
import { LoggerPopup } from './logger-popup';
|
|
6
|
+
import { LoggerProvider } from '../hooks/use-logger-context';
|
|
4
7
|
import * as Sentry from '@sentry/nextjs';
|
|
5
8
|
import { initSentry } from '../sentry';
|
|
6
9
|
import { useEffect } from 'react';
|
|
@@ -12,7 +15,9 @@ export default function ClientRoot({
|
|
|
12
15
|
children: React.ReactNode;
|
|
13
16
|
sessionId?: string;
|
|
14
17
|
}) {
|
|
15
|
-
const { preventPageRender } = useMobileIframeHandler({
|
|
18
|
+
const { preventPageRender } = useMobileIframeHandler({
|
|
19
|
+
sessionId: sessionId || ''
|
|
20
|
+
});
|
|
16
21
|
|
|
17
22
|
const initializeSentry = async () => {
|
|
18
23
|
const response = await fetch('/api/sentry', { next: { revalidate: 0 } });
|
|
@@ -35,5 +40,10 @@ export default function ClientRoot({
|
|
|
35
40
|
return null;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
return
|
|
43
|
+
return (
|
|
44
|
+
<LoggerProvider>
|
|
45
|
+
{children}
|
|
46
|
+
<LoggerPopup />
|
|
47
|
+
</LoggerProvider>
|
|
48
|
+
);
|
|
39
49
|
}
|
package/components/index.ts
CHANGED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useCallback, memo, useMemo } from 'react';
|
|
4
|
+
import { LogEntry } from '../hooks/use-logger';
|
|
5
|
+
import { useLoggerContext } from '../hooks/use-logger-context';
|
|
6
|
+
|
|
7
|
+
const LoggerAnimations = ({ color = '#dc2626' }: { color?: string }) => (
|
|
8
|
+
<style jsx global>{`
|
|
9
|
+
@keyframes pulse {
|
|
10
|
+
0% {
|
|
11
|
+
transform: scale(0.95);
|
|
12
|
+
box-shadow: 0 0 0 0 ${color}80;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
70% {
|
|
16
|
+
transform: scale(1.05);
|
|
17
|
+
box-shadow: 0 0 0 10px ${color}00;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
100% {
|
|
21
|
+
transform: scale(0.95);
|
|
22
|
+
box-shadow: 0 0 0 0 ${color}00;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`}</style>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const LogLevelColors = {
|
|
29
|
+
warn: '#ff9800', // orange
|
|
30
|
+
error: '#f44336' // red
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const LoggerTrigger = memo(
|
|
34
|
+
({
|
|
35
|
+
onClick,
|
|
36
|
+
logCount = 0,
|
|
37
|
+
currentColor
|
|
38
|
+
}: {
|
|
39
|
+
onClick: () => void;
|
|
40
|
+
logCount?: number;
|
|
41
|
+
currentColor: string;
|
|
42
|
+
}) => {
|
|
43
|
+
return (
|
|
44
|
+
<button
|
|
45
|
+
onClick={onClick}
|
|
46
|
+
className="fixed bottom-4 right-4 w-14 h-14 border border-white rounded-full flex items-center justify-center shadow-lg z-[9999] hover:opacity-90 transition-colors"
|
|
47
|
+
aria-label="Open Logger"
|
|
48
|
+
style={{
|
|
49
|
+
backgroundColor: currentColor,
|
|
50
|
+
...(logCount > 0 && {
|
|
51
|
+
animation: 'pulse 2s infinite'
|
|
52
|
+
})
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<svg
|
|
56
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
57
|
+
fill="none"
|
|
58
|
+
viewBox="0 0 24 24"
|
|
59
|
+
strokeWidth="1.5"
|
|
60
|
+
stroke="currentColor"
|
|
61
|
+
className="text-white size-6"
|
|
62
|
+
>
|
|
63
|
+
<path
|
|
64
|
+
strokeLinecap="round"
|
|
65
|
+
strokeLinejoin="round"
|
|
66
|
+
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"
|
|
67
|
+
/>
|
|
68
|
+
</svg>
|
|
69
|
+
|
|
70
|
+
{logCount > 0 && (
|
|
71
|
+
<span
|
|
72
|
+
className="absolute w-5 h-5 -bottom-[5px] p-1 -left-[5px] border-2 border-white rounded-full flex items-center justify-center text-[8px] text-white font-bold"
|
|
73
|
+
style={{
|
|
74
|
+
backgroundColor: currentColor
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
{logCount > 99 ? '99+' : logCount}
|
|
78
|
+
</span>
|
|
79
|
+
)}
|
|
80
|
+
</button>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
LoggerTrigger.displayName = 'LoggerTrigger';
|
|
86
|
+
|
|
87
|
+
const LogItem = memo(({ log }: { log: LogEntry }) => {
|
|
88
|
+
const [expanded, setExpanded] = useState(false);
|
|
89
|
+
const hasPayload = log.payload && Object.keys(log.payload).length > 0;
|
|
90
|
+
|
|
91
|
+
const toggleExpanded = useCallback(() => {
|
|
92
|
+
setExpanded((prev) => !prev);
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="border-b border-gray-200 py-2">
|
|
97
|
+
<div className="relative">
|
|
98
|
+
<div
|
|
99
|
+
className="absolute top-0 left-0 w-3 h-3 rounded-full mt-1.5 mr-2 flex-shrink-0"
|
|
100
|
+
style={{
|
|
101
|
+
backgroundColor: LogLevelColors[log.level],
|
|
102
|
+
boxShadow: `0 0 5px ${LogLevelColors[log.level]}`
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
<div className="ml-6">
|
|
106
|
+
<div className="flex justify-between">
|
|
107
|
+
<span
|
|
108
|
+
className="font-medium capitalize"
|
|
109
|
+
style={{
|
|
110
|
+
color: LogLevelColors[log.level]
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
{log.level}
|
|
114
|
+
</span>
|
|
115
|
+
<span className="text-xs text-gray-500">
|
|
116
|
+
{log.timestamp.toLocaleTimeString()}
|
|
117
|
+
</span>
|
|
118
|
+
</div>
|
|
119
|
+
<p className="text-sm">{log.message}</p>
|
|
120
|
+
{hasPayload && (
|
|
121
|
+
<button
|
|
122
|
+
onClick={toggleExpanded}
|
|
123
|
+
className="text-xs text-blue-500 mt-1 hover:text-blue-700 transition-colors"
|
|
124
|
+
>
|
|
125
|
+
{expanded ? 'Hide Details' : 'Show Details'}
|
|
126
|
+
</button>
|
|
127
|
+
)}
|
|
128
|
+
{expanded && hasPayload && (
|
|
129
|
+
<pre className="text-xs bg-gray-100 p-2 mt-1 rounded overflow-auto max-h-96 max-w-full">
|
|
130
|
+
{JSON.stringify(log.payload, null, 2)}
|
|
131
|
+
</pre>
|
|
132
|
+
)}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
LogItem.displayName = 'LogItem';
|
|
140
|
+
|
|
141
|
+
export const LoggerPopup = () => {
|
|
142
|
+
const {
|
|
143
|
+
logs,
|
|
144
|
+
isVisible,
|
|
145
|
+
toggleVisibility,
|
|
146
|
+
clearLogs,
|
|
147
|
+
isDevelopment,
|
|
148
|
+
hasError,
|
|
149
|
+
hasWarning
|
|
150
|
+
} = useLoggerContext();
|
|
151
|
+
|
|
152
|
+
const currentColor = useMemo(() => {
|
|
153
|
+
if (logs.length === 0) return '#b5afaf';
|
|
154
|
+
if (hasError) return '#dc2626';
|
|
155
|
+
if (hasWarning) return '#ff9800';
|
|
156
|
+
|
|
157
|
+
return '#b5afaf';
|
|
158
|
+
}, [logs.length, hasError, hasWarning]);
|
|
159
|
+
|
|
160
|
+
if (!isDevelopment) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!isVisible) {
|
|
165
|
+
return (
|
|
166
|
+
<>
|
|
167
|
+
<LoggerAnimations color={currentColor} />
|
|
168
|
+
<LoggerTrigger
|
|
169
|
+
onClick={toggleVisibility}
|
|
170
|
+
logCount={logs.length}
|
|
171
|
+
currentColor={currentColor}
|
|
172
|
+
/>
|
|
173
|
+
</>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<>
|
|
179
|
+
<LoggerAnimations color={currentColor} />
|
|
180
|
+
<LoggerTrigger
|
|
181
|
+
onClick={toggleVisibility}
|
|
182
|
+
logCount={logs.length}
|
|
183
|
+
currentColor={currentColor}
|
|
184
|
+
/>
|
|
185
|
+
<div className="fixed bottom-20 right-4 w-96 max-w-[calc(100vw-2rem)] bg-white rounded-lg shadow-xl z-50 border-2 border-gray-200 max-h-[70vh] flex flex-col">
|
|
186
|
+
<div className="flex items-center justify-between p-3 border-b border-gray-200">
|
|
187
|
+
<h3 className="font-bold flex items-center">Development Logger</h3>
|
|
188
|
+
<div className="flex space-x-2">
|
|
189
|
+
<button
|
|
190
|
+
onClick={clearLogs}
|
|
191
|
+
className="text-xs bg-gray-200 hover:bg-gray-300 px-2 py-1 rounded transition-colors"
|
|
192
|
+
>
|
|
193
|
+
Clear
|
|
194
|
+
</button>
|
|
195
|
+
<button
|
|
196
|
+
onClick={toggleVisibility}
|
|
197
|
+
className="text-xs bg-gray-200 hover:bg-gray-300 px-2 py-1 rounded transition-colors"
|
|
198
|
+
>
|
|
199
|
+
Close
|
|
200
|
+
</button>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
<div className="overflow-y-auto flex-grow p-3">
|
|
204
|
+
{logs.length === 0 ? (
|
|
205
|
+
<p className="text-gray-500 text-center py-4">No logs yet</p>
|
|
206
|
+
) : (
|
|
207
|
+
logs.map((log) => <LogItem key={log.id} log={log} />)
|
|
208
|
+
)}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</>
|
|
212
|
+
);
|
|
213
|
+
};
|
|
@@ -55,6 +55,7 @@ export enum Component {
|
|
|
55
55
|
SavedCard = 'SavedCardOption',
|
|
56
56
|
VirtualTryOnPlugin = 'VirtualTryOnPlugin',
|
|
57
57
|
BasketVirtualTryOn = 'BasketVirtualTryOn',
|
|
58
|
+
BarcodeScannerPlugin = 'BarcodeScannerPlugin',
|
|
58
59
|
IyzicoSavedCard = 'IyzicoSavedCardOption',
|
|
59
60
|
Hepsipay = 'Hepsipay',
|
|
60
61
|
FlowPayment = 'FlowPayment',
|
|
@@ -117,7 +118,7 @@ const PluginComponents = new Map([
|
|
|
117
118
|
[Plugin.FlowPayment, [Component.FlowPayment]],
|
|
118
119
|
[
|
|
119
120
|
Plugin.VirtualTryOn,
|
|
120
|
-
[Component.VirtualTryOnPlugin, Component.BasketVirtualTryOn]
|
|
121
|
+
[Component.VirtualTryOnPlugin, Component.BasketVirtualTryOn, Component.BarcodeScannerPlugin]
|
|
121
122
|
],
|
|
122
123
|
[Plugin.Hepsipay, [Component.Hepsipay]],
|
|
123
124
|
[Plugin.MasterpassRest, [Component.MasterpassRest]],
|
package/data/client/account.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
AccountOrderCancellation,
|
|
8
8
|
AccountOrderCancellationReason,
|
|
9
9
|
ContactFormType,
|
|
10
|
+
LoyaltyBalanceItem,
|
|
10
11
|
Order,
|
|
11
12
|
Quotations
|
|
12
13
|
} from '../../types';
|
|
@@ -220,7 +221,10 @@ const accountApi = api.injectEndpoints({
|
|
|
220
221
|
method: 'PATCH'
|
|
221
222
|
})
|
|
222
223
|
}),
|
|
223
|
-
getLoyaltyBalance: builder.query<
|
|
224
|
+
getLoyaltyBalance: builder.query<
|
|
225
|
+
{ balance: number; balances?: LoyaltyBalanceItem[] },
|
|
226
|
+
void
|
|
227
|
+
>({
|
|
224
228
|
query: () => buildClientRequestUrl(account.loyaltyBalance)
|
|
225
229
|
}),
|
|
226
230
|
getLoyaltyTransactions: builder.query<LoyaltyTransactions, void>({
|
package/data/client/basket.ts
CHANGED
|
@@ -104,6 +104,45 @@ export const basketApi = api.injectEndpoints({
|
|
|
104
104
|
method: 'PUT',
|
|
105
105
|
body
|
|
106
106
|
}),
|
|
107
|
+
async onQueryStarted(_, { dispatch, queryFulfilled }) {
|
|
108
|
+
try {
|
|
109
|
+
const { data } = await queryFulfilled;
|
|
110
|
+
|
|
111
|
+
dispatch(
|
|
112
|
+
basketApi.util.updateQueryData(
|
|
113
|
+
'getBasket',
|
|
114
|
+
undefined,
|
|
115
|
+
() => data.basket
|
|
116
|
+
)
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (data.basket.namespace) {
|
|
120
|
+
dispatch(
|
|
121
|
+
basketApi.util.updateQueryData(
|
|
122
|
+
'getBasketDetail',
|
|
123
|
+
{ namespace: data.basket.namespace },
|
|
124
|
+
() => data.basket
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
dispatch(
|
|
130
|
+
basketApi.util.updateQueryData(
|
|
131
|
+
'getAllBaskets',
|
|
132
|
+
undefined,
|
|
133
|
+
(baskets) => {
|
|
134
|
+
if (!baskets) return baskets;
|
|
135
|
+
|
|
136
|
+
return baskets.map((basket) =>
|
|
137
|
+
basket.pk === data.basket.pk ? data.basket : basket
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('Error updating quantity:', error);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
107
146
|
invalidatesTags: ['MultiBasket', 'Basket', 'MiniBasket']
|
|
108
147
|
}),
|
|
109
148
|
clearBasket: build.mutation<Basket, void>({
|