@akinon/next 2.0.0-beta.20 → 2.0.0-beta.22
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 +25 -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,710 +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 { WithDesignerFeatures } from '../components/with-designer-features';
|
|
9
|
-
import { useThemeSettingsContext } from '../theme-settings-context';
|
|
10
|
-
import {
|
|
11
|
-
applyAlphaToColor,
|
|
12
|
-
getCSSStyles,
|
|
13
|
-
getResponsiveValue,
|
|
14
|
-
resolveThemeCssVariables
|
|
15
|
-
} from '../utils';
|
|
16
|
-
import { Section } from '../theme-section';
|
|
17
|
-
|
|
18
|
-
interface CouponBannerSectionProps {
|
|
19
|
-
section: Section;
|
|
20
|
-
currentBreakpoint?: string;
|
|
21
|
-
placeholderId?: string;
|
|
22
|
-
isDesigner?: boolean;
|
|
23
|
-
selectedBlockId?: string | null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const pickImageSource = (value: unknown, breakpoint: string): string => {
|
|
27
|
-
if (typeof value === 'string') {
|
|
28
|
-
return value.trim();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (typeof value === 'object' && value !== null) {
|
|
32
|
-
const objectValue = value as Record<string, unknown>;
|
|
33
|
-
if (typeof objectValue.url === 'string') {
|
|
34
|
-
return objectValue.url.trim();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const responsiveValue = getResponsiveValue(value, breakpoint, '');
|
|
38
|
-
if (typeof responsiveValue === 'string') {
|
|
39
|
-
return responsiveValue.trim();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const firstStringValue = Object.values(objectValue).find(
|
|
43
|
-
(item) => typeof item === 'string'
|
|
44
|
-
);
|
|
45
|
-
if (typeof firstStringValue === 'string') {
|
|
46
|
-
return firstStringValue.trim();
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return '';
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const normalizeImageSource = (src: string): string => {
|
|
54
|
-
if (!src) return '';
|
|
55
|
-
if (src.startsWith('data:image')) return src;
|
|
56
|
-
if (src.startsWith('/')) return src;
|
|
57
|
-
if (src.startsWith('//')) return src;
|
|
58
|
-
if (src.startsWith('http://') || src.startsWith('https://')) return src;
|
|
59
|
-
|
|
60
|
-
const cloudName = process.env.NEXT_PUBLIC_IMAGE_CLOUD_NAME?.trim();
|
|
61
|
-
if (!cloudName) return '';
|
|
62
|
-
|
|
63
|
-
return `https://${cloudName}/${src.replace(/^\/+/, '')}`;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const hasValidImageSource = (src: string): boolean => {
|
|
67
|
-
if (!src) return false;
|
|
68
|
-
if (src.startsWith('data:image')) return true;
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
new URL(src, 'http://localhost');
|
|
72
|
-
return true;
|
|
73
|
-
} catch {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const parseNumber = (value: unknown, fallback: number): number => {
|
|
79
|
-
if (value === undefined || value === null || value === '') return fallback;
|
|
80
|
-
const parsed = typeof value === 'string' ? Number(value) : Number(value);
|
|
81
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const clampNumber = (value: number, min: number, max: number) =>
|
|
85
|
-
Math.min(Math.max(value, min), max);
|
|
86
|
-
|
|
87
|
-
const escapeHtml = (value: string): string =>
|
|
88
|
-
value
|
|
89
|
-
.replace(/&/g, '&')
|
|
90
|
-
.replace(/</g, '<')
|
|
91
|
-
.replace(/>/g, '>')
|
|
92
|
-
.replace(/"/g, '"')
|
|
93
|
-
.replace(/'/g, ''');
|
|
94
|
-
|
|
95
|
-
const toHtmlText = (value: string): string => `<p>${escapeHtml(value)}</p>`;
|
|
96
|
-
|
|
97
|
-
const resolveCouponBannerRole = (
|
|
98
|
-
block: Pick<Block, 'properties'>,
|
|
99
|
-
breakpoint: string
|
|
100
|
-
): string => {
|
|
101
|
-
const value = getResponsiveValue(block.properties?.couponBannerRole, breakpoint, '');
|
|
102
|
-
return typeof value === 'string' ? value : '';
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const CONTROL_LAYOUT_STYLE_KEYS = new Set([
|
|
106
|
-
'display',
|
|
107
|
-
'width',
|
|
108
|
-
'height',
|
|
109
|
-
'min-width',
|
|
110
|
-
'min-height',
|
|
111
|
-
'max-width',
|
|
112
|
-
'max-height',
|
|
113
|
-
'margin',
|
|
114
|
-
'margin-top',
|
|
115
|
-
'margin-right',
|
|
116
|
-
'margin-bottom',
|
|
117
|
-
'margin-left',
|
|
118
|
-
'padding',
|
|
119
|
-
'padding-top',
|
|
120
|
-
'padding-right',
|
|
121
|
-
'padding-bottom',
|
|
122
|
-
'padding-left',
|
|
123
|
-
'font-size',
|
|
124
|
-
'font-weight',
|
|
125
|
-
'line-height',
|
|
126
|
-
'align-items',
|
|
127
|
-
'justify-content'
|
|
128
|
-
]);
|
|
129
|
-
|
|
130
|
-
const cloneCouponBlock = (
|
|
131
|
-
block: Block,
|
|
132
|
-
overrides?: {
|
|
133
|
-
properties?: Record<string, unknown>;
|
|
134
|
-
value?: unknown;
|
|
135
|
-
}
|
|
136
|
-
): Block => ({
|
|
137
|
-
...block,
|
|
138
|
-
styleSourceId: block.styleSourceId || block.id,
|
|
139
|
-
value:
|
|
140
|
-
overrides && Object.prototype.hasOwnProperty.call(overrides, 'value')
|
|
141
|
-
? overrides.value
|
|
142
|
-
: block.value,
|
|
143
|
-
properties: {
|
|
144
|
-
...(block.properties || {}),
|
|
145
|
-
...(overrides?.properties || {})
|
|
146
|
-
},
|
|
147
|
-
styles: { ...(block.styles || {}) },
|
|
148
|
-
blocks: block.blocks?.map((childBlock) => cloneCouponBlock(childBlock))
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const copyToClipboard = async (value: string): Promise<boolean> => {
|
|
152
|
-
if (!value) return false;
|
|
153
|
-
|
|
154
|
-
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
|
155
|
-
try {
|
|
156
|
-
await navigator.clipboard.writeText(value);
|
|
157
|
-
return true;
|
|
158
|
-
} catch {
|
|
159
|
-
// Fallback below.
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (typeof document === 'undefined') return false;
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
const textarea = document.createElement('textarea');
|
|
167
|
-
textarea.value = value;
|
|
168
|
-
textarea.setAttribute('readonly', '');
|
|
169
|
-
textarea.style.position = 'fixed';
|
|
170
|
-
textarea.style.opacity = '0';
|
|
171
|
-
textarea.style.pointerEvents = 'none';
|
|
172
|
-
|
|
173
|
-
document.body.appendChild(textarea);
|
|
174
|
-
textarea.focus();
|
|
175
|
-
textarea.select();
|
|
176
|
-
|
|
177
|
-
const copied = document.execCommand('copy');
|
|
178
|
-
document.body.removeChild(textarea);
|
|
179
|
-
|
|
180
|
-
return copied;
|
|
181
|
-
} catch {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const CouponBannerSection: React.FC<CouponBannerSectionProps> = ({
|
|
187
|
-
section,
|
|
188
|
-
currentBreakpoint = 'desktop',
|
|
189
|
-
placeholderId = '',
|
|
190
|
-
isDesigner = false,
|
|
191
|
-
selectedBlockId = null
|
|
192
|
-
}) => {
|
|
193
|
-
const themeSettings = useThemeSettingsContext();
|
|
194
|
-
|
|
195
|
-
const sortedBlocks = [...(section.blocks || [])]
|
|
196
|
-
.sort((a, b) => (a.order || 0) - (b.order || 0))
|
|
197
|
-
.filter((block) => (isDesigner ? true : !block.hidden));
|
|
198
|
-
|
|
199
|
-
const backgroundBlock = sortedBlocks.find((block) => block.type === 'image');
|
|
200
|
-
const foregroundBlocks = backgroundBlock
|
|
201
|
-
? sortedBlocks.filter((block) => block.id !== backgroundBlock.id)
|
|
202
|
-
: sortedBlocks;
|
|
203
|
-
const contentBlocks = foregroundBlocks.filter(
|
|
204
|
-
(block) => !resolveCouponBannerRole(block, currentBreakpoint)
|
|
205
|
-
);
|
|
206
|
-
const couponLabelBlock = foregroundBlocks.find(
|
|
207
|
-
(block) => resolveCouponBannerRole(block, currentBreakpoint) === 'coupon-label'
|
|
208
|
-
);
|
|
209
|
-
const couponCodeBoxBlock = foregroundBlocks.find(
|
|
210
|
-
(block) => resolveCouponBannerRole(block, currentBreakpoint) === 'coupon-code-box'
|
|
211
|
-
);
|
|
212
|
-
const couponCodeTextBlock = foregroundBlocks.find(
|
|
213
|
-
(block) => resolveCouponBannerRole(block, currentBreakpoint) === 'coupon-code-text'
|
|
214
|
-
);
|
|
215
|
-
const couponCopyButtonBlock = foregroundBlocks.find(
|
|
216
|
-
(block) => resolveCouponBannerRole(block, currentBreakpoint) === 'coupon-copy-button'
|
|
217
|
-
);
|
|
218
|
-
const couponNoteBlock = foregroundBlocks.find(
|
|
219
|
-
(block) => resolveCouponBannerRole(block, currentBreakpoint) === 'coupon-note-text'
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
const firstButtonIndex = contentBlocks.findIndex((block) => block.type === 'button');
|
|
223
|
-
|
|
224
|
-
const leadingBlocks =
|
|
225
|
-
firstButtonIndex >= 0
|
|
226
|
-
? contentBlocks.slice(0, firstButtonIndex)
|
|
227
|
-
: contentBlocks;
|
|
228
|
-
const trailingBlocks =
|
|
229
|
-
firstButtonIndex >= 0 ? contentBlocks.slice(firstButtonIndex) : [];
|
|
230
|
-
|
|
231
|
-
const couponCode = String(
|
|
232
|
-
getResponsiveValue(section.properties?.['coupon-code'], currentBreakpoint, 'SAVE20') ||
|
|
233
|
-
'SAVE20'
|
|
234
|
-
).trim();
|
|
235
|
-
const couponNote = String(
|
|
236
|
-
getResponsiveValue(
|
|
237
|
-
section.properties?.['coupon-note'],
|
|
238
|
-
currentBreakpoint,
|
|
239
|
-
'One-time use per customer'
|
|
240
|
-
) || 'One-time use per customer'
|
|
241
|
-
).trim();
|
|
242
|
-
const copyButtonText = String(
|
|
243
|
-
getResponsiveValue(
|
|
244
|
-
section.properties?.['copy-button-text'],
|
|
245
|
-
currentBreakpoint,
|
|
246
|
-
'Copy Code'
|
|
247
|
-
) || 'Copy Code'
|
|
248
|
-
);
|
|
249
|
-
const copiedButtonText = String(
|
|
250
|
-
getResponsiveValue(
|
|
251
|
-
section.properties?.['copied-button-text'],
|
|
252
|
-
currentBreakpoint,
|
|
253
|
-
'Copied'
|
|
254
|
-
) || 'Copied'
|
|
255
|
-
);
|
|
256
|
-
const copiedStateDuration = clampNumber(
|
|
257
|
-
parseNumber(
|
|
258
|
-
getResponsiveValue(
|
|
259
|
-
section.properties?.['copied-state-duration'],
|
|
260
|
-
currentBreakpoint,
|
|
261
|
-
2000
|
|
262
|
-
),
|
|
263
|
-
2000
|
|
264
|
-
),
|
|
265
|
-
500,
|
|
266
|
-
10000
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
const [isCopied, setIsCopied] = useState(false);
|
|
270
|
-
|
|
271
|
-
useEffect(() => {
|
|
272
|
-
if (!isCopied) return;
|
|
273
|
-
const timer = window.setTimeout(() => setIsCopied(false), copiedStateDuration);
|
|
274
|
-
return () => window.clearTimeout(timer);
|
|
275
|
-
}, [isCopied, copiedStateDuration]);
|
|
276
|
-
|
|
277
|
-
const maxWidth = getResponsiveValue(
|
|
278
|
-
section.styles?.['max-width'],
|
|
279
|
-
currentBreakpoint,
|
|
280
|
-
'full'
|
|
281
|
-
);
|
|
282
|
-
const maxWidthClass =
|
|
283
|
-
maxWidth === 'narrow'
|
|
284
|
-
? 'max-w-4xl'
|
|
285
|
-
: maxWidth === 'none'
|
|
286
|
-
? ''
|
|
287
|
-
: 'max-w-7xl';
|
|
288
|
-
const hasMaxWidth = maxWidth !== 'none';
|
|
289
|
-
const rawSectionBorderRadius = getResponsiveValue(
|
|
290
|
-
section.styles?.['border-radius'],
|
|
291
|
-
currentBreakpoint,
|
|
292
|
-
'0px'
|
|
293
|
-
);
|
|
294
|
-
const normalizedSectionBorderRadius =
|
|
295
|
-
rawSectionBorderRadius === undefined || rawSectionBorderRadius === null
|
|
296
|
-
? '0px'
|
|
297
|
-
: String(rawSectionBorderRadius);
|
|
298
|
-
const parsedSectionBorderRadius = Number.parseFloat(normalizedSectionBorderRadius);
|
|
299
|
-
const sectionBorderRadius = resolveThemeCssVariables(
|
|
300
|
-
Number.isFinite(parsedSectionBorderRadius) && parsedSectionBorderRadius === 12
|
|
301
|
-
? '0px'
|
|
302
|
-
: normalizedSectionBorderRadius,
|
|
303
|
-
themeSettings
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
const filteredStyles = Object.fromEntries(
|
|
307
|
-
Object.entries(section.styles || {}).filter(
|
|
308
|
-
([key]) =>
|
|
309
|
-
![
|
|
310
|
-
'max-width',
|
|
311
|
-
'border-radius',
|
|
312
|
-
'overlay-color',
|
|
313
|
-
'overlay-opacity',
|
|
314
|
-
'copy-row-gap',
|
|
315
|
-
'code-background-color',
|
|
316
|
-
'code-text-color',
|
|
317
|
-
'code-border-color',
|
|
318
|
-
'code-border-radius',
|
|
319
|
-
'copy-button-background-color',
|
|
320
|
-
'copy-button-text-color',
|
|
321
|
-
'copy-button-border-color',
|
|
322
|
-
'copy-button-border-radius'
|
|
323
|
-
].includes(key)
|
|
324
|
-
)
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
const sectionStyles = getCSSStyles(filteredStyles, themeSettings, currentBreakpoint);
|
|
328
|
-
|
|
329
|
-
const backgroundImageSrc = useMemo(() => {
|
|
330
|
-
if (!backgroundBlock) return '';
|
|
331
|
-
const raw = pickImageSource(backgroundBlock.value, currentBreakpoint);
|
|
332
|
-
const normalized = normalizeImageSource(raw);
|
|
333
|
-
return hasValidImageSource(normalized) ? normalized : '';
|
|
334
|
-
}, [backgroundBlock, currentBreakpoint]);
|
|
335
|
-
const hasBackgroundImage = Boolean(backgroundBlock && backgroundImageSrc);
|
|
336
|
-
|
|
337
|
-
const overlayColor = resolveThemeCssVariables(
|
|
338
|
-
String(
|
|
339
|
-
getResponsiveValue(section.styles?.['overlay-color'], currentBreakpoint, '#0f172a')
|
|
340
|
-
),
|
|
341
|
-
themeSettings
|
|
342
|
-
);
|
|
343
|
-
const overlayOpacity =
|
|
344
|
-
clampNumber(
|
|
345
|
-
parseNumber(
|
|
346
|
-
getResponsiveValue(section.styles?.['overlay-opacity'], currentBreakpoint, 12),
|
|
347
|
-
12
|
|
348
|
-
),
|
|
349
|
-
0,
|
|
350
|
-
100
|
|
351
|
-
) / 100;
|
|
352
|
-
const overlayBackground = applyAlphaToColor(overlayColor, overlayOpacity);
|
|
353
|
-
|
|
354
|
-
const copyRowGap = clampNumber(
|
|
355
|
-
parseNumber(getResponsiveValue(section.styles?.['copy-row-gap'], currentBreakpoint, 10), 10),
|
|
356
|
-
0,
|
|
357
|
-
32
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
const codeBackgroundColor = resolveThemeCssVariables(
|
|
361
|
-
String(
|
|
362
|
-
getResponsiveValue(section.styles?.['code-background-color'], currentBreakpoint, '#ffffff')
|
|
363
|
-
),
|
|
364
|
-
themeSettings
|
|
365
|
-
);
|
|
366
|
-
const codeTextColor = resolveThemeCssVariables(
|
|
367
|
-
String(getResponsiveValue(section.styles?.['code-text-color'], currentBreakpoint, '#0f172a')),
|
|
368
|
-
themeSettings
|
|
369
|
-
);
|
|
370
|
-
const codeBorderColor = resolveThemeCssVariables(
|
|
371
|
-
String(getResponsiveValue(section.styles?.['code-border-color'], currentBreakpoint, '#cbd5e1')),
|
|
372
|
-
themeSettings
|
|
373
|
-
);
|
|
374
|
-
const codeBorderRadius = String(
|
|
375
|
-
getResponsiveValue(section.styles?.['code-border-radius'], currentBreakpoint, '10px')
|
|
376
|
-
);
|
|
377
|
-
|
|
378
|
-
const copyButtonBackgroundColor = resolveThemeCssVariables(
|
|
379
|
-
String(
|
|
380
|
-
getResponsiveValue(
|
|
381
|
-
section.styles?.['copy-button-background-color'],
|
|
382
|
-
currentBreakpoint,
|
|
383
|
-
'#0f172a'
|
|
384
|
-
)
|
|
385
|
-
),
|
|
386
|
-
themeSettings
|
|
387
|
-
);
|
|
388
|
-
const copyButtonTextColor = resolveThemeCssVariables(
|
|
389
|
-
String(
|
|
390
|
-
getResponsiveValue(section.styles?.['copy-button-text-color'], currentBreakpoint, '#ffffff')
|
|
391
|
-
),
|
|
392
|
-
themeSettings
|
|
393
|
-
);
|
|
394
|
-
const copyButtonBorderColor = resolveThemeCssVariables(
|
|
395
|
-
String(
|
|
396
|
-
getResponsiveValue(
|
|
397
|
-
section.styles?.['copy-button-border-color'],
|
|
398
|
-
currentBreakpoint,
|
|
399
|
-
'#0f172a'
|
|
400
|
-
)
|
|
401
|
-
),
|
|
402
|
-
themeSettings
|
|
403
|
-
);
|
|
404
|
-
const copyButtonBorderRadius = String(
|
|
405
|
-
getResponsiveValue(section.styles?.['copy-button-border-radius'], currentBreakpoint, '10px')
|
|
406
|
-
);
|
|
407
|
-
|
|
408
|
-
const postBlockAction = (type: string, blockId: string, label?: string) => {
|
|
409
|
-
if (!window.parent) return;
|
|
410
|
-
window.parent.postMessage(
|
|
411
|
-
{
|
|
412
|
-
type,
|
|
413
|
-
data: {
|
|
414
|
-
placeholderId,
|
|
415
|
-
sectionId: section.id,
|
|
416
|
-
blockId,
|
|
417
|
-
...(label ? { label } : {})
|
|
418
|
-
}
|
|
419
|
-
},
|
|
420
|
-
'*'
|
|
421
|
-
);
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
const renderBlock = (block: Block, keyOverride?: React.Key) => {
|
|
425
|
-
const actionBlockId = block.styleSourceId || block.id;
|
|
426
|
-
|
|
427
|
-
return (
|
|
428
|
-
<ThemeBlock
|
|
429
|
-
key={keyOverride || block.id}
|
|
430
|
-
block={block}
|
|
431
|
-
placeholderId={placeholderId}
|
|
432
|
-
sectionId={section.id}
|
|
433
|
-
isDesigner={isDesigner}
|
|
434
|
-
isSelected={
|
|
435
|
-
selectedBlockId === actionBlockId || selectedBlockId === block.id
|
|
436
|
-
}
|
|
437
|
-
selectedBlockId={selectedBlockId}
|
|
438
|
-
currentBreakpoint={currentBreakpoint}
|
|
439
|
-
onMoveUp={() => postBlockAction('MOVE_BLOCK_UP', actionBlockId)}
|
|
440
|
-
onMoveDown={() => postBlockAction('MOVE_BLOCK_DOWN', actionBlockId)}
|
|
441
|
-
onDuplicate={() => postBlockAction('DUPLICATE_BLOCK', actionBlockId)}
|
|
442
|
-
onToggleVisibility={() =>
|
|
443
|
-
postBlockAction('TOGGLE_BLOCK_VISIBILITY', actionBlockId)
|
|
444
|
-
}
|
|
445
|
-
onDelete={() => postBlockAction('DELETE_BLOCK', actionBlockId)}
|
|
446
|
-
onRename={(newLabel) =>
|
|
447
|
-
postBlockAction('RENAME_BLOCK', actionBlockId, newLabel)
|
|
448
|
-
}
|
|
449
|
-
/>
|
|
450
|
-
);
|
|
451
|
-
};
|
|
452
|
-
|
|
453
|
-
const renderSelectableWrapper = (
|
|
454
|
-
block: Block | undefined,
|
|
455
|
-
children: React.ReactNode,
|
|
456
|
-
options: {
|
|
457
|
-
className?: string;
|
|
458
|
-
style?: React.CSSProperties;
|
|
459
|
-
keyOverride?: string;
|
|
460
|
-
} = {}
|
|
461
|
-
) => {
|
|
462
|
-
if (!block) {
|
|
463
|
-
return (
|
|
464
|
-
<div
|
|
465
|
-
key={options.keyOverride}
|
|
466
|
-
className={options.className}
|
|
467
|
-
style={options.style}
|
|
468
|
-
>
|
|
469
|
-
{children}
|
|
470
|
-
</div>
|
|
471
|
-
);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return (
|
|
475
|
-
<WithDesignerFeatures
|
|
476
|
-
key={options.keyOverride || block.id}
|
|
477
|
-
block={block}
|
|
478
|
-
placeholderId={placeholderId}
|
|
479
|
-
sectionId={section.id}
|
|
480
|
-
isDesigner={isDesigner}
|
|
481
|
-
isSelected={selectedBlockId === (block.styleSourceId || block.id)}
|
|
482
|
-
currentBreakpoint={currentBreakpoint}
|
|
483
|
-
className={options.className}
|
|
484
|
-
style={{
|
|
485
|
-
...(options.style || {}),
|
|
486
|
-
...getCSSStyles(block.styles || {}, themeSettings, currentBreakpoint)
|
|
487
|
-
}}
|
|
488
|
-
onMoveUp={() => postBlockAction('MOVE_BLOCK_UP', block.styleSourceId || block.id)}
|
|
489
|
-
onMoveDown={() => postBlockAction('MOVE_BLOCK_DOWN', block.styleSourceId || block.id)}
|
|
490
|
-
onDuplicate={() => postBlockAction('DUPLICATE_BLOCK', block.styleSourceId || block.id)}
|
|
491
|
-
onToggleVisibility={() =>
|
|
492
|
-
postBlockAction('TOGGLE_BLOCK_VISIBILITY', block.styleSourceId || block.id)
|
|
493
|
-
}
|
|
494
|
-
onDelete={() => postBlockAction('DELETE_BLOCK', block.styleSourceId || block.id)}
|
|
495
|
-
onRename={(newLabel) =>
|
|
496
|
-
postBlockAction('RENAME_BLOCK', block.styleSourceId || block.id, newLabel)
|
|
497
|
-
}
|
|
498
|
-
>
|
|
499
|
-
{children}
|
|
500
|
-
</WithDesignerFeatures>
|
|
501
|
-
);
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
const renderSelectableElement = (
|
|
505
|
-
block: Block | undefined,
|
|
506
|
-
children: React.ReactNode,
|
|
507
|
-
options: {
|
|
508
|
-
wrapperClassName?: string;
|
|
509
|
-
wrapperStyle?: React.CSSProperties;
|
|
510
|
-
keyOverride?: string;
|
|
511
|
-
} = {}
|
|
512
|
-
) => {
|
|
513
|
-
if (!block) {
|
|
514
|
-
return (
|
|
515
|
-
<div
|
|
516
|
-
key={options.keyOverride}
|
|
517
|
-
className={options.wrapperClassName}
|
|
518
|
-
style={options.wrapperStyle}
|
|
519
|
-
>
|
|
520
|
-
{children}
|
|
521
|
-
</div>
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
return (
|
|
526
|
-
<WithDesignerFeatures
|
|
527
|
-
key={options.keyOverride || block.id}
|
|
528
|
-
block={block}
|
|
529
|
-
placeholderId={placeholderId}
|
|
530
|
-
sectionId={section.id}
|
|
531
|
-
isDesigner={isDesigner}
|
|
532
|
-
isSelected={selectedBlockId === (block.styleSourceId || block.id)}
|
|
533
|
-
currentBreakpoint={currentBreakpoint}
|
|
534
|
-
className={options.wrapperClassName}
|
|
535
|
-
style={options.wrapperStyle}
|
|
536
|
-
onMoveUp={() => postBlockAction('MOVE_BLOCK_UP', block.styleSourceId || block.id)}
|
|
537
|
-
onMoveDown={() => postBlockAction('MOVE_BLOCK_DOWN', block.styleSourceId || block.id)}
|
|
538
|
-
onDuplicate={() => postBlockAction('DUPLICATE_BLOCK', block.styleSourceId || block.id)}
|
|
539
|
-
onToggleVisibility={() =>
|
|
540
|
-
postBlockAction('TOGGLE_BLOCK_VISIBILITY', block.styleSourceId || block.id)
|
|
541
|
-
}
|
|
542
|
-
onDelete={() => postBlockAction('DELETE_BLOCK', block.styleSourceId || block.id)}
|
|
543
|
-
onRename={(newLabel) =>
|
|
544
|
-
postBlockAction('RENAME_BLOCK', block.styleSourceId || block.id, newLabel)
|
|
545
|
-
}
|
|
546
|
-
>
|
|
547
|
-
{children}
|
|
548
|
-
</WithDesignerFeatures>
|
|
549
|
-
);
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
const handleCopyClick = async () => {
|
|
553
|
-
const copied = await copyToClipboard(couponCode || 'SAVE20');
|
|
554
|
-
if (copied) {
|
|
555
|
-
setIsCopied(true);
|
|
556
|
-
}
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
const isMobileLayout = currentBreakpoint === 'mobile';
|
|
560
|
-
const fallbackBackgroundColor =
|
|
561
|
-
typeof sectionStyles.backgroundColor === 'string'
|
|
562
|
-
? sectionStyles.backgroundColor
|
|
563
|
-
: '#f2f5fb';
|
|
564
|
-
const couponCodeBoxStyle: React.CSSProperties = {
|
|
565
|
-
backgroundColor: codeBackgroundColor,
|
|
566
|
-
color: codeTextColor,
|
|
567
|
-
border: `1px dashed ${codeBorderColor}`,
|
|
568
|
-
borderRadius: codeBorderRadius
|
|
569
|
-
};
|
|
570
|
-
const couponCopyButtonStyle: React.CSSProperties = {
|
|
571
|
-
backgroundColor: copyButtonBackgroundColor,
|
|
572
|
-
color: copyButtonTextColor,
|
|
573
|
-
border: `1px solid ${copyButtonBorderColor}`,
|
|
574
|
-
borderRadius: copyButtonBorderRadius
|
|
575
|
-
};
|
|
576
|
-
const couponCopyButtonControlStyle: React.CSSProperties = {
|
|
577
|
-
...couponCopyButtonStyle,
|
|
578
|
-
...(couponCopyButtonBlock
|
|
579
|
-
? getCSSStyles(
|
|
580
|
-
Object.fromEntries(
|
|
581
|
-
Object.entries(couponCopyButtonBlock.styles || {}).filter(
|
|
582
|
-
([styleKey]) => !CONTROL_LAYOUT_STYLE_KEYS.has(styleKey)
|
|
583
|
-
)
|
|
584
|
-
),
|
|
585
|
-
themeSettings,
|
|
586
|
-
currentBreakpoint
|
|
587
|
-
)
|
|
588
|
-
: {})
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
return (
|
|
592
|
-
<div
|
|
593
|
-
className={twMerge(
|
|
594
|
-
clsx(
|
|
595
|
-
'coupon-banner relative z-10 overflow-hidden',
|
|
596
|
-
hasMaxWidth && 'mx-auto',
|
|
597
|
-
maxWidthClass
|
|
598
|
-
)
|
|
599
|
-
)}
|
|
600
|
-
style={{
|
|
601
|
-
...sectionStyles,
|
|
602
|
-
position: 'relative',
|
|
603
|
-
borderRadius: sectionBorderRadius
|
|
604
|
-
}}
|
|
605
|
-
>
|
|
606
|
-
{hasBackgroundImage ? (
|
|
607
|
-
<div className="absolute inset-0 z-0">{renderBlock(backgroundBlock)}</div>
|
|
608
|
-
) : (
|
|
609
|
-
<div
|
|
610
|
-
className="absolute inset-0 z-0"
|
|
611
|
-
style={{ backgroundColor: fallbackBackgroundColor }}
|
|
612
|
-
/>
|
|
613
|
-
)}
|
|
614
|
-
|
|
615
|
-
{hasBackgroundImage && overlayOpacity > 0 && (
|
|
616
|
-
<div
|
|
617
|
-
className="absolute inset-0 z-10 pointer-events-none"
|
|
618
|
-
style={{ backgroundColor: overlayBackground }}
|
|
619
|
-
/>
|
|
620
|
-
)}
|
|
621
|
-
|
|
622
|
-
<div
|
|
623
|
-
className={clsx(
|
|
624
|
-
'relative z-20 flex w-full gap-5',
|
|
625
|
-
isMobileLayout ? 'flex-col' : 'items-end justify-between'
|
|
626
|
-
)}
|
|
627
|
-
>
|
|
628
|
-
<div className={clsx('flex min-w-0 flex-col gap-4', !isMobileLayout && 'max-w-3xl')}>
|
|
629
|
-
{leadingBlocks.map(renderBlock)}
|
|
630
|
-
{trailingBlocks.map(renderBlock)}
|
|
631
|
-
</div>
|
|
632
|
-
|
|
633
|
-
<div
|
|
634
|
-
className={clsx(
|
|
635
|
-
'flex shrink-0 flex-col gap-2',
|
|
636
|
-
isMobileLayout ? 'w-full' : 'w-[360px] max-w-full'
|
|
637
|
-
)}
|
|
638
|
-
>
|
|
639
|
-
{couponLabelBlock ? (
|
|
640
|
-
renderBlock(couponLabelBlock)
|
|
641
|
-
) : (
|
|
642
|
-
<div
|
|
643
|
-
className="text-xs font-semibold uppercase tracking-[0.08em]"
|
|
644
|
-
style={{ color: codeTextColor }}
|
|
645
|
-
>
|
|
646
|
-
Coupon code
|
|
647
|
-
</div>
|
|
648
|
-
)}
|
|
649
|
-
<div
|
|
650
|
-
className="inline-flex w-full flex-wrap items-stretch"
|
|
651
|
-
style={{ gap: `${copyRowGap}px` }}
|
|
652
|
-
>
|
|
653
|
-
{renderSelectableWrapper(
|
|
654
|
-
couponCodeBoxBlock,
|
|
655
|
-
couponCodeTextBlock ? (
|
|
656
|
-
renderBlock(
|
|
657
|
-
cloneCouponBlock(couponCodeTextBlock, {
|
|
658
|
-
value: toHtmlText(couponCode || 'SAVE20')
|
|
659
|
-
})
|
|
660
|
-
)
|
|
661
|
-
) : (
|
|
662
|
-
<div className="inline-flex min-h-[42px] flex-1 items-center justify-center px-4 py-2 text-sm font-semibold uppercase tracking-[0.12em]">
|
|
663
|
-
{couponCode || 'SAVE20'}
|
|
664
|
-
</div>
|
|
665
|
-
),
|
|
666
|
-
{
|
|
667
|
-
className:
|
|
668
|
-
'inline-flex min-h-[42px] flex-1 items-center justify-center px-4 py-2 text-sm font-semibold uppercase tracking-[0.12em]',
|
|
669
|
-
style: couponCodeBoxStyle,
|
|
670
|
-
keyOverride: 'coupon-code-box'
|
|
671
|
-
}
|
|
672
|
-
)}
|
|
673
|
-
|
|
674
|
-
{renderSelectableElement(
|
|
675
|
-
couponCopyButtonBlock,
|
|
676
|
-
<button
|
|
677
|
-
type="button"
|
|
678
|
-
className="inline-flex min-h-[42px] items-center justify-center px-4 py-2 text-sm font-semibold"
|
|
679
|
-
style={couponCopyButtonControlStyle}
|
|
680
|
-
onClick={handleCopyClick}
|
|
681
|
-
>
|
|
682
|
-
{isCopied ? copiedButtonText : copyButtonText}
|
|
683
|
-
</button>,
|
|
684
|
-
{ keyOverride: 'coupon-copy-button' }
|
|
685
|
-
)}
|
|
686
|
-
</div>
|
|
687
|
-
{couponNoteBlock ? (
|
|
688
|
-
renderBlock(
|
|
689
|
-
cloneCouponBlock(couponNoteBlock, {
|
|
690
|
-
value: toHtmlText(couponNote)
|
|
691
|
-
})
|
|
692
|
-
)
|
|
693
|
-
) : (
|
|
694
|
-
<div
|
|
695
|
-
className="text-xs font-medium"
|
|
696
|
-
style={{
|
|
697
|
-
color: codeTextColor,
|
|
698
|
-
opacity: 0.72
|
|
699
|
-
}}
|
|
700
|
-
>
|
|
701
|
-
{couponNote}
|
|
702
|
-
</div>
|
|
703
|
-
)}
|
|
704
|
-
</div>
|
|
705
|
-
</div>
|
|
706
|
-
</div>
|
|
707
|
-
);
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
export default CouponBannerSection;
|