@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.
@@ -4,9 +4,6 @@ import { GetCategoryResponse, SearchParams } from '../../types';
4
4
  import { generateCommerceSearchParams } from '../../utils';
5
5
  import appFetch from '../../utils/app-fetch';
6
6
  import { ServerVariables } from '../../utils/server-variables';
7
- import { optimizeCategoryResponse } from '../../utils/payload-optimizer';
8
- import logger from '../../utils/log';
9
- import settings from 'settings';
10
7
 
11
8
  const getSpecialPageDataHandler = (
12
9
  pk: number,
@@ -48,7 +45,7 @@ export const getSpecialPageData = async ({
48
45
  searchParams: SearchParams;
49
46
  headers?: Record<string, string>;
50
47
  }) => {
51
- const result = await Cache.wrap(
48
+ return Cache.wrap(
52
49
  CacheKey.SpecialPage(pk, searchParams, headers),
53
50
  locale,
54
51
  getSpecialPageDataHandler(pk, locale, currency, searchParams, headers),
@@ -57,14 +54,4 @@ export const getSpecialPageData = async ({
57
54
  compressed: true
58
55
  }
59
56
  );
60
-
61
- if (settings.payloadOptimization?.enabled && result) {
62
- try {
63
- return optimizeCategoryResponse(result, settings.payloadOptimization);
64
- } catch (e) {
65
- logger.error('Payload optimization failed for special-page', { pk, error: (e as Error).message });
66
- }
67
- }
68
-
69
- return result;
70
57
  };
@@ -4,9 +4,6 @@ import { CacheOptions, WidgetResultType, WidgetSchemaType } from '../../types';
4
4
  import appFetch from '../../utils/app-fetch';
5
5
  import { widgets } from '../urls';
6
6
  import { ServerVariables } from '../../utils/server-variables';
7
- import { optimizeWidgetResponse } from '../../utils/payload-optimizer';
8
- import logger from '../../utils/log';
9
- import settings from 'settings';
10
7
 
11
8
  const getWidgetDataHandler =
12
9
  (
@@ -56,7 +53,7 @@ export const getWidgetData = async <T>({
56
53
  cacheOptions?: CacheOptions;
57
54
  headers?: Record<string, string>;
58
55
  }): Promise<WidgetResultType<T>> => {
59
- const result = await Cache.wrap(
56
+ return Cache.wrap(
60
57
  CacheKey.Widget(slug),
61
58
  locale,
62
59
  getWidgetDataHandler(slug, locale, currency, headers),
@@ -65,16 +62,6 @@ export const getWidgetData = async <T>({
65
62
  ...cacheOptions
66
63
  }
67
64
  );
68
-
69
- if (settings.payloadOptimization?.enabled && result) {
70
- try {
71
- return optimizeWidgetResponse(result, settings.payloadOptimization) as WidgetResultType<T>;
72
- } catch (e) {
73
- logger.error('Payload optimization failed for widget', { slug, error: (e as Error).message });
74
- }
75
- }
76
-
77
- return result as WidgetResultType<T>;
78
65
  };
79
66
 
80
67
  const getCollectionWidgetDataHandler =
package/data/urls.ts CHANGED
@@ -183,11 +183,7 @@ export const product = {
183
183
  breadcrumbUrl: (menuitemmodel: string) =>
184
184
  `/menus/generate_breadcrumb/?item=${menuitemmodel}&generator_name=menu_item`,
185
185
  bundleProduct: (productPk: string, queryString: string) =>
186
- `/bundle-product/${productPk}/?${queryString}`,
187
- similarProducts: (params?: string) =>
188
- `/similar-products${params ? `?${params}` : ''}`,
189
- similarProductsList: (params?: string) =>
190
- `/similar-product-list${params ? `?${params}` : ''}`
186
+ `/bundle-product/${productPk}/?${queryString}`
191
187
  };
192
188
 
193
189
  export const wishlist = {
package/hooks/index.ts CHANGED
@@ -14,3 +14,4 @@ export * from './use-logger';
14
14
  export * from './use-logger-context';
15
15
  export * from './use-sentry-uncaught-errors';
16
16
  export * from './use-pz-params';
17
+ export * from './use-toast';
@@ -39,7 +39,7 @@ export const useCaptcha = () => {
39
39
  };
40
40
 
41
41
  if (csrfToken) {
42
- setCookie('csrftoken', csrfToken, { secure: true });
42
+ setCookie('csrftoken', csrfToken);
43
43
  }
44
44
 
45
45
  const onCaptchaChange = useCallback(async (response) => {
@@ -0,0 +1,56 @@
1
+ 'use client'
2
+
3
+ import { useCallback } from 'react'
4
+ import { useAppDispatch } from '../redux/hooks'
5
+ import {
6
+ addToast,
7
+ removeToast,
8
+ ToastType,
9
+ ToastInput
10
+ } from '../redux/reducers/toast'
11
+
12
+ export type ToastOptions = Omit<ToastInput, 'type' | 'message'>
13
+
14
+ export const useToast = () => {
15
+ const dispatch = useAppDispatch()
16
+
17
+ const toast = useCallback(
18
+ (type: ToastType, message: string, options?: ToastOptions) => {
19
+ dispatch(addToast({ type, message, ...options }))
20
+ },
21
+ [dispatch]
22
+ )
23
+
24
+ const success = useCallback(
25
+ (message: string, options?: ToastOptions) =>
26
+ toast('success', message, options),
27
+ [toast]
28
+ )
29
+
30
+ const error = useCallback(
31
+ (message: string, options?: ToastOptions) =>
32
+ toast('error', message, options),
33
+ [toast]
34
+ )
35
+
36
+ const warning = useCallback(
37
+ (message: string, options?: ToastOptions) =>
38
+ toast('warning', message, options),
39
+ [toast]
40
+ )
41
+
42
+ const info = useCallback(
43
+ (message: string, options?: ToastOptions) =>
44
+ toast('info', message, options),
45
+ [toast]
46
+ )
47
+
48
+ const dismiss = useCallback(
49
+ (id: string) => {
50
+ dispatch(removeToast(id))
51
+ },
52
+ [dispatch]
53
+ )
54
+
55
+ return { toast, success, error, warning, info, dismiss }
56
+ }
@@ -3,6 +3,7 @@ import * as Sentry from '@sentry/nextjs';
3
3
 
4
4
  export async function register() {
5
5
  if (process.env.NEXT_RUNTIME === 'nodejs') {
6
+ await import('./node');
6
7
  initSentry('Server');
7
8
  }
8
9
 
@@ -1,2 +1,224 @@
1
- // OpenTelemetry tracing is handled by Sentry.
2
- // Custom NodeSDK setup removed due to version incompatibility with Sentry 10's OpenTelemetry v2.x requirements.
1
+ import { context, trace, type SpanAttributes } from '@opentelemetry/api';
2
+ import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
3
+ import { ExportResultCode, type ExportResult } from '@opentelemetry/core';
4
+ import {
5
+ BasicTracerProvider,
6
+ SimpleSpanProcessor,
7
+ type ReadableSpan,
8
+ type SpanExporter
9
+ } from '@opentelemetry/sdk-trace-base';
10
+
11
+ type OTelAttributeValue =
12
+ | { stringValue: string }
13
+ | { intValue: number }
14
+ | { doubleValue: number }
15
+ | { boolValue: boolean };
16
+
17
+ const PZ_DASHBOARD_TRACING_STARTED_KEY = '__pzDashboardTracingStarted__';
18
+ const SERVICE_NAME = 'pz-next-app';
19
+ const DASHBOARD_FALLBACK_URL = 'http://localhost:3005';
20
+
21
+ type PzDashboardGlobal = typeof globalThis & {
22
+ [PZ_DASHBOARD_TRACING_STARTED_KEY]?: boolean;
23
+ };
24
+
25
+ const getDashboardTraceUrl = () => {
26
+ const baseUrl = process.env.PZ_DASHBOARD_URL ?? DASHBOARD_FALLBACK_URL;
27
+
28
+ return `${baseUrl.replace(/\/$/, '')}/api/traces`;
29
+ };
30
+
31
+ const hrTimeToUnixNano = ([seconds, nanos]: [number, number]) =>
32
+ (BigInt(seconds) * BigInt(1000000000) + BigInt(nanos)).toString();
33
+
34
+ const getAttributeValue = (value: unknown): OTelAttributeValue => {
35
+ if (typeof value === 'string') {
36
+ return { stringValue: value };
37
+ }
38
+
39
+ if (typeof value === 'number') {
40
+ return Number.isInteger(value)
41
+ ? { intValue: value }
42
+ : { doubleValue: value };
43
+ }
44
+
45
+ if (typeof value === 'boolean') {
46
+ return { boolValue: value };
47
+ }
48
+
49
+ return { stringValue: JSON.stringify(value ?? '') };
50
+ };
51
+
52
+ const getAttributes = (attributes: SpanAttributes = {}) =>
53
+ Object.entries(attributes)
54
+ .filter(([, value]) => value !== undefined)
55
+ .map(([key, value]) => ({
56
+ key,
57
+ value: getAttributeValue(value)
58
+ }));
59
+
60
+ const getParentSpanId = (span: ReadableSpan) => {
61
+ if ('parentSpanId' in span && typeof span.parentSpanId === 'string') {
62
+ return span.parentSpanId;
63
+ }
64
+
65
+ if (
66
+ 'parentSpanContext' in span &&
67
+ span.parentSpanContext &&
68
+ typeof span.parentSpanContext === 'object' &&
69
+ 'spanId' in span.parentSpanContext
70
+ ) {
71
+ return String(span.parentSpanContext.spanId);
72
+ }
73
+
74
+ return undefined;
75
+ };
76
+
77
+ const getResourceAttributes = (span: ReadableSpan) => ({
78
+ ...(span.resource?.attributes ?? {}),
79
+ 'service.name':
80
+ process.env.OTEL_SERVICE_NAME ??
81
+ process.env.NEXT_PUBLIC_APP_NAME ??
82
+ SERVICE_NAME
83
+ });
84
+
85
+ const getScope = (span: ReadableSpan) => {
86
+ const scopedSpan = span as ReadableSpan & {
87
+ instrumentationLibrary?: {
88
+ name?: string;
89
+ version?: string;
90
+ };
91
+ instrumentationScope?: {
92
+ name?: string;
93
+ version?: string;
94
+ };
95
+ };
96
+ const scope =
97
+ scopedSpan.instrumentationScope ?? scopedSpan.instrumentationLibrary;
98
+
99
+ return {
100
+ name: scope?.name ?? SERVICE_NAME,
101
+ version: scope?.version ?? ''
102
+ };
103
+ };
104
+
105
+ const getSpanPayload = (span: ReadableSpan) => ({
106
+ traceId: span.spanContext().traceId,
107
+ spanId: span.spanContext().spanId,
108
+ parentSpanId: getParentSpanId(span),
109
+ name: span.name,
110
+ kind: span.kind,
111
+ startTimeUnixNano: hrTimeToUnixNano(span.startTime),
112
+ endTimeUnixNano: hrTimeToUnixNano(span.endTime),
113
+ attributes: getAttributes(span.attributes),
114
+ droppedAttributesCount: span.droppedAttributesCount ?? 0,
115
+ events:
116
+ span.events?.map((event) => ({
117
+ name: event.name,
118
+ timeUnixNano: hrTimeToUnixNano(event.time),
119
+ attributes: getAttributes(event.attributes),
120
+ droppedAttributesCount: event.droppedAttributesCount ?? 0
121
+ })) ?? [],
122
+ droppedEventsCount: span.droppedEventsCount ?? 0,
123
+ status: span.status,
124
+ links:
125
+ span.links?.map((link) => ({
126
+ traceId: link.context.traceId,
127
+ spanId: link.context.spanId,
128
+ attributes: getAttributes(link.attributes),
129
+ droppedAttributesCount: link.droppedAttributesCount ?? 0
130
+ })) ?? [],
131
+ droppedLinksCount: span.droppedLinksCount ?? 0
132
+ });
133
+
134
+ class PzDashboardTraceExporter implements SpanExporter {
135
+ constructor(private readonly url: string) {}
136
+
137
+ export(
138
+ spans: ReadableSpan[],
139
+ resultCallback: (result: ExportResult) => void
140
+ ) {
141
+ const [firstSpan] = spans;
142
+
143
+ if (!firstSpan) {
144
+ resultCallback({ code: ExportResultCode.SUCCESS });
145
+ return;
146
+ }
147
+
148
+ void this.sendSpans(spans, firstSpan, resultCallback);
149
+ }
150
+
151
+ private async sendSpans(
152
+ spans: ReadableSpan[],
153
+ firstSpan: ReadableSpan,
154
+ resultCallback: (result: ExportResult) => void
155
+ ) {
156
+ try {
157
+ const response = await fetch(this.url, {
158
+ method: 'POST',
159
+ headers: { 'Content-Type': 'application/json' },
160
+ body: JSON.stringify({
161
+ resourceSpans: [
162
+ {
163
+ resource: {
164
+ attributes: getAttributes(getResourceAttributes(firstSpan)),
165
+ droppedAttributesCount: 0
166
+ },
167
+ scopeSpans: spans.map((span) => ({
168
+ scope: getScope(span),
169
+ spans: [getSpanPayload(span)]
170
+ }))
171
+ }
172
+ ]
173
+ })
174
+ });
175
+
176
+ if (!response.ok) {
177
+ throw new Error(`PZ Dashboard trace export failed: ${response.status}`);
178
+ }
179
+
180
+ resultCallback({ code: ExportResultCode.SUCCESS });
181
+ } catch (error) {
182
+ resultCallback({
183
+ code: ExportResultCode.FAILED,
184
+ error: error instanceof Error ? error : new Error(String(error))
185
+ });
186
+ }
187
+ }
188
+
189
+ shutdown() {
190
+ return Promise.resolve();
191
+ }
192
+ }
193
+
194
+ const startDashboardTracing = () => {
195
+ const pzDashboardGlobal = globalThis as PzDashboardGlobal;
196
+
197
+ if (pzDashboardGlobal[PZ_DASHBOARD_TRACING_STARTED_KEY]) {
198
+ return;
199
+ }
200
+
201
+ const provider = new BasicTracerProvider({
202
+ spanProcessors: [
203
+ new SimpleSpanProcessor(
204
+ new PzDashboardTraceExporter(getDashboardTraceUrl())
205
+ )
206
+ ]
207
+ });
208
+
209
+ context.setGlobalContextManager(
210
+ new AsyncLocalStorageContextManager().enable()
211
+ );
212
+
213
+ const registered = trace.setGlobalTracerProvider(provider);
214
+
215
+ if (!registered) {
216
+ return;
217
+ }
218
+
219
+ pzDashboardGlobal[PZ_DASHBOARD_TRACING_STARTED_KEY] = true;
220
+ };
221
+
222
+ if (process.env.NODE_ENV === 'development') {
223
+ startDashboardTracing();
224
+ }
@@ -0,0 +1,146 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import * as crypto from 'crypto'
4
+ import logger from '../utils/log'
5
+
6
+ export const MockMode = {
7
+ RECORD: 'record',
8
+ REPLAY: 'replay'
9
+ } as const
10
+
11
+ export type MockMode = (typeof MockMode)[keyof typeof MockMode]
12
+
13
+ interface FixtureData {
14
+ request: {
15
+ method: string
16
+ url: string
17
+ body?: any
18
+ }
19
+ response: {
20
+ status: number
21
+ headers: Record<string, string>
22
+ body: any
23
+ }
24
+ recorded_at: string
25
+ }
26
+
27
+ const SENSITIVE_HEADERS = [
28
+ 'authorization',
29
+ 'cookie',
30
+ 'set-cookie',
31
+ 'x-csrftoken',
32
+ 'x-forwarded-for'
33
+ ]
34
+
35
+ class FixtureManager {
36
+ private fixturesDir: string
37
+ private dirEnsured = false
38
+
39
+ constructor() {
40
+ this.fixturesDir = path.join(process.cwd(), '.pz-fixtures')
41
+ }
42
+
43
+ private ensureDir(): void {
44
+ if (this.dirEnsured) return
45
+ fs.mkdirSync(this.fixturesDir, { recursive: true })
46
+ this.dirEnsured = true
47
+ }
48
+
49
+ generateKey(method: string, url: string, body?: any): string {
50
+ const normalized = method?.toUpperCase() ?? 'GET'
51
+ const parts = [normalized, url]
52
+
53
+ if (body && normalized !== 'GET') {
54
+ const bodyStr =
55
+ typeof body === 'string' ? body : JSON.stringify(body)
56
+ parts.push(bodyStr)
57
+ }
58
+
59
+ return crypto.createHash('md5').update(parts.join(':')).digest('hex')
60
+ }
61
+
62
+ extractHeaders(headers: Headers): Record<string, string> {
63
+ const result: Record<string, string> = {}
64
+ headers.forEach((value, key) => {
65
+ result[key] = value
66
+ })
67
+ return result
68
+ }
69
+
70
+ private stripSensitiveHeaders(
71
+ headers: Record<string, string>
72
+ ): Record<string, string> {
73
+ const cleaned: Record<string, string> = {}
74
+
75
+ for (const [key, value] of Object.entries(headers)) {
76
+ if (!SENSITIVE_HEADERS.includes(key.toLowerCase())) {
77
+ cleaned[key] = value
78
+ }
79
+ }
80
+
81
+ return cleaned
82
+ }
83
+
84
+ async write(
85
+ method: string,
86
+ url: string,
87
+ body: any,
88
+ response: { status: number; headers: Record<string, string>; body: any }
89
+ ): Promise<void> {
90
+ try {
91
+ this.ensureDir()
92
+
93
+ const normalized = method?.toUpperCase() ?? 'GET'
94
+ const key = this.generateKey(normalized, url, body)
95
+ const fixture: FixtureData = {
96
+ request: {
97
+ method: normalized,
98
+ url
99
+ },
100
+ response: {
101
+ status: response.status,
102
+ headers: this.stripSensitiveHeaders(response.headers),
103
+ body: response.body
104
+ },
105
+ recorded_at: new Date().toISOString()
106
+ }
107
+
108
+ if (body && normalized !== 'GET') {
109
+ fixture.request.body = body
110
+ }
111
+
112
+ const filePath = path.join(this.fixturesDir, `${key}.json`)
113
+ await fs.promises.writeFile(filePath, JSON.stringify(fixture, null, 2))
114
+
115
+ logger.debug(`[pz-mock] Recorded fixture: ${normalized} ${url} → ${filePath}`)
116
+ } catch (error) {
117
+ logger.error(`[pz-mock] Failed to write fixture`, { url, error })
118
+ }
119
+ }
120
+
121
+ async read(
122
+ method: string,
123
+ url: string,
124
+ body?: any
125
+ ): Promise<{ found: boolean; fixture?: FixtureData }> {
126
+ try {
127
+ const key = this.generateKey(method, url, body)
128
+ const filePath = path.join(this.fixturesDir, `${key}.json`)
129
+
130
+ const raw = await fs.promises.readFile(filePath, 'utf-8')
131
+ const fixture = JSON.parse(raw) as FixtureData
132
+
133
+ logger.debug(`[pz-mock] Replaying fixture: ${method} ${url} → ${key}`)
134
+ return { found: true, fixture }
135
+ } catch (error) {
136
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
137
+ logger.warn(`[pz-mock] No fixture found: ${method} ${url}`)
138
+ return { found: false }
139
+ }
140
+ logger.error(`[pz-mock] Failed to read fixture`, { url, error })
141
+ return { found: false }
142
+ }
143
+ }
144
+ }
145
+
146
+ export const fixtureManager = new FixtureManager()
@@ -547,8 +547,7 @@ const withPzDefault =
547
547
  'csrftoken',
548
548
  csrf_token,
549
549
  {
550
- domain: rootHostname,
551
- secure: true
550
+ domain: rootHostname
552
551
  }
553
552
  );
554
553
  }