@akinon/projectzero 2.0.39-rc.0 → 2.0.39
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 +1 -7
- package/app-template/.env.example +0 -1
- package/app-template/AGENTS.md +0 -8
- package/app-template/CHANGELOG.md +32 -78
- package/app-template/README.md +1 -25
- package/app-template/next.config.mjs +1 -7
- package/app-template/package.json +41 -41
- package/app-template/src/app/[pz]/account/layout.tsx +13 -2
- package/app-template/src/app/[pz]/account/orders/[id]/layout.tsx +26 -2
- package/app-template/src/app/[pz]/basket/layout.tsx +25 -0
- package/app-template/src/app/[pz]/category/[pk]/page.tsx +1 -1
- package/app-template/src/app/[pz]/client-root.tsx +2 -0
- package/app-template/src/app/[pz]/configure-page-context.ts +17 -0
- package/app-template/src/app/[pz]/flat-page/[pk]/page.tsx +14 -3
- package/app-template/src/app/[pz]/layout.tsx +1 -0
- package/app-template/src/app/[pz]/list/page.tsx +1 -1
- package/app-template/src/app/[pz]/orders/checkout/layout.tsx +23 -0
- package/app-template/src/app/[pz]/orders/completed/[token]/layout.tsx +24 -2
- package/app-template/src/app/[pz]/page.tsx +1 -1
- package/app-template/src/app/[pz]/pages/[slug]/page.tsx +1 -1
- package/app-template/src/app/[pz]/special-page/[pk]/page.tsx +10 -1
- package/app-template/src/hooks/index.ts +0 -2
- package/app-template/src/plugins.js +0 -1
- package/app-template/src/proxy.ts +1 -2
- package/app-template/src/views/category/layout.tsx +12 -2
- package/app-template/src/views/header/search/index.tsx +5 -13
- package/app-template/src/views/product/layout.tsx +8 -5
- package/app-template/src/views/product/slider.tsx +38 -85
- package/commands/plugins.ts +0 -4
- package/dist/commands/plugins.js +0 -4
- package/package.json +1 -1
- package/app-template/src/app/api/client-env/route.ts +0 -5
- package/app-template/src/app/global-error.tsx +0 -22
- package/app-template/src/utils/__tests__/theme-page-context.test.ts +0 -148
- package/app-template/src/utils/theme-page-context.ts +0 -309
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
import { ROUTES } from '@theme/routes';
|
|
2
|
-
import type {
|
|
3
|
-
Basket,
|
|
4
|
-
BreadcrumbResultType,
|
|
5
|
-
GetCategoryResponse,
|
|
6
|
-
Product,
|
|
7
|
-
ProductResult
|
|
8
|
-
} from '@akinon/next/types';
|
|
9
|
-
|
|
10
|
-
type UnknownRecord = Record<string, unknown>;
|
|
11
|
-
|
|
12
|
-
const toRecord = (value: unknown): UnknownRecord =>
|
|
13
|
-
value && typeof value === 'object' ? (value as UnknownRecord) : {};
|
|
14
|
-
|
|
15
|
-
const toStringValue = (value: unknown, fallback = ''): string =>
|
|
16
|
-
typeof value === 'string' ? value : fallback;
|
|
17
|
-
|
|
18
|
-
const humanizeSlug = (value: string) =>
|
|
19
|
-
value
|
|
20
|
-
.split('-')
|
|
21
|
-
.filter(Boolean)
|
|
22
|
-
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
23
|
-
.join(' ');
|
|
24
|
-
|
|
25
|
-
const normalizeProductImages = (
|
|
26
|
-
product?: Product | null
|
|
27
|
-
): Array<Record<string, unknown>> =>
|
|
28
|
-
(product?.productimage_set || []).map((item, index) => ({
|
|
29
|
-
...item,
|
|
30
|
-
url: item.image,
|
|
31
|
-
alt: `${product?.name || 'Product'} ${index + 1}`
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
const normalizeAttributeEntries = (
|
|
35
|
-
attributes: unknown
|
|
36
|
-
): Array<Record<string, string>> => {
|
|
37
|
-
const record = toRecord(attributes);
|
|
38
|
-
|
|
39
|
-
return Object.values(record)
|
|
40
|
-
.map(entry => {
|
|
41
|
-
const typedEntry = toRecord(entry);
|
|
42
|
-
const label = toStringValue(typedEntry.label);
|
|
43
|
-
const value = toStringValue(typedEntry.value);
|
|
44
|
-
|
|
45
|
-
if (!label && !value) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return { label, value };
|
|
50
|
-
})
|
|
51
|
-
.filter((entry): entry is { label: string; value: string } => entry !== null);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export const normalizeProductEntry = (
|
|
55
|
-
product?: Product | null
|
|
56
|
-
): Record<string, unknown> | null => {
|
|
57
|
-
if (!product) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const activePrice = toRecord((product as Product & UnknownRecord).active_price);
|
|
62
|
-
const images = normalizeProductImages(product);
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
...product,
|
|
66
|
-
title: product.name,
|
|
67
|
-
name: product.name,
|
|
68
|
-
url: product.absolute_url,
|
|
69
|
-
image: product.image || images[0]?.url || '',
|
|
70
|
-
images,
|
|
71
|
-
price: activePrice.price ?? product.price,
|
|
72
|
-
retailPrice: activePrice.retail_price ?? product.retail_price,
|
|
73
|
-
currency: activePrice.currency_type ?? product.currency_type ?? '',
|
|
74
|
-
description: product.description || '',
|
|
75
|
-
attributes: normalizeAttributeEntries(product.attributes_kwargs)
|
|
76
|
-
};
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
export const buildProductPageContext = (data: ProductResult) => {
|
|
80
|
-
const normalizedProduct = normalizeProductEntry(data.product);
|
|
81
|
-
const normalizedSelectedVariant = normalizeProductEntry(data.selected_variant);
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
page: {
|
|
85
|
-
type: 'product',
|
|
86
|
-
title: normalizedProduct?.title || 'Product',
|
|
87
|
-
path: normalizedProduct?.url || ''
|
|
88
|
-
},
|
|
89
|
-
product: {
|
|
90
|
-
...normalizedProduct,
|
|
91
|
-
selectedVariant: normalizedSelectedVariant,
|
|
92
|
-
selected_variant: normalizedSelectedVariant,
|
|
93
|
-
variants: data.variants || []
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export const buildHomepagePageContext = () => ({
|
|
99
|
-
page: {
|
|
100
|
-
type: 'homepage',
|
|
101
|
-
title: 'Homepage',
|
|
102
|
-
slug: 'homepage',
|
|
103
|
-
path: ROUTES.HOME
|
|
104
|
-
},
|
|
105
|
-
homepage: {
|
|
106
|
-
title: 'Homepage',
|
|
107
|
-
slug: 'homepage',
|
|
108
|
-
path: ROUTES.HOME
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
export const buildCustomPageContext = (slug: string) => {
|
|
113
|
-
const title = humanizeSlug(slug) || 'Custom Page';
|
|
114
|
-
const path = `/pages/${slug}`;
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
page: {
|
|
118
|
-
type: 'custom-page',
|
|
119
|
-
title,
|
|
120
|
-
slug,
|
|
121
|
-
path
|
|
122
|
-
},
|
|
123
|
-
customPage: {
|
|
124
|
-
title,
|
|
125
|
-
slug,
|
|
126
|
-
path
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
export const buildListingPageContext = ({
|
|
132
|
-
data,
|
|
133
|
-
breadcrumbData,
|
|
134
|
-
pageType,
|
|
135
|
-
path
|
|
136
|
-
}: {
|
|
137
|
-
data: GetCategoryResponse;
|
|
138
|
-
breadcrumbData?: BreadcrumbResultType[];
|
|
139
|
-
pageType: 'category' | 'search' | 'plp';
|
|
140
|
-
path?: string;
|
|
141
|
-
}) => {
|
|
142
|
-
const normalizedProducts = (data.products || [])
|
|
143
|
-
.map(product => normalizeProductEntry(product))
|
|
144
|
-
.filter((product): product is Record<string, unknown> => product !== null);
|
|
145
|
-
const categorySeoTitle = toStringValue(
|
|
146
|
-
data.category?.attributes?.category_seo_title?.value?.text
|
|
147
|
-
);
|
|
148
|
-
const effectivePath = path || data.category?.absolute_url || ROUTES.HOME;
|
|
149
|
-
const totalProducts =
|
|
150
|
-
Number(toRecord(data.pagination).count) || normalizedProducts.length;
|
|
151
|
-
const title =
|
|
152
|
-
pageType === 'search'
|
|
153
|
-
? data.search_text
|
|
154
|
-
? `Search results for ${data.search_text}`
|
|
155
|
-
: 'Search Results'
|
|
156
|
-
: data.category?.name || 'Product Listing';
|
|
157
|
-
|
|
158
|
-
const category = {
|
|
159
|
-
...(data.category || {}),
|
|
160
|
-
title: data.category?.name || '',
|
|
161
|
-
name: data.category?.name || '',
|
|
162
|
-
url: data.category?.absolute_url || '',
|
|
163
|
-
description: categorySeoTitle,
|
|
164
|
-
seoTitle: categorySeoTitle,
|
|
165
|
-
facets: data.facets || [],
|
|
166
|
-
products: normalizedProducts
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const listingBase = {
|
|
170
|
-
title,
|
|
171
|
-
path: effectivePath,
|
|
172
|
-
query: data.search_text || '',
|
|
173
|
-
products: normalizedProducts,
|
|
174
|
-
totalProducts,
|
|
175
|
-
resultCount: totalProducts,
|
|
176
|
-
facets: data.facets || [],
|
|
177
|
-
sorters: data.sorters || [],
|
|
178
|
-
pagination: data.pagination || {},
|
|
179
|
-
category
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
page: {
|
|
184
|
-
type: pageType,
|
|
185
|
-
title,
|
|
186
|
-
path: effectivePath
|
|
187
|
-
},
|
|
188
|
-
category,
|
|
189
|
-
listing: {
|
|
190
|
-
...listingBase,
|
|
191
|
-
breadcrumbs: breadcrumbData || []
|
|
192
|
-
},
|
|
193
|
-
search: {
|
|
194
|
-
...listingBase,
|
|
195
|
-
title: data.search_text
|
|
196
|
-
? `Search results for ${data.search_text}`
|
|
197
|
-
: 'Search Results'
|
|
198
|
-
},
|
|
199
|
-
plp: {
|
|
200
|
-
...listingBase,
|
|
201
|
-
title: data.category?.name || 'Product Listing'
|
|
202
|
-
},
|
|
203
|
-
breadcrumbs: breadcrumbData || []
|
|
204
|
-
};
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
export const buildBasketPageContext = ({
|
|
208
|
-
basket,
|
|
209
|
-
path
|
|
210
|
-
}: {
|
|
211
|
-
basket?: Basket | null;
|
|
212
|
-
path?: string;
|
|
213
|
-
}) => {
|
|
214
|
-
const normalizedItems = (basket?.basketitem_set || []).map(item => {
|
|
215
|
-
const normalizedProduct = normalizeProductEntry(item.product);
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
...item,
|
|
219
|
-
title: normalizedProduct?.title || item.product?.name || '',
|
|
220
|
-
name: normalizedProduct?.name || item.product?.name || '',
|
|
221
|
-
url: normalizedProduct?.url || item.product?.absolute_url || '',
|
|
222
|
-
image:
|
|
223
|
-
item.image ||
|
|
224
|
-
(typeof normalizedProduct?.image === 'string'
|
|
225
|
-
? normalizedProduct.image
|
|
226
|
-
: ''),
|
|
227
|
-
price: item.price || normalizedProduct?.price || '',
|
|
228
|
-
retailPrice: item.retail_price || normalizedProduct?.retailPrice || '',
|
|
229
|
-
totalAmount: item.total_amount || '',
|
|
230
|
-
unitPrice: item.unit_price || '',
|
|
231
|
-
currency: item.currency_type || normalizedProduct?.currency || '',
|
|
232
|
-
quantity: item.quantity,
|
|
233
|
-
attributes: normalizeAttributeEntries(
|
|
234
|
-
item.attributes_kwargs || item.product?.attributes_kwargs
|
|
235
|
-
),
|
|
236
|
-
product: normalizedProduct
|
|
237
|
-
};
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
return {
|
|
241
|
-
page: {
|
|
242
|
-
type: 'basket',
|
|
243
|
-
title: 'Basket',
|
|
244
|
-
path: path || ROUTES.BASKET
|
|
245
|
-
},
|
|
246
|
-
basket: {
|
|
247
|
-
id: basket?.pk || null,
|
|
248
|
-
path: path || ROUTES.BASKET,
|
|
249
|
-
checkoutUrl: ROUTES.CHECKOUT,
|
|
250
|
-
continueShoppingUrl: ROUTES.HOME,
|
|
251
|
-
isEmpty: normalizedItems.length === 0,
|
|
252
|
-
itemCount: normalizedItems.length,
|
|
253
|
-
totalQuantity: basket?.total_quantity || 0,
|
|
254
|
-
totalAmount: basket?.total_amount || '',
|
|
255
|
-
totalProductAmount: basket?.total_product_amount || '',
|
|
256
|
-
totalDiscountAmount: basket?.total_discount_amount || '',
|
|
257
|
-
voucherCode: basket?.voucher_code || '',
|
|
258
|
-
discounts: basket?.discounts || [],
|
|
259
|
-
items: normalizedItems
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
export const buildAccountPageContext = ({
|
|
265
|
-
path,
|
|
266
|
-
profileInfo,
|
|
267
|
-
sessionUser
|
|
268
|
-
}: {
|
|
269
|
-
path?: string;
|
|
270
|
-
profileInfo?: UnknownRecord | null;
|
|
271
|
-
sessionUser?: UnknownRecord | null;
|
|
272
|
-
}) => {
|
|
273
|
-
const normalizedPath = path || ROUTES.ACCOUNT;
|
|
274
|
-
const firstName =
|
|
275
|
-
toStringValue(profileInfo?.first_name) ||
|
|
276
|
-
toStringValue(sessionUser?.firstName) ||
|
|
277
|
-
toStringValue(sessionUser?.name).split(' ')[0] ||
|
|
278
|
-
'';
|
|
279
|
-
const lastName =
|
|
280
|
-
toStringValue(profileInfo?.last_name) ||
|
|
281
|
-
toStringValue(sessionUser?.lastName) ||
|
|
282
|
-
toStringValue(sessionUser?.name).split(' ').slice(1).join(' ') ||
|
|
283
|
-
'';
|
|
284
|
-
const email =
|
|
285
|
-
toStringValue(profileInfo?.email) || toStringValue(sessionUser?.email);
|
|
286
|
-
const fullName =
|
|
287
|
-
[firstName, lastName].filter(Boolean).join(' ') ||
|
|
288
|
-
toStringValue(sessionUser?.name);
|
|
289
|
-
|
|
290
|
-
return {
|
|
291
|
-
page: {
|
|
292
|
-
type: 'account',
|
|
293
|
-
title: 'Account',
|
|
294
|
-
path: normalizedPath
|
|
295
|
-
},
|
|
296
|
-
account: {
|
|
297
|
-
path: normalizedPath,
|
|
298
|
-
route: normalizedPath,
|
|
299
|
-
user: {
|
|
300
|
-
firstName,
|
|
301
|
-
lastName,
|
|
302
|
-
fullName,
|
|
303
|
-
email,
|
|
304
|
-
phone: toStringValue(profileInfo?.phone),
|
|
305
|
-
gender: toStringValue(profileInfo?.gender)
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
};
|
|
309
|
-
};
|