@akinon/next 2.0.0-beta.2 → 2.0.0-beta.20
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/.eslintrc.js +12 -0
- package/CHANGELOG.md +377 -7
- package/__tests__/next-config.test.ts +83 -0
- package/__tests__/tsconfig.json +23 -0
- package/api/auth.ts +133 -44
- package/api/barcode-search.ts +59 -0
- package/api/cache.ts +41 -5
- package/api/client.ts +21 -4
- package/api/form.ts +85 -0
- package/api/image-proxy.ts +75 -0
- package/api/product-categories.ts +53 -0
- package/api/similar-product-list.ts +63 -0
- package/api/similar-products.ts +111 -0
- package/api/virtual-try-on.ts +382 -0
- package/assets/styles/index.scss +84 -0
- package/babel.config.js +6 -0
- package/bin/pz-generate-routes.js +115 -0
- package/bin/pz-prebuild.js +1 -0
- package/bin/pz-predev.js +1 -0
- package/bin/pz-run-tests.js +99 -0
- package/bin/run-prebuild-tests.js +46 -0
- package/components/accordion.tsx +20 -5
- package/components/button.tsx +51 -36
- package/components/client-root.tsx +138 -2
- package/components/file-input.tsx +65 -3
- package/components/index.ts +1 -0
- package/components/input.tsx +1 -1
- package/components/link.tsx +46 -16
- package/components/logger-popup.tsx +213 -0
- package/components/modal.tsx +32 -16
- package/components/plugin-module.tsx +62 -3
- package/components/price.tsx +2 -2
- package/components/select.tsx +1 -1
- package/components/selected-payment-option-view.tsx +21 -0
- package/components/theme-editor/blocks/accordion-block.tsx +136 -0
- package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
- package/components/theme-editor/blocks/button-block.tsx +593 -0
- package/components/theme-editor/blocks/counter-block.tsx +348 -0
- package/components/theme-editor/blocks/divider-block.tsx +20 -0
- package/components/theme-editor/blocks/embed-block.tsx +208 -0
- package/components/theme-editor/blocks/group-block.tsx +116 -0
- package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
- package/components/theme-editor/blocks/icon-block.tsx +230 -0
- package/components/theme-editor/blocks/image-block.tsx +137 -0
- package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
- package/components/theme-editor/blocks/input-block.tsx +123 -0
- package/components/theme-editor/blocks/link-block.tsx +216 -0
- package/components/theme-editor/blocks/lottie-block.tsx +325 -0
- package/components/theme-editor/blocks/map-block.tsx +89 -0
- package/components/theme-editor/blocks/slider-block.tsx +595 -0
- package/components/theme-editor/blocks/tab-block.tsx +10 -0
- package/components/theme-editor/blocks/text-block.tsx +52 -0
- package/components/theme-editor/blocks/video-block.tsx +122 -0
- package/components/theme-editor/components/action-toolbar.tsx +305 -0
- package/components/theme-editor/components/designer-overlay.tsx +74 -0
- package/components/theme-editor/components/with-designer-features.tsx +142 -0
- package/components/theme-editor/dynamic-font-loader.tsx +79 -0
- package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
- package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
- package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
- package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
- package/components/theme-editor/placeholder-registry.ts +31 -0
- package/components/theme-editor/sections/before-after-section.tsx +245 -0
- package/components/theme-editor/sections/contact-form-section.tsx +563 -0
- package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
- package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
- package/components/theme-editor/sections/divider-section.tsx +62 -0
- package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
- package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
- package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
- package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
- package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
- package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
- package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
- package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
- package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
- package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
- package/components/theme-editor/sections/section-wrapper.tsx +135 -0
- package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
- package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
- package/components/theme-editor/sections/tabs-section.tsx +578 -0
- package/components/theme-editor/theme-block.tsx +102 -0
- package/components/theme-editor/theme-placeholder-client.tsx +218 -0
- package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
- package/components/theme-editor/theme-placeholder.tsx +288 -0
- package/components/theme-editor/theme-section.tsx +1224 -0
- package/components/theme-editor/theme-settings-context.tsx +13 -0
- package/components/theme-editor/utils/index.ts +792 -0
- package/components/theme-editor/utils/iterator-utils.ts +234 -0
- package/components/theme-editor/utils/publish-window.ts +86 -0
- package/components/theme-editor/utils/visibility-rules.ts +188 -0
- package/data/client/account.ts +17 -2
- package/data/client/api.ts +2 -0
- package/data/client/basket.ts +66 -5
- package/data/client/checkout.ts +391 -99
- package/data/client/misc.ts +38 -2
- package/data/client/product.ts +19 -2
- package/data/client/user.ts +16 -8
- package/data/server/category.ts +11 -9
- package/data/server/flatpage.ts +11 -4
- package/data/server/form.ts +15 -4
- package/data/server/landingpage.ts +11 -4
- package/data/server/list.ts +5 -4
- package/data/server/menu.ts +11 -3
- package/data/server/product.ts +111 -55
- package/data/server/seo.ts +14 -4
- package/data/server/special-page.ts +5 -4
- package/data/server/widget.ts +90 -5
- package/data/urls.ts +16 -5
- package/hocs/client/with-segment-defaults.tsx +2 -2
- package/hocs/server/with-segment-defaults.tsx +65 -20
- package/hooks/index.ts +4 -0
- package/hooks/use-localization.ts +24 -10
- package/hooks/use-logger-context.tsx +114 -0
- package/hooks/use-logger.ts +92 -0
- package/hooks/use-loyalty-availability.ts +21 -0
- package/hooks/use-payment-options.ts +2 -1
- package/hooks/use-pz-params.ts +37 -0
- package/hooks/use-router.ts +51 -14
- package/hooks/use-sentry-uncaught-errors.ts +24 -0
- package/instrumentation/index.ts +10 -1
- package/instrumentation/node.ts +2 -20
- package/jest.config.js +25 -0
- package/lib/cache-handler.mjs +534 -16
- package/lib/cache.ts +272 -37
- package/localization/index.ts +2 -1
- package/localization/provider.tsx +2 -5
- package/middlewares/bfcache-headers.ts +18 -0
- package/middlewares/checkout-provider.ts +1 -1
- package/middlewares/complete-gpay.ts +32 -26
- package/middlewares/complete-masterpass.ts +33 -26
- package/middlewares/complete-wallet.ts +182 -0
- package/middlewares/default.ts +360 -215
- package/middlewares/index.ts +10 -2
- package/middlewares/locale.ts +34 -11
- package/middlewares/masterpass-rest-callback.ts +230 -0
- package/middlewares/oauth-login.ts +200 -57
- package/middlewares/pretty-url.ts +21 -8
- package/middlewares/redirection-payment.ts +32 -26
- package/middlewares/saved-card-redirection.ts +33 -26
- package/middlewares/three-d-redirection.ts +32 -26
- package/middlewares/url-redirection.ts +11 -1
- package/middlewares/wallet-complete-redirection.ts +206 -0
- package/package.json +25 -10
- package/plugins.d.ts +19 -4
- package/plugins.js +10 -1
- package/redux/actions.ts +47 -0
- package/redux/middlewares/checkout.ts +63 -138
- package/redux/middlewares/index.ts +14 -10
- package/redux/middlewares/pre-order/address.ts +7 -2
- package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/delivery-option.ts +7 -1
- package/redux/middlewares/pre-order/index.ts +16 -10
- package/redux/middlewares/pre-order/installment-option.ts +8 -1
- package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
- package/redux/middlewares/pre-order/payment-option.ts +7 -1
- package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
- package/redux/middlewares/pre-order/redirection.ts +8 -2
- package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
- package/redux/middlewares/pre-order/shipping-option.ts +7 -1
- package/redux/middlewares/pre-order/shipping-step.ts +5 -1
- package/redux/reducers/checkout.ts +23 -3
- package/redux/reducers/index.ts +11 -3
- package/redux/reducers/root.ts +7 -2
- package/redux/reducers/widget.ts +80 -0
- package/sentry/index.ts +69 -13
- package/tailwind/content.js +16 -0
- package/types/commerce/account.ts +5 -1
- package/types/commerce/checkout.ts +35 -1
- package/types/commerce/widget.ts +33 -0
- package/types/index.ts +101 -6
- package/types/next-auth.d.ts +2 -2
- package/types/widget.ts +80 -0
- package/utils/app-fetch.ts +7 -2
- package/utils/generate-commerce-search-params.ts +3 -2
- package/utils/get-checkout-path.ts +3 -0
- package/utils/get-root-hostname.ts +28 -0
- package/utils/index.ts +64 -10
- package/utils/localization.ts +4 -0
- package/utils/mobile-3d-iframe.ts +8 -2
- package/utils/override-middleware.ts +7 -12
- package/utils/pz-segments.ts +92 -0
- package/utils/redirect-ignore.ts +35 -0
- package/utils/redirect.ts +9 -3
- package/utils/redirection-iframe.ts +8 -2
- package/utils/widget-styles.ts +107 -0
- package/views/error-page.tsx +93 -0
- package/with-pz-config.js +13 -6
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { Block } from '../theme-block';
|
|
2
|
+
|
|
3
|
+
interface IteratorOptions {
|
|
4
|
+
iteratorBlock: Block;
|
|
5
|
+
sectionDataSource?: any;
|
|
6
|
+
isDesigner?: boolean;
|
|
7
|
+
forceIteratorCount?: number;
|
|
8
|
+
forceIteratorOffset?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const parsePositiveNumber = (value: unknown, fallback: number): number => {
|
|
12
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
13
|
+
return Math.max(0, Math.floor(value));
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === 'string') {
|
|
16
|
+
const parsed = Number(value);
|
|
17
|
+
if (Number.isFinite(parsed)) {
|
|
18
|
+
return Math.max(0, Math.floor(parsed));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return fallback;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const extractProducts = (
|
|
25
|
+
collectionData: any,
|
|
26
|
+
dataPath: string | undefined
|
|
27
|
+
): Record<string, unknown>[] | null => {
|
|
28
|
+
if (!collectionData) return null;
|
|
29
|
+
|
|
30
|
+
// Respect explicit iterator path first.
|
|
31
|
+
if (dataPath) {
|
|
32
|
+
if (Array.isArray(collectionData)) {
|
|
33
|
+
if (dataPath === 'products' || dataPath === 'items') {
|
|
34
|
+
return collectionData as Record<string, unknown>[];
|
|
35
|
+
}
|
|
36
|
+
} else if (collectionData && typeof collectionData === 'object') {
|
|
37
|
+
const pathParts = dataPath.split('.');
|
|
38
|
+
let value: unknown = collectionData;
|
|
39
|
+
|
|
40
|
+
for (const part of pathParts) {
|
|
41
|
+
value = (value as Record<string, unknown>)?.[part];
|
|
42
|
+
if (value === undefined) break;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
return value as Record<string, unknown>[];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fallback to generic products array for backward compatibility.
|
|
52
|
+
if (Array.isArray(collectionData)) {
|
|
53
|
+
return collectionData as Record<string, unknown>[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (Array.isArray((collectionData as any)?.products)) {
|
|
57
|
+
return (collectionData as any).products;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const getProductsFromDataSource = (
|
|
64
|
+
sectionDataSource: any,
|
|
65
|
+
dataPath: string | undefined,
|
|
66
|
+
isDesigner: boolean
|
|
67
|
+
): Record<string, unknown>[] => {
|
|
68
|
+
if (!sectionDataSource?.details) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const isEditorMode =
|
|
73
|
+
isDesigner && typeof window !== 'undefined' && window.parent !== window;
|
|
74
|
+
|
|
75
|
+
const collection = sectionDataSource.details.collection;
|
|
76
|
+
|
|
77
|
+
// In editor mode, prefer live products; on storefront, prefer fetched data.
|
|
78
|
+
// Fall back to the other source if the primary one yields no products.
|
|
79
|
+
const candidates = isEditorMode
|
|
80
|
+
? [collection?.products, collection?.data]
|
|
81
|
+
: [collection?.data, collection?.products];
|
|
82
|
+
|
|
83
|
+
for (const candidate of candidates) {
|
|
84
|
+
const products = extractProducts(candidate, dataPath);
|
|
85
|
+
if (products && products.length > 0) {
|
|
86
|
+
return products;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return [];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const replaceBlockValues = (
|
|
94
|
+
blockToReplace: Block,
|
|
95
|
+
productData: Record<string, unknown>,
|
|
96
|
+
productIndex: number
|
|
97
|
+
): Block => {
|
|
98
|
+
const newBlock: Block = {
|
|
99
|
+
...blockToReplace,
|
|
100
|
+
properties: blockToReplace.properties
|
|
101
|
+
? { ...blockToReplace.properties }
|
|
102
|
+
: blockToReplace.properties
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const isPlaceholderMode = !productData || Object.keys(productData).length === 0;
|
|
106
|
+
|
|
107
|
+
if (newBlock.properties?.dataBinding && !isPlaceholderMode) {
|
|
108
|
+
const bindingPath = newBlock.properties.dataBinding.replace('item.', '');
|
|
109
|
+
const pathParts = bindingPath.split('.');
|
|
110
|
+
|
|
111
|
+
let value: unknown = productData;
|
|
112
|
+
for (const part of pathParts) {
|
|
113
|
+
const arrayMatch = part.match(/^(.+)\[(\d+)\]$/);
|
|
114
|
+
if (arrayMatch) {
|
|
115
|
+
const [, arrayName, indexStr] = arrayMatch;
|
|
116
|
+
const obj = value as Record<string, unknown>;
|
|
117
|
+
const arr = obj?.[arrayName];
|
|
118
|
+
if (Array.isArray(arr)) {
|
|
119
|
+
value = arr[parseInt(indexStr, 10)];
|
|
120
|
+
} else {
|
|
121
|
+
value = undefined;
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
value = (value as Record<string, unknown>)?.[part];
|
|
125
|
+
}
|
|
126
|
+
if (value === undefined) break;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (value !== undefined) {
|
|
130
|
+
if (newBlock.properties.tag === 'a') {
|
|
131
|
+
newBlock.properties = {
|
|
132
|
+
...newBlock.properties,
|
|
133
|
+
href: value
|
|
134
|
+
};
|
|
135
|
+
} else {
|
|
136
|
+
newBlock.value = value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} else if (isPlaceholderMode && newBlock.type === 'image') {
|
|
140
|
+
const placeholderImages = [
|
|
141
|
+
'/assets/images/product-placeholder-1.jpg',
|
|
142
|
+
'/assets/images/product-placeholder-2.jpg',
|
|
143
|
+
'/assets/images/product-placeholder-3.jpg',
|
|
144
|
+
'/assets/images/product-placeholder-4.jpg'
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const placeholderIndex = productIndex % placeholderImages.length;
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
newBlock.value &&
|
|
151
|
+
typeof newBlock.value === 'string' &&
|
|
152
|
+
newBlock.value.includes('product-placeholder')
|
|
153
|
+
) {
|
|
154
|
+
newBlock.value = placeholderImages[placeholderIndex];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (newBlock.blocks && newBlock.blocks.length > 0) {
|
|
159
|
+
newBlock.blocks = newBlock.blocks.map(childBlock =>
|
|
160
|
+
replaceBlockValues(childBlock, productData, productIndex)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return newBlock;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const buildIteratorBlock = ({
|
|
168
|
+
iteratorBlock,
|
|
169
|
+
sectionDataSource,
|
|
170
|
+
isDesigner = false,
|
|
171
|
+
forceIteratorCount,
|
|
172
|
+
forceIteratorOffset
|
|
173
|
+
}: IteratorOptions): Block => {
|
|
174
|
+
if (
|
|
175
|
+
!iteratorBlock.isIterator ||
|
|
176
|
+
!iteratorBlock.blocks ||
|
|
177
|
+
iteratorBlock.blocks.length === 0
|
|
178
|
+
) {
|
|
179
|
+
return iteratorBlock;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const template = iteratorBlock.blocks[0];
|
|
183
|
+
const dataPath =
|
|
184
|
+
iteratorBlock.iteratorDataPath || iteratorBlock.properties?.iteratorDataPath;
|
|
185
|
+
const products = getProductsFromDataSource(
|
|
186
|
+
sectionDataSource,
|
|
187
|
+
dataPath,
|
|
188
|
+
isDesigner
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const useIteratorCount =
|
|
192
|
+
forceIteratorCount !== undefined
|
|
193
|
+
? true
|
|
194
|
+
: iteratorBlock.properties?.useIteratorCount === true ||
|
|
195
|
+
iteratorBlock.properties?.useIteratorCount === 'true';
|
|
196
|
+
const iteratorCount =
|
|
197
|
+
forceIteratorCount !== undefined
|
|
198
|
+
? Math.max(1, Math.floor(forceIteratorCount))
|
|
199
|
+
: parsePositiveNumber(iteratorBlock.properties?.iteratorCount, 1);
|
|
200
|
+
const iteratorOffset =
|
|
201
|
+
forceIteratorOffset !== undefined
|
|
202
|
+
? Math.max(0, Math.floor(forceIteratorOffset))
|
|
203
|
+
: parsePositiveNumber(iteratorBlock.properties?.iteratorOffset, 0);
|
|
204
|
+
|
|
205
|
+
const displayProducts =
|
|
206
|
+
products.length > 0
|
|
207
|
+
? useIteratorCount
|
|
208
|
+
? products.slice(iteratorOffset, iteratorOffset + iteratorCount)
|
|
209
|
+
: products
|
|
210
|
+
: [];
|
|
211
|
+
|
|
212
|
+
const actualCount =
|
|
213
|
+
displayProducts.length > 0 ? displayProducts.length : Math.max(iteratorCount, 1);
|
|
214
|
+
|
|
215
|
+
const clonedBlocks = Array.from({ length: actualCount }, (_, index) => {
|
|
216
|
+
const product = displayProducts[index] || {};
|
|
217
|
+
|
|
218
|
+
const templateCopy: Block = {
|
|
219
|
+
...template,
|
|
220
|
+
id: `${template.id}-clone-${index}`,
|
|
221
|
+
properties: template.properties
|
|
222
|
+
? { ...template.properties }
|
|
223
|
+
: template.properties,
|
|
224
|
+
blocks: template.blocks ? template.blocks.map(b => ({ ...b })) : undefined
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return replaceBlockValues(templateCopy, product, index);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
...iteratorBlock,
|
|
232
|
+
blocks: clonedBlocks
|
|
233
|
+
};
|
|
234
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export interface PublishWindowValue {
|
|
2
|
+
enabled?: boolean;
|
|
3
|
+
startAt?: string;
|
|
4
|
+
endAt?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type PublishWindowStatus =
|
|
8
|
+
| 'inactive'
|
|
9
|
+
| 'scheduled'
|
|
10
|
+
| 'active'
|
|
11
|
+
| 'expired';
|
|
12
|
+
|
|
13
|
+
const isPublishWindowShape = (value: unknown): value is PublishWindowValue => {
|
|
14
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const candidate = value as Record<string, unknown>;
|
|
19
|
+
return (
|
|
20
|
+
candidate.enabled !== undefined ||
|
|
21
|
+
candidate.startAt !== undefined ||
|
|
22
|
+
candidate.endAt !== undefined
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const normalizePublishWindowValue = (
|
|
27
|
+
value: unknown
|
|
28
|
+
): PublishWindowValue => {
|
|
29
|
+
if (isPublishWindowShape(value)) {
|
|
30
|
+
return {
|
|
31
|
+
enabled: value.enabled === true,
|
|
32
|
+
startAt: typeof value.startAt === 'string' ? value.startAt : '',
|
|
33
|
+
endAt: typeof value.endAt === 'string' ? value.endAt : ''
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
38
|
+
const responsiveValue = value as Record<string, unknown>;
|
|
39
|
+
const nestedValue =
|
|
40
|
+
responsiveValue.desktop ||
|
|
41
|
+
responsiveValue.mobile ||
|
|
42
|
+
responsiveValue.tablet;
|
|
43
|
+
|
|
44
|
+
if (nestedValue && nestedValue !== value) {
|
|
45
|
+
return normalizePublishWindowValue(nestedValue);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
enabled: false,
|
|
51
|
+
startAt: '',
|
|
52
|
+
endAt: ''
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const parsePublishTime = (value?: string): number | null => {
|
|
57
|
+
if (!value) return null;
|
|
58
|
+
const parsed = Date.parse(value);
|
|
59
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const getPublishWindowStatus = (
|
|
63
|
+
value: unknown,
|
|
64
|
+
now: Date = new Date()
|
|
65
|
+
): PublishWindowStatus => {
|
|
66
|
+
const publishWindow = normalizePublishWindowValue(value);
|
|
67
|
+
|
|
68
|
+
if (!publishWindow.enabled) return 'inactive';
|
|
69
|
+
|
|
70
|
+
const currentTime = now.getTime();
|
|
71
|
+
const startAt = parsePublishTime(publishWindow.startAt);
|
|
72
|
+
const endAt = parsePublishTime(publishWindow.endAt);
|
|
73
|
+
|
|
74
|
+
if (startAt !== null && currentTime < startAt) return 'scheduled';
|
|
75
|
+
if (endAt !== null && currentTime > endAt) return 'expired';
|
|
76
|
+
|
|
77
|
+
return 'active';
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const isPublishWindowVisible = (
|
|
81
|
+
value: unknown,
|
|
82
|
+
now: Date = new Date()
|
|
83
|
+
): boolean => {
|
|
84
|
+
const status = getPublishWindowStatus(value, now);
|
|
85
|
+
return status === 'inactive' || status === 'active';
|
|
86
|
+
};
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Block } from '../theme-block';
|
|
2
|
+
import { Section } from '../theme-section';
|
|
3
|
+
|
|
4
|
+
export type VisibilityAuthState = 'all' | 'authenticated' | 'guest';
|
|
5
|
+
export type VisibilityPathMatchType =
|
|
6
|
+
| 'any'
|
|
7
|
+
| 'equals'
|
|
8
|
+
| 'contains'
|
|
9
|
+
| 'startsWith';
|
|
10
|
+
|
|
11
|
+
export interface VisibilityRules {
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
authState?: VisibilityAuthState;
|
|
14
|
+
allowedBreakpoints?: string[];
|
|
15
|
+
allowedLocales?: string[];
|
|
16
|
+
allowedCurrencies?: string[];
|
|
17
|
+
pathnameMatchType?: VisibilityPathMatchType;
|
|
18
|
+
pathnameValue?: string;
|
|
19
|
+
queryParamKey?: string;
|
|
20
|
+
queryParamValue?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface VisibilityRuleContext {
|
|
24
|
+
authState: 'authenticated' | 'guest' | 'loading';
|
|
25
|
+
breakpoint: string;
|
|
26
|
+
locale: string;
|
|
27
|
+
currency: string;
|
|
28
|
+
pathname: string;
|
|
29
|
+
searchParams?: {
|
|
30
|
+
get: (key: string) => string | null;
|
|
31
|
+
} | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const normalizeStringArray = (value: unknown): string[] => {
|
|
35
|
+
if (!Array.isArray(value)) return [];
|
|
36
|
+
return value
|
|
37
|
+
.map(item => String(item || '').trim())
|
|
38
|
+
.filter(Boolean);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const normalizeVisibilityRules = (value: unknown): VisibilityRules => {
|
|
42
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
43
|
+
return {
|
|
44
|
+
enabled: false,
|
|
45
|
+
authState: 'all',
|
|
46
|
+
allowedBreakpoints: [],
|
|
47
|
+
allowedLocales: [],
|
|
48
|
+
allowedCurrencies: [],
|
|
49
|
+
pathnameMatchType: 'any',
|
|
50
|
+
pathnameValue: '',
|
|
51
|
+
queryParamKey: '',
|
|
52
|
+
queryParamValue: ''
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const rules = value as Record<string, unknown>;
|
|
57
|
+
return {
|
|
58
|
+
enabled: rules.enabled === true,
|
|
59
|
+
authState:
|
|
60
|
+
rules.authState === 'authenticated' || rules.authState === 'guest'
|
|
61
|
+
? rules.authState
|
|
62
|
+
: 'all',
|
|
63
|
+
allowedBreakpoints: normalizeStringArray(rules.allowedBreakpoints),
|
|
64
|
+
allowedLocales: normalizeStringArray(rules.allowedLocales),
|
|
65
|
+
allowedCurrencies: normalizeStringArray(rules.allowedCurrencies).map(item =>
|
|
66
|
+
item.toUpperCase()
|
|
67
|
+
),
|
|
68
|
+
pathnameMatchType:
|
|
69
|
+
rules.pathnameMatchType === 'equals' ||
|
|
70
|
+
rules.pathnameMatchType === 'contains' ||
|
|
71
|
+
rules.pathnameMatchType === 'startsWith'
|
|
72
|
+
? rules.pathnameMatchType
|
|
73
|
+
: 'any',
|
|
74
|
+
pathnameValue: String(rules.pathnameValue || '').trim(),
|
|
75
|
+
queryParamKey: String(rules.queryParamKey || '').trim(),
|
|
76
|
+
queryParamValue: String(rules.queryParamValue || '').trim()
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const evaluateVisibilityRules = (
|
|
81
|
+
rawRules: unknown,
|
|
82
|
+
context: VisibilityRuleContext
|
|
83
|
+
): boolean => {
|
|
84
|
+
const rules = normalizeVisibilityRules(rawRules);
|
|
85
|
+
if (!rules.enabled) return true;
|
|
86
|
+
|
|
87
|
+
if (rules.authState && rules.authState !== 'all') {
|
|
88
|
+
if (context.authState === 'loading') return false;
|
|
89
|
+
if (rules.authState === 'authenticated') {
|
|
90
|
+
if (context.authState !== 'authenticated') return false;
|
|
91
|
+
} else if (context.authState !== 'guest') {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
rules.allowedBreakpoints &&
|
|
98
|
+
rules.allowedBreakpoints.length > 0 &&
|
|
99
|
+
!rules.allowedBreakpoints.includes(context.breakpoint)
|
|
100
|
+
) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
rules.allowedLocales &&
|
|
106
|
+
rules.allowedLocales.length > 0 &&
|
|
107
|
+
!rules.allowedLocales.includes(context.locale)
|
|
108
|
+
) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (
|
|
113
|
+
rules.allowedCurrencies &&
|
|
114
|
+
rules.allowedCurrencies.length > 0 &&
|
|
115
|
+
!rules.allowedCurrencies.includes(String(context.currency || '').toUpperCase())
|
|
116
|
+
) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const pathname = String(context.pathname || '');
|
|
121
|
+
if (rules.pathnameMatchType && rules.pathnameMatchType !== 'any') {
|
|
122
|
+
const ruleValue = String(rules.pathnameValue || '').trim();
|
|
123
|
+
if (!ruleValue) return false;
|
|
124
|
+
|
|
125
|
+
if (rules.pathnameMatchType === 'equals' && pathname !== ruleValue) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (
|
|
130
|
+
rules.pathnameMatchType === 'contains' &&
|
|
131
|
+
!pathname.includes(ruleValue)
|
|
132
|
+
) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
rules.pathnameMatchType === 'startsWith' &&
|
|
138
|
+
!pathname.startsWith(ruleValue)
|
|
139
|
+
) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (rules.queryParamKey) {
|
|
145
|
+
const actualValue = context.searchParams?.get(rules.queryParamKey) ?? null;
|
|
146
|
+
if (actualValue === null) return false;
|
|
147
|
+
if (rules.queryParamValue && actualValue !== rules.queryParamValue) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return true;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const applyBlockVisibility = (
|
|
156
|
+
block: Block,
|
|
157
|
+
context: VisibilityRuleContext
|
|
158
|
+
): Block => {
|
|
159
|
+
const blocks = block.blocks?.map(child => applyBlockVisibility(child, context));
|
|
160
|
+
const isVisibleByRules = evaluateVisibilityRules(
|
|
161
|
+
block.properties?.visibilityRules,
|
|
162
|
+
context
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
...block,
|
|
167
|
+
hidden: Boolean(block.hidden) || !isVisibleByRules,
|
|
168
|
+
blocks
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const applyVisibilityRulesToSections = (
|
|
173
|
+
sections: Section[],
|
|
174
|
+
context: VisibilityRuleContext
|
|
175
|
+
): Section[] =>
|
|
176
|
+
sections.map(section => {
|
|
177
|
+
const blocks = section.blocks.map(block => applyBlockVisibility(block, context));
|
|
178
|
+
const isVisibleByRules = evaluateVisibilityRules(
|
|
179
|
+
section.properties?.visibilityRules,
|
|
180
|
+
context
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
...section,
|
|
185
|
+
hidden: Boolean(section.hidden) || !isVisibleByRules,
|
|
186
|
+
blocks
|
|
187
|
+
};
|
|
188
|
+
});
|
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';
|
|
@@ -77,6 +78,10 @@ interface LoyaltyTransactions {
|
|
|
77
78
|
}[];
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
interface PasswordResetValidateResponse {
|
|
82
|
+
validlink: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
80
85
|
const accountApi = api.injectEndpoints({
|
|
81
86
|
endpoints: (builder) => ({
|
|
82
87
|
updatePassword: builder.mutation<void, AccountChangePasswordFormType>({
|
|
@@ -216,11 +221,20 @@ const accountApi = api.injectEndpoints({
|
|
|
216
221
|
method: 'PATCH'
|
|
217
222
|
})
|
|
218
223
|
}),
|
|
219
|
-
getLoyaltyBalance: builder.query<
|
|
224
|
+
getLoyaltyBalance: builder.query<
|
|
225
|
+
{ balance: number; balances?: LoyaltyBalanceItem[] },
|
|
226
|
+
void
|
|
227
|
+
>({
|
|
220
228
|
query: () => buildClientRequestUrl(account.loyaltyBalance)
|
|
221
229
|
}),
|
|
222
230
|
getLoyaltyTransactions: builder.query<LoyaltyTransactions, void>({
|
|
223
231
|
query: () => buildClientRequestUrl(account.loyaltyTransactions)
|
|
232
|
+
}),
|
|
233
|
+
getValidatePasswordResetToken: builder.query<
|
|
234
|
+
PasswordResetValidateResponse,
|
|
235
|
+
string
|
|
236
|
+
>({
|
|
237
|
+
query: (slug) => buildClientRequestUrl(account.passwordReset(slug))
|
|
224
238
|
})
|
|
225
239
|
}),
|
|
226
240
|
overrideExisting: true
|
|
@@ -247,5 +261,6 @@ export const {
|
|
|
247
261
|
usePasswordResetMutation,
|
|
248
262
|
useAnonymizeMutation,
|
|
249
263
|
useGetLoyaltyBalanceQuery,
|
|
250
|
-
useGetLoyaltyTransactionsQuery
|
|
264
|
+
useGetLoyaltyTransactionsQuery,
|
|
265
|
+
useGetValidatePasswordResetTokenQuery
|
|
251
266
|
} = accountApi;
|
package/data/client/api.ts
CHANGED
package/data/client/basket.ts
CHANGED
|
@@ -24,6 +24,26 @@ export const basketApi = api.injectEndpoints({
|
|
|
24
24
|
transformResponse: (response: { basket: Basket }) => response.basket,
|
|
25
25
|
providesTags: ['Basket']
|
|
26
26
|
}),
|
|
27
|
+
getMiniBasket: build.query<
|
|
28
|
+
{ pk: number | null; total_quantity: number },
|
|
29
|
+
void
|
|
30
|
+
>({
|
|
31
|
+
query: () =>
|
|
32
|
+
buildClientRequestUrl(basket.getMiniBasket, {
|
|
33
|
+
contentType: 'application/json'
|
|
34
|
+
}),
|
|
35
|
+
providesTags: ['MiniBasket']
|
|
36
|
+
}),
|
|
37
|
+
getMiniBasketDetail: build.query<
|
|
38
|
+
{ pk: number | null; total_quantity: number },
|
|
39
|
+
{ namespace: string }
|
|
40
|
+
>({
|
|
41
|
+
query: ({ namespace }) =>
|
|
42
|
+
buildClientRequestUrl(basket.getMiniBasketDetail(namespace), {
|
|
43
|
+
contentType: 'application/json'
|
|
44
|
+
}),
|
|
45
|
+
providesTags: ['MiniBasket']
|
|
46
|
+
}),
|
|
27
47
|
getBasketDetail: build.query<Basket, { namespace: string }>({
|
|
28
48
|
query: ({ namespace }) =>
|
|
29
49
|
buildClientRequestUrl(basket.getBasketDetail(namespace)),
|
|
@@ -46,7 +66,7 @@ export const basketApi = api.injectEndpoints({
|
|
|
46
66
|
method: 'DELETE',
|
|
47
67
|
body: { pk }
|
|
48
68
|
}),
|
|
49
|
-
invalidatesTags: ['MultiBasket', 'Basket']
|
|
69
|
+
invalidatesTags: ['MultiBasket', 'Basket', 'MiniBasket']
|
|
50
70
|
}),
|
|
51
71
|
selectMainBasket: build.mutation<Basket, { pk: number }>({
|
|
52
72
|
query: ({ pk }) => ({
|
|
@@ -57,7 +77,7 @@ export const basketApi = api.injectEndpoints({
|
|
|
57
77
|
body: { pk }
|
|
58
78
|
}),
|
|
59
79
|
transformResponse: (response: { baskets: Basket }) => response.baskets,
|
|
60
|
-
invalidatesTags: ['MultiBasket', 'Basket']
|
|
80
|
+
invalidatesTags: ['MultiBasket', 'Basket', 'MiniBasket']
|
|
61
81
|
}),
|
|
62
82
|
selectNameSpaceMainBasket: build.mutation<Basket, { namespace: string }>({
|
|
63
83
|
query: ({ namespace }) => ({
|
|
@@ -71,7 +91,7 @@ export const basketApi = api.injectEndpoints({
|
|
|
71
91
|
body: { namespace }
|
|
72
92
|
}),
|
|
73
93
|
transformResponse: (response: { baskets: Basket }) => response.baskets,
|
|
74
|
-
invalidatesTags: ['MultiBasket', 'Basket']
|
|
94
|
+
invalidatesTags: ['MultiBasket', 'Basket', 'MiniBasket']
|
|
75
95
|
}),
|
|
76
96
|
updateQuantity: build.mutation<
|
|
77
97
|
UpdateQuantityResponse,
|
|
@@ -84,7 +104,46 @@ export const basketApi = api.injectEndpoints({
|
|
|
84
104
|
method: 'PUT',
|
|
85
105
|
body
|
|
86
106
|
}),
|
|
87
|
-
|
|
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
|
+
},
|
|
146
|
+
invalidatesTags: ['MultiBasket', 'Basket', 'MiniBasket']
|
|
88
147
|
}),
|
|
89
148
|
clearBasket: build.mutation<Basket, void>({
|
|
90
149
|
query: (body) => ({
|
|
@@ -95,7 +154,7 @@ export const basketApi = api.injectEndpoints({
|
|
|
95
154
|
body
|
|
96
155
|
}),
|
|
97
156
|
transformResponse: (response: { basket: Basket }) => response.basket,
|
|
98
|
-
invalidatesTags: ['Basket']
|
|
157
|
+
invalidatesTags: ['Basket', 'MiniBasket', 'MiniBasket']
|
|
99
158
|
}),
|
|
100
159
|
applyVoucherCode: build.mutation<Basket, { voucher_code: string }>({
|
|
101
160
|
query: (body) => ({
|
|
@@ -125,6 +184,8 @@ export const basketApi = api.injectEndpoints({
|
|
|
125
184
|
|
|
126
185
|
export const {
|
|
127
186
|
useGetBasketQuery,
|
|
187
|
+
useGetMiniBasketQuery,
|
|
188
|
+
useGetMiniBasketDetailQuery,
|
|
128
189
|
useLazyGetBasketDetailQuery,
|
|
129
190
|
useGetAllBasketsQuery,
|
|
130
191
|
useRemoveBasketMutation,
|