@akinon/next 2.0.0-beta.19 → 2.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +20 -13
  2. package/assets/styles/index.scss +84 -0
  3. package/components/client-root.tsx +107 -1
  4. package/components/link.tsx +46 -16
  5. package/components/theme-editor/blocks/accordion-block.tsx +136 -0
  6. package/components/theme-editor/blocks/block-renderer-registry.tsx +77 -0
  7. package/components/theme-editor/blocks/button-block.tsx +593 -0
  8. package/components/theme-editor/blocks/counter-block.tsx +348 -0
  9. package/components/theme-editor/blocks/divider-block.tsx +20 -0
  10. package/components/theme-editor/blocks/embed-block.tsx +208 -0
  11. package/components/theme-editor/blocks/group-block.tsx +116 -0
  12. package/components/theme-editor/blocks/hotspot-block.tsx +147 -0
  13. package/components/theme-editor/blocks/icon-block.tsx +230 -0
  14. package/components/theme-editor/blocks/image-block.tsx +137 -0
  15. package/components/theme-editor/blocks/image-gallery-block.tsx +269 -0
  16. package/components/theme-editor/blocks/input-block.tsx +123 -0
  17. package/components/theme-editor/blocks/link-block.tsx +216 -0
  18. package/components/theme-editor/blocks/lottie-block.tsx +325 -0
  19. package/components/theme-editor/blocks/map-block.tsx +89 -0
  20. package/components/theme-editor/blocks/slider-block.tsx +595 -0
  21. package/components/theme-editor/blocks/tab-block.tsx +10 -0
  22. package/components/theme-editor/blocks/text-block.tsx +52 -0
  23. package/components/theme-editor/blocks/video-block.tsx +122 -0
  24. package/components/theme-editor/components/action-toolbar.tsx +305 -0
  25. package/components/theme-editor/components/designer-overlay.tsx +74 -0
  26. package/components/theme-editor/components/with-designer-features.tsx +142 -0
  27. package/components/theme-editor/dynamic-font-loader.tsx +79 -0
  28. package/components/theme-editor/hooks/use-designer-features.tsx +100 -0
  29. package/components/theme-editor/hooks/use-external-designer.tsx +95 -0
  30. package/components/theme-editor/hooks/use-native-widget-data.ts +188 -0
  31. package/components/theme-editor/hooks/use-visibility-context.ts +27 -0
  32. package/components/theme-editor/placeholder-registry.ts +31 -0
  33. package/components/theme-editor/sections/before-after-section.tsx +245 -0
  34. package/components/theme-editor/sections/contact-form-section.tsx +563 -0
  35. package/components/theme-editor/sections/countdown-campaign-banner-section.tsx +433 -0
  36. package/components/theme-editor/sections/coupon-banner-section.tsx +710 -0
  37. package/components/theme-editor/sections/divider-section.tsx +62 -0
  38. package/components/theme-editor/sections/featured-product-spotlight-section.tsx +507 -0
  39. package/components/theme-editor/sections/find-in-store-section.tsx +1995 -0
  40. package/components/theme-editor/sections/hover-showcase-section.tsx +326 -0
  41. package/components/theme-editor/sections/image-hotspot-section.tsx +142 -0
  42. package/components/theme-editor/sections/installment-options-section.tsx +1065 -0
  43. package/components/theme-editor/sections/notification-banner-section.tsx +173 -0
  44. package/components/theme-editor/sections/order-tracking-lookup-section.tsx +1379 -0
  45. package/components/theme-editor/sections/posts-slider-section.tsx +472 -0
  46. package/components/theme-editor/sections/pre-order-launch-banner-section.tsx +663 -0
  47. package/components/theme-editor/sections/section-renderer-registry.tsx +89 -0
  48. package/components/theme-editor/sections/section-wrapper.tsx +135 -0
  49. package/components/theme-editor/sections/shipping-threshold-progress-section.tsx +586 -0
  50. package/components/theme-editor/sections/stats-counter-section.tsx +486 -0
  51. package/components/theme-editor/sections/tabs-section.tsx +578 -0
  52. package/components/theme-editor/theme-block.tsx +102 -0
  53. package/components/theme-editor/theme-placeholder-client.tsx +218 -0
  54. package/components/theme-editor/theme-placeholder-wrapper.tsx +732 -0
  55. package/components/theme-editor/theme-placeholder.tsx +288 -0
  56. package/components/theme-editor/theme-section.tsx +1224 -0
  57. package/components/theme-editor/theme-settings-context.tsx +13 -0
  58. package/components/theme-editor/utils/index.ts +792 -0
  59. package/components/theme-editor/utils/iterator-utils.ts +234 -0
  60. package/components/theme-editor/utils/publish-window.ts +86 -0
  61. package/components/theme-editor/utils/visibility-rules.ts +188 -0
  62. package/data/client/misc.ts +13 -1
  63. package/data/server/widget.ts +68 -1
  64. package/data/urls.ts +3 -1
  65. package/hooks/use-router.ts +53 -19
  66. package/lib/cache.ts +1 -0
  67. package/package.json +4 -2
  68. package/redux/reducers/index.ts +2 -0
  69. package/redux/reducers/widget.ts +80 -0
  70. package/types/commerce/widget.ts +33 -0
  71. package/types/widget.ts +80 -0
  72. package/utils/widget-styles.ts +107 -0
  73. package/with-pz-config.js +1 -1
@@ -0,0 +1,593 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect, useState } from 'react';
4
+ import { useLocalization } from '@akinon/next/hooks';
5
+ import { useEmailSubscriptionMutation } from '../../../data/client/misc';
6
+ import { Modal } from '../../modal';
7
+ import {
8
+ colorToRgba,
9
+ getResponsiveValue,
10
+ resolveThemeCssVariables
11
+ } from '../utils';
12
+ import { BlockRendererProps } from './block-renderer-registry';
13
+ import { useThemeSettingsContext } from '../theme-settings-context';
14
+
15
+ const ButtonBlock = ({
16
+ block,
17
+ currentBreakpoint = 'desktop',
18
+ isDesigner = false
19
+ }: BlockRendererProps) => {
20
+ const themeSettings = useThemeSettingsContext();
21
+ const { locale } = useLocalization();
22
+ const [emailSubscription] = useEmailSubscriptionMutation();
23
+ const [feedbackModal, setFeedbackModal] = useState({
24
+ open: false,
25
+ title: '',
26
+ message: ''
27
+ });
28
+ const defaultLocale = process.env.NEXT_PUBLIC_DEFAULT_LOCALE || 'en';
29
+
30
+ const getStyleEntry = (kebabKey: string) => {
31
+ if (!block.styles) return undefined;
32
+ const styles = block.styles as Record<string, unknown>;
33
+ const camelKey = kebabKey.replace(/-([a-z])/g, (_, letter) =>
34
+ letter.toUpperCase()
35
+ );
36
+ return styles[kebabKey] ?? styles[camelKey];
37
+ };
38
+
39
+ const normalizeKey = (key: string): string => {
40
+ if (key.includes('-')) return key;
41
+ return key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
42
+ };
43
+
44
+ const getLocalizedText = (textValue: unknown): string => {
45
+ let value = textValue;
46
+
47
+ if (typeof value === 'string' && value.startsWith('{')) {
48
+ try {
49
+ value = JSON.parse(value);
50
+ } catch (e) {
51
+ return String(value);
52
+ }
53
+ }
54
+
55
+ if (typeof value === 'object' && value !== null) {
56
+ const localized = value as Record<string, unknown>;
57
+ if (localized[locale]) {
58
+ return String(localized[locale]);
59
+ }
60
+ if (localized[defaultLocale]) {
61
+ return String(localized[defaultLocale]);
62
+ }
63
+
64
+ const responsiveValue = getResponsiveValue(
65
+ value,
66
+ currentBreakpoint,
67
+ 'Click Me'
68
+ );
69
+ if (responsiveValue && responsiveValue !== 'Click Me') {
70
+ return String(responsiveValue);
71
+ }
72
+
73
+ const firstValue = Object.values(localized)[0];
74
+ return typeof firstValue === 'string' ? firstValue : 'Click Me';
75
+ }
76
+
77
+ if (typeof value === 'string') {
78
+ return value;
79
+ }
80
+
81
+ return 'Click Me';
82
+ };
83
+
84
+ const textValue = block.properties?.text || block.value;
85
+ const text = getLocalizedText(textValue);
86
+
87
+ const url = block.properties?.url
88
+ ? String(getResponsiveValue(block.properties.url, currentBreakpoint, '#'))
89
+ : '#';
90
+
91
+ const target = block.properties?.target
92
+ ? String(
93
+ getResponsiveValue(block.properties.target, currentBreakpoint, '_self')
94
+ )
95
+ : '_self';
96
+
97
+ const tag = block.properties?.tag
98
+ ? String(getResponsiveValue(block.properties.tag, currentBreakpoint, 'a'))
99
+ : 'a';
100
+
101
+ const normalizeButtonType = (
102
+ value: string
103
+ ): 'button' | 'submit' | 'reset' => {
104
+ if (value === 'submit' || value === 'reset') {
105
+ return value;
106
+ }
107
+
108
+ return 'button';
109
+ };
110
+
111
+ const buttonType =
112
+ tag === 'button'
113
+ ? normalizeButtonType(
114
+ String(
115
+ getResponsiveValue(
116
+ block.properties?.type,
117
+ currentBreakpoint,
118
+ 'button'
119
+ )
120
+ )
121
+ )
122
+ : 'button';
123
+
124
+ const iconUrl = block.properties?.icon
125
+ ? String(getResponsiveValue(block.properties.icon, currentBreakpoint, ''))
126
+ : '';
127
+
128
+ const iconPosition = block.properties?.iconPosition
129
+ ? String(
130
+ getResponsiveValue(
131
+ block.properties.iconPosition,
132
+ currentBreakpoint,
133
+ 'left'
134
+ )
135
+ )
136
+ : 'left';
137
+
138
+ const iconSize = block.properties?.iconSize
139
+ ? String(
140
+ getResponsiveValue(block.properties.iconSize, currentBreakpoint, '16')
141
+ )
142
+ : '16';
143
+
144
+ const iconGap = block.properties?.iconGap
145
+ ? String(
146
+ getResponsiveValue(block.properties.iconGap, currentBreakpoint, '8px')
147
+ )
148
+ : '8px';
149
+
150
+ const showIcon = iconUrl && iconPosition !== 'none';
151
+
152
+ const fontFamily = block.styles?.['font-family']
153
+ ? String(
154
+ getResponsiveValue(
155
+ block.styles['font-family'],
156
+ currentBreakpoint,
157
+ 'inherit'
158
+ )
159
+ )
160
+ : 'inherit';
161
+
162
+ useEffect(() => {
163
+ if (!fontFamily || fontFamily === 'inherit') return;
164
+
165
+ const cleanFontFamily = fontFamily.replace(/['"]/g, '').trim();
166
+
167
+ const systemFonts = [
168
+ 'Arial',
169
+ 'Helvetica',
170
+ 'Times New Roman',
171
+ 'Georgia',
172
+ 'Courier New',
173
+ 'Verdana'
174
+ ];
175
+ if (systemFonts.includes(cleanFontFamily)) return;
176
+
177
+ const fontParam = `${cleanFontFamily.replace(
178
+ /\s+/g,
179
+ '+'
180
+ )}:wght@100;300;400;500;600;700;800;900`;
181
+ const fontUrl = `https://fonts.googleapis.com/css2?family=${fontParam}&display=swap`;
182
+
183
+ const linkId = `google-font-${cleanFontFamily
184
+ .replace(/\s+/g, '-')
185
+ .toLowerCase()}`;
186
+ const existingLink = document.getElementById(linkId);
187
+ if (existingLink) return;
188
+
189
+ const link = document.createElement('link');
190
+ link.href = fontUrl;
191
+ link.rel = 'stylesheet';
192
+ link.id = linkId;
193
+
194
+ document.head.appendChild(link);
195
+ }, [fontFamily]);
196
+
197
+ const toAlpha = (value: unknown, fallback = 100): number | null => {
198
+ const numeric = Number(
199
+ value === undefined || value === null || value === '' ? fallback : value
200
+ );
201
+ if (Number.isNaN(numeric)) return null;
202
+ return Math.max(0, Math.min(1, numeric / 100));
203
+ };
204
+
205
+ const getButtonStyles = (): React.CSSProperties => {
206
+ if (!block.styles) return {};
207
+
208
+ const styles: React.CSSProperties = {};
209
+
210
+ const textOpacityValue = getStyleEntry('text-opacity')
211
+ ? getResponsiveValue(
212
+ getStyleEntry('text-opacity'),
213
+ currentBreakpoint,
214
+ '100'
215
+ )
216
+ : '100';
217
+ const textOpacity = Number(textOpacityValue);
218
+
219
+ const backgroundOpacityValue = getStyleEntry('background-opacity')
220
+ ? getResponsiveValue(
221
+ getStyleEntry('background-opacity'),
222
+ currentBreakpoint,
223
+ '100'
224
+ )
225
+ : '100';
226
+ const backgroundAlpha = toAlpha(backgroundOpacityValue, 100);
227
+
228
+ Object.keys(block.styles).forEach((key) => {
229
+ const value = getResponsiveValue(block.styles[key], currentBreakpoint);
230
+ if (value === undefined || value === null) return;
231
+
232
+ const normalizedKey = normalizeKey(key);
233
+
234
+ if (
235
+ normalizedKey === 'text-opacity' ||
236
+ normalizedKey === 'background-opacity' ||
237
+ normalizedKey === 'hover-opacity' ||
238
+ normalizedKey === 'hover-color'
239
+ ) {
240
+ return;
241
+ }
242
+
243
+ const camelKey = key.replace(/-([a-z])/g, (_, letter) =>
244
+ letter.toUpperCase()
245
+ );
246
+
247
+ let resolvedValue = value;
248
+ if (typeof value === 'string') {
249
+ resolvedValue = resolveThemeCssVariables(value, themeSettings);
250
+ }
251
+
252
+ if (normalizedKey === 'width' && resolvedValue === 'fill') {
253
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
254
+ (styles as any)[camelKey] = '100%';
255
+ return;
256
+ }
257
+
258
+ if (normalizedKey === 'width' && resolvedValue === 'fit') {
259
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
260
+ (styles as any)[camelKey] = 'fit-content';
261
+ return;
262
+ }
263
+
264
+ if (normalizedKey === 'height' && resolvedValue === 'fill') {
265
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
266
+ (styles as any)[camelKey] = '100%';
267
+ return;
268
+ }
269
+
270
+ if (normalizedKey === 'height' && resolvedValue === 'fit') {
271
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
272
+ (styles as any)[camelKey] = 'fit-content';
273
+ return;
274
+ }
275
+
276
+ if (
277
+ normalizedKey === 'color' &&
278
+ typeof resolvedValue === 'string' &&
279
+ !isNaN(textOpacity)
280
+ ) {
281
+ const alpha = toAlpha(textOpacityValue, 100);
282
+ if (alpha !== null) {
283
+ const rgba = colorToRgba(resolvedValue, alpha);
284
+ if (rgba) {
285
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
286
+ (styles as any)[camelKey] = rgba;
287
+ return;
288
+ }
289
+ }
290
+
291
+ // Fallback if not a valid hex (e.g. named color or rgb)
292
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
293
+ (styles as any)[camelKey] = resolvedValue;
294
+ } else if (
295
+ (normalizedKey === 'background-color' ||
296
+ normalizedKey === 'background') &&
297
+ typeof resolvedValue === 'string' &&
298
+ backgroundAlpha !== null
299
+ ) {
300
+ if (backgroundAlpha === 0) {
301
+ // Ensure background disappears even if color can't be parsed (e.g. unresolved CSS var).
302
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
303
+ (styles as any).backgroundColor = 'transparent';
304
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
305
+ (styles as any).backgroundImage = 'none';
306
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
307
+ delete (styles as any).background;
308
+ return;
309
+ }
310
+
311
+ const rgba = colorToRgba(resolvedValue, backgroundAlpha);
312
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
313
+ (styles as any).backgroundColor = rgba || resolvedValue;
314
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
315
+ delete (styles as any).background;
316
+ } else {
317
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
318
+ (styles as any)[camelKey] = resolvedValue;
319
+ }
320
+ });
321
+
322
+ return styles;
323
+ };
324
+
325
+ const buttonStyles = getButtonStyles();
326
+
327
+ const finalStyles: React.CSSProperties = {
328
+ textDecoration: 'none',
329
+ display: showIcon ? 'inline-flex' : buttonStyles.display || 'inline-block',
330
+ alignItems: showIcon ? 'center' : undefined,
331
+ justifyContent: showIcon ? 'center' : undefined,
332
+ gap: showIcon ? iconGap : undefined,
333
+ ...buttonStyles
334
+ };
335
+
336
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
337
+
338
+ const extractMessage = (payload: unknown, fallback: string): string => {
339
+ if (typeof payload === 'string' && payload.trim()) {
340
+ return payload;
341
+ }
342
+
343
+ if (!payload || typeof payload !== 'object') {
344
+ return fallback;
345
+ }
346
+
347
+ const payloadObject = payload as Record<string, unknown>;
348
+
349
+ const directKeys = ['message', 'detail', 'non_field_errors', 'error'];
350
+ for (const key of directKeys) {
351
+ const value = payloadObject[key];
352
+ if (typeof value === 'string' && value.trim()) {
353
+ return value;
354
+ }
355
+ if (Array.isArray(value) && typeof value[0] === 'string') {
356
+ return value[0];
357
+ }
358
+ }
359
+
360
+ const emailValue = payloadObject.email;
361
+ if (Array.isArray(emailValue) && typeof emailValue[0] === 'string') {
362
+ return emailValue[0];
363
+ }
364
+
365
+ const dataValue = payloadObject.data;
366
+ if (dataValue && typeof dataValue === 'object') {
367
+ return extractMessage(dataValue, fallback);
368
+ }
369
+
370
+ return fallback;
371
+ };
372
+
373
+ const showPopup = (message: string, success = false) => {
374
+ if (!message?.trim()) return;
375
+
376
+ setFeedbackModal({
377
+ open: true,
378
+ title: success ? 'Başarılı' : 'Bilgilendirme',
379
+ message
380
+ });
381
+ };
382
+
383
+ const handleClick = async (e: React.MouseEvent<HTMLElement>) => {
384
+ if (isDesigner) {
385
+ e.preventDefault();
386
+ return;
387
+ }
388
+
389
+ if (tag === 'button' && buttonType === 'submit') {
390
+ e.preventDefault();
391
+ }
392
+
393
+ if (tag !== 'button' || buttonType !== 'submit') {
394
+ return;
395
+ }
396
+
397
+ const currentTarget = e.currentTarget as HTMLElement;
398
+ const newsletterContainer = currentTarget.closest(
399
+ '[data-newsletter-signup="true"]'
400
+ ) as HTMLElement | null;
401
+
402
+ if (!newsletterContainer) {
403
+ return;
404
+ }
405
+
406
+ const rawEndpoint =
407
+ newsletterContainer.getAttribute('data-newsletter-endpoint')?.trim() || '';
408
+ if (!rawEndpoint) {
409
+ return;
410
+ }
411
+
412
+ const endpoint =
413
+ rawEndpoint.startsWith('/api/client') ||
414
+ rawEndpoint.startsWith('http://') ||
415
+ rawEndpoint.startsWith('https://')
416
+ ? rawEndpoint
417
+ : rawEndpoint.startsWith('/')
418
+ ? `/api/client${rawEndpoint}`
419
+ : `/api/client/${rawEndpoint}`;
420
+
421
+ const isEmailSubscriptionEndpoint =
422
+ rawEndpoint.includes('email-subscription') ||
423
+ endpoint.includes('/api/client/email-subscription');
424
+
425
+ const inputElement = newsletterContainer.querySelector(
426
+ 'input[type="email"], input[name="email"], input'
427
+ ) as HTMLInputElement | null;
428
+
429
+ const email = inputElement?.value?.trim() || '';
430
+ if (!email) {
431
+ inputElement?.focus();
432
+ showPopup('Email is required.');
433
+ return;
434
+ }
435
+
436
+ if (!EMAIL_REGEX.test(email)) {
437
+ inputElement?.focus();
438
+ showPopup('Enter a valid email address.');
439
+ return;
440
+ }
441
+
442
+ const successMessage =
443
+ newsletterContainer.getAttribute('data-newsletter-success-message') ||
444
+ 'Successfully subscribed.';
445
+ const errorMessage =
446
+ newsletterContainer.getAttribute('data-newsletter-error-message') ||
447
+ 'Subscription failed. Please try again.';
448
+
449
+ try {
450
+ let popupMessage = successMessage;
451
+
452
+ if (isEmailSubscriptionEndpoint) {
453
+ const result = await emailSubscription({
454
+ email,
455
+ subscribe_contract: false
456
+ }).unwrap();
457
+ popupMessage = extractMessage(result, successMessage);
458
+ } else {
459
+ const response = await fetch(endpoint, {
460
+ method: 'POST',
461
+ headers: {
462
+ Accept: '*/*',
463
+ 'Content-Type': 'application/json'
464
+ },
465
+ credentials: 'include',
466
+ body: JSON.stringify({ email })
467
+ });
468
+
469
+ let responsePayload: unknown = null;
470
+
471
+ try {
472
+ const contentType = response.headers.get('content-type') || '';
473
+ if (contentType.includes('application/json')) {
474
+ responsePayload = await response.json();
475
+ } else {
476
+ const textPayload = await response.text();
477
+ responsePayload = textPayload || null;
478
+ }
479
+ } catch {
480
+ responsePayload = null;
481
+ }
482
+
483
+ if (!response.ok) {
484
+ throw new Error(extractMessage(responsePayload, errorMessage));
485
+ }
486
+
487
+ popupMessage = extractMessage(responsePayload, successMessage);
488
+ }
489
+
490
+ newsletterContainer.dispatchEvent(
491
+ new CustomEvent('newsletter:submit', {
492
+ bubbles: true,
493
+ detail: {
494
+ success: true,
495
+ message: popupMessage,
496
+ email
497
+ }
498
+ })
499
+ );
500
+ showPopup(popupMessage, true);
501
+ } catch (error) {
502
+ const message =
503
+ error instanceof Error && error.message
504
+ ? error.message
505
+ : extractMessage(error, errorMessage);
506
+
507
+ newsletterContainer.dispatchEvent(
508
+ new CustomEvent('newsletter:submit', {
509
+ bubbles: true,
510
+ detail: {
511
+ success: false,
512
+ message,
513
+ email
514
+ }
515
+ })
516
+ );
517
+ showPopup(message);
518
+ }
519
+ };
520
+
521
+ const shouldShowText = text && text.trim() !== '';
522
+
523
+ const iconElement = (position: 'left' | 'right') =>
524
+ showIcon &&
525
+ iconPosition === position && (
526
+ // eslint-disable-next-line @next/next/no-img-element
527
+ <img
528
+ src={iconUrl}
529
+ alt="icon"
530
+ style={{
531
+ width: `${iconSize}px`,
532
+ height: `${iconSize}px`,
533
+ flexShrink: 0
534
+ }}
535
+ />
536
+ );
537
+
538
+ const content = (
539
+ <>
540
+ {iconElement('left')}
541
+ {shouldShowText && <span>{text}</span>}
542
+ {iconElement('right')}
543
+ </>
544
+ );
545
+
546
+ const feedbackModalElement = (
547
+ <Modal
548
+ portalId={`newsletter-feedback-modal-${block.id}`}
549
+ open={feedbackModal.open}
550
+ setOpen={(open) =>
551
+ setFeedbackModal((prev) => ({
552
+ ...prev,
553
+ open
554
+ }))
555
+ }
556
+ title={feedbackModal.title}
557
+ showCloseButton={true}
558
+ className="w-[92vw] max-w-[420px] rounded"
559
+ >
560
+ <div className="p-6 text-sm leading-6 text-center text-black">
561
+ {feedbackModal.message}
562
+ </div>
563
+ </Modal>
564
+ );
565
+
566
+ if (tag === 'button') {
567
+ return (
568
+ <>
569
+ <button type={buttonType} style={finalStyles} onClick={handleClick}>
570
+ {content}
571
+ </button>
572
+ {feedbackModalElement}
573
+ </>
574
+ );
575
+ }
576
+
577
+ return (
578
+ <>
579
+ <a
580
+ href={url}
581
+ target={target}
582
+ rel={target === '_blank' ? 'noopener noreferrer' : undefined}
583
+ style={finalStyles}
584
+ onClick={handleClick}
585
+ >
586
+ {content}
587
+ </a>
588
+ {feedbackModalElement}
589
+ </>
590
+ );
591
+ };
592
+
593
+ export default ButtonBlock;