@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.
Files changed (189) hide show
  1. package/.eslintrc.js +12 -0
  2. package/CHANGELOG.md +377 -7
  3. package/__tests__/next-config.test.ts +83 -0
  4. package/__tests__/tsconfig.json +23 -0
  5. package/api/auth.ts +133 -44
  6. package/api/barcode-search.ts +59 -0
  7. package/api/cache.ts +41 -5
  8. package/api/client.ts +21 -4
  9. package/api/form.ts +85 -0
  10. package/api/image-proxy.ts +75 -0
  11. package/api/product-categories.ts +53 -0
  12. package/api/similar-product-list.ts +63 -0
  13. package/api/similar-products.ts +111 -0
  14. package/api/virtual-try-on.ts +382 -0
  15. package/assets/styles/index.scss +84 -0
  16. package/babel.config.js +6 -0
  17. package/bin/pz-generate-routes.js +115 -0
  18. package/bin/pz-prebuild.js +1 -0
  19. package/bin/pz-predev.js +1 -0
  20. package/bin/pz-run-tests.js +99 -0
  21. package/bin/run-prebuild-tests.js +46 -0
  22. package/components/accordion.tsx +20 -5
  23. package/components/button.tsx +51 -36
  24. package/components/client-root.tsx +138 -2
  25. package/components/file-input.tsx +65 -3
  26. package/components/index.ts +1 -0
  27. package/components/input.tsx +1 -1
  28. package/components/link.tsx +46 -16
  29. package/components/logger-popup.tsx +213 -0
  30. package/components/modal.tsx +32 -16
  31. package/components/plugin-module.tsx +62 -3
  32. package/components/price.tsx +2 -2
  33. package/components/select.tsx +1 -1
  34. package/components/selected-payment-option-view.tsx +21 -0
  35. package/components/theme-editor/blocks/accordion-block.tsx +136 -0
  36. package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
  37. package/components/theme-editor/blocks/button-block.tsx +593 -0
  38. package/components/theme-editor/blocks/counter-block.tsx +348 -0
  39. package/components/theme-editor/blocks/divider-block.tsx +20 -0
  40. package/components/theme-editor/blocks/embed-block.tsx +208 -0
  41. package/components/theme-editor/blocks/group-block.tsx +116 -0
  42. package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
  43. package/components/theme-editor/blocks/icon-block.tsx +230 -0
  44. package/components/theme-editor/blocks/image-block.tsx +137 -0
  45. package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
  46. package/components/theme-editor/blocks/input-block.tsx +123 -0
  47. package/components/theme-editor/blocks/link-block.tsx +216 -0
  48. package/components/theme-editor/blocks/lottie-block.tsx +325 -0
  49. package/components/theme-editor/blocks/map-block.tsx +89 -0
  50. package/components/theme-editor/blocks/slider-block.tsx +595 -0
  51. package/components/theme-editor/blocks/tab-block.tsx +10 -0
  52. package/components/theme-editor/blocks/text-block.tsx +52 -0
  53. package/components/theme-editor/blocks/video-block.tsx +122 -0
  54. package/components/theme-editor/components/action-toolbar.tsx +305 -0
  55. package/components/theme-editor/components/designer-overlay.tsx +74 -0
  56. package/components/theme-editor/components/with-designer-features.tsx +142 -0
  57. package/components/theme-editor/dynamic-font-loader.tsx +79 -0
  58. package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
  59. package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
  60. package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
  61. package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
  62. package/components/theme-editor/placeholder-registry.ts +31 -0
  63. package/components/theme-editor/sections/before-after-section.tsx +245 -0
  64. package/components/theme-editor/sections/contact-form-section.tsx +563 -0
  65. package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
  66. package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
  67. package/components/theme-editor/sections/divider-section.tsx +62 -0
  68. package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
  69. package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
  70. package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
  71. package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
  72. package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
  73. package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
  74. package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
  75. package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
  76. package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
  77. package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
  78. package/components/theme-editor/sections/section-wrapper.tsx +135 -0
  79. package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
  80. package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
  81. package/components/theme-editor/sections/tabs-section.tsx +578 -0
  82. package/components/theme-editor/theme-block.tsx +102 -0
  83. package/components/theme-editor/theme-placeholder-client.tsx +218 -0
  84. package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
  85. package/components/theme-editor/theme-placeholder.tsx +288 -0
  86. package/components/theme-editor/theme-section.tsx +1224 -0
  87. package/components/theme-editor/theme-settings-context.tsx +13 -0
  88. package/components/theme-editor/utils/index.ts +792 -0
  89. package/components/theme-editor/utils/iterator-utils.ts +234 -0
  90. package/components/theme-editor/utils/publish-window.ts +86 -0
  91. package/components/theme-editor/utils/visibility-rules.ts +188 -0
  92. package/data/client/account.ts +17 -2
  93. package/data/client/api.ts +2 -0
  94. package/data/client/basket.ts +66 -5
  95. package/data/client/checkout.ts +391 -99
  96. package/data/client/misc.ts +38 -2
  97. package/data/client/product.ts +19 -2
  98. package/data/client/user.ts +16 -8
  99. package/data/server/category.ts +11 -9
  100. package/data/server/flatpage.ts +11 -4
  101. package/data/server/form.ts +15 -4
  102. package/data/server/landingpage.ts +11 -4
  103. package/data/server/list.ts +5 -4
  104. package/data/server/menu.ts +11 -3
  105. package/data/server/product.ts +111 -55
  106. package/data/server/seo.ts +14 -4
  107. package/data/server/special-page.ts +5 -4
  108. package/data/server/widget.ts +90 -5
  109. package/data/urls.ts +16 -5
  110. package/hocs/client/with-segment-defaults.tsx +2 -2
  111. package/hocs/server/with-segment-defaults.tsx +65 -20
  112. package/hooks/index.ts +4 -0
  113. package/hooks/use-localization.ts +24 -10
  114. package/hooks/use-logger-context.tsx +114 -0
  115. package/hooks/use-logger.ts +92 -0
  116. package/hooks/use-loyalty-availability.ts +21 -0
  117. package/hooks/use-payment-options.ts +2 -1
  118. package/hooks/use-pz-params.ts +37 -0
  119. package/hooks/use-router.ts +51 -14
  120. package/hooks/use-sentry-uncaught-errors.ts +24 -0
  121. package/instrumentation/index.ts +10 -1
  122. package/instrumentation/node.ts +2 -20
  123. package/jest.config.js +25 -0
  124. package/lib/cache-handler.mjs +534 -16
  125. package/lib/cache.ts +272 -37
  126. package/localization/index.ts +2 -1
  127. package/localization/provider.tsx +2 -5
  128. package/middlewares/bfcache-headers.ts +18 -0
  129. package/middlewares/checkout-provider.ts +1 -1
  130. package/middlewares/complete-gpay.ts +32 -26
  131. package/middlewares/complete-masterpass.ts +33 -26
  132. package/middlewares/complete-wallet.ts +182 -0
  133. package/middlewares/default.ts +360 -215
  134. package/middlewares/index.ts +10 -2
  135. package/middlewares/locale.ts +34 -11
  136. package/middlewares/masterpass-rest-callback.ts +230 -0
  137. package/middlewares/oauth-login.ts +200 -57
  138. package/middlewares/pretty-url.ts +21 -8
  139. package/middlewares/redirection-payment.ts +32 -26
  140. package/middlewares/saved-card-redirection.ts +33 -26
  141. package/middlewares/three-d-redirection.ts +32 -26
  142. package/middlewares/url-redirection.ts +11 -1
  143. package/middlewares/wallet-complete-redirection.ts +206 -0
  144. package/package.json +25 -10
  145. package/plugins.d.ts +19 -4
  146. package/plugins.js +10 -1
  147. package/redux/actions.ts +47 -0
  148. package/redux/middlewares/checkout.ts +63 -138
  149. package/redux/middlewares/index.ts +14 -10
  150. package/redux/middlewares/pre-order/address.ts +7 -2
  151. package/redux/middlewares/pre-order/attribute-based-shipping-option.ts +7 -1
  152. package/redux/middlewares/pre-order/data-source-shipping-option.ts +7 -1
  153. package/redux/middlewares/pre-order/delivery-option.ts +7 -1
  154. package/redux/middlewares/pre-order/index.ts +16 -10
  155. package/redux/middlewares/pre-order/installment-option.ts +8 -1
  156. package/redux/middlewares/pre-order/payment-option-reset.ts +37 -0
  157. package/redux/middlewares/pre-order/payment-option.ts +7 -1
  158. package/redux/middlewares/pre-order/pre-order-validation.ts +8 -3
  159. package/redux/middlewares/pre-order/redirection.ts +8 -2
  160. package/redux/middlewares/pre-order/set-pre-order.ts +6 -2
  161. package/redux/middlewares/pre-order/shipping-option.ts +7 -1
  162. package/redux/middlewares/pre-order/shipping-step.ts +5 -1
  163. package/redux/reducers/checkout.ts +23 -3
  164. package/redux/reducers/index.ts +11 -3
  165. package/redux/reducers/root.ts +7 -2
  166. package/redux/reducers/widget.ts +80 -0
  167. package/sentry/index.ts +69 -13
  168. package/tailwind/content.js +16 -0
  169. package/types/commerce/account.ts +5 -1
  170. package/types/commerce/checkout.ts +35 -1
  171. package/types/commerce/widget.ts +33 -0
  172. package/types/index.ts +101 -6
  173. package/types/next-auth.d.ts +2 -2
  174. package/types/widget.ts +80 -0
  175. package/utils/app-fetch.ts +7 -2
  176. package/utils/generate-commerce-search-params.ts +3 -2
  177. package/utils/get-checkout-path.ts +3 -0
  178. package/utils/get-root-hostname.ts +28 -0
  179. package/utils/index.ts +64 -10
  180. package/utils/localization.ts +4 -0
  181. package/utils/mobile-3d-iframe.ts +8 -2
  182. package/utils/override-middleware.ts +7 -12
  183. package/utils/pz-segments.ts +92 -0
  184. package/utils/redirect-ignore.ts +35 -0
  185. package/utils/redirect.ts +9 -3
  186. package/utils/redirection-iframe.ts +8 -2
  187. package/utils/widget-styles.ts +107 -0
  188. package/views/error-page.tsx +93 -0
  189. 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
+ });
@@ -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<{ balance: number }, void>({
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;
@@ -68,10 +68,12 @@ export const api = createApi({
68
68
  tagTypes: [
69
69
  'Basket',
70
70
  'MultiBasket',
71
+ 'MiniBasket',
71
72
  'BasketB2b',
72
73
  'DraftsB2b',
73
74
  'Product',
74
75
  'Checkout',
76
+ 'PaymentOptions',
75
77
  'Favorite',
76
78
  'Addresses',
77
79
  'Profile',
@@ -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
- invalidatesTags: ['MultiBasket', 'Basket']
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,