@akinon/next 2.0.0-beta.20 → 2.0.0-beta.21
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 +23 -0
- package/api/auth.ts +292 -60
- package/bin/pz-install-plugins.js +1 -1
- package/package.json +3 -3
- package/types/index.ts +19 -6
- package/types/next-auth.d.ts +1 -1
- package/with-pz-config.js +8 -1
- package/components/theme-editor/blocks/accordion-block.tsx +0 -136
- package/components/theme-editor/blocks/block-renderer-registry.tsx +0 -77
- package/components/theme-editor/blocks/button-block.tsx +0 -593
- package/components/theme-editor/blocks/counter-block.tsx +0 -348
- package/components/theme-editor/blocks/divider-block.tsx +0 -20
- package/components/theme-editor/blocks/embed-block.tsx +0 -208
- package/components/theme-editor/blocks/group-block.tsx +0 -116
- package/components/theme-editor/blocks/hotspot-block.tsx +0 -147
- package/components/theme-editor/blocks/icon-block.tsx +0 -230
- package/components/theme-editor/blocks/image-block.tsx +0 -137
- package/components/theme-editor/blocks/image-gallery-block.tsx +0 -269
- package/components/theme-editor/blocks/input-block.tsx +0 -123
- package/components/theme-editor/blocks/link-block.tsx +0 -216
- package/components/theme-editor/blocks/lottie-block.tsx +0 -325
- package/components/theme-editor/blocks/map-block.tsx +0 -89
- package/components/theme-editor/blocks/slider-block.tsx +0 -595
- package/components/theme-editor/blocks/tab-block.tsx +0 -10
- package/components/theme-editor/blocks/text-block.tsx +0 -52
- package/components/theme-editor/blocks/video-block.tsx +0 -122
- package/components/theme-editor/components/action-toolbar.tsx +0 -305
- package/components/theme-editor/components/designer-overlay.tsx +0 -74
- package/components/theme-editor/components/with-designer-features.tsx +0 -142
- package/components/theme-editor/dynamic-font-loader.tsx +0 -79
- package/components/theme-editor/hooks/use-designer-features.tsx +0 -100
- package/components/theme-editor/hooks/use-external-designer.tsx +0 -95
- package/components/theme-editor/hooks/use-native-widget-data.ts +0 -188
- package/components/theme-editor/hooks/use-visibility-context.ts +0 -27
- package/components/theme-editor/placeholder-registry.ts +0 -31
- package/components/theme-editor/sections/before-after-section.tsx +0 -245
- package/components/theme-editor/sections/contact-form-section.tsx +0 -563
- package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +0 -433
- package/components/theme-editor/sections/coupon-banner-section.tsx +0 -710
- package/components/theme-editor/sections/divider-section.tsx +0 -62
- package/components/theme-editor/sections/featured-product-spotlight-section.tsx +0 -507
- package/components/theme-editor/sections/find-in-store-section.tsx +0 -1995
- package/components/theme-editor/sections/hover-showcase-section.tsx +0 -326
- package/components/theme-editor/sections/image-hotspot-section.tsx +0 -142
- package/components/theme-editor/sections/installment-options-section.tsx +0 -1065
- package/components/theme-editor/sections/notification-banner-section.tsx +0 -173
- package/components/theme-editor/sections/order-tracking-lookup-section.tsx +0 -1379
- package/components/theme-editor/sections/posts-slider-section.tsx +0 -472
- package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +0 -663
- package/components/theme-editor/sections/section-renderer-registry.tsx +0 -89
- package/components/theme-editor/sections/section-wrapper.tsx +0 -135
- package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +0 -586
- package/components/theme-editor/sections/stats-counter-section.tsx +0 -486
- package/components/theme-editor/sections/tabs-section.tsx +0 -578
- package/components/theme-editor/theme-block.tsx +0 -102
- package/components/theme-editor/theme-placeholder-client.tsx +0 -218
- package/components/theme-editor/theme-placeholder-wrapper.tsx +0 -732
- package/components/theme-editor/theme-placeholder.tsx +0 -288
- package/components/theme-editor/theme-section.tsx +0 -1224
- package/components/theme-editor/theme-settings-context.tsx +0 -13
- package/components/theme-editor/utils/index.ts +0 -792
- package/components/theme-editor/utils/iterator-utils.ts +0 -234
- package/components/theme-editor/utils/publish-window.ts +0 -86
- package/components/theme-editor/utils/visibility-rules.ts +0 -188
|
@@ -1,1995 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useLocalization } from '@akinon/next/hooks';
|
|
4
|
-
import { buildClientRequestUrl } from '@akinon/next/utils';
|
|
5
|
-
import Settings from 'settings';
|
|
6
|
-
import clsx from 'clsx';
|
|
7
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
8
|
-
|
|
9
|
-
import { useGetRetailStoreQuery } from '../../../data/client/address';
|
|
10
|
-
import {
|
|
11
|
-
useGetProductByPkQuery,
|
|
12
|
-
useGetRetailStoreStockMutation
|
|
13
|
-
} from '../../../data/client/product';
|
|
14
|
-
import {
|
|
15
|
-
Product,
|
|
16
|
-
ProductResult,
|
|
17
|
-
StockResultType,
|
|
18
|
-
VariantType
|
|
19
|
-
} from '../../../types';
|
|
20
|
-
import { Image } from '../../image';
|
|
21
|
-
import ThemeBlock, { Block } from '../theme-block';
|
|
22
|
-
import { WithDesignerFeatures } from '../components/with-designer-features';
|
|
23
|
-
import { useThemeSettingsContext } from '../theme-settings-context';
|
|
24
|
-
import { Section } from '../theme-section';
|
|
25
|
-
import {
|
|
26
|
-
getCSSStyles,
|
|
27
|
-
getResponsiveValue,
|
|
28
|
-
resolveThemeCssVariables
|
|
29
|
-
} from '../utils';
|
|
30
|
-
|
|
31
|
-
interface FindInStoreSectionProps {
|
|
32
|
-
section: Section;
|
|
33
|
-
currentBreakpoint?: string;
|
|
34
|
-
placeholderId?: string;
|
|
35
|
-
isDesigner?: boolean;
|
|
36
|
-
selectedBlockId?: string | null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type ProductRecord = Product & Record<string, unknown>;
|
|
40
|
-
|
|
41
|
-
type StoreOption = {
|
|
42
|
-
pk: number | string;
|
|
43
|
-
name: string;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
type StoreStockItem = StockResultType[number];
|
|
47
|
-
|
|
48
|
-
const readString = (
|
|
49
|
-
properties: Record<string, unknown> | undefined,
|
|
50
|
-
key: string,
|
|
51
|
-
fallback: string,
|
|
52
|
-
breakpoint: string
|
|
53
|
-
): string => {
|
|
54
|
-
const value = getResponsiveValue(properties?.[key], breakpoint, fallback);
|
|
55
|
-
if (value == null) return fallback;
|
|
56
|
-
return String(value);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const readBoolean = (
|
|
60
|
-
properties: Record<string, unknown> | undefined,
|
|
61
|
-
key: string,
|
|
62
|
-
fallback: boolean,
|
|
63
|
-
breakpoint: string
|
|
64
|
-
): boolean => {
|
|
65
|
-
const value = getResponsiveValue(properties?.[key], breakpoint, fallback);
|
|
66
|
-
if (typeof value === 'boolean') return value;
|
|
67
|
-
if (typeof value === 'string') {
|
|
68
|
-
const normalized = value.trim().toLowerCase();
|
|
69
|
-
if (normalized === 'true') return true;
|
|
70
|
-
if (normalized === 'false') return false;
|
|
71
|
-
}
|
|
72
|
-
return fallback;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const readNumber = (
|
|
76
|
-
properties: Record<string, unknown> | undefined,
|
|
77
|
-
key: string,
|
|
78
|
-
fallback: number,
|
|
79
|
-
breakpoint: string
|
|
80
|
-
): number => {
|
|
81
|
-
const value = getResponsiveValue(properties?.[key], breakpoint, fallback);
|
|
82
|
-
const numeric =
|
|
83
|
-
typeof value === 'number' ? value : Number.parseInt(String(value), 10);
|
|
84
|
-
return Number.isFinite(numeric) ? numeric : fallback;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const parsePositiveInt = (value: unknown): number | null => {
|
|
88
|
-
const parsed =
|
|
89
|
-
typeof value === 'number'
|
|
90
|
-
? value
|
|
91
|
-
: Number.parseInt(String(value || ''), 10);
|
|
92
|
-
|
|
93
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return parsed;
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const getProductImage = (product: Partial<ProductRecord> | null): string => {
|
|
101
|
-
if (!product) return '';
|
|
102
|
-
if (typeof product.image === 'string' && product.image) return product.image;
|
|
103
|
-
|
|
104
|
-
const imageSet = product.productimage_set;
|
|
105
|
-
if (Array.isArray(imageSet) && imageSet[0]?.image) {
|
|
106
|
-
return String(imageSet[0].image);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return '';
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const parsePriceValue = (value: unknown): number | null => {
|
|
113
|
-
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
114
|
-
return value;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (typeof value !== 'string') return null;
|
|
118
|
-
|
|
119
|
-
const cleaned = value.trim().replace(/[^\d,.-]/g, '');
|
|
120
|
-
if (!cleaned) return null;
|
|
121
|
-
|
|
122
|
-
let normalized = cleaned;
|
|
123
|
-
const hasComma = normalized.includes(',');
|
|
124
|
-
const hasDot = normalized.includes('.');
|
|
125
|
-
|
|
126
|
-
if (hasComma && hasDot) {
|
|
127
|
-
const lastComma = normalized.lastIndexOf(',');
|
|
128
|
-
const lastDot = normalized.lastIndexOf('.');
|
|
129
|
-
normalized =
|
|
130
|
-
lastComma > lastDot
|
|
131
|
-
? normalized.replace(/\./g, '').replace(',', '.')
|
|
132
|
-
: normalized.replace(/,/g, '');
|
|
133
|
-
} else if (hasComma) {
|
|
134
|
-
const unsigned = normalized.replace(/^-/, '');
|
|
135
|
-
const isThousandsPattern = /^\d{1,3}(,\d{3})+$/.test(unsigned);
|
|
136
|
-
normalized = isThousandsPattern
|
|
137
|
-
? normalized.replace(/,/g, '')
|
|
138
|
-
: normalized.replace(/,/g, '.');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const parsed = Number(normalized);
|
|
142
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const normalizeCurrencyLabel = (currency: unknown): string => {
|
|
146
|
-
const raw = String(currency || '')
|
|
147
|
-
.trim()
|
|
148
|
-
.toUpperCase();
|
|
149
|
-
if (!raw) return 'TL';
|
|
150
|
-
|
|
151
|
-
const map: Record<string, string> = {
|
|
152
|
-
TRY: 'TL',
|
|
153
|
-
TL: 'TL',
|
|
154
|
-
USD: 'USD',
|
|
155
|
-
EUR: 'EUR',
|
|
156
|
-
GBP: 'GBP'
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
return map[raw] || raw;
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const formatPriceWithCurrency = (
|
|
163
|
-
value: unknown,
|
|
164
|
-
currency: unknown
|
|
165
|
-
): string | null => {
|
|
166
|
-
if (value == null || value === '') return null;
|
|
167
|
-
|
|
168
|
-
const price = parsePriceValue(value);
|
|
169
|
-
if (price === null) {
|
|
170
|
-
const text = String(value).trim();
|
|
171
|
-
return text || null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const hasDecimals = Math.abs(price % 1) > 0.00001;
|
|
175
|
-
const formatted = price.toLocaleString('tr-TR', {
|
|
176
|
-
minimumFractionDigits: hasDecimals ? 2 : 0,
|
|
177
|
-
maximumFractionDigits: 2
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
return `${formatted} ${normalizeCurrencyLabel(currency)}`;
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const getDiscountState = (
|
|
184
|
-
currentPrice: unknown,
|
|
185
|
-
retailPrice: unknown
|
|
186
|
-
): { current: string | null; retail: string | null; hasDiscount: boolean } => {
|
|
187
|
-
const currentNumeric = parsePriceValue(currentPrice);
|
|
188
|
-
const retailNumeric = parsePriceValue(retailPrice);
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
current: String(currentPrice || '').trim() ? String(currentPrice) : null,
|
|
192
|
-
retail: String(retailPrice || '').trim() ? String(retailPrice) : null,
|
|
193
|
-
hasDiscount:
|
|
194
|
-
currentNumeric !== null &&
|
|
195
|
-
retailNumeric !== null &&
|
|
196
|
-
retailNumeric > currentNumeric
|
|
197
|
-
};
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const findSizeAttributeKey = (): string => {
|
|
201
|
-
const match = Settings.commonProductAttributes.find(
|
|
202
|
-
(item) => item.translationKey === 'size'
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
return String(match?.key || 'size').toLowerCase();
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
const getSizeOptions = (variants: VariantType[] | undefined) => {
|
|
209
|
-
const sizeKey = findSizeAttributeKey();
|
|
210
|
-
|
|
211
|
-
if (!variants?.length) return [];
|
|
212
|
-
|
|
213
|
-
const seen = new Set<string>();
|
|
214
|
-
|
|
215
|
-
return variants
|
|
216
|
-
.filter((variant) => {
|
|
217
|
-
const attributeName = String(variant.attribute_name || '').toLowerCase();
|
|
218
|
-
const attributeKey = String(variant.attribute_key || '').toLowerCase();
|
|
219
|
-
|
|
220
|
-
return (
|
|
221
|
-
attributeName === sizeKey ||
|
|
222
|
-
attributeKey === sizeKey ||
|
|
223
|
-
attributeName.includes('size') ||
|
|
224
|
-
attributeName.includes('beden') ||
|
|
225
|
-
attributeKey.includes('size') ||
|
|
226
|
-
attributeKey.includes('beden')
|
|
227
|
-
);
|
|
228
|
-
})
|
|
229
|
-
.flatMap((variant) => variant.options || [])
|
|
230
|
-
.filter((option) => {
|
|
231
|
-
if (!option?.value || seen.has(String(option.value))) {
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
seen.add(String(option.value));
|
|
235
|
-
return true;
|
|
236
|
-
})
|
|
237
|
-
.map((option) => ({
|
|
238
|
-
value: String(option.value),
|
|
239
|
-
label: option.label || String(option.value)
|
|
240
|
-
}));
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
const getStockTone = (
|
|
244
|
-
t: (key: string) => string,
|
|
245
|
-
stock: string | number
|
|
246
|
-
): { label: string; background: string; color: string } => {
|
|
247
|
-
const numeric = Number.parseInt(String(stock || 0), 10);
|
|
248
|
-
|
|
249
|
-
if (!numeric) {
|
|
250
|
-
return {
|
|
251
|
-
label: t('product.find_in_store.out_of_stock'),
|
|
252
|
-
background: '#fee2e2',
|
|
253
|
-
color: '#b91c1c'
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (numeric < 5) {
|
|
258
|
-
return {
|
|
259
|
-
label: t('product.find_in_store.limited_stock'),
|
|
260
|
-
background: '#fef3c7',
|
|
261
|
-
color: '#92400e'
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
label: t('product.find_in_store.available'),
|
|
267
|
-
background: '#dcfce7',
|
|
268
|
-
color: '#166534'
|
|
269
|
-
};
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const createStoreHours = (
|
|
273
|
-
hours: StoreStockItem['store_hours'],
|
|
274
|
-
t: (key: string) => string
|
|
275
|
-
): string => {
|
|
276
|
-
if (!Array.isArray(hours) || !hours.length) {
|
|
277
|
-
return t('product.find_in_store.unknown');
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const firstEntry = Array.isArray(hours[0]) ? hours[0] : hours;
|
|
281
|
-
if (!Array.isArray(firstEntry) || firstEntry.length < 2) {
|
|
282
|
-
return t('product.find_in_store.unknown');
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const start =
|
|
286
|
-
typeof firstEntry[0] === 'string' ? firstEntry[0].slice(0, 5) : '';
|
|
287
|
-
const end =
|
|
288
|
-
typeof firstEntry[1] === 'string' ? firstEntry[1].slice(0, 5) : '';
|
|
289
|
-
|
|
290
|
-
return start && end
|
|
291
|
-
? `${start} - ${end}`
|
|
292
|
-
: t('product.find_in_store.unknown');
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
const replaceTokens = (
|
|
296
|
-
input: string,
|
|
297
|
-
tokens: Record<string, string>
|
|
298
|
-
): string => {
|
|
299
|
-
let output = input;
|
|
300
|
-
|
|
301
|
-
Object.entries(tokens).forEach(([key, value]) => {
|
|
302
|
-
output = output.split(`{{${key}}}`).join(value);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
return output;
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
const replaceTokensInUnknown = (
|
|
309
|
-
value: unknown,
|
|
310
|
-
tokens: Record<string, string>
|
|
311
|
-
): unknown => {
|
|
312
|
-
if (typeof value === 'string') {
|
|
313
|
-
return replaceTokens(value, tokens);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (Array.isArray(value)) {
|
|
317
|
-
return value.map((item) => replaceTokensInUnknown(item, tokens));
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (typeof value === 'object' && value !== null) {
|
|
321
|
-
return Object.fromEntries(
|
|
322
|
-
Object.entries(value).map(([key, nestedValue]) => [
|
|
323
|
-
key,
|
|
324
|
-
replaceTokensInUnknown(nestedValue, tokens)
|
|
325
|
-
])
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return value;
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
const resolveResponsiveString = (
|
|
333
|
-
value: unknown,
|
|
334
|
-
breakpoint: string
|
|
335
|
-
): string => {
|
|
336
|
-
if (typeof value === 'string') return value;
|
|
337
|
-
|
|
338
|
-
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
339
|
-
const responsiveValue = getResponsiveValue(value, breakpoint, '');
|
|
340
|
-
if (typeof responsiveValue === 'string') {
|
|
341
|
-
return responsiveValue;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return '';
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
const getFindInStoreRole = (
|
|
349
|
-
block: Pick<Block, 'properties'>,
|
|
350
|
-
breakpoint: string
|
|
351
|
-
): string =>
|
|
352
|
-
resolveResponsiveString(block.properties?.findInStoreRole, breakpoint);
|
|
353
|
-
|
|
354
|
-
const CONTROL_LAYOUT_STYLE_KEYS = new Set([
|
|
355
|
-
'display',
|
|
356
|
-
'width',
|
|
357
|
-
'height',
|
|
358
|
-
'min-width',
|
|
359
|
-
'min-height',
|
|
360
|
-
'max-width',
|
|
361
|
-
'max-height',
|
|
362
|
-
'padding-top',
|
|
363
|
-
'padding-right',
|
|
364
|
-
'padding-bottom',
|
|
365
|
-
'padding-left',
|
|
366
|
-
'font-size',
|
|
367
|
-
'font-weight',
|
|
368
|
-
'line-height',
|
|
369
|
-
'align-items',
|
|
370
|
-
'justify-content'
|
|
371
|
-
]);
|
|
372
|
-
|
|
373
|
-
const cloneFindInStoreBlock = (
|
|
374
|
-
block: Block,
|
|
375
|
-
tokens: Record<string, string>,
|
|
376
|
-
overrides?: {
|
|
377
|
-
properties?: Record<string, unknown>;
|
|
378
|
-
value?: unknown;
|
|
379
|
-
}
|
|
380
|
-
): Block => ({
|
|
381
|
-
...block,
|
|
382
|
-
styleSourceId: block.styleSourceId || block.id,
|
|
383
|
-
value:
|
|
384
|
-
overrides && Object.prototype.hasOwnProperty.call(overrides, 'value')
|
|
385
|
-
? overrides.value
|
|
386
|
-
: (replaceTokensInUnknown(block.value, tokens) as Block['value']),
|
|
387
|
-
properties: {
|
|
388
|
-
...(replaceTokensInUnknown(block.properties || {}, tokens) as Record<
|
|
389
|
-
string,
|
|
390
|
-
unknown
|
|
391
|
-
>),
|
|
392
|
-
...(overrides?.properties || {})
|
|
393
|
-
},
|
|
394
|
-
styles: replaceTokensInUnknown(block.styles || {}, tokens) as Block['styles'],
|
|
395
|
-
blocks: block.blocks?.map((childBlock) =>
|
|
396
|
-
cloneFindInStoreBlock(childBlock, tokens)
|
|
397
|
-
)
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
const findFindInStoreRoleBlock = (
|
|
401
|
-
blocks: Block[],
|
|
402
|
-
role: string,
|
|
403
|
-
breakpoint: string
|
|
404
|
-
): Block | null => {
|
|
405
|
-
for (const block of blocks) {
|
|
406
|
-
if (getFindInStoreRole(block, breakpoint) === role) {
|
|
407
|
-
return block;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if (block.blocks?.length) {
|
|
411
|
-
const nestedMatch = findFindInStoreRoleBlock(
|
|
412
|
-
block.blocks,
|
|
413
|
-
role,
|
|
414
|
-
breakpoint
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
if (nestedMatch) {
|
|
418
|
-
return nestedMatch;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
return null;
|
|
424
|
-
};
|
|
425
|
-
|
|
426
|
-
const buildPrettyUrlCandidates = (rawInput: string): string[] => {
|
|
427
|
-
const trimmed = rawInput.trim();
|
|
428
|
-
if (!trimmed) return [];
|
|
429
|
-
|
|
430
|
-
let pathname = trimmed;
|
|
431
|
-
|
|
432
|
-
try {
|
|
433
|
-
pathname = new URL(trimmed, window.location.origin).pathname;
|
|
434
|
-
} catch (_error) {
|
|
435
|
-
pathname = trimmed;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
pathname = pathname.split('?')[0]?.split('#')[0]?.trim() || '';
|
|
439
|
-
if (!pathname) return [];
|
|
440
|
-
|
|
441
|
-
if (!pathname.startsWith('/')) {
|
|
442
|
-
pathname = `/${pathname}`;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
pathname = pathname.replace(/\/{2,}/g, '/').replace(/\/+$/, '');
|
|
446
|
-
const pathSegments = pathname.split('/').filter(Boolean);
|
|
447
|
-
if (!pathSegments.length) return [];
|
|
448
|
-
|
|
449
|
-
const candidates = new Set<string>();
|
|
450
|
-
const addCandidate = (segments: string[]) => {
|
|
451
|
-
if (!segments.length) return;
|
|
452
|
-
candidates.add(`/${segments.join('/')}/`);
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
addCandidate(pathSegments);
|
|
456
|
-
|
|
457
|
-
const currentSegments = window.location.pathname.split('/').filter(Boolean);
|
|
458
|
-
const currentPrefix = currentSegments.slice(0, 3);
|
|
459
|
-
const hasCurrentPrefix =
|
|
460
|
-
currentPrefix.length > 0 &&
|
|
461
|
-
pathSegments.length > currentPrefix.length &&
|
|
462
|
-
currentPrefix.every((segment, index) => pathSegments[index] === segment);
|
|
463
|
-
|
|
464
|
-
if (hasCurrentPrefix) {
|
|
465
|
-
addCandidate(pathSegments.slice(currentPrefix.length));
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
for (let cut = 1; cut <= Math.min(3, pathSegments.length - 1); cut += 1) {
|
|
469
|
-
addCandidate(pathSegments.slice(cut));
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
return Array.from(candidates);
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
const resolveProductPkFromInput = async (
|
|
476
|
-
rawInput: string
|
|
477
|
-
): Promise<number | null> => {
|
|
478
|
-
const directPk = parsePositiveInt(rawInput.trim());
|
|
479
|
-
if (directPk) {
|
|
480
|
-
return directPk;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const candidates = buildPrettyUrlCandidates(rawInput);
|
|
484
|
-
|
|
485
|
-
for (const candidate of candidates) {
|
|
486
|
-
const requestUrl = buildClientRequestUrl(
|
|
487
|
-
`/pretty_urls/?new_path__exact=${encodeURIComponent(candidate)}`,
|
|
488
|
-
{
|
|
489
|
-
cache: false
|
|
490
|
-
}
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
const response = await fetch(requestUrl);
|
|
494
|
-
|
|
495
|
-
if (!response.ok) {
|
|
496
|
-
continue;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const data = await response.json();
|
|
500
|
-
const firstResult = Array.isArray(data?.results) ? data.results[0] : null;
|
|
501
|
-
const oldPath =
|
|
502
|
-
firstResult && typeof firstResult.old_path === 'string'
|
|
503
|
-
? firstResult.old_path
|
|
504
|
-
: '';
|
|
505
|
-
const kwargsProductId = parsePositiveInt(firstResult?.kwargs?.product_id);
|
|
506
|
-
const kwargsPk = parsePositiveInt(firstResult?.kwargs?.pk);
|
|
507
|
-
|
|
508
|
-
const productMatch = oldPath.match(/^\/product\/(\d+)\/$/);
|
|
509
|
-
|
|
510
|
-
if (productMatch) {
|
|
511
|
-
return Number.parseInt(productMatch[1], 10);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
if (kwargsProductId) {
|
|
515
|
-
return kwargsProductId;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if (kwargsPk) {
|
|
519
|
-
return kwargsPk;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
return null;
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
export default function FindInStoreSection({
|
|
527
|
-
section,
|
|
528
|
-
currentBreakpoint = 'desktop',
|
|
529
|
-
placeholderId = '',
|
|
530
|
-
isDesigner = false,
|
|
531
|
-
selectedBlockId = null
|
|
532
|
-
}: FindInStoreSectionProps) {
|
|
533
|
-
const { t } = useLocalization();
|
|
534
|
-
const themeSettings = useThemeSettingsContext();
|
|
535
|
-
|
|
536
|
-
const [productUrlInput, setProductUrlInput] = useState('');
|
|
537
|
-
const [resolvedProductPk, setResolvedProductPk] = useState<number | null>(
|
|
538
|
-
null
|
|
539
|
-
);
|
|
540
|
-
const [selectedCityId, setSelectedCityId] = useState('');
|
|
541
|
-
const [selectedSize, setSelectedSize] = useState('');
|
|
542
|
-
const [formError, setFormError] = useState('');
|
|
543
|
-
const [searchError, setSearchError] = useState('');
|
|
544
|
-
const [hasSubmitted, setHasSubmitted] = useState(false);
|
|
545
|
-
const [isResolvingProduct, setIsResolvingProduct] = useState(false);
|
|
546
|
-
|
|
547
|
-
const setupText = readString(
|
|
548
|
-
section.properties,
|
|
549
|
-
'setup-text',
|
|
550
|
-
'Paste a product detail URL or enter a product PK to load the product before checking pickup availability.',
|
|
551
|
-
currentBreakpoint
|
|
552
|
-
);
|
|
553
|
-
const urlPlaceholder = readString(
|
|
554
|
-
section.properties,
|
|
555
|
-
'product-url-placeholder',
|
|
556
|
-
'Paste a product URL or enter a product PK',
|
|
557
|
-
currentBreakpoint
|
|
558
|
-
);
|
|
559
|
-
const searchButtonText = readString(
|
|
560
|
-
section.properties,
|
|
561
|
-
'search-button-text',
|
|
562
|
-
'SEARCH PRODUCT',
|
|
563
|
-
currentBreakpoint
|
|
564
|
-
);
|
|
565
|
-
const urlRequiredText = readString(
|
|
566
|
-
section.properties,
|
|
567
|
-
'url-required-text',
|
|
568
|
-
'Please enter a product URL or product PK.',
|
|
569
|
-
currentBreakpoint
|
|
570
|
-
);
|
|
571
|
-
const invalidProductText = readString(
|
|
572
|
-
section.properties,
|
|
573
|
-
'invalid-product-text',
|
|
574
|
-
'We could not find a product for this URL or PK. Please try a valid product detail link or numeric product ID.',
|
|
575
|
-
currentBreakpoint
|
|
576
|
-
);
|
|
577
|
-
const initialProductInput = readString(
|
|
578
|
-
section.properties,
|
|
579
|
-
'initial-product-input',
|
|
580
|
-
'',
|
|
581
|
-
currentBreakpoint
|
|
582
|
-
);
|
|
583
|
-
const buttonText = readString(
|
|
584
|
-
section.properties,
|
|
585
|
-
'button-text',
|
|
586
|
-
t('product.find_in_store.submit'),
|
|
587
|
-
currentBreakpoint
|
|
588
|
-
);
|
|
589
|
-
const emptyStateText = readString(
|
|
590
|
-
section.properties,
|
|
591
|
-
'empty-state-text',
|
|
592
|
-
t('product.find_in_store.store_not_found'),
|
|
593
|
-
currentBreakpoint
|
|
594
|
-
);
|
|
595
|
-
|
|
596
|
-
const { data: productResponse, isLoading: productLoading } =
|
|
597
|
-
useGetProductByPkQuery(resolvedProductPk as number, {
|
|
598
|
-
skip: !resolvedProductPk
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
const fullProduct = (productResponse as ProductResult | undefined)?.product;
|
|
602
|
-
const variants = (productResponse as ProductResult | undefined)?.variants;
|
|
603
|
-
const product = fullProduct as ProductRecord | null;
|
|
604
|
-
const activeProductPk =
|
|
605
|
-
parsePositiveInt(fullProduct?.pk) ?? resolvedProductPk;
|
|
606
|
-
|
|
607
|
-
const { data: retailStore } = useGetRetailStoreQuery();
|
|
608
|
-
const [
|
|
609
|
-
getRetailStock,
|
|
610
|
-
{
|
|
611
|
-
data: stockResponse,
|
|
612
|
-
isLoading: stockLoading,
|
|
613
|
-
isError: isStockError,
|
|
614
|
-
reset: resetStock
|
|
615
|
-
}
|
|
616
|
-
] = useGetRetailStoreStockMutation();
|
|
617
|
-
|
|
618
|
-
const cityOptions = useMemo(
|
|
619
|
-
() =>
|
|
620
|
-
((retailStore?.results || []) as StoreOption[]).map((item) => ({
|
|
621
|
-
value: String(item.pk),
|
|
622
|
-
label: item.name
|
|
623
|
-
})),
|
|
624
|
-
[retailStore?.results]
|
|
625
|
-
);
|
|
626
|
-
|
|
627
|
-
const sizeOptions = useMemo(() => getSizeOptions(variants), [variants]);
|
|
628
|
-
|
|
629
|
-
useEffect(() => {
|
|
630
|
-
setSelectedCityId('');
|
|
631
|
-
setSelectedSize('');
|
|
632
|
-
setFormError('');
|
|
633
|
-
setHasSubmitted(false);
|
|
634
|
-
resetStock();
|
|
635
|
-
}, [resolvedProductPk]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
636
|
-
|
|
637
|
-
useEffect(() => {
|
|
638
|
-
if (sizeOptions.length === 1 && !selectedSize) {
|
|
639
|
-
setSelectedSize(sizeOptions[0].value);
|
|
640
|
-
}
|
|
641
|
-
}, [selectedSize, sizeOptions]);
|
|
642
|
-
|
|
643
|
-
useEffect(() => {
|
|
644
|
-
let cancelled = false;
|
|
645
|
-
|
|
646
|
-
const preloadInitialProduct = async () => {
|
|
647
|
-
const trimmedInitialProduct = initialProductInput.trim();
|
|
648
|
-
if (!trimmedInitialProduct) return;
|
|
649
|
-
|
|
650
|
-
setProductUrlInput(currentValue => currentValue || trimmedInitialProduct);
|
|
651
|
-
setIsResolvingProduct(true);
|
|
652
|
-
|
|
653
|
-
try {
|
|
654
|
-
const resolvedPk = await resolveProductPkFromInput(trimmedInitialProduct);
|
|
655
|
-
if (!cancelled && resolvedPk) {
|
|
656
|
-
setResolvedProductPk(resolvedPk);
|
|
657
|
-
setSearchError('');
|
|
658
|
-
}
|
|
659
|
-
} catch {
|
|
660
|
-
if (!cancelled) {
|
|
661
|
-
setSearchError('');
|
|
662
|
-
}
|
|
663
|
-
} finally {
|
|
664
|
-
if (!cancelled) {
|
|
665
|
-
setIsResolvingProduct(false);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
|
|
670
|
-
preloadInitialProduct();
|
|
671
|
-
|
|
672
|
-
return () => {
|
|
673
|
-
cancelled = true;
|
|
674
|
-
};
|
|
675
|
-
}, [initialProductInput]);
|
|
676
|
-
|
|
677
|
-
const showProductCard = readBoolean(
|
|
678
|
-
section.properties,
|
|
679
|
-
'show-product-card',
|
|
680
|
-
true,
|
|
681
|
-
currentBreakpoint
|
|
682
|
-
);
|
|
683
|
-
const showWorkingHours = readBoolean(
|
|
684
|
-
section.properties,
|
|
685
|
-
'show-working-hours',
|
|
686
|
-
true,
|
|
687
|
-
currentBreakpoint
|
|
688
|
-
);
|
|
689
|
-
const showDirections = readBoolean(
|
|
690
|
-
section.properties,
|
|
691
|
-
'show-directions',
|
|
692
|
-
true,
|
|
693
|
-
currentBreakpoint
|
|
694
|
-
);
|
|
695
|
-
const maxResults = readNumber(
|
|
696
|
-
section.properties,
|
|
697
|
-
'max-results',
|
|
698
|
-
6,
|
|
699
|
-
currentBreakpoint
|
|
700
|
-
);
|
|
701
|
-
|
|
702
|
-
const maxWidth = getResponsiveValue(
|
|
703
|
-
section.styles?.['max-width'],
|
|
704
|
-
currentBreakpoint,
|
|
705
|
-
'normal'
|
|
706
|
-
) as string;
|
|
707
|
-
const maxWidthClass =
|
|
708
|
-
maxWidth === 'narrow'
|
|
709
|
-
? 'max-w-4xl'
|
|
710
|
-
: maxWidth === 'normal'
|
|
711
|
-
? 'max-w-7xl'
|
|
712
|
-
: maxWidth === 'full'
|
|
713
|
-
? 'w-full'
|
|
714
|
-
: '';
|
|
715
|
-
const hasMaxWidth = maxWidth !== 'none' && maxWidth !== 'full';
|
|
716
|
-
|
|
717
|
-
const filteredStyles = Object.fromEntries(
|
|
718
|
-
Object.entries(section.styles || {}).filter(
|
|
719
|
-
([key]) =>
|
|
720
|
-
![
|
|
721
|
-
'max-width',
|
|
722
|
-
'form-gap',
|
|
723
|
-
'input-background-color',
|
|
724
|
-
'input-text-color',
|
|
725
|
-
'input-border-color',
|
|
726
|
-
'input-border-radius',
|
|
727
|
-
'input-padding',
|
|
728
|
-
'button-background-color',
|
|
729
|
-
'button-text-color',
|
|
730
|
-
'button-hover-background-color',
|
|
731
|
-
'button-border-radius',
|
|
732
|
-
'button-padding',
|
|
733
|
-
'panel-background-color',
|
|
734
|
-
'panel-border-color',
|
|
735
|
-
'panel-border-radius',
|
|
736
|
-
'product-card-background-color',
|
|
737
|
-
'product-card-border-color',
|
|
738
|
-
'result-card-background-color',
|
|
739
|
-
'result-card-border-color',
|
|
740
|
-
'result-card-border-radius'
|
|
741
|
-
].includes(key)
|
|
742
|
-
)
|
|
743
|
-
);
|
|
744
|
-
|
|
745
|
-
const sectionStyles = getCSSStyles(
|
|
746
|
-
filteredStyles,
|
|
747
|
-
themeSettings,
|
|
748
|
-
currentBreakpoint
|
|
749
|
-
);
|
|
750
|
-
|
|
751
|
-
const formGap = Number(
|
|
752
|
-
getResponsiveValue(section.styles?.['form-gap'], currentBreakpoint, 16)
|
|
753
|
-
);
|
|
754
|
-
const inputBg = resolveThemeCssVariables(
|
|
755
|
-
String(
|
|
756
|
-
getResponsiveValue(
|
|
757
|
-
section.styles?.['input-background-color'],
|
|
758
|
-
currentBreakpoint,
|
|
759
|
-
'#ffffff'
|
|
760
|
-
)
|
|
761
|
-
),
|
|
762
|
-
themeSettings
|
|
763
|
-
);
|
|
764
|
-
const inputTextColor = resolveThemeCssVariables(
|
|
765
|
-
String(
|
|
766
|
-
getResponsiveValue(
|
|
767
|
-
section.styles?.['input-text-color'],
|
|
768
|
-
currentBreakpoint,
|
|
769
|
-
'#0f172a'
|
|
770
|
-
)
|
|
771
|
-
),
|
|
772
|
-
themeSettings
|
|
773
|
-
);
|
|
774
|
-
const inputBorderColor = resolveThemeCssVariables(
|
|
775
|
-
String(
|
|
776
|
-
getResponsiveValue(
|
|
777
|
-
section.styles?.['input-border-color'],
|
|
778
|
-
currentBreakpoint,
|
|
779
|
-
'#cbd5e1'
|
|
780
|
-
)
|
|
781
|
-
),
|
|
782
|
-
themeSettings
|
|
783
|
-
);
|
|
784
|
-
const inputBorderRadius = String(
|
|
785
|
-
getResponsiveValue(
|
|
786
|
-
section.styles?.['input-border-radius'],
|
|
787
|
-
currentBreakpoint,
|
|
788
|
-
'10px'
|
|
789
|
-
)
|
|
790
|
-
);
|
|
791
|
-
const inputPadding = String(
|
|
792
|
-
getResponsiveValue(
|
|
793
|
-
section.styles?.['input-padding'],
|
|
794
|
-
currentBreakpoint,
|
|
795
|
-
'14px 16px'
|
|
796
|
-
)
|
|
797
|
-
);
|
|
798
|
-
const buttonBg = resolveThemeCssVariables(
|
|
799
|
-
String(
|
|
800
|
-
getResponsiveValue(
|
|
801
|
-
section.styles?.['button-background-color'],
|
|
802
|
-
currentBreakpoint,
|
|
803
|
-
'#0f172a'
|
|
804
|
-
)
|
|
805
|
-
),
|
|
806
|
-
themeSettings
|
|
807
|
-
);
|
|
808
|
-
const buttonTextColor = resolveThemeCssVariables(
|
|
809
|
-
String(
|
|
810
|
-
getResponsiveValue(
|
|
811
|
-
section.styles?.['button-text-color'],
|
|
812
|
-
currentBreakpoint,
|
|
813
|
-
'#ffffff'
|
|
814
|
-
)
|
|
815
|
-
),
|
|
816
|
-
themeSettings
|
|
817
|
-
);
|
|
818
|
-
const buttonHoverBg = resolveThemeCssVariables(
|
|
819
|
-
String(
|
|
820
|
-
getResponsiveValue(
|
|
821
|
-
section.styles?.['button-hover-background-color'],
|
|
822
|
-
currentBreakpoint,
|
|
823
|
-
'#1e293b'
|
|
824
|
-
)
|
|
825
|
-
),
|
|
826
|
-
themeSettings
|
|
827
|
-
);
|
|
828
|
-
const buttonBorderRadius = String(
|
|
829
|
-
getResponsiveValue(
|
|
830
|
-
section.styles?.['button-border-radius'],
|
|
831
|
-
currentBreakpoint,
|
|
832
|
-
'10px'
|
|
833
|
-
)
|
|
834
|
-
);
|
|
835
|
-
const buttonPadding = String(
|
|
836
|
-
getResponsiveValue(
|
|
837
|
-
section.styles?.['button-padding'],
|
|
838
|
-
currentBreakpoint,
|
|
839
|
-
'14px 28px'
|
|
840
|
-
)
|
|
841
|
-
);
|
|
842
|
-
const panelBackground = resolveThemeCssVariables(
|
|
843
|
-
String(
|
|
844
|
-
getResponsiveValue(
|
|
845
|
-
section.styles?.['panel-background-color'],
|
|
846
|
-
currentBreakpoint,
|
|
847
|
-
'#ffffff'
|
|
848
|
-
)
|
|
849
|
-
),
|
|
850
|
-
themeSettings
|
|
851
|
-
);
|
|
852
|
-
const panelBorderColor = resolveThemeCssVariables(
|
|
853
|
-
String(
|
|
854
|
-
getResponsiveValue(
|
|
855
|
-
section.styles?.['panel-border-color'],
|
|
856
|
-
currentBreakpoint,
|
|
857
|
-
'#e2e8f0'
|
|
858
|
-
)
|
|
859
|
-
),
|
|
860
|
-
themeSettings
|
|
861
|
-
);
|
|
862
|
-
const panelBorderRadius = String(
|
|
863
|
-
getResponsiveValue(
|
|
864
|
-
section.styles?.['panel-border-radius'],
|
|
865
|
-
currentBreakpoint,
|
|
866
|
-
'20px'
|
|
867
|
-
)
|
|
868
|
-
);
|
|
869
|
-
const productCardBackground = resolveThemeCssVariables(
|
|
870
|
-
String(
|
|
871
|
-
getResponsiveValue(
|
|
872
|
-
section.styles?.['product-card-background-color'],
|
|
873
|
-
currentBreakpoint,
|
|
874
|
-
'#ffffff'
|
|
875
|
-
)
|
|
876
|
-
),
|
|
877
|
-
themeSettings
|
|
878
|
-
);
|
|
879
|
-
const productCardBorderColor = resolveThemeCssVariables(
|
|
880
|
-
String(
|
|
881
|
-
getResponsiveValue(
|
|
882
|
-
section.styles?.['product-card-border-color'],
|
|
883
|
-
currentBreakpoint,
|
|
884
|
-
'#e2e8f0'
|
|
885
|
-
)
|
|
886
|
-
),
|
|
887
|
-
themeSettings
|
|
888
|
-
);
|
|
889
|
-
const resultCardBackground = resolveThemeCssVariables(
|
|
890
|
-
String(
|
|
891
|
-
getResponsiveValue(
|
|
892
|
-
section.styles?.['result-card-background-color'],
|
|
893
|
-
currentBreakpoint,
|
|
894
|
-
'#ffffff'
|
|
895
|
-
)
|
|
896
|
-
),
|
|
897
|
-
themeSettings
|
|
898
|
-
);
|
|
899
|
-
const resultCardBorderColor = resolveThemeCssVariables(
|
|
900
|
-
String(
|
|
901
|
-
getResponsiveValue(
|
|
902
|
-
section.styles?.['result-card-border-color'],
|
|
903
|
-
currentBreakpoint,
|
|
904
|
-
'#e2e8f0'
|
|
905
|
-
)
|
|
906
|
-
),
|
|
907
|
-
themeSettings
|
|
908
|
-
);
|
|
909
|
-
const resultCardBorderRadius = String(
|
|
910
|
-
getResponsiveValue(
|
|
911
|
-
section.styles?.['result-card-border-radius'],
|
|
912
|
-
currentBreakpoint,
|
|
913
|
-
'16px'
|
|
914
|
-
)
|
|
915
|
-
);
|
|
916
|
-
|
|
917
|
-
const sortedBlocks = [...(section.blocks || [])]
|
|
918
|
-
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
|
919
|
-
.filter((block) => (isDesigner ? true : !block.hidden));
|
|
920
|
-
|
|
921
|
-
const contentBlocks = sortedBlocks.filter(
|
|
922
|
-
(block) => !getFindInStoreRole(block, currentBreakpoint)
|
|
923
|
-
);
|
|
924
|
-
|
|
925
|
-
const productEyebrowBlock = findFindInStoreRoleBlock(
|
|
926
|
-
sortedBlocks,
|
|
927
|
-
'product-eyebrow',
|
|
928
|
-
currentBreakpoint
|
|
929
|
-
);
|
|
930
|
-
const productLinkBlock = findFindInStoreRoleBlock(
|
|
931
|
-
sortedBlocks,
|
|
932
|
-
'product-link',
|
|
933
|
-
currentBreakpoint
|
|
934
|
-
);
|
|
935
|
-
const productImageBlock = findFindInStoreRoleBlock(
|
|
936
|
-
sortedBlocks,
|
|
937
|
-
'product-image',
|
|
938
|
-
currentBreakpoint
|
|
939
|
-
);
|
|
940
|
-
const productNameBlock = findFindInStoreRoleBlock(
|
|
941
|
-
sortedBlocks,
|
|
942
|
-
'product-name',
|
|
943
|
-
currentBreakpoint
|
|
944
|
-
);
|
|
945
|
-
const productPriceBlock = findFindInStoreRoleBlock(
|
|
946
|
-
sortedBlocks,
|
|
947
|
-
'product-price',
|
|
948
|
-
currentBreakpoint
|
|
949
|
-
);
|
|
950
|
-
const productCardWrapperBlock = findFindInStoreRoleBlock(
|
|
951
|
-
sortedBlocks,
|
|
952
|
-
'product-card-wrapper',
|
|
953
|
-
currentBreakpoint
|
|
954
|
-
);
|
|
955
|
-
const searchPanelWrapperBlock = findFindInStoreRoleBlock(
|
|
956
|
-
sortedBlocks,
|
|
957
|
-
'search-panel-wrapper',
|
|
958
|
-
currentBreakpoint
|
|
959
|
-
);
|
|
960
|
-
const availabilityPanelWrapperBlock = findFindInStoreRoleBlock(
|
|
961
|
-
sortedBlocks,
|
|
962
|
-
'availability-panel-wrapper',
|
|
963
|
-
currentBreakpoint
|
|
964
|
-
);
|
|
965
|
-
const resultCardWrapperBlock = findFindInStoreRoleBlock(
|
|
966
|
-
sortedBlocks,
|
|
967
|
-
'result-card-wrapper',
|
|
968
|
-
currentBreakpoint
|
|
969
|
-
);
|
|
970
|
-
const searchHeadingBlock = findFindInStoreRoleBlock(
|
|
971
|
-
sortedBlocks,
|
|
972
|
-
'search-heading',
|
|
973
|
-
currentBreakpoint
|
|
974
|
-
);
|
|
975
|
-
const searchDescriptionBlock = findFindInStoreRoleBlock(
|
|
976
|
-
sortedBlocks,
|
|
977
|
-
'search-description',
|
|
978
|
-
currentBreakpoint
|
|
979
|
-
);
|
|
980
|
-
const searchInputBlock = findFindInStoreRoleBlock(
|
|
981
|
-
sortedBlocks,
|
|
982
|
-
'search-input',
|
|
983
|
-
currentBreakpoint
|
|
984
|
-
);
|
|
985
|
-
const searchButtonBlock = findFindInStoreRoleBlock(
|
|
986
|
-
sortedBlocks,
|
|
987
|
-
'search-button',
|
|
988
|
-
currentBreakpoint
|
|
989
|
-
);
|
|
990
|
-
const availabilityHeadingBlock = findFindInStoreRoleBlock(
|
|
991
|
-
sortedBlocks,
|
|
992
|
-
'availability-heading',
|
|
993
|
-
currentBreakpoint
|
|
994
|
-
);
|
|
995
|
-
const availabilityDescriptionBlock = findFindInStoreRoleBlock(
|
|
996
|
-
sortedBlocks,
|
|
997
|
-
'availability-description',
|
|
998
|
-
currentBreakpoint
|
|
999
|
-
);
|
|
1000
|
-
const citySelectBlock = findFindInStoreRoleBlock(
|
|
1001
|
-
sortedBlocks,
|
|
1002
|
-
'city-select',
|
|
1003
|
-
currentBreakpoint
|
|
1004
|
-
);
|
|
1005
|
-
const sizeSelectBlock = findFindInStoreRoleBlock(
|
|
1006
|
-
sortedBlocks,
|
|
1007
|
-
'size-select',
|
|
1008
|
-
currentBreakpoint
|
|
1009
|
-
);
|
|
1010
|
-
const availabilityButtonBlock = findFindInStoreRoleBlock(
|
|
1011
|
-
sortedBlocks,
|
|
1012
|
-
'availability-button',
|
|
1013
|
-
currentBreakpoint
|
|
1014
|
-
);
|
|
1015
|
-
const storeNameBlock = findFindInStoreRoleBlock(
|
|
1016
|
-
sortedBlocks,
|
|
1017
|
-
'store-name',
|
|
1018
|
-
currentBreakpoint
|
|
1019
|
-
);
|
|
1020
|
-
const storeAddressBlock = findFindInStoreRoleBlock(
|
|
1021
|
-
sortedBlocks,
|
|
1022
|
-
'store-address',
|
|
1023
|
-
currentBreakpoint
|
|
1024
|
-
);
|
|
1025
|
-
const storeStatusBlock = findFindInStoreRoleBlock(
|
|
1026
|
-
sortedBlocks,
|
|
1027
|
-
'store-status',
|
|
1028
|
-
currentBreakpoint
|
|
1029
|
-
);
|
|
1030
|
-
const storeHoursBlock = findFindInStoreRoleBlock(
|
|
1031
|
-
sortedBlocks,
|
|
1032
|
-
'store-hours',
|
|
1033
|
-
currentBreakpoint
|
|
1034
|
-
);
|
|
1035
|
-
const storeStockBlock = findFindInStoreRoleBlock(
|
|
1036
|
-
sortedBlocks,
|
|
1037
|
-
'store-stock',
|
|
1038
|
-
currentBreakpoint
|
|
1039
|
-
);
|
|
1040
|
-
const storeDirectionsBlock = findFindInStoreRoleBlock(
|
|
1041
|
-
sortedBlocks,
|
|
1042
|
-
'store-directions',
|
|
1043
|
-
currentBreakpoint
|
|
1044
|
-
);
|
|
1045
|
-
|
|
1046
|
-
const postBlockAction = (type: string, blockId: string, label?: string) => {
|
|
1047
|
-
if (!window.parent) return;
|
|
1048
|
-
window.parent.postMessage(
|
|
1049
|
-
{
|
|
1050
|
-
type,
|
|
1051
|
-
data: {
|
|
1052
|
-
placeholderId,
|
|
1053
|
-
sectionId: section.id,
|
|
1054
|
-
blockId,
|
|
1055
|
-
...(label ? { label } : {})
|
|
1056
|
-
}
|
|
1057
|
-
},
|
|
1058
|
-
'*'
|
|
1059
|
-
);
|
|
1060
|
-
};
|
|
1061
|
-
|
|
1062
|
-
const renderBlock = (block: Block, keyOverride?: string) => {
|
|
1063
|
-
const actionBlockId = block.styleSourceId || block.id;
|
|
1064
|
-
|
|
1065
|
-
return (
|
|
1066
|
-
<ThemeBlock
|
|
1067
|
-
key={keyOverride || block.id}
|
|
1068
|
-
block={block}
|
|
1069
|
-
placeholderId={placeholderId}
|
|
1070
|
-
sectionId={section.id}
|
|
1071
|
-
isDesigner={isDesigner}
|
|
1072
|
-
isSelected={
|
|
1073
|
-
selectedBlockId === actionBlockId || selectedBlockId === block.id
|
|
1074
|
-
}
|
|
1075
|
-
selectedBlockId={selectedBlockId}
|
|
1076
|
-
currentBreakpoint={currentBreakpoint}
|
|
1077
|
-
onMoveUp={() => postBlockAction('MOVE_BLOCK_UP', actionBlockId)}
|
|
1078
|
-
onMoveDown={() => postBlockAction('MOVE_BLOCK_DOWN', actionBlockId)}
|
|
1079
|
-
onDuplicate={() => postBlockAction('DUPLICATE_BLOCK', actionBlockId)}
|
|
1080
|
-
onToggleVisibility={() =>
|
|
1081
|
-
postBlockAction('TOGGLE_BLOCK_VISIBILITY', actionBlockId)
|
|
1082
|
-
}
|
|
1083
|
-
onDelete={() => postBlockAction('DELETE_BLOCK', actionBlockId)}
|
|
1084
|
-
onRename={(newLabel) =>
|
|
1085
|
-
postBlockAction('RENAME_BLOCK', actionBlockId, newLabel)
|
|
1086
|
-
}
|
|
1087
|
-
/>
|
|
1088
|
-
);
|
|
1089
|
-
};
|
|
1090
|
-
|
|
1091
|
-
const renderSelectableWrapper = (
|
|
1092
|
-
block: Block | null,
|
|
1093
|
-
children: React.ReactNode,
|
|
1094
|
-
options: {
|
|
1095
|
-
className?: string;
|
|
1096
|
-
style?: React.CSSProperties;
|
|
1097
|
-
keyOverride?: string;
|
|
1098
|
-
} = {}
|
|
1099
|
-
) => {
|
|
1100
|
-
if (!block) {
|
|
1101
|
-
return (
|
|
1102
|
-
<div
|
|
1103
|
-
key={options.keyOverride}
|
|
1104
|
-
className={options.className}
|
|
1105
|
-
style={options.style}
|
|
1106
|
-
>
|
|
1107
|
-
{children}
|
|
1108
|
-
</div>
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
1111
|
-
|
|
1112
|
-
const actionBlockId = block.styleSourceId || block.id;
|
|
1113
|
-
const wrapperStyle = {
|
|
1114
|
-
...(options.style || {}),
|
|
1115
|
-
...getCSSStyles(block.styles || {}, themeSettings, currentBreakpoint)
|
|
1116
|
-
} as React.CSSProperties;
|
|
1117
|
-
|
|
1118
|
-
return (
|
|
1119
|
-
<WithDesignerFeatures
|
|
1120
|
-
key={options.keyOverride || block.id}
|
|
1121
|
-
block={block}
|
|
1122
|
-
placeholderId={placeholderId}
|
|
1123
|
-
sectionId={section.id}
|
|
1124
|
-
isDesigner={isDesigner}
|
|
1125
|
-
isSelected={
|
|
1126
|
-
selectedBlockId === actionBlockId || selectedBlockId === block.id
|
|
1127
|
-
}
|
|
1128
|
-
currentBreakpoint={currentBreakpoint}
|
|
1129
|
-
className={options.className}
|
|
1130
|
-
style={wrapperStyle}
|
|
1131
|
-
onMoveUp={() => postBlockAction('MOVE_BLOCK_UP', actionBlockId)}
|
|
1132
|
-
onMoveDown={() => postBlockAction('MOVE_BLOCK_DOWN', actionBlockId)}
|
|
1133
|
-
onDuplicate={() => postBlockAction('DUPLICATE_BLOCK', actionBlockId)}
|
|
1134
|
-
onToggleVisibility={() =>
|
|
1135
|
-
postBlockAction('TOGGLE_BLOCK_VISIBILITY', actionBlockId)
|
|
1136
|
-
}
|
|
1137
|
-
onDelete={() => postBlockAction('DELETE_BLOCK', actionBlockId)}
|
|
1138
|
-
onRename={(newLabel) =>
|
|
1139
|
-
postBlockAction('RENAME_BLOCK', actionBlockId, newLabel)
|
|
1140
|
-
}
|
|
1141
|
-
>
|
|
1142
|
-
{children}
|
|
1143
|
-
</WithDesignerFeatures>
|
|
1144
|
-
);
|
|
1145
|
-
};
|
|
1146
|
-
|
|
1147
|
-
const renderSelectableElement = (
|
|
1148
|
-
block: Block | null,
|
|
1149
|
-
children: React.ReactNode,
|
|
1150
|
-
options: {
|
|
1151
|
-
wrapperClassName?: string;
|
|
1152
|
-
wrapperStyle?: React.CSSProperties;
|
|
1153
|
-
keyOverride?: string;
|
|
1154
|
-
} = {}
|
|
1155
|
-
) => {
|
|
1156
|
-
if (!block) {
|
|
1157
|
-
return (
|
|
1158
|
-
<div
|
|
1159
|
-
key={options.keyOverride}
|
|
1160
|
-
className={options.wrapperClassName}
|
|
1161
|
-
style={options.wrapperStyle}
|
|
1162
|
-
>
|
|
1163
|
-
{children}
|
|
1164
|
-
</div>
|
|
1165
|
-
);
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
const actionBlockId = block.styleSourceId || block.id;
|
|
1169
|
-
|
|
1170
|
-
return (
|
|
1171
|
-
<WithDesignerFeatures
|
|
1172
|
-
key={options.keyOverride || block.id}
|
|
1173
|
-
block={block}
|
|
1174
|
-
placeholderId={placeholderId}
|
|
1175
|
-
sectionId={section.id}
|
|
1176
|
-
isDesigner={isDesigner}
|
|
1177
|
-
isSelected={
|
|
1178
|
-
selectedBlockId === actionBlockId || selectedBlockId === block.id
|
|
1179
|
-
}
|
|
1180
|
-
currentBreakpoint={currentBreakpoint}
|
|
1181
|
-
className={options.wrapperClassName}
|
|
1182
|
-
style={options.wrapperStyle}
|
|
1183
|
-
onMoveUp={() => postBlockAction('MOVE_BLOCK_UP', actionBlockId)}
|
|
1184
|
-
onMoveDown={() => postBlockAction('MOVE_BLOCK_DOWN', actionBlockId)}
|
|
1185
|
-
onDuplicate={() => postBlockAction('DUPLICATE_BLOCK', actionBlockId)}
|
|
1186
|
-
onToggleVisibility={() =>
|
|
1187
|
-
postBlockAction('TOGGLE_BLOCK_VISIBILITY', actionBlockId)
|
|
1188
|
-
}
|
|
1189
|
-
onDelete={() => postBlockAction('DELETE_BLOCK', actionBlockId)}
|
|
1190
|
-
onRename={(newLabel) =>
|
|
1191
|
-
postBlockAction('RENAME_BLOCK', actionBlockId, newLabel)
|
|
1192
|
-
}
|
|
1193
|
-
>
|
|
1194
|
-
{children}
|
|
1195
|
-
</WithDesignerFeatures>
|
|
1196
|
-
);
|
|
1197
|
-
};
|
|
1198
|
-
|
|
1199
|
-
const productName = product?.name || 'Selected product';
|
|
1200
|
-
const productUrl =
|
|
1201
|
-
typeof product?.absolute_url === 'string' ? product.absolute_url : '';
|
|
1202
|
-
const productImage = getProductImage(product);
|
|
1203
|
-
const activePrice = (product?.active_price || {}) as Record<string, unknown>;
|
|
1204
|
-
const priceValue = activePrice.price ?? product?.price;
|
|
1205
|
-
const retailPriceValue = activePrice.retail_price ?? product?.retail_price;
|
|
1206
|
-
const currency =
|
|
1207
|
-
activePrice.currency_type ?? product?.currency_type ?? product?.currency;
|
|
1208
|
-
const priceText = formatPriceWithCurrency(priceValue, currency);
|
|
1209
|
-
const retailPriceText = formatPriceWithCurrency(retailPriceValue, currency);
|
|
1210
|
-
const priceState = getDiscountState(priceValue, retailPriceValue);
|
|
1211
|
-
|
|
1212
|
-
const results = useMemo(
|
|
1213
|
-
() => ((stockResponse || []) as StoreStockItem[]).slice(0, maxResults),
|
|
1214
|
-
[maxResults, stockResponse]
|
|
1215
|
-
);
|
|
1216
|
-
const isMobile = currentBreakpoint === 'mobile';
|
|
1217
|
-
const isProductResolved = !!activeProductPk && !!product;
|
|
1218
|
-
|
|
1219
|
-
const displayProductName = isProductResolved
|
|
1220
|
-
? productName
|
|
1221
|
-
: 'Yellow Gold Bell-Shaped Necklace';
|
|
1222
|
-
const displayProductPrice = isProductResolved
|
|
1223
|
-
? priceText || '249,90 USD'
|
|
1224
|
-
: '249,90 USD';
|
|
1225
|
-
const displayProductImage = isProductResolved ? productImage || '' : '';
|
|
1226
|
-
const productCardTokens = {
|
|
1227
|
-
product_name: displayProductName,
|
|
1228
|
-
product_price: displayProductPrice
|
|
1229
|
-
};
|
|
1230
|
-
|
|
1231
|
-
const handleResolveProduct = async () => {
|
|
1232
|
-
if (!productUrlInput.trim()) {
|
|
1233
|
-
setSearchError(urlRequiredText);
|
|
1234
|
-
setResolvedProductPk(null);
|
|
1235
|
-
return;
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
setIsResolvingProduct(true);
|
|
1239
|
-
setSearchError('');
|
|
1240
|
-
setFormError('');
|
|
1241
|
-
setHasSubmitted(false);
|
|
1242
|
-
|
|
1243
|
-
try {
|
|
1244
|
-
const resolvedPk = await resolveProductPkFromInput(productUrlInput);
|
|
1245
|
-
|
|
1246
|
-
if (!resolvedPk) {
|
|
1247
|
-
setResolvedProductPk(null);
|
|
1248
|
-
setSearchError(invalidProductText);
|
|
1249
|
-
return;
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
setResolvedProductPk(resolvedPk);
|
|
1253
|
-
} catch (_error) {
|
|
1254
|
-
setResolvedProductPk(null);
|
|
1255
|
-
setSearchError(invalidProductText);
|
|
1256
|
-
} finally {
|
|
1257
|
-
setIsResolvingProduct(false);
|
|
1258
|
-
}
|
|
1259
|
-
};
|
|
1260
|
-
|
|
1261
|
-
const handleSubmit = async () => {
|
|
1262
|
-
if (!activeProductPk) {
|
|
1263
|
-
setFormError(setupText);
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
if (!selectedCityId) {
|
|
1268
|
-
setFormError(t('product.find_in_store.required'));
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
const resolvedSize =
|
|
1273
|
-
selectedSize || (sizeOptions.length === 1 ? sizeOptions[0].value : '');
|
|
1274
|
-
|
|
1275
|
-
if (sizeOptions.length > 1 && !resolvedSize) {
|
|
1276
|
-
setFormError(t('product.find_in_store.required'));
|
|
1277
|
-
return;
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
setFormError('');
|
|
1281
|
-
setHasSubmitted(true);
|
|
1282
|
-
|
|
1283
|
-
const params = new URLSearchParams();
|
|
1284
|
-
params.set('city_id', selectedCityId);
|
|
1285
|
-
if (resolvedSize) {
|
|
1286
|
-
params.set('size', resolvedSize);
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
await getRetailStock({
|
|
1290
|
-
productPk: String(activeProductPk),
|
|
1291
|
-
queryString: params.toString()
|
|
1292
|
-
});
|
|
1293
|
-
};
|
|
1294
|
-
|
|
1295
|
-
const inputStyle: React.CSSProperties = {
|
|
1296
|
-
width: '100%',
|
|
1297
|
-
backgroundColor: inputBg,
|
|
1298
|
-
color: inputTextColor,
|
|
1299
|
-
border: `1px solid ${inputBorderColor}`,
|
|
1300
|
-
borderRadius: inputBorderRadius,
|
|
1301
|
-
padding: inputPadding,
|
|
1302
|
-
fontSize: '15px',
|
|
1303
|
-
outline: 'none',
|
|
1304
|
-
fontFamily: 'inherit'
|
|
1305
|
-
};
|
|
1306
|
-
|
|
1307
|
-
const buttonStyle: React.CSSProperties = {
|
|
1308
|
-
width: '100%',
|
|
1309
|
-
backgroundColor: buttonBg,
|
|
1310
|
-
color: buttonTextColor,
|
|
1311
|
-
border: 'none',
|
|
1312
|
-
borderRadius: buttonBorderRadius,
|
|
1313
|
-
padding: buttonPadding,
|
|
1314
|
-
fontSize: '14px',
|
|
1315
|
-
fontWeight: 700,
|
|
1316
|
-
cursor: stockLoading || isResolvingProduct ? 'default' : 'pointer',
|
|
1317
|
-
opacity: stockLoading || isResolvingProduct ? 0.7 : 1,
|
|
1318
|
-
transition: 'all 0.2s ease',
|
|
1319
|
-
fontFamily: 'inherit',
|
|
1320
|
-
whiteSpace: 'nowrap',
|
|
1321
|
-
lineHeight: 1.2
|
|
1322
|
-
};
|
|
1323
|
-
|
|
1324
|
-
const secondaryButtonStyle: React.CSSProperties = {
|
|
1325
|
-
backgroundColor: 'transparent',
|
|
1326
|
-
color: buttonBg,
|
|
1327
|
-
border: `1px solid ${panelBorderColor}`,
|
|
1328
|
-
borderRadius: buttonBorderRadius,
|
|
1329
|
-
padding: '10px 16px',
|
|
1330
|
-
fontSize: '13px',
|
|
1331
|
-
fontWeight: 700,
|
|
1332
|
-
transition: 'all 0.2s ease',
|
|
1333
|
-
fontFamily: 'inherit',
|
|
1334
|
-
textDecoration: 'none',
|
|
1335
|
-
display: 'inline-flex',
|
|
1336
|
-
alignItems: 'center',
|
|
1337
|
-
justifyContent: 'center'
|
|
1338
|
-
};
|
|
1339
|
-
|
|
1340
|
-
const panelStyle: React.CSSProperties = {
|
|
1341
|
-
backgroundColor: panelBackground,
|
|
1342
|
-
border: `1px solid ${panelBorderColor}`,
|
|
1343
|
-
borderRadius: panelBorderRadius
|
|
1344
|
-
};
|
|
1345
|
-
|
|
1346
|
-
const productCardStyle: React.CSSProperties = {
|
|
1347
|
-
backgroundColor: productCardBackground,
|
|
1348
|
-
border: `1px solid ${productCardBorderColor}`,
|
|
1349
|
-
borderRadius: panelBorderRadius
|
|
1350
|
-
};
|
|
1351
|
-
|
|
1352
|
-
const resultCardStyle: React.CSSProperties = {
|
|
1353
|
-
backgroundColor: resultCardBackground,
|
|
1354
|
-
border: `1px solid ${resultCardBorderColor}`,
|
|
1355
|
-
borderRadius: resultCardBorderRadius
|
|
1356
|
-
};
|
|
1357
|
-
const mergeControlStyles = (
|
|
1358
|
-
baseStyle: React.CSSProperties,
|
|
1359
|
-
block: Block | null
|
|
1360
|
-
): React.CSSProperties => ({
|
|
1361
|
-
...baseStyle,
|
|
1362
|
-
...(block
|
|
1363
|
-
? getCSSStyles(
|
|
1364
|
-
Object.fromEntries(
|
|
1365
|
-
Object.entries(block.styles || {}).filter(
|
|
1366
|
-
([styleKey]) => !CONTROL_LAYOUT_STYLE_KEYS.has(styleKey)
|
|
1367
|
-
)
|
|
1368
|
-
),
|
|
1369
|
-
themeSettings,
|
|
1370
|
-
currentBreakpoint
|
|
1371
|
-
)
|
|
1372
|
-
: {})
|
|
1373
|
-
});
|
|
1374
|
-
const searchInputStyle = mergeControlStyles(inputStyle, searchInputBlock);
|
|
1375
|
-
const citySelectStyle = mergeControlStyles(inputStyle, citySelectBlock);
|
|
1376
|
-
const sizeSelectStyle = mergeControlStyles(inputStyle, sizeSelectBlock);
|
|
1377
|
-
const searchButtonControlStyle = mergeControlStyles(buttonStyle, searchButtonBlock);
|
|
1378
|
-
const availabilityButtonControlStyle = mergeControlStyles(
|
|
1379
|
-
buttonStyle,
|
|
1380
|
-
availabilityButtonBlock
|
|
1381
|
-
);
|
|
1382
|
-
const searchButtonBaseBackground =
|
|
1383
|
-
typeof searchButtonControlStyle.backgroundColor === 'string'
|
|
1384
|
-
? searchButtonControlStyle.backgroundColor
|
|
1385
|
-
: buttonBg;
|
|
1386
|
-
const availabilityButtonBaseBackground =
|
|
1387
|
-
typeof availabilityButtonControlStyle.backgroundColor === 'string'
|
|
1388
|
-
? availabilityButtonControlStyle.backgroundColor
|
|
1389
|
-
: buttonBg;
|
|
1390
|
-
const availabilityDescriptionText = isProductResolved
|
|
1391
|
-
? t('product.find_in_store.description')
|
|
1392
|
-
: 'Search for a product URL first, then select a city and size to check store stock.';
|
|
1393
|
-
const shouldRenderProductCard =
|
|
1394
|
-
showProductCard && (isProductResolved || isDesigner);
|
|
1395
|
-
|
|
1396
|
-
return (
|
|
1397
|
-
<div
|
|
1398
|
-
className={clsx('w-full', hasMaxWidth && 'mx-auto', maxWidthClass)}
|
|
1399
|
-
style={sectionStyles}
|
|
1400
|
-
>
|
|
1401
|
-
<div className="flex flex-col gap-6">
|
|
1402
|
-
{contentBlocks.length > 0 && (
|
|
1403
|
-
<div className="flex flex-col gap-4">
|
|
1404
|
-
{contentBlocks.map((block) =>
|
|
1405
|
-
renderBlock(block, `content-${block.id}`)
|
|
1406
|
-
)}
|
|
1407
|
-
</div>
|
|
1408
|
-
)}
|
|
1409
|
-
|
|
1410
|
-
<div
|
|
1411
|
-
className={clsx(
|
|
1412
|
-
'grid gap-6 items-stretch',
|
|
1413
|
-
shouldRenderProductCard && !isMobile
|
|
1414
|
-
? 'grid-cols-[minmax(0,0.92fr)_minmax(0,1.08fr)]'
|
|
1415
|
-
: 'grid-cols-1'
|
|
1416
|
-
)}
|
|
1417
|
-
>
|
|
1418
|
-
{shouldRenderProductCard &&
|
|
1419
|
-
renderSelectableWrapper(
|
|
1420
|
-
productCardWrapperBlock,
|
|
1421
|
-
<div className="flex h-full flex-col gap-5">
|
|
1422
|
-
<div className="flex items-center justify-between gap-3">
|
|
1423
|
-
{productEyebrowBlock ? (
|
|
1424
|
-
renderBlock(
|
|
1425
|
-
cloneFindInStoreBlock(
|
|
1426
|
-
productEyebrowBlock,
|
|
1427
|
-
productCardTokens
|
|
1428
|
-
),
|
|
1429
|
-
'product-eyebrow'
|
|
1430
|
-
)
|
|
1431
|
-
) : (
|
|
1432
|
-
<span className="text-[12px] font-semibold uppercase tracking-[0.18em] text-slate-500">
|
|
1433
|
-
Selected product
|
|
1434
|
-
</span>
|
|
1435
|
-
)}
|
|
1436
|
-
|
|
1437
|
-
{productLinkBlock && (productUrl || isDesigner) ? (
|
|
1438
|
-
renderBlock(
|
|
1439
|
-
cloneFindInStoreBlock(
|
|
1440
|
-
productLinkBlock,
|
|
1441
|
-
productCardTokens,
|
|
1442
|
-
{
|
|
1443
|
-
properties: {
|
|
1444
|
-
href: productUrl || '#'
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
),
|
|
1448
|
-
'product-link'
|
|
1449
|
-
)
|
|
1450
|
-
) : productUrl ? (
|
|
1451
|
-
<a
|
|
1452
|
-
href={productUrl}
|
|
1453
|
-
className="text-[13px] font-semibold text-slate-800 underline underline-offset-4"
|
|
1454
|
-
>
|
|
1455
|
-
View product
|
|
1456
|
-
</a>
|
|
1457
|
-
) : null}
|
|
1458
|
-
</div>
|
|
1459
|
-
|
|
1460
|
-
<div
|
|
1461
|
-
className={clsx(
|
|
1462
|
-
'flex h-full flex-col rounded-[24px] border border-slate-200 bg-white',
|
|
1463
|
-
isMobile ? 'p-5' : 'p-4 xl:p-5'
|
|
1464
|
-
)}
|
|
1465
|
-
>
|
|
1466
|
-
<div
|
|
1467
|
-
className={clsx(
|
|
1468
|
-
'w-full',
|
|
1469
|
-
isMobile
|
|
1470
|
-
? 'max-w-[320px]'
|
|
1471
|
-
: 'max-w-[240px] xl:max-w-[260px]'
|
|
1472
|
-
)}
|
|
1473
|
-
>
|
|
1474
|
-
{productImageBlock ? (
|
|
1475
|
-
renderBlock(
|
|
1476
|
-
cloneFindInStoreBlock(
|
|
1477
|
-
productImageBlock,
|
|
1478
|
-
productCardTokens,
|
|
1479
|
-
{
|
|
1480
|
-
value: displayProductImage
|
|
1481
|
-
}
|
|
1482
|
-
),
|
|
1483
|
-
'product-image'
|
|
1484
|
-
)
|
|
1485
|
-
) : productImage ? (
|
|
1486
|
-
<div className="relative aspect-square w-full overflow-hidden rounded-[22px] bg-slate-100">
|
|
1487
|
-
<Image
|
|
1488
|
-
src={productImage}
|
|
1489
|
-
alt={productName}
|
|
1490
|
-
fill
|
|
1491
|
-
sizes="(min-width: 1280px) 260px, (min-width: 768px) 240px, 100vw"
|
|
1492
|
-
aspectRatio={1}
|
|
1493
|
-
imageClassName="object-contain"
|
|
1494
|
-
/>
|
|
1495
|
-
</div>
|
|
1496
|
-
) : (
|
|
1497
|
-
<div className="flex aspect-square items-center justify-center rounded-[22px] bg-slate-100 text-sm font-medium text-slate-400">
|
|
1498
|
-
No image
|
|
1499
|
-
</div>
|
|
1500
|
-
)}
|
|
1501
|
-
</div>
|
|
1502
|
-
|
|
1503
|
-
<div
|
|
1504
|
-
className={clsx(
|
|
1505
|
-
'flex min-w-0 flex-1 flex-col gap-3',
|
|
1506
|
-
isMobile ? 'mt-5' : 'mt-4'
|
|
1507
|
-
)}
|
|
1508
|
-
>
|
|
1509
|
-
{productNameBlock ? (
|
|
1510
|
-
renderBlock(
|
|
1511
|
-
cloneFindInStoreBlock(
|
|
1512
|
-
productNameBlock,
|
|
1513
|
-
productCardTokens
|
|
1514
|
-
),
|
|
1515
|
-
'product-name'
|
|
1516
|
-
)
|
|
1517
|
-
) : (
|
|
1518
|
-
<h3
|
|
1519
|
-
className={clsx(
|
|
1520
|
-
'font-bold leading-[1.2] text-slate-950',
|
|
1521
|
-
isMobile ? 'text-[22px]' : 'text-[20px]'
|
|
1522
|
-
)}
|
|
1523
|
-
style={{
|
|
1524
|
-
display: '-webkit-box',
|
|
1525
|
-
WebkitLineClamp: isMobile ? 3 : 2,
|
|
1526
|
-
WebkitBoxOrient: 'vertical',
|
|
1527
|
-
overflow: 'hidden'
|
|
1528
|
-
}}
|
|
1529
|
-
>
|
|
1530
|
-
{displayProductName}
|
|
1531
|
-
</h3>
|
|
1532
|
-
)}
|
|
1533
|
-
|
|
1534
|
-
{productPriceBlock ? (
|
|
1535
|
-
renderBlock(
|
|
1536
|
-
cloneFindInStoreBlock(
|
|
1537
|
-
productPriceBlock,
|
|
1538
|
-
productCardTokens
|
|
1539
|
-
),
|
|
1540
|
-
'product-price'
|
|
1541
|
-
)
|
|
1542
|
-
) : priceText ? (
|
|
1543
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
1544
|
-
{priceState.hasDiscount && retailPriceText ? (
|
|
1545
|
-
<span className="text-[15px] font-medium text-slate-400 line-through">
|
|
1546
|
-
{retailPriceText}
|
|
1547
|
-
</span>
|
|
1548
|
-
) : null}
|
|
1549
|
-
<span
|
|
1550
|
-
className={clsx(
|
|
1551
|
-
'font-bold leading-none text-slate-950',
|
|
1552
|
-
isMobile ? 'text-[28px]' : 'text-[24px]'
|
|
1553
|
-
)}
|
|
1554
|
-
>
|
|
1555
|
-
{priceText}
|
|
1556
|
-
</span>
|
|
1557
|
-
</div>
|
|
1558
|
-
) : null}
|
|
1559
|
-
</div>
|
|
1560
|
-
</div>
|
|
1561
|
-
</div>,
|
|
1562
|
-
{
|
|
1563
|
-
className: 'h-full overflow-hidden',
|
|
1564
|
-
style: productCardStyle,
|
|
1565
|
-
keyOverride: 'product-card-wrapper'
|
|
1566
|
-
}
|
|
1567
|
-
)}
|
|
1568
|
-
|
|
1569
|
-
<div className="flex h-full flex-col gap-4">
|
|
1570
|
-
{renderSelectableWrapper(
|
|
1571
|
-
searchPanelWrapperBlock,
|
|
1572
|
-
<div className="flex flex-col" style={{ gap: `${formGap}px` }}>
|
|
1573
|
-
<div className="flex flex-col gap-2">
|
|
1574
|
-
{searchHeadingBlock ? (
|
|
1575
|
-
renderBlock(
|
|
1576
|
-
cloneFindInStoreBlock(searchHeadingBlock, {}),
|
|
1577
|
-
'search-heading'
|
|
1578
|
-
)
|
|
1579
|
-
) : (
|
|
1580
|
-
<h3 className="text-[22px] font-bold leading-tight text-slate-950">
|
|
1581
|
-
Find a product first
|
|
1582
|
-
</h3>
|
|
1583
|
-
)}
|
|
1584
|
-
{searchDescriptionBlock ? (
|
|
1585
|
-
renderBlock(
|
|
1586
|
-
cloneFindInStoreBlock(searchDescriptionBlock, {
|
|
1587
|
-
setup_text: setupText
|
|
1588
|
-
}),
|
|
1589
|
-
'search-description'
|
|
1590
|
-
)
|
|
1591
|
-
) : (
|
|
1592
|
-
<p className="text-[15px] leading-7 text-slate-600">
|
|
1593
|
-
{setupText}
|
|
1594
|
-
</p>
|
|
1595
|
-
)}
|
|
1596
|
-
</div>
|
|
1597
|
-
|
|
1598
|
-
<div
|
|
1599
|
-
className={clsx(
|
|
1600
|
-
'grid gap-3',
|
|
1601
|
-
isMobile ? 'grid-cols-1' : 'grid-cols-[minmax(0,1fr)_190px]'
|
|
1602
|
-
)}
|
|
1603
|
-
>
|
|
1604
|
-
{renderSelectableElement(
|
|
1605
|
-
searchInputBlock,
|
|
1606
|
-
<input
|
|
1607
|
-
type="text"
|
|
1608
|
-
value={productUrlInput}
|
|
1609
|
-
onChange={(event) => setProductUrlInput(event.target.value)}
|
|
1610
|
-
placeholder={urlPlaceholder}
|
|
1611
|
-
inputMode="text"
|
|
1612
|
-
style={searchInputStyle}
|
|
1613
|
-
/>,
|
|
1614
|
-
{
|
|
1615
|
-
keyOverride: 'search-input'
|
|
1616
|
-
}
|
|
1617
|
-
)}
|
|
1618
|
-
|
|
1619
|
-
{renderSelectableElement(
|
|
1620
|
-
searchButtonBlock,
|
|
1621
|
-
<button
|
|
1622
|
-
type="button"
|
|
1623
|
-
style={searchButtonControlStyle}
|
|
1624
|
-
disabled={isResolvingProduct}
|
|
1625
|
-
onClick={handleResolveProduct}
|
|
1626
|
-
onMouseEnter={(event) => {
|
|
1627
|
-
if (!isResolvingProduct) {
|
|
1628
|
-
event.currentTarget.style.backgroundColor =
|
|
1629
|
-
buttonHoverBg;
|
|
1630
|
-
}
|
|
1631
|
-
}}
|
|
1632
|
-
onMouseLeave={(event) => {
|
|
1633
|
-
if (!isResolvingProduct) {
|
|
1634
|
-
event.currentTarget.style.backgroundColor =
|
|
1635
|
-
searchButtonBaseBackground;
|
|
1636
|
-
}
|
|
1637
|
-
}}
|
|
1638
|
-
>
|
|
1639
|
-
{isResolvingProduct ? 'Searching...' : searchButtonText}
|
|
1640
|
-
</button>,
|
|
1641
|
-
{
|
|
1642
|
-
keyOverride: 'search-button'
|
|
1643
|
-
}
|
|
1644
|
-
)}
|
|
1645
|
-
</div>
|
|
1646
|
-
|
|
1647
|
-
{searchError ? (
|
|
1648
|
-
<p className="rounded-[12px] border border-red-200 bg-red-50 px-4 py-3 text-sm leading-6 text-red-600">
|
|
1649
|
-
{searchError}
|
|
1650
|
-
</p>
|
|
1651
|
-
) : null}
|
|
1652
|
-
|
|
1653
|
-
{!searchError && !isProductResolved && !isResolvingProduct ? (
|
|
1654
|
-
<p className="text-sm leading-6 text-slate-500">
|
|
1655
|
-
{setupText}
|
|
1656
|
-
</p>
|
|
1657
|
-
) : null}
|
|
1658
|
-
|
|
1659
|
-
{resolvedProductPk && productLoading ? (
|
|
1660
|
-
<p className="text-sm leading-6 text-slate-500">
|
|
1661
|
-
Loading product details...
|
|
1662
|
-
</p>
|
|
1663
|
-
) : null}
|
|
1664
|
-
</div>,
|
|
1665
|
-
{
|
|
1666
|
-
className: 'p-4 sm:p-5',
|
|
1667
|
-
style: panelStyle,
|
|
1668
|
-
keyOverride: 'search-panel-wrapper'
|
|
1669
|
-
}
|
|
1670
|
-
)}
|
|
1671
|
-
|
|
1672
|
-
{renderSelectableWrapper(
|
|
1673
|
-
availabilityPanelWrapperBlock,
|
|
1674
|
-
<div className="flex flex-col" style={{ gap: `${formGap}px` }}>
|
|
1675
|
-
<div className="flex flex-col gap-2">
|
|
1676
|
-
{availabilityHeadingBlock ? (
|
|
1677
|
-
renderBlock(
|
|
1678
|
-
cloneFindInStoreBlock(availabilityHeadingBlock, {}),
|
|
1679
|
-
'availability-heading'
|
|
1680
|
-
)
|
|
1681
|
-
) : (
|
|
1682
|
-
<h3 className="text-[22px] font-bold leading-tight text-slate-950">
|
|
1683
|
-
Pickup availability
|
|
1684
|
-
</h3>
|
|
1685
|
-
)}
|
|
1686
|
-
{availabilityDescriptionBlock ? (
|
|
1687
|
-
renderBlock(
|
|
1688
|
-
cloneFindInStoreBlock(availabilityDescriptionBlock, {
|
|
1689
|
-
availability_description: availabilityDescriptionText
|
|
1690
|
-
}),
|
|
1691
|
-
'availability-description'
|
|
1692
|
-
)
|
|
1693
|
-
) : (
|
|
1694
|
-
<p className="text-[15px] leading-7 text-slate-600">
|
|
1695
|
-
{availabilityDescriptionText}
|
|
1696
|
-
</p>
|
|
1697
|
-
)}
|
|
1698
|
-
</div>
|
|
1699
|
-
|
|
1700
|
-
<div
|
|
1701
|
-
className={clsx(
|
|
1702
|
-
'grid gap-3',
|
|
1703
|
-
sizeOptions.length > 0 && !isMobile
|
|
1704
|
-
? 'grid-cols-2'
|
|
1705
|
-
: 'grid-cols-1'
|
|
1706
|
-
)}
|
|
1707
|
-
>
|
|
1708
|
-
{renderSelectableElement(
|
|
1709
|
-
citySelectBlock,
|
|
1710
|
-
<select
|
|
1711
|
-
value={selectedCityId}
|
|
1712
|
-
onChange={(event) => {
|
|
1713
|
-
setSelectedCityId(event.target.value);
|
|
1714
|
-
setFormError('');
|
|
1715
|
-
}}
|
|
1716
|
-
style={citySelectStyle}
|
|
1717
|
-
disabled={!isProductResolved}
|
|
1718
|
-
>
|
|
1719
|
-
<option value="">
|
|
1720
|
-
{t('product.find_in_store.select_an_option')}
|
|
1721
|
-
</option>
|
|
1722
|
-
{cityOptions.map((option) => (
|
|
1723
|
-
<option key={option.value} value={option.value}>
|
|
1724
|
-
{option.label}
|
|
1725
|
-
</option>
|
|
1726
|
-
))}
|
|
1727
|
-
</select>,
|
|
1728
|
-
{
|
|
1729
|
-
keyOverride: 'city-select'
|
|
1730
|
-
}
|
|
1731
|
-
)}
|
|
1732
|
-
|
|
1733
|
-
{sizeOptions.length > 0 && (
|
|
1734
|
-
renderSelectableElement(
|
|
1735
|
-
sizeSelectBlock,
|
|
1736
|
-
<select
|
|
1737
|
-
value={selectedSize}
|
|
1738
|
-
onChange={(event) => {
|
|
1739
|
-
setSelectedSize(event.target.value);
|
|
1740
|
-
setFormError('');
|
|
1741
|
-
}}
|
|
1742
|
-
style={sizeSelectStyle}
|
|
1743
|
-
disabled={!isProductResolved}
|
|
1744
|
-
>
|
|
1745
|
-
<option value="">
|
|
1746
|
-
{t('product.find_in_store.select_an_option')}
|
|
1747
|
-
</option>
|
|
1748
|
-
{sizeOptions.map((option) => (
|
|
1749
|
-
<option key={option.value} value={option.value}>
|
|
1750
|
-
{option.label}
|
|
1751
|
-
</option>
|
|
1752
|
-
))}
|
|
1753
|
-
</select>,
|
|
1754
|
-
{
|
|
1755
|
-
keyOverride: 'size-select'
|
|
1756
|
-
}
|
|
1757
|
-
)
|
|
1758
|
-
)}
|
|
1759
|
-
</div>
|
|
1760
|
-
|
|
1761
|
-
{renderSelectableElement(
|
|
1762
|
-
availabilityButtonBlock,
|
|
1763
|
-
<button
|
|
1764
|
-
type="button"
|
|
1765
|
-
style={availabilityButtonControlStyle}
|
|
1766
|
-
disabled={stockLoading || !isProductResolved}
|
|
1767
|
-
onClick={handleSubmit}
|
|
1768
|
-
onMouseEnter={(event) => {
|
|
1769
|
-
if (!stockLoading && isProductResolved) {
|
|
1770
|
-
event.currentTarget.style.backgroundColor = buttonHoverBg;
|
|
1771
|
-
}
|
|
1772
|
-
}}
|
|
1773
|
-
onMouseLeave={(event) => {
|
|
1774
|
-
if (!stockLoading && isProductResolved) {
|
|
1775
|
-
event.currentTarget.style.backgroundColor =
|
|
1776
|
-
availabilityButtonBaseBackground;
|
|
1777
|
-
}
|
|
1778
|
-
}}
|
|
1779
|
-
>
|
|
1780
|
-
{stockLoading ? 'Checking...' : buttonText}
|
|
1781
|
-
</button>,
|
|
1782
|
-
{
|
|
1783
|
-
keyOverride: 'availability-button'
|
|
1784
|
-
}
|
|
1785
|
-
)}
|
|
1786
|
-
|
|
1787
|
-
{formError ? (
|
|
1788
|
-
<p className="rounded-[12px] border border-red-200 bg-red-50 px-4 py-3 text-sm leading-6 text-red-600">
|
|
1789
|
-
{formError}
|
|
1790
|
-
</p>
|
|
1791
|
-
) : null}
|
|
1792
|
-
</div>,
|
|
1793
|
-
{
|
|
1794
|
-
className: 'p-4 sm:p-5',
|
|
1795
|
-
style: panelStyle,
|
|
1796
|
-
keyOverride: 'availability-panel-wrapper'
|
|
1797
|
-
}
|
|
1798
|
-
)}
|
|
1799
|
-
</div>
|
|
1800
|
-
</div>
|
|
1801
|
-
|
|
1802
|
-
{(stockLoading || hasSubmitted) && (
|
|
1803
|
-
<div className="flex flex-col gap-3">
|
|
1804
|
-
{stockLoading &&
|
|
1805
|
-
renderSelectableWrapper(
|
|
1806
|
-
resultCardWrapperBlock,
|
|
1807
|
-
<div className="flex min-h-[120px] items-center justify-center">
|
|
1808
|
-
<div className="text-sm font-medium text-slate-500">
|
|
1809
|
-
Checking stores...
|
|
1810
|
-
</div>
|
|
1811
|
-
</div>,
|
|
1812
|
-
{
|
|
1813
|
-
className: 'p-4',
|
|
1814
|
-
style: resultCardStyle,
|
|
1815
|
-
keyOverride: 'result-card-loading'
|
|
1816
|
-
}
|
|
1817
|
-
)}
|
|
1818
|
-
|
|
1819
|
-
{!stockLoading &&
|
|
1820
|
-
results.map((store, index) => {
|
|
1821
|
-
const tone = getStockTone(t, store.stock);
|
|
1822
|
-
const directionsUrl =
|
|
1823
|
-
store.latitude && store.longitude
|
|
1824
|
-
? `https://maps.google.com/?q=${store.latitude},${store.longitude}`
|
|
1825
|
-
: '';
|
|
1826
|
-
const storeTokens = {
|
|
1827
|
-
store_name: store.name || 'Store',
|
|
1828
|
-
store_address: store.address || '',
|
|
1829
|
-
store_status: tone.label,
|
|
1830
|
-
store_hours: createStoreHours(store.store_hours, t),
|
|
1831
|
-
store_stock: `Stock: ${store.stock}`
|
|
1832
|
-
};
|
|
1833
|
-
|
|
1834
|
-
return renderSelectableWrapper(
|
|
1835
|
-
resultCardWrapperBlock
|
|
1836
|
-
? {
|
|
1837
|
-
...cloneFindInStoreBlock(
|
|
1838
|
-
resultCardWrapperBlock,
|
|
1839
|
-
storeTokens
|
|
1840
|
-
),
|
|
1841
|
-
id: `${resultCardWrapperBlock.id}-clone-${index}`,
|
|
1842
|
-
styleSourceId:
|
|
1843
|
-
resultCardWrapperBlock.styleSourceId ||
|
|
1844
|
-
resultCardWrapperBlock.id
|
|
1845
|
-
}
|
|
1846
|
-
: null,
|
|
1847
|
-
<div className="flex flex-col gap-4">
|
|
1848
|
-
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
|
1849
|
-
<div className="min-w-0">
|
|
1850
|
-
{storeNameBlock ? (
|
|
1851
|
-
renderBlock(
|
|
1852
|
-
cloneFindInStoreBlock(storeNameBlock, storeTokens),
|
|
1853
|
-
`store-name-${index}`
|
|
1854
|
-
)
|
|
1855
|
-
) : (
|
|
1856
|
-
<h4 className="text-lg font-bold leading-tight text-slate-950">
|
|
1857
|
-
{store.name}
|
|
1858
|
-
</h4>
|
|
1859
|
-
)}
|
|
1860
|
-
{storeAddressBlock ? (
|
|
1861
|
-
renderBlock(
|
|
1862
|
-
cloneFindInStoreBlock(
|
|
1863
|
-
storeAddressBlock,
|
|
1864
|
-
storeTokens
|
|
1865
|
-
),
|
|
1866
|
-
`store-address-${index}`
|
|
1867
|
-
)
|
|
1868
|
-
) : (
|
|
1869
|
-
<p className="mt-2 text-sm leading-6 text-slate-600">
|
|
1870
|
-
{store.address}
|
|
1871
|
-
</p>
|
|
1872
|
-
)}
|
|
1873
|
-
</div>
|
|
1874
|
-
|
|
1875
|
-
<div
|
|
1876
|
-
className="inline-flex w-fit items-center rounded-full px-3 py-1"
|
|
1877
|
-
style={{
|
|
1878
|
-
backgroundColor: tone.background,
|
|
1879
|
-
color: tone.color
|
|
1880
|
-
}}
|
|
1881
|
-
>
|
|
1882
|
-
{storeStatusBlock ? (
|
|
1883
|
-
renderBlock(
|
|
1884
|
-
cloneFindInStoreBlock(
|
|
1885
|
-
storeStatusBlock,
|
|
1886
|
-
storeTokens
|
|
1887
|
-
),
|
|
1888
|
-
`store-status-${index}`
|
|
1889
|
-
)
|
|
1890
|
-
) : (
|
|
1891
|
-
<span className="text-xs font-semibold">
|
|
1892
|
-
{tone.label}
|
|
1893
|
-
</span>
|
|
1894
|
-
)}
|
|
1895
|
-
</div>
|
|
1896
|
-
</div>
|
|
1897
|
-
|
|
1898
|
-
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
1899
|
-
<div className="flex flex-col gap-1">
|
|
1900
|
-
{showWorkingHours &&
|
|
1901
|
-
(storeHoursBlock ? (
|
|
1902
|
-
renderBlock(
|
|
1903
|
-
cloneFindInStoreBlock(
|
|
1904
|
-
storeHoursBlock,
|
|
1905
|
-
storeTokens
|
|
1906
|
-
),
|
|
1907
|
-
`store-hours-${index}`
|
|
1908
|
-
)
|
|
1909
|
-
) : (
|
|
1910
|
-
<p className="text-sm leading-6 text-slate-500">
|
|
1911
|
-
<span className="font-semibold text-slate-700">
|
|
1912
|
-
{t('product.find_in_store.working_hours')}:
|
|
1913
|
-
</span>{' '}
|
|
1914
|
-
{createStoreHours(store.store_hours, t)}
|
|
1915
|
-
</p>
|
|
1916
|
-
))}
|
|
1917
|
-
{storeStockBlock ? (
|
|
1918
|
-
renderBlock(
|
|
1919
|
-
cloneFindInStoreBlock(storeStockBlock, storeTokens),
|
|
1920
|
-
`store-stock-${index}`
|
|
1921
|
-
)
|
|
1922
|
-
) : (
|
|
1923
|
-
<p className="text-sm leading-6 text-slate-500">
|
|
1924
|
-
<span className="font-semibold text-slate-700">
|
|
1925
|
-
Stock:
|
|
1926
|
-
</span>{' '}
|
|
1927
|
-
{store.stock}
|
|
1928
|
-
</p>
|
|
1929
|
-
)}
|
|
1930
|
-
</div>
|
|
1931
|
-
|
|
1932
|
-
{showDirections && directionsUrl ? (
|
|
1933
|
-
storeDirectionsBlock ? (
|
|
1934
|
-
renderBlock(
|
|
1935
|
-
cloneFindInStoreBlock(
|
|
1936
|
-
storeDirectionsBlock,
|
|
1937
|
-
storeTokens,
|
|
1938
|
-
{
|
|
1939
|
-
properties: {
|
|
1940
|
-
href: directionsUrl
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
),
|
|
1944
|
-
`store-directions-${index}`
|
|
1945
|
-
)
|
|
1946
|
-
) : (
|
|
1947
|
-
<a
|
|
1948
|
-
href={directionsUrl}
|
|
1949
|
-
target="_blank"
|
|
1950
|
-
rel="noreferrer"
|
|
1951
|
-
style={secondaryButtonStyle}
|
|
1952
|
-
onMouseEnter={(event) => {
|
|
1953
|
-
event.currentTarget.style.backgroundColor =
|
|
1954
|
-
panelBackground;
|
|
1955
|
-
}}
|
|
1956
|
-
onMouseLeave={(event) => {
|
|
1957
|
-
event.currentTarget.style.backgroundColor =
|
|
1958
|
-
'transparent';
|
|
1959
|
-
}}
|
|
1960
|
-
>
|
|
1961
|
-
{t('product.find_in_store.directions')}
|
|
1962
|
-
</a>
|
|
1963
|
-
)
|
|
1964
|
-
) : null}
|
|
1965
|
-
</div>
|
|
1966
|
-
</div>,
|
|
1967
|
-
{
|
|
1968
|
-
className: 'p-4',
|
|
1969
|
-
style: resultCardStyle,
|
|
1970
|
-
keyOverride: `result-card-wrapper-${index}`
|
|
1971
|
-
}
|
|
1972
|
-
);
|
|
1973
|
-
})}
|
|
1974
|
-
|
|
1975
|
-
{!stockLoading &&
|
|
1976
|
-
!results.length &&
|
|
1977
|
-
renderSelectableWrapper(
|
|
1978
|
-
resultCardWrapperBlock,
|
|
1979
|
-
<div className="text-sm leading-6 text-slate-500">
|
|
1980
|
-
{isStockError
|
|
1981
|
-
? t('product.find_in_store.store_not_found')
|
|
1982
|
-
: emptyStateText}
|
|
1983
|
-
</div>,
|
|
1984
|
-
{
|
|
1985
|
-
className: 'p-5',
|
|
1986
|
-
style: resultCardStyle,
|
|
1987
|
-
keyOverride: 'result-card-empty'
|
|
1988
|
-
}
|
|
1989
|
-
)}
|
|
1990
|
-
</div>
|
|
1991
|
-
)}
|
|
1992
|
-
</div>
|
|
1993
|
-
</div>
|
|
1994
|
-
);
|
|
1995
|
-
}
|