@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.
Files changed (64) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/api/auth.ts +292 -60
  3. package/bin/pz-install-plugins.js +1 -1
  4. package/package.json +3 -3
  5. package/types/index.ts +19 -6
  6. package/types/next-auth.d.ts +1 -1
  7. package/with-pz-config.js +8 -1
  8. package/components/theme-editor/blocks/accordion-block.tsx +0 -136
  9. package/components/theme-editor/blocks/block-renderer-registry.tsx +0 -77
  10. package/components/theme-editor/blocks/button-block.tsx +0 -593
  11. package/components/theme-editor/blocks/counter-block.tsx +0 -348
  12. package/components/theme-editor/blocks/divider-block.tsx +0 -20
  13. package/components/theme-editor/blocks/embed-block.tsx +0 -208
  14. package/components/theme-editor/blocks/group-block.tsx +0 -116
  15. package/components/theme-editor/blocks/hotspot-block.tsx +0 -147
  16. package/components/theme-editor/blocks/icon-block.tsx +0 -230
  17. package/components/theme-editor/blocks/image-block.tsx +0 -137
  18. package/components/theme-editor/blocks/image-gallery-block.tsx +0 -269
  19. package/components/theme-editor/blocks/input-block.tsx +0 -123
  20. package/components/theme-editor/blocks/link-block.tsx +0 -216
  21. package/components/theme-editor/blocks/lottie-block.tsx +0 -325
  22. package/components/theme-editor/blocks/map-block.tsx +0 -89
  23. package/components/theme-editor/blocks/slider-block.tsx +0 -595
  24. package/components/theme-editor/blocks/tab-block.tsx +0 -10
  25. package/components/theme-editor/blocks/text-block.tsx +0 -52
  26. package/components/theme-editor/blocks/video-block.tsx +0 -122
  27. package/components/theme-editor/components/action-toolbar.tsx +0 -305
  28. package/components/theme-editor/components/designer-overlay.tsx +0 -74
  29. package/components/theme-editor/components/with-designer-features.tsx +0 -142
  30. package/components/theme-editor/dynamic-font-loader.tsx +0 -79
  31. package/components/theme-editor/hooks/use-designer-features.tsx +0 -100
  32. package/components/theme-editor/hooks/use-external-designer.tsx +0 -95
  33. package/components/theme-editor/hooks/use-native-widget-data.ts +0 -188
  34. package/components/theme-editor/hooks/use-visibility-context.ts +0 -27
  35. package/components/theme-editor/placeholder-registry.ts +0 -31
  36. package/components/theme-editor/sections/before-after-section.tsx +0 -245
  37. package/components/theme-editor/sections/contact-form-section.tsx +0 -563
  38. package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +0 -433
  39. package/components/theme-editor/sections/coupon-banner-section.tsx +0 -710
  40. package/components/theme-editor/sections/divider-section.tsx +0 -62
  41. package/components/theme-editor/sections/featured-product-spotlight-section.tsx +0 -507
  42. package/components/theme-editor/sections/find-in-store-section.tsx +0 -1995
  43. package/components/theme-editor/sections/hover-showcase-section.tsx +0 -326
  44. package/components/theme-editor/sections/image-hotspot-section.tsx +0 -142
  45. package/components/theme-editor/sections/installment-options-section.tsx +0 -1065
  46. package/components/theme-editor/sections/notification-banner-section.tsx +0 -173
  47. package/components/theme-editor/sections/order-tracking-lookup-section.tsx +0 -1379
  48. package/components/theme-editor/sections/posts-slider-section.tsx +0 -472
  49. package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +0 -663
  50. package/components/theme-editor/sections/section-renderer-registry.tsx +0 -89
  51. package/components/theme-editor/sections/section-wrapper.tsx +0 -135
  52. package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +0 -586
  53. package/components/theme-editor/sections/stats-counter-section.tsx +0 -486
  54. package/components/theme-editor/sections/tabs-section.tsx +0 -578
  55. package/components/theme-editor/theme-block.tsx +0 -102
  56. package/components/theme-editor/theme-placeholder-client.tsx +0 -218
  57. package/components/theme-editor/theme-placeholder-wrapper.tsx +0 -732
  58. package/components/theme-editor/theme-placeholder.tsx +0 -288
  59. package/components/theme-editor/theme-section.tsx +0 -1224
  60. package/components/theme-editor/theme-settings-context.tsx +0 -13
  61. package/components/theme-editor/utils/index.ts +0 -792
  62. package/components/theme-editor/utils/iterator-utils.ts +0 -234
  63. package/components/theme-editor/utils/publish-window.ts +0 -86
  64. package/components/theme-editor/utils/visibility-rules.ts +0 -188
@@ -1,595 +0,0 @@
1
- import React from 'react';
2
- import Carousel from 'react-multi-carousel';
3
- import 'react-multi-carousel/lib/styles.css';
4
- import clsx from 'clsx';
5
- import { twMerge } from 'tailwind-merge';
6
-
7
- import ThemeBlock from '../theme-block';
8
- import { useThemeSettingsContext } from '../theme-settings-context';
9
- import {
10
- getCSSStyles,
11
- getResponsiveValue,
12
- resolveThemeCssVariables
13
- } from '../utils';
14
- import { BlockRendererProps } from './block-renderer-registry';
15
-
16
- const parseNumber = (value: unknown, fallback: number) => {
17
- if (value === undefined || value === null || value === '') return fallback;
18
- const parsed = typeof value === 'string' ? Number(value) : Number(value);
19
- return Number.isFinite(parsed) ? parsed : fallback;
20
- };
21
-
22
- const clampNumber = (value: number, min: number, max: number) =>
23
- Math.min(Math.max(value, min), max);
24
-
25
- const SliderBlock: React.FC<BlockRendererProps> = ({
26
- block,
27
- placeholderId,
28
- sectionId,
29
- isDesigner = false,
30
- selectedBlockId = null,
31
- currentBreakpoint = 'desktop',
32
- onMoveUp,
33
- onMoveDown,
34
- onDuplicate,
35
- onToggleVisibility,
36
- onDelete,
37
- onRename
38
- }) => {
39
- const themeSettings = useThemeSettingsContext();
40
- const carouselRef = React.useRef<Carousel>(null);
41
- const containerRef = React.useRef<HTMLDivElement>(null);
42
-
43
- const leftArrowIconBlock = block.blocks?.find(
44
- (b: any) => b.type === 'icon' && b.label === 'Left Arrow Icon'
45
- );
46
-
47
- const rightArrowIconBlock = block.blocks?.find(
48
- (b: any) => b.type === 'icon' && b.label === 'Right Arrow Icon'
49
- );
50
-
51
- const getConfigEntry = (key: string) =>
52
- block.properties?.[key] ?? block.styles?.[key];
53
-
54
- const normalizeResponsiveNumber = (
55
- value: unknown,
56
- fallback: { desktop: number; mobile: number }
57
- ) => {
58
- if (value === undefined || value === null || value === '') {
59
- return { ...fallback };
60
- }
61
-
62
- if (typeof value === 'number' || typeof value === 'string') {
63
- const parsed = parseNumber(value, fallback.desktop);
64
- return { desktop: parsed, mobile: parsed };
65
- }
66
-
67
- if (typeof value === 'object') {
68
- const responsiveValue = value as Record<string, unknown>;
69
- const candidate =
70
- typeof responsiveValue.desktop === 'object' &&
71
- responsiveValue.desktop !== null
72
- ? (responsiveValue.desktop as Record<string, unknown>)
73
- : responsiveValue;
74
-
75
- return {
76
- desktop: parseNumber(candidate.desktop, fallback.desktop),
77
- mobile: parseNumber(candidate.mobile, fallback.mobile)
78
- };
79
- }
80
-
81
- return { ...fallback };
82
- };
83
-
84
- const itemsConfig = normalizeResponsiveNumber(
85
- getConfigEntry('items-per-slide'),
86
- { desktop: 4, mobile: 1 }
87
- );
88
- const slidesConfig = normalizeResponsiveNumber(
89
- getConfigEntry('slides-to-slide'),
90
- { desktop: 1, mobile: 1 }
91
- );
92
-
93
- const sortedBlocks = [...(block.blocks || [])]
94
- .filter(
95
- (childBlock) =>
96
- childBlock.type !== 'icon' && (isDesigner ? true : !childBlock.hidden)
97
- )
98
- .sort((a, b) => (a.order || 0) - (b.order || 0));
99
- const visibleCount = sortedBlocks.length;
100
-
101
- const desktopItems = clampNumber(itemsConfig.desktop, 1, 8);
102
- const mobileItems = clampNumber(itemsConfig.mobile, 1, 4);
103
- const desktopSlides = clampNumber(slidesConfig.desktop, 1, 8);
104
- const mobileSlides = clampNumber(slidesConfig.mobile, 1, 8);
105
-
106
- const carouselResponsive = {
107
- desktop: {
108
- breakpoint: { max: 4000, min: 768 },
109
- items: desktopItems
110
- },
111
- mobile: {
112
- breakpoint: { max: 768, min: 0 },
113
- items: mobileItems
114
- }
115
- };
116
-
117
- const currentSlidesToSlide =
118
- currentBreakpoint === 'mobile' ? mobileSlides : desktopSlides;
119
-
120
- const showArrows = getResponsiveValue(
121
- getConfigEntry('show-arrows'),
122
- currentBreakpoint,
123
- true
124
- );
125
- const showDots = getResponsiveValue(
126
- getConfigEntry('show-dots'),
127
- currentBreakpoint,
128
- false
129
- );
130
- const useAutoPlay = getResponsiveValue(
131
- getConfigEntry('autoplay'),
132
- currentBreakpoint,
133
- false
134
- );
135
-
136
- const useInfinite = useAutoPlay;
137
-
138
- const autoPlaySpeedRaw = getConfigEntry('autoplay-speed');
139
- const autoPlaySpeed = parseNumber(autoPlaySpeedRaw, 3000);
140
-
141
- const slideGap = parseNumber(getConfigEntry('slide-gap'), 16);
142
- const slidePadding = slideGap / 2;
143
-
144
- const blockStyles = block.styles || {};
145
- const sectionStyles = getCSSStyles(
146
- typeof blockStyles === 'object' && blockStyles !== null
147
- ? (blockStyles as Record<string, unknown>)
148
- : {},
149
- themeSettings,
150
- currentBreakpoint
151
- );
152
-
153
- const containerStylesRaw = blockStyles['max-width'];
154
- const hasMaxWidth =
155
- containerStylesRaw !== undefined &&
156
- containerStylesRaw !== null &&
157
- containerStylesRaw !== '';
158
- const maxWidthValue = getResponsiveValue(
159
- containerStylesRaw,
160
- currentBreakpoint,
161
- '1200px'
162
- );
163
- const maxWidthClass = hasMaxWidth ? `max-w-[${maxWidthValue}]` : '';
164
-
165
- const blockAction = (action: string, blockId: string, label?: string) => {
166
- if (!isDesigner) return;
167
- window.parent.postMessage(
168
- {
169
- type: 'BLOCK_ACTION',
170
- action,
171
- blockId,
172
- sectionId,
173
- placeholderId,
174
- label
175
- },
176
- '*'
177
- );
178
- };
179
-
180
- const modifySVGColor = (svgContent: string, color: string) => {
181
- if (color === 'currentColor' || !color) return svgContent;
182
-
183
- return svgContent
184
- .replace(/fill\s*=\s*["'][^"']*["']/gi, `fill="${color}"`)
185
- .replace(/stroke\s*=\s*["'][^"']*["']/gi, `stroke="${color}"`)
186
- .replace(/fill\s*:\s*[^;}\s]+/gi, `fill: ${color}`)
187
- .replace(/stroke\s*:\s*[^;}\s]+/gi, `stroke: ${color}`)
188
- .replace(/<path(?![^>]*fill)/gi, `<path fill="${color}"`)
189
- .replace(/<circle(?![^>]*fill)/gi, `<circle fill="${color}"`)
190
- .replace(/<rect(?![^>]*fill)/gi, `<rect fill="${color}"`)
191
- .replace(/<polygon(?![^>]*fill)/gi, `<polygon fill="${color}"`)
192
- .replace(/<ellipse(?![^>]*fill)/gi, `<ellipse fill="${color}"`);
193
- };
194
-
195
- const isInlineSvg = (value: string): boolean => {
196
- return (
197
- typeof value === 'string' &&
198
- (value.includes('<svg') || value.includes('<?xml'))
199
- );
200
- };
201
-
202
- const processInlineSVG = (svgContent: string, color: string) => {
203
- try {
204
- const modifiedSVG = modifySVGColor(svgContent, color);
205
- const base64 = btoa(modifiedSVG);
206
- return `data:image/svg+xml;base64,${base64}`;
207
- } catch (error) {
208
- console.error('Error processing inline SVG:', error);
209
- return '';
210
- }
211
- };
212
-
213
- const processBase64SVG = (base64String: string, color: string) => {
214
- try {
215
- const base64Content = base64String.replace(
216
- /^data:image\/svg\+xml;base64,/,
217
- ''
218
- );
219
- const svgContent = atob(base64Content);
220
- const modifiedSVG = modifySVGColor(svgContent, color);
221
- const modifiedBase64 = `data:image/svg+xml;base64,${btoa(modifiedSVG)}`;
222
- return modifiedBase64;
223
- } catch (error) {
224
- console.error('Error processing SVG base64:', error);
225
- return base64String;
226
- }
227
- };
228
-
229
- const getIconUrl = (iconBlock: any): string => {
230
- if (!iconBlock?.value) return '';
231
- const value = iconBlock.value;
232
-
233
- if (typeof value === 'string') {
234
- return value;
235
- }
236
-
237
- if (typeof value === 'object') {
238
- // Handle responsive value structure
239
- const responsiveUrl = getResponsiveValue(value, currentBreakpoint, '');
240
- if (typeof responsiveUrl === 'string') {
241
- return responsiveUrl;
242
- }
243
- // Handle {url: '...', alt: '...'} structure
244
- if (value.url) {
245
- return value.url;
246
- }
247
- }
248
-
249
- return '';
250
- };
251
-
252
- const defaultLeftArrowSvg =
253
- 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE1IDZMOSAxMkwxNSAxOCIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPg==';
254
- const defaultRightArrowSvg =
255
- 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTkgNkwxNSAxMkw5IDE4IiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+';
256
-
257
- const leftArrowIconUrl = getIconUrl(leftArrowIconBlock);
258
- const rightArrowIconUrl = getIconUrl(rightArrowIconBlock);
259
-
260
- const leftArrowIconSize = String(
261
- getResponsiveValue(leftArrowIconBlock?.styles?.size, '24')
262
- );
263
- const rightArrowIconSize = String(
264
- getResponsiveValue(rightArrowIconBlock?.styles?.size, '24')
265
- );
266
-
267
- const leftArrowIconColor = resolveThemeCssVariables(
268
- String(
269
- getResponsiveValue(leftArrowIconBlock?.styles?.['color'], '#ffffff')
270
- ),
271
- themeSettings
272
- );
273
- const rightArrowIconColor = resolveThemeCssVariables(
274
- String(
275
- getResponsiveValue(rightArrowIconBlock?.styles?.['color'], '#ffffff')
276
- ),
277
- themeSettings
278
- );
279
-
280
- const leftArrowIconOpacity =
281
- parseFloat(
282
- String(getResponsiveValue(leftArrowIconBlock?.styles?.['opacity'], '1'))
283
- ) || 1;
284
- const rightArrowIconOpacity =
285
- parseFloat(
286
- String(getResponsiveValue(rightArrowIconBlock?.styles?.['opacity'], '1'))
287
- ) || 1;
288
-
289
- // Process SVGs with color
290
- const processedLeftArrowUrl = isInlineSvg(leftArrowIconUrl)
291
- ? processInlineSVG(leftArrowIconUrl, leftArrowIconColor)
292
- : (leftArrowIconUrl || defaultLeftArrowSvg).includes(
293
- 'data:image/svg+xml;base64,'
294
- )
295
- ? processBase64SVG(
296
- leftArrowIconUrl || defaultLeftArrowSvg,
297
- leftArrowIconColor
298
- )
299
- : leftArrowIconUrl || defaultLeftArrowSvg;
300
-
301
- const processedRightArrowUrl = isInlineSvg(rightArrowIconUrl)
302
- ? processInlineSVG(rightArrowIconUrl, rightArrowIconColor)
303
- : (rightArrowIconUrl || defaultRightArrowSvg).includes(
304
- 'data:image/svg+xml;base64,'
305
- )
306
- ? processBase64SVG(
307
- rightArrowIconUrl || defaultRightArrowSvg,
308
- rightArrowIconColor
309
- )
310
- : rightArrowIconUrl || defaultRightArrowSvg;
311
-
312
- const leftArrowIcon = {
313
- url: processedLeftArrowUrl,
314
- alt: 'Previous'
315
- };
316
- const rightArrowIcon = {
317
- url: processedRightArrowUrl,
318
- alt: 'Next'
319
- };
320
-
321
- const leftArrowBackground = resolveThemeCssVariables(
322
- String(
323
- getResponsiveValue(
324
- leftArrowIconBlock?.styles?.['background'] ??
325
- leftArrowIconBlock?.styles?.['backgroundColor'],
326
- 'rgba(0, 0, 0, 0.5)'
327
- )
328
- ),
329
- themeSettings
330
- );
331
-
332
- const rightArrowBackground = resolveThemeCssVariables(
333
- String(
334
- getResponsiveValue(
335
- rightArrowIconBlock?.styles?.['background'] ??
336
- rightArrowIconBlock?.styles?.['backgroundColor'],
337
- 'rgba(0, 0, 0, 0.5)'
338
- )
339
- ),
340
- themeSettings
341
- );
342
-
343
- const handleLeftArrowClick = (e: React.MouseEvent) => {
344
- e.preventDefault();
345
- e.stopPropagation();
346
-
347
- carouselRef.current?.previous?.(1);
348
- };
349
-
350
- const handleRightArrowClick = (e: React.MouseEvent) => {
351
- e.preventDefault();
352
- e.stopPropagation();
353
-
354
- carouselRef.current?.next?.(1);
355
- };
356
-
357
- const CustomLeftArrow = ({ onClick }: any) => (
358
- <button
359
- onClick={(e) => {
360
- e.stopPropagation();
361
- if (!isDesigner) {
362
- onClick?.();
363
- }
364
- handleLeftArrowClick(e);
365
- }}
366
- className={clsx(
367
- 'absolute left-4 top-1/2 -translate-y-1/2 z-10',
368
- isDesigner &&
369
- selectedBlockId === leftArrowIconBlock?.id &&
370
- 'ring-2 ring-blue-500 ring-offset-2'
371
- )}
372
- style={{
373
- backgroundColor: leftArrowBackground,
374
- border: 'none',
375
- borderRadius: '50%',
376
- width: '40px',
377
- height: '40px',
378
- display: 'flex',
379
- alignItems: 'center',
380
- justifyContent: 'center',
381
- cursor: 'pointer',
382
- padding: 0
383
- }}
384
- aria-label="Previous"
385
- >
386
- <img
387
- src={leftArrowIcon.url}
388
- alt={leftArrowIcon.alt}
389
- style={{
390
- width: `${leftArrowIconSize}px`,
391
- height: `${leftArrowIconSize}px`,
392
- opacity: leftArrowIconOpacity,
393
- pointerEvents: 'none'
394
- }}
395
- />
396
- </button>
397
- );
398
-
399
- const CustomRightArrow = ({ onClick }: any) => (
400
- <button
401
- onClick={(e) => {
402
- e.stopPropagation();
403
- if (!isDesigner) {
404
- onClick?.();
405
- }
406
- handleRightArrowClick(e);
407
- }}
408
- className={clsx(
409
- 'absolute right-4 top-1/2 -translate-y-1/2 z-10',
410
- isDesigner &&
411
- selectedBlockId === rightArrowIconBlock?.id &&
412
- 'ring-2 ring-blue-500 ring-offset-2'
413
- )}
414
- style={{
415
- backgroundColor: rightArrowBackground,
416
- border: 'none',
417
- borderRadius: '50%',
418
- width: '40px',
419
- height: '40px',
420
- display: 'flex',
421
- alignItems: 'center',
422
- justifyContent: 'center',
423
- cursor: 'pointer',
424
- padding: 0
425
- }}
426
- aria-label="Next"
427
- >
428
- <img
429
- src={rightArrowIcon.url}
430
- alt={rightArrowIcon.alt}
431
- style={{
432
- width: `${rightArrowIconSize}px`,
433
- height: `${rightArrowIconSize}px`,
434
- opacity: rightArrowIconOpacity,
435
- pointerEvents: 'none'
436
- }}
437
- />
438
- </button>
439
- );
440
-
441
- if (visibleCount === 0) {
442
- return (
443
- <div
444
- className="p-4 text-gray-400 border border-dashed border-gray-300 rounded"
445
- style={sectionStyles}
446
- >
447
- No items available
448
- </div>
449
- );
450
- }
451
-
452
- return (
453
- <div
454
- className={twMerge(
455
- clsx('relative z-10 w-full', hasMaxWidth && 'mx-auto', maxWidthClass)
456
- )}
457
- style={sectionStyles}
458
- >
459
- <div style={{ position: 'relative' }}>
460
- <Carousel
461
- ref={carouselRef}
462
- key={`${desktopItems}-${mobileItems}-${desktopSlides}-${mobileSlides}-${visibleCount}-${useInfinite}-${useAutoPlay}-${autoPlaySpeed}`}
463
- responsive={carouselResponsive}
464
- slidesToSlide={currentSlidesToSlide}
465
- swipeable={true}
466
- draggable={true}
467
- arrows={isDesigner ? false : Boolean(showArrows)}
468
- showDots={Boolean(showDots)}
469
- renderDotsOutside={Boolean(showDots)}
470
- infinite={Boolean(useInfinite)}
471
- rewind={Boolean(useInfinite)}
472
- rewindWithAnimation={Boolean(useInfinite)}
473
- autoPlay={Boolean(useAutoPlay)}
474
- autoPlaySpeed={autoPlaySpeed}
475
- containerClass="slider-carousel"
476
- dotListClass="slider-dots"
477
- customLeftArrow={
478
- !isDesigner && showArrows ? <CustomLeftArrow /> : undefined
479
- }
480
- customRightArrow={
481
- !isDesigner && showArrows ? <CustomRightArrow /> : undefined
482
- }
483
- >
484
- {sortedBlocks.map((childBlock) => (
485
- <div
486
- key={childBlock.id}
487
- style={{
488
- paddingLeft: slidePadding,
489
- paddingRight: slidePadding
490
- }}
491
- >
492
- <ThemeBlock
493
- block={childBlock}
494
- placeholderId={placeholderId}
495
- sectionId={sectionId}
496
- isDesigner={isDesigner}
497
- isSelected={selectedBlockId === childBlock.id}
498
- selectedBlockId={selectedBlockId}
499
- currentBreakpoint={currentBreakpoint}
500
- onMoveUp={() => blockAction('MOVE_BLOCK_UP', childBlock.id)}
501
- onMoveDown={() => blockAction('MOVE_BLOCK_DOWN', childBlock.id)}
502
- onDuplicate={() =>
503
- blockAction('DUPLICATE_BLOCK', childBlock.id)
504
- }
505
- onToggleVisibility={() =>
506
- blockAction('TOGGLE_BLOCK_VISIBILITY', childBlock.id)
507
- }
508
- onDelete={() => blockAction('DELETE_BLOCK', childBlock.id)}
509
- onRename={(newLabel: string) =>
510
- blockAction('RENAME_BLOCK', childBlock.id, newLabel)
511
- }
512
- />
513
- </div>
514
- ))}
515
- </Carousel>
516
-
517
- {isDesigner &&
518
- showArrows &&
519
- sortedBlocks.length > 0 &&
520
- leftArrowIconBlock &&
521
- rightArrowIconBlock ? (
522
- <>
523
- <button
524
- onClick={handleLeftArrowClick}
525
- className={clsx(
526
- 'absolute left-4 top-1/2 -translate-y-1/2 z-10',
527
- selectedBlockId === leftArrowIconBlock?.id &&
528
- 'ring-2 ring-blue-500 ring-offset-2'
529
- )}
530
- style={{
531
- backgroundColor: leftArrowBackground,
532
- border: 'none',
533
- borderRadius: '50%',
534
- width: '40px',
535
- height: '40px',
536
- display: 'flex',
537
- alignItems: 'center',
538
- justifyContent: 'center',
539
- cursor: 'pointer',
540
- padding: 0
541
- }}
542
- aria-label="Previous"
543
- >
544
- <img
545
- src={leftArrowIcon.url}
546
- alt={leftArrowIcon.alt}
547
- style={{
548
- width: `${leftArrowIconSize}px`,
549
- height: `${leftArrowIconSize}px`,
550
- opacity: leftArrowIconOpacity,
551
- pointerEvents: 'none'
552
- }}
553
- />
554
- </button>
555
-
556
- <button
557
- onClick={handleRightArrowClick}
558
- className={clsx(
559
- 'absolute right-4 top-1/2 -translate-y-1/2 z-10',
560
- selectedBlockId === rightArrowIconBlock?.id &&
561
- 'ring-2 ring-blue-500 ring-offset-2'
562
- )}
563
- style={{
564
- backgroundColor: rightArrowBackground,
565
- border: 'none',
566
- borderRadius: '50%',
567
- width: '40px',
568
- height: '40px',
569
- display: 'flex',
570
- alignItems: 'center',
571
- justifyContent: 'center',
572
- cursor: 'pointer',
573
- padding: 0
574
- }}
575
- aria-label="Next"
576
- >
577
- <img
578
- src={rightArrowIcon.url}
579
- alt={rightArrowIcon.alt}
580
- style={{
581
- width: `${rightArrowIconSize}px`,
582
- height: `${rightArrowIconSize}px`,
583
- opacity: rightArrowIconOpacity,
584
- pointerEvents: 'none'
585
- }}
586
- />
587
- </button>
588
- </>
589
- ) : null}
590
- </div>
591
- </div>
592
- );
593
- };
594
-
595
- export default SliderBlock;
@@ -1,10 +0,0 @@
1
- import React from 'react';
2
- import { BlockRendererProps } from './block-renderer-registry';
3
-
4
- // Tab blocks are rendered by TabsSection, not individually
5
- // This component should not be used directly
6
- const TabBlock: React.FC<BlockRendererProps> = () => {
7
- return null;
8
- };
9
-
10
- export default TabBlock;
@@ -1,52 +0,0 @@
1
- 'use client';
2
-
3
- import { useLocalization } from '@akinon/next/hooks';
4
- import React, { useMemo } from 'react';
5
- import { BlockRendererProps } from './block-renderer-registry';
6
- import { getCSSStyles } from '../utils';
7
- import { useThemeSettingsContext } from '../theme-settings-context';
8
-
9
- const TextBlock = ({
10
- block,
11
- currentBreakpoint = 'desktop'
12
- }: BlockRendererProps) => {
13
- const { locale } = useLocalization();
14
- const defaultLocale = process.env.NEXT_PUBLIC_DEFAULT_LOCALE || 'en';
15
- const themeSettings = useThemeSettingsContext();
16
-
17
- const getLocalizedContent = (): string => {
18
- let value = block.value;
19
-
20
- if (typeof value === 'string' && value.startsWith('{')) {
21
- try {
22
- value = JSON.parse(value);
23
- } catch (e) {
24
- return value;
25
- }
26
- }
27
-
28
- if (typeof value === 'object' && value !== null) {
29
- return (
30
- value[locale] || value[defaultLocale] || Object.values(value)[0] || ''
31
- );
32
- }
33
-
34
- if (typeof value === 'string') {
35
- return value;
36
- }
37
-
38
- return '';
39
- };
40
-
41
- const content = getLocalizedContent();
42
-
43
- const wrapperStyles = useMemo(() => {
44
- return getCSSStyles(block.styles || {}, themeSettings, currentBreakpoint);
45
- }, [block.styles, themeSettings, currentBreakpoint]);
46
-
47
- return (
48
- <div style={wrapperStyles} dangerouslySetInnerHTML={{ __html: content }} />
49
- );
50
- };
51
-
52
- export default TextBlock;