@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,433 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
4
|
-
import clsx from 'clsx';
|
|
5
|
-
import { twMerge } from 'tailwind-merge';
|
|
6
|
-
|
|
7
|
-
import ThemeBlock, { Block } from '../theme-block';
|
|
8
|
-
import { useThemeSettingsContext } from '../theme-settings-context';
|
|
9
|
-
import {
|
|
10
|
-
applyAlphaToColor,
|
|
11
|
-
getCSSStyles,
|
|
12
|
-
getResponsiveValue,
|
|
13
|
-
resolveThemeCssVariables
|
|
14
|
-
} from '../utils';
|
|
15
|
-
import { Section } from '../theme-section';
|
|
16
|
-
|
|
17
|
-
interface CountdownCampaignBannerSectionProps {
|
|
18
|
-
section: Section;
|
|
19
|
-
currentBreakpoint?: string;
|
|
20
|
-
placeholderId?: string;
|
|
21
|
-
isDesigner?: boolean;
|
|
22
|
-
selectedBlockId?: string | null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type CountdownUnit = 'days' | 'hours' | 'minutes' | 'seconds';
|
|
26
|
-
|
|
27
|
-
type CountdownValues = Record<CountdownUnit, string>;
|
|
28
|
-
|
|
29
|
-
const pickImageSource = (value: unknown, breakpoint: string): string => {
|
|
30
|
-
if (typeof value === 'string') {
|
|
31
|
-
return value.trim();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (typeof value === 'object' && value !== null) {
|
|
35
|
-
const objectValue = value as Record<string, unknown>;
|
|
36
|
-
if (typeof objectValue.url === 'string') {
|
|
37
|
-
return objectValue.url.trim();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const responsiveValue = getResponsiveValue(value, breakpoint, '');
|
|
41
|
-
if (typeof responsiveValue === 'string') {
|
|
42
|
-
return responsiveValue.trim();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const firstStringValue = Object.values(objectValue).find(
|
|
46
|
-
(item) => typeof item === 'string'
|
|
47
|
-
);
|
|
48
|
-
if (typeof firstStringValue === 'string') {
|
|
49
|
-
return firstStringValue.trim();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return '';
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const normalizeImageSource = (src: string): string => {
|
|
57
|
-
if (!src) return '';
|
|
58
|
-
if (src.startsWith('data:image')) return src;
|
|
59
|
-
if (src.startsWith('/')) return src;
|
|
60
|
-
if (src.startsWith('//')) return src;
|
|
61
|
-
if (src.startsWith('http://') || src.startsWith('https://')) return src;
|
|
62
|
-
|
|
63
|
-
const cloudName = process.env.NEXT_PUBLIC_IMAGE_CLOUD_NAME?.trim();
|
|
64
|
-
if (!cloudName) return '';
|
|
65
|
-
|
|
66
|
-
return `https://${cloudName}/${src.replace(/^\/+/, '')}`;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const hasValidImageSource = (src: string): boolean => {
|
|
70
|
-
if (!src) return false;
|
|
71
|
-
if (src.startsWith('data:image')) return true;
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
new URL(src, 'http://localhost');
|
|
75
|
-
return true;
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const parseNumber = (value: unknown, fallback: number): number => {
|
|
82
|
-
if (value === undefined || value === null || value === '') return fallback;
|
|
83
|
-
const parsed = typeof value === 'string' ? Number(value) : Number(value);
|
|
84
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const clampNumber = (value: number, min: number, max: number) =>
|
|
88
|
-
Math.min(Math.max(value, min), max);
|
|
89
|
-
|
|
90
|
-
const formatTwoDigits = (value: number) => String(Math.max(0, value)).padStart(2, '0');
|
|
91
|
-
|
|
92
|
-
const parseBoolean = (value: unknown, fallback: boolean): boolean => {
|
|
93
|
-
if (typeof value === 'boolean') return value;
|
|
94
|
-
if (typeof value === 'string') {
|
|
95
|
-
const normalized = value.trim().toLowerCase();
|
|
96
|
-
if (normalized === 'true') return true;
|
|
97
|
-
if (normalized === 'false') return false;
|
|
98
|
-
}
|
|
99
|
-
if (typeof value === 'number') return value !== 0;
|
|
100
|
-
return fallback;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const parseEndDate = (value: unknown): Date | null => {
|
|
104
|
-
if (value === undefined || value === null || value === '') return null;
|
|
105
|
-
|
|
106
|
-
if (value instanceof Date) {
|
|
107
|
-
return Number.isNaN(value.getTime()) ? null : value;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (typeof value === 'number') {
|
|
111
|
-
const timestamp = value < 10_000_000_000 ? value * 1000 : value;
|
|
112
|
-
const date = new Date(timestamp);
|
|
113
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (typeof value !== 'string') return null;
|
|
117
|
-
|
|
118
|
-
const trimmed = value.trim();
|
|
119
|
-
if (!trimmed) return null;
|
|
120
|
-
|
|
121
|
-
if (/^\d+$/.test(trimmed)) {
|
|
122
|
-
const numeric = Number(trimmed);
|
|
123
|
-
if (!Number.isFinite(numeric)) return null;
|
|
124
|
-
const timestamp = numeric < 10_000_000_000 ? numeric * 1000 : numeric;
|
|
125
|
-
const date = new Date(timestamp);
|
|
126
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const parsed = new Date(trimmed);
|
|
130
|
-
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const getRemainingTime = (
|
|
134
|
-
endDate: Date | null
|
|
135
|
-
): { isExpired: boolean; values: CountdownValues } => {
|
|
136
|
-
const empty = {
|
|
137
|
-
days: '00',
|
|
138
|
-
hours: '00',
|
|
139
|
-
minutes: '00',
|
|
140
|
-
seconds: '00'
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
if (!endDate) {
|
|
144
|
-
return { isExpired: false, values: empty };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const diffMs = endDate.getTime() - Date.now();
|
|
148
|
-
if (diffMs <= 0) {
|
|
149
|
-
return { isExpired: true, values: empty };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const totalSeconds = Math.floor(diffMs / 1000);
|
|
153
|
-
const days = Math.floor(totalSeconds / (60 * 60 * 24));
|
|
154
|
-
const hours = Math.floor((totalSeconds % (60 * 60 * 24)) / (60 * 60));
|
|
155
|
-
const minutes = Math.floor((totalSeconds % (60 * 60)) / 60);
|
|
156
|
-
const seconds = totalSeconds % 60;
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
isExpired: false,
|
|
160
|
-
values: {
|
|
161
|
-
days: formatTwoDigits(days),
|
|
162
|
-
hours: formatTwoDigits(hours),
|
|
163
|
-
minutes: formatTwoDigits(minutes),
|
|
164
|
-
seconds: formatTwoDigits(seconds)
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const CountdownCampaignBannerSection: React.FC<CountdownCampaignBannerSectionProps> = ({
|
|
170
|
-
section,
|
|
171
|
-
currentBreakpoint = 'desktop',
|
|
172
|
-
placeholderId = '',
|
|
173
|
-
isDesigner = false,
|
|
174
|
-
selectedBlockId = null
|
|
175
|
-
}) => {
|
|
176
|
-
const themeSettings = useThemeSettingsContext();
|
|
177
|
-
|
|
178
|
-
const sortedBlocks = [...(section.blocks || [])]
|
|
179
|
-
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
|
180
|
-
.filter((block) => (isDesigner ? true : !block.hidden));
|
|
181
|
-
|
|
182
|
-
const backgroundBlock = sortedBlocks.find((block) => block.type === 'image');
|
|
183
|
-
const foregroundBlocks = backgroundBlock
|
|
184
|
-
? sortedBlocks.filter((block) => block.id !== backgroundBlock.id)
|
|
185
|
-
: sortedBlocks;
|
|
186
|
-
|
|
187
|
-
const firstButtonIndex = foregroundBlocks.findIndex(
|
|
188
|
-
(block) => block.type === 'button'
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
const leadingBlocks =
|
|
192
|
-
firstButtonIndex >= 0
|
|
193
|
-
? foregroundBlocks.slice(0, firstButtonIndex)
|
|
194
|
-
: foregroundBlocks;
|
|
195
|
-
const trailingBlocks =
|
|
196
|
-
firstButtonIndex >= 0 ? foregroundBlocks.slice(firstButtonIndex) : [];
|
|
197
|
-
|
|
198
|
-
const endDateRaw = getResponsiveValue(
|
|
199
|
-
section.properties?.['end-date'],
|
|
200
|
-
currentBreakpoint,
|
|
201
|
-
''
|
|
202
|
-
);
|
|
203
|
-
const endDate = useMemo(() => parseEndDate(endDateRaw), [endDateRaw]);
|
|
204
|
-
|
|
205
|
-
const showSeconds = parseBoolean(
|
|
206
|
-
getResponsiveValue(section.properties?.['show-seconds'], currentBreakpoint, true),
|
|
207
|
-
true
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
const expiredMessage = String(
|
|
211
|
-
getResponsiveValue(
|
|
212
|
-
section.properties?.['expired-message'],
|
|
213
|
-
currentBreakpoint,
|
|
214
|
-
'Campaign ended'
|
|
215
|
-
) || 'Campaign ended'
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
const [countdown, setCountdown] = useState(() => getRemainingTime(endDate));
|
|
219
|
-
|
|
220
|
-
useEffect(() => {
|
|
221
|
-
const tick = () => setCountdown(getRemainingTime(endDate));
|
|
222
|
-
tick();
|
|
223
|
-
|
|
224
|
-
if (!endDate) return;
|
|
225
|
-
|
|
226
|
-
const intervalId = window.setInterval(tick, 1000);
|
|
227
|
-
return () => window.clearInterval(intervalId);
|
|
228
|
-
}, [endDate]);
|
|
229
|
-
|
|
230
|
-
const maxWidth = getResponsiveValue(
|
|
231
|
-
section.styles?.['max-width'],
|
|
232
|
-
currentBreakpoint,
|
|
233
|
-
'normal'
|
|
234
|
-
);
|
|
235
|
-
const maxWidthClass =
|
|
236
|
-
maxWidth === 'narrow'
|
|
237
|
-
? 'max-w-4xl'
|
|
238
|
-
: maxWidth === 'normal'
|
|
239
|
-
? 'max-w-7xl'
|
|
240
|
-
: '';
|
|
241
|
-
const hasMaxWidth = maxWidth !== 'none' && maxWidth !== 'full';
|
|
242
|
-
|
|
243
|
-
const filteredStyles = Object.fromEntries(
|
|
244
|
-
Object.entries(section.styles || {}).filter(
|
|
245
|
-
([key]) =>
|
|
246
|
-
![
|
|
247
|
-
'max-width',
|
|
248
|
-
'overlay-color',
|
|
249
|
-
'overlay-opacity',
|
|
250
|
-
'timer-background-color',
|
|
251
|
-
'timer-text-color',
|
|
252
|
-
'timer-label-color',
|
|
253
|
-
'timer-border-radius',
|
|
254
|
-
'timer-gap'
|
|
255
|
-
].includes(key)
|
|
256
|
-
)
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
const sectionStyles = getCSSStyles(filteredStyles, themeSettings, currentBreakpoint);
|
|
260
|
-
const backgroundImageSrc = useMemo(() => {
|
|
261
|
-
if (!backgroundBlock) return '';
|
|
262
|
-
const raw = pickImageSource(backgroundBlock.value, currentBreakpoint);
|
|
263
|
-
const normalized = normalizeImageSource(raw);
|
|
264
|
-
return hasValidImageSource(normalized) ? normalized : '';
|
|
265
|
-
}, [backgroundBlock, currentBreakpoint]);
|
|
266
|
-
|
|
267
|
-
const overlayColor = resolveThemeCssVariables(
|
|
268
|
-
String(
|
|
269
|
-
getResponsiveValue(section.styles?.['overlay-color'], currentBreakpoint, '#0f172a')
|
|
270
|
-
),
|
|
271
|
-
themeSettings
|
|
272
|
-
);
|
|
273
|
-
const overlayOpacity =
|
|
274
|
-
clampNumber(
|
|
275
|
-
parseNumber(
|
|
276
|
-
getResponsiveValue(section.styles?.['overlay-opacity'], currentBreakpoint, 45),
|
|
277
|
-
45
|
|
278
|
-
),
|
|
279
|
-
0,
|
|
280
|
-
100
|
|
281
|
-
) / 100;
|
|
282
|
-
const overlayBackground = applyAlphaToColor(overlayColor, overlayOpacity);
|
|
283
|
-
|
|
284
|
-
const timerBackgroundColor = resolveThemeCssVariables(
|
|
285
|
-
String(
|
|
286
|
-
getResponsiveValue(
|
|
287
|
-
section.styles?.['timer-background-color'],
|
|
288
|
-
currentBreakpoint,
|
|
289
|
-
'#ffffff'
|
|
290
|
-
)
|
|
291
|
-
),
|
|
292
|
-
themeSettings
|
|
293
|
-
);
|
|
294
|
-
const timerTextColor = resolveThemeCssVariables(
|
|
295
|
-
String(
|
|
296
|
-
getResponsiveValue(section.styles?.['timer-text-color'], currentBreakpoint, '#0f172a')
|
|
297
|
-
),
|
|
298
|
-
themeSettings
|
|
299
|
-
);
|
|
300
|
-
const timerLabelColor = resolveThemeCssVariables(
|
|
301
|
-
String(
|
|
302
|
-
getResponsiveValue(section.styles?.['timer-label-color'], currentBreakpoint, '#64748b')
|
|
303
|
-
),
|
|
304
|
-
themeSettings
|
|
305
|
-
);
|
|
306
|
-
const timerBorderRadius = String(
|
|
307
|
-
getResponsiveValue(section.styles?.['timer-border-radius'], currentBreakpoint, '10px')
|
|
308
|
-
);
|
|
309
|
-
const timerGap = clampNumber(
|
|
310
|
-
parseNumber(getResponsiveValue(section.styles?.['timer-gap'], currentBreakpoint, 10), 10),
|
|
311
|
-
0,
|
|
312
|
-
32
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
const postBlockAction = (type: string, blockId: string, label?: string) => {
|
|
316
|
-
if (!window.parent) return;
|
|
317
|
-
window.parent.postMessage(
|
|
318
|
-
{
|
|
319
|
-
type,
|
|
320
|
-
data: {
|
|
321
|
-
placeholderId,
|
|
322
|
-
sectionId: section.id,
|
|
323
|
-
blockId,
|
|
324
|
-
...(label ? { label } : {})
|
|
325
|
-
}
|
|
326
|
-
},
|
|
327
|
-
'*'
|
|
328
|
-
);
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const renderBlock = (block: Block) => (
|
|
332
|
-
<ThemeBlock
|
|
333
|
-
key={block.id}
|
|
334
|
-
block={block}
|
|
335
|
-
placeholderId={placeholderId}
|
|
336
|
-
sectionId={section.id}
|
|
337
|
-
isDesigner={isDesigner}
|
|
338
|
-
isSelected={selectedBlockId === block.id}
|
|
339
|
-
selectedBlockId={selectedBlockId}
|
|
340
|
-
currentBreakpoint={currentBreakpoint}
|
|
341
|
-
onMoveUp={() => postBlockAction('MOVE_BLOCK_UP', block.id)}
|
|
342
|
-
onMoveDown={() => postBlockAction('MOVE_BLOCK_DOWN', block.id)}
|
|
343
|
-
onDuplicate={() => postBlockAction('DUPLICATE_BLOCK', block.id)}
|
|
344
|
-
onToggleVisibility={() => postBlockAction('TOGGLE_BLOCK_VISIBILITY', block.id)}
|
|
345
|
-
onDelete={() => postBlockAction('DELETE_BLOCK', block.id)}
|
|
346
|
-
onRename={(newLabel) => postBlockAction('RENAME_BLOCK', block.id, newLabel)}
|
|
347
|
-
/>
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
const timerUnits: Array<{ key: CountdownUnit; label: string }> = showSeconds
|
|
351
|
-
? [
|
|
352
|
-
{ key: 'days', label: 'Days' },
|
|
353
|
-
{ key: 'hours', label: 'Hours' },
|
|
354
|
-
{ key: 'minutes', label: 'Minutes' },
|
|
355
|
-
{ key: 'seconds', label: 'Seconds' }
|
|
356
|
-
]
|
|
357
|
-
: [
|
|
358
|
-
{ key: 'days', label: 'Days' },
|
|
359
|
-
{ key: 'hours', label: 'Hours' },
|
|
360
|
-
{ key: 'minutes', label: 'Minutes' }
|
|
361
|
-
];
|
|
362
|
-
|
|
363
|
-
return (
|
|
364
|
-
<div
|
|
365
|
-
className={twMerge(
|
|
366
|
-
clsx(
|
|
367
|
-
'countdown-campaign-banner relative z-10 w-full overflow-hidden',
|
|
368
|
-
hasMaxWidth && 'mx-auto',
|
|
369
|
-
maxWidthClass
|
|
370
|
-
)
|
|
371
|
-
)}
|
|
372
|
-
style={{ ...sectionStyles, position: 'relative' }}
|
|
373
|
-
>
|
|
374
|
-
{backgroundBlock && backgroundImageSrc ? (
|
|
375
|
-
<div className="absolute inset-0 z-0">{renderBlock(backgroundBlock)}</div>
|
|
376
|
-
) : (
|
|
377
|
-
<div className="absolute inset-0 z-0 bg-slate-600" />
|
|
378
|
-
)}
|
|
379
|
-
|
|
380
|
-
<div
|
|
381
|
-
className="absolute inset-0 z-10 pointer-events-none"
|
|
382
|
-
style={{ backgroundColor: overlayBackground }}
|
|
383
|
-
/>
|
|
384
|
-
|
|
385
|
-
<div className="relative z-20 flex w-full max-w-3xl flex-col gap-4">
|
|
386
|
-
{leadingBlocks.map(renderBlock)}
|
|
387
|
-
|
|
388
|
-
{countdown.isExpired ? (
|
|
389
|
-
<div
|
|
390
|
-
className="inline-flex w-fit items-center justify-center px-4 py-2 text-sm font-semibold"
|
|
391
|
-
style={{
|
|
392
|
-
color: timerTextColor,
|
|
393
|
-
backgroundColor: timerBackgroundColor,
|
|
394
|
-
borderRadius: timerBorderRadius
|
|
395
|
-
}}
|
|
396
|
-
>
|
|
397
|
-
{expiredMessage}
|
|
398
|
-
</div>
|
|
399
|
-
) : (
|
|
400
|
-
<div className="flex flex-wrap items-stretch" style={{ gap: `${timerGap}px` }}>
|
|
401
|
-
{timerUnits.map((unit) => (
|
|
402
|
-
<div
|
|
403
|
-
key={unit.key}
|
|
404
|
-
className="min-w-[64px] px-3 py-2 text-center"
|
|
405
|
-
style={{
|
|
406
|
-
backgroundColor: timerBackgroundColor,
|
|
407
|
-
borderRadius: timerBorderRadius
|
|
408
|
-
}}
|
|
409
|
-
>
|
|
410
|
-
<div
|
|
411
|
-
className="text-lg font-semibold leading-none"
|
|
412
|
-
style={{ color: timerTextColor }}
|
|
413
|
-
>
|
|
414
|
-
{countdown.values[unit.key]}
|
|
415
|
-
</div>
|
|
416
|
-
<div
|
|
417
|
-
className="mt-1 text-[11px] font-medium uppercase tracking-[0.04em] leading-none"
|
|
418
|
-
style={{ color: timerLabelColor }}
|
|
419
|
-
>
|
|
420
|
-
{unit.label}
|
|
421
|
-
</div>
|
|
422
|
-
</div>
|
|
423
|
-
))}
|
|
424
|
-
</div>
|
|
425
|
-
)}
|
|
426
|
-
|
|
427
|
-
{trailingBlocks.map(renderBlock)}
|
|
428
|
-
</div>
|
|
429
|
-
</div>
|
|
430
|
-
);
|
|
431
|
-
};
|
|
432
|
-
|
|
433
|
-
export default CountdownCampaignBannerSection;
|