@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,563 +0,0 @@
1
- 'use client';
2
-
3
- import React, { useState } from 'react';
4
- import clsx from 'clsx';
5
- import { twMerge } from 'tailwind-merge';
6
- import { useForm, SubmitHandler } from 'react-hook-form';
7
- import { yupResolver } from '@hookform/resolvers/yup';
8
- import * as yup from 'yup';
9
-
10
- import ThemeBlock, { Block } from '../theme-block';
11
- import { useThemeSettingsContext } from '../theme-settings-context';
12
- import {
13
- getCSSStyles,
14
- getResponsiveValue,
15
- resolveThemeCssVariables
16
- } from '../utils';
17
- import { Section } from '../theme-section';
18
- import { Modal } from '../../modal';
19
- import {
20
- useGetContactSubjectsQuery,
21
- useSendContactMutation
22
- } from '../../../data/client/account';
23
- import { ContactFormType } from '../../../types';
24
-
25
- interface ContactFormSectionProps {
26
- section: Section;
27
- currentBreakpoint?: string;
28
- placeholderId?: string;
29
- isDesigner?: boolean;
30
- selectedBlockId?: string | null;
31
- }
32
-
33
- const contactFormSchema = yup.object().shape({
34
- full_name: yup.string().required('Bu alan zorunludur.'),
35
- email: yup
36
- .string()
37
- .email('Geçerli bir e-posta adresi girin.')
38
- .required('Bu alan zorunludur.'),
39
- phone: yup
40
- .string()
41
- .transform((value: string) => value.replace(/_/g, '').replace(/ /g, ''))
42
- .length(11, 'Telefon numarası 11 haneli olmalıdır.')
43
- .required('Bu alan zorunludur.'),
44
- subject: yup.string().required('Bu alan zorunludur.'),
45
- message: yup
46
- .string()
47
- .required('Bu alan zorunludur.')
48
- .min(10, 'Mesaj en az 10 karakter olmalıdır.')
49
- .label('message'),
50
- order: yup
51
- .string()
52
- .nullable()
53
- .notRequired()
54
- .when('subject', {
55
- is: (value: string) => value === '2',
56
- then: yup.string().required('Bu alan zorunludur.')
57
- }),
58
- file: yup.mixed()
59
- });
60
-
61
- const readProperty = (
62
- properties: Record<string, unknown> | undefined,
63
- key: string,
64
- fallback: string,
65
- breakpoint: string
66
- ): string => {
67
- if (!properties) return fallback;
68
- const raw = properties[key];
69
- if (raw === undefined || raw === null) return fallback;
70
- if (typeof raw === 'string') return raw;
71
- if (typeof raw === 'object') {
72
- const responsive = raw as Record<string, unknown>;
73
- const picked =
74
- responsive[breakpoint] ?? responsive.desktop ?? Object.values(responsive)[0];
75
- return picked == null ? fallback : String(picked);
76
- }
77
- return String(raw);
78
- };
79
-
80
- const ContactFormSection: React.FC<ContactFormSectionProps> = ({
81
- section,
82
- currentBreakpoint = 'desktop',
83
- placeholderId = '',
84
- isDesigner = false,
85
- selectedBlockId = null
86
- }) => {
87
- const themeSettings = useThemeSettingsContext();
88
-
89
- // RTK Query hooks
90
- const {
91
- data: contactSubjects,
92
- isLoading: subjectsLoading,
93
- isSuccess: subjectsSuccess
94
- } = useGetContactSubjectsQuery();
95
-
96
- const [
97
- sendContact,
98
- { isSuccess: formSuccess, isLoading: isSubmitting, error: submitError }
99
- ] = useSendContactMutation();
100
-
101
- // Build filtered subjects
102
- const filteredSubjects: { label: string; value: string }[] = [
103
- { label: readProperty(section.properties, 'subject-placeholder', 'Konu seçiniz', currentBreakpoint), value: '' }
104
- ];
105
-
106
- if (subjectsSuccess && contactSubjects) {
107
- contactSubjects.forEach((item) => {
108
- filteredSubjects.push({ label: item.text, value: String(item.id) });
109
- });
110
- }
111
-
112
- const [selectedSubject, setSelectedSubject] = useState<string | null>(null);
113
-
114
- // Feedback modal
115
- const [feedbackModal, setFeedbackModal] = useState({
116
- open: false,
117
- title: '',
118
- message: ''
119
- });
120
-
121
- // react-hook-form
122
- const {
123
- register,
124
- reset,
125
- handleSubmit,
126
- formState: { errors }
127
- } = useForm<ContactFormType>({
128
- resolver: yupResolver(contactFormSchema) as any
129
- });
130
-
131
- // Read section properties
132
- const props = section.properties || {};
133
- const fullNamePlaceholder = readProperty(props, 'full-name-placeholder', 'Adınız Soyadınız', currentBreakpoint);
134
- const emailPlaceholder = readProperty(props, 'email-placeholder', 'E-posta adresiniz', currentBreakpoint);
135
- const phonePlaceholder = readProperty(props, 'phone-placeholder', 'Telefon numaranız', currentBreakpoint);
136
- const messagePlaceholder = readProperty(props, 'message-placeholder', 'Mesajınızı yazın...', currentBreakpoint);
137
- const buttonText = readProperty(props, 'button-text', 'Gönder', currentBreakpoint);
138
- const successMessage = readProperty(props, 'success-message', 'Mesajınız başarıyla gönderildi!', currentBreakpoint);
139
- const errorMessageText = readProperty(props, 'error-message', 'Bir hata oluştu. Lütfen tekrar deneyin.', currentBreakpoint);
140
- const showFileUploadRaw = getResponsiveValue(props['show-file-upload'], currentBreakpoint, true);
141
- const showFileUpload =
142
- showFileUploadRaw === false || showFileUploadRaw === 'false' ? false : true;
143
- // Read section styles
144
- const maxWidth = getResponsiveValue(
145
- section.styles?.['max-width'],
146
- currentBreakpoint,
147
- 'normal'
148
- );
149
- const maxWidthClass =
150
- maxWidth === 'narrow'
151
- ? 'max-w-4xl'
152
- : maxWidth === 'normal'
153
- ? 'max-w-7xl'
154
- : '';
155
- const hasMaxWidth = maxWidth !== 'none' && maxWidth !== 'full';
156
-
157
- const formGap = getResponsiveValue(section.styles?.['form-gap'], currentBreakpoint, 16);
158
-
159
- const inputBg = resolveThemeCssVariables(
160
- String(getResponsiveValue(section.styles?.['input-background-color'], currentBreakpoint, '#ffffff')),
161
- themeSettings
162
- );
163
- const inputTextColor = resolveThemeCssVariables(
164
- String(getResponsiveValue(section.styles?.['input-text-color'], currentBreakpoint, '#0f172a')),
165
- themeSettings
166
- );
167
- const inputBorderColor = resolveThemeCssVariables(
168
- String(getResponsiveValue(section.styles?.['input-border-color'], currentBreakpoint, '#cbd5e1')),
169
- themeSettings
170
- );
171
- const inputBorderRadius = String(
172
- getResponsiveValue(section.styles?.['input-border-radius'], currentBreakpoint, '8px')
173
- );
174
- const inputPadding = String(
175
- getResponsiveValue(section.styles?.['input-padding'], currentBreakpoint, '14px 16px')
176
- );
177
- const buttonBg = resolveThemeCssVariables(
178
- String(getResponsiveValue(section.styles?.['button-background-color'], currentBreakpoint, '#0f172a')),
179
- themeSettings
180
- );
181
- const buttonTextColor = resolveThemeCssVariables(
182
- String(getResponsiveValue(section.styles?.['button-text-color'], currentBreakpoint, '#ffffff')),
183
- themeSettings
184
- );
185
- const buttonHoverBg = resolveThemeCssVariables(
186
- String(getResponsiveValue(section.styles?.['button-hover-background-color'], currentBreakpoint, '#1e293b')),
187
- themeSettings
188
- );
189
- const buttonBorderRadius = String(
190
- getResponsiveValue(section.styles?.['button-border-radius'], currentBreakpoint, '8px')
191
- );
192
- const buttonPadding = String(
193
- getResponsiveValue(section.styles?.['button-padding'], currentBreakpoint, '14px 32px')
194
- );
195
-
196
- const filteredStyles = Object.fromEntries(
197
- Object.entries(section.styles || {}).filter(
198
- ([key]) =>
199
- ![
200
- 'max-width',
201
- 'form-gap',
202
- 'input-background-color',
203
- 'input-text-color',
204
- 'input-border-color',
205
- 'input-border-radius',
206
- 'input-padding',
207
- 'button-background-color',
208
- 'button-text-color',
209
- 'button-hover-background-color',
210
- 'button-border-radius',
211
- 'button-padding'
212
- ].includes(key)
213
- )
214
- );
215
-
216
- const sectionStyles = getCSSStyles(filteredStyles, themeSettings, currentBreakpoint);
217
-
218
- const inputStyle: React.CSSProperties = {
219
- backgroundColor: inputBg,
220
- color: inputTextColor,
221
- border: `1px solid ${inputBorderColor}`,
222
- borderRadius: inputBorderRadius,
223
- padding: inputPadding,
224
- fontSize: '15px',
225
- outline: 'none',
226
- width: '100%',
227
- fontFamily: 'inherit'
228
- };
229
-
230
- const buttonStyle: React.CSSProperties = {
231
- backgroundColor: buttonBg,
232
- color: buttonTextColor,
233
- border: 'none',
234
- borderRadius: buttonBorderRadius,
235
- padding: buttonPadding,
236
- fontSize: '15px',
237
- fontWeight: 600,
238
- cursor: 'pointer',
239
- transition: 'all 0.2s ease',
240
- fontFamily: 'inherit'
241
- };
242
-
243
- // Blocks rendering
244
- const sortedBlocks = [...(section.blocks || [])]
245
- .sort((a, b) => (a.order || 0) - (b.order || 0))
246
- .filter((block) => (isDesigner ? true : !block.hidden));
247
-
248
- const contentGroup = sortedBlocks.find(
249
- (block) => block.type === 'group' && block.label === 'Content Container'
250
- );
251
-
252
- const textBlocks: Block[] = [];
253
- if (contentGroup?.blocks) {
254
- contentGroup.blocks
255
- .filter((b) => b.type === 'text')
256
- .forEach((b) => textBlocks.push(b));
257
- }
258
-
259
- const postBlockAction = (type: string, blockId: string, label?: string) => {
260
- if (!window.parent) return;
261
- window.parent.postMessage(
262
- {
263
- type,
264
- data: {
265
- placeholderId,
266
- sectionId: section.id,
267
- blockId,
268
- ...(label ? { label } : {})
269
- }
270
- },
271
- '*'
272
- );
273
- };
274
-
275
- const renderBlock = (block: Block) => (
276
- <ThemeBlock
277
- key={block.id}
278
- block={block}
279
- placeholderId={placeholderId}
280
- sectionId={section.id}
281
- isDesigner={isDesigner}
282
- isSelected={selectedBlockId === block.id}
283
- selectedBlockId={selectedBlockId}
284
- currentBreakpoint={currentBreakpoint}
285
- onMoveUp={() => postBlockAction('MOVE_BLOCK_UP', block.id)}
286
- onMoveDown={() => postBlockAction('MOVE_BLOCK_DOWN', block.id)}
287
- onDuplicate={() => postBlockAction('DUPLICATE_BLOCK', block.id)}
288
- onToggleVisibility={() => postBlockAction('TOGGLE_BLOCK_VISIBILITY', block.id)}
289
- onDelete={() => postBlockAction('DELETE_BLOCK', block.id)}
290
- onRename={(newLabel) => postBlockAction('RENAME_BLOCK', block.id, newLabel)}
291
- />
292
- );
293
-
294
- // Form submit handler — uses useSendContactMutation
295
- const onSubmit: SubmitHandler<ContactFormType> = (data) => {
296
- if (isDesigner) return;
297
-
298
- const formData = new FormData();
299
-
300
- Object.keys(data ?? {}).forEach((key) => {
301
- if (key === 'file' && data[key]) {
302
- formData.append(key, (data[key] as FileList)[0]);
303
- } else if (data[key] && key !== 'file') {
304
- formData.append(key, String(data[key]));
305
- }
306
- });
307
-
308
- sendContact(formData)
309
- .unwrap()
310
- .then(() => {
311
- setFeedbackModal({
312
- open: true,
313
- title: 'Başarılı',
314
- message: successMessage
315
- });
316
- reset();
317
- setSelectedSubject(null);
318
- })
319
- .catch((err) => {
320
- let msg = errorMessageText;
321
- if (err?.data && typeof err.data === 'object') {
322
- const entries = Object.entries(err.data)
323
- .map(([, msgs]) =>
324
- Array.isArray(msgs) ? msgs.join(', ') : String(msgs)
325
- );
326
- if (entries.length > 0) msg = entries.join(' ');
327
- }
328
- setFeedbackModal({ open: true, title: 'Hata', message: msg });
329
- });
330
- };
331
-
332
- const handleSubjectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
333
- setSelectedSubject(e.target.value);
334
- };
335
-
336
- const isMobile = currentBreakpoint === 'mobile';
337
-
338
- const errorStyle: React.CSSProperties = {
339
- color: '#ef4444',
340
- fontSize: '12px',
341
- marginTop: '4px'
342
- };
343
-
344
-
345
- return (
346
- <>
347
- <div
348
- className={twMerge(
349
- clsx(
350
- 'contact-form-section relative z-10 w-full',
351
- hasMaxWidth && 'mx-auto',
352
- maxWidthClass
353
- )
354
- )}
355
- style={sectionStyles}
356
- data-contact-form="true"
357
- >
358
- <div
359
- style={{
360
- display: 'flex',
361
- flexDirection: 'column',
362
- gap: '20px',
363
- maxWidth: '600px',
364
- width: '100%'
365
- }}
366
- >
367
- {/* Render heading and description text blocks */}
368
- {textBlocks.map(renderBlock)}
369
-
370
- {/* Form */}
371
- <form
372
- onSubmit={handleSubmit(onSubmit)}
373
- style={{ display: 'flex', flexDirection: 'column', gap: `${formGap}px` }}
374
- >
375
- {/* Name + Email row */}
376
- <div
377
- style={{
378
- display: 'flex',
379
- flexDirection: isMobile ? 'column' : 'row',
380
- gap: `${formGap}px`
381
- }}
382
- >
383
- <div style={{ flex: 1 }}>
384
- <input
385
- type="text"
386
- placeholder={fullNamePlaceholder}
387
- {...register('full_name')}
388
- style={{
389
- ...inputStyle,
390
- borderColor: errors.full_name ? '#ef4444' : inputBorderColor
391
- }}
392
- />
393
- {errors.full_name && (
394
- <div style={errorStyle}>{errors.full_name.message}</div>
395
- )}
396
- </div>
397
- <div style={{ flex: 1 }}>
398
- <input
399
- type="email"
400
- placeholder={emailPlaceholder}
401
- {...register('email')}
402
- style={{
403
- ...inputStyle,
404
- borderColor: errors.email ? '#ef4444' : inputBorderColor
405
- }}
406
- />
407
- {errors.email && (
408
- <div style={errorStyle}>{errors.email.message}</div>
409
- )}
410
- </div>
411
- </div>
412
-
413
- {/* Phone */}
414
- <div>
415
- <input
416
- type="tel"
417
- placeholder={phonePlaceholder}
418
- {...register('phone')}
419
- style={{
420
- ...inputStyle,
421
- borderColor: errors.phone ? '#ef4444' : inputBorderColor
422
- }}
423
- />
424
- {errors.phone && (
425
- <div style={errorStyle}>{errors.phone.message}</div>
426
- )}
427
- </div>
428
-
429
- {/* Subject — select from API */}
430
- <div style={{ position: 'relative' }}>
431
- {subjectsLoading && (
432
- <div style={{ padding: '10px', fontSize: '13px', color: '#94a3b8' }}>
433
- Konular yükleniyor...
434
- </div>
435
- )}
436
- {subjectsSuccess && (
437
- <select
438
- {...register('subject')}
439
- onChange={(e) => {
440
- register('subject').onChange(e);
441
- handleSubjectChange(e);
442
- }}
443
- style={{
444
- ...inputStyle,
445
- appearance: 'none',
446
- WebkitAppearance: 'none',
447
- backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E")`,
448
- backgroundRepeat: 'no-repeat',
449
- backgroundPosition: 'right 16px center',
450
- paddingRight: '40px',
451
- borderColor: errors.subject ? '#ef4444' : inputBorderColor
452
- }}
453
- >
454
- {filteredSubjects.map((opt) => (
455
- <option key={opt.value} value={opt.value}>
456
- {opt.label}
457
- </option>
458
- ))}
459
- </select>
460
- )}
461
- {errors.subject && (
462
- <div style={errorStyle}>{errors.subject.message}</div>
463
- )}
464
- </div>
465
-
466
- {/* Message */}
467
- <div>
468
- <textarea
469
- placeholder={messagePlaceholder}
470
- rows={5}
471
- {...register('message')}
472
- style={{
473
- ...inputStyle,
474
- minHeight: '120px',
475
- resize: 'vertical' as const,
476
- borderColor: errors.message ? '#ef4444' : inputBorderColor
477
- }}
478
- />
479
- {errors.message && (
480
- <div style={errorStyle}>{errors.message.message}</div>
481
- )}
482
- </div>
483
-
484
- {/* File upload */}
485
- {showFileUpload && (
486
- <div>
487
- <input
488
- type="file"
489
- // @ts-ignore - react-hook-form FileList type issue
490
- {...register('file')}
491
- style={{
492
- ...inputStyle,
493
- cursor: 'pointer'
494
- }}
495
- />
496
- </div>
497
- )}
498
-
499
- {/* Submit button */}
500
- <div>
501
- <button
502
- type="submit"
503
- disabled={isSubmitting}
504
- style={{
505
- ...buttonStyle,
506
- opacity: isSubmitting ? 0.7 : 1
507
- }}
508
- onMouseEnter={(e) => {
509
- if (!isSubmitting) {
510
- (e.currentTarget as HTMLButtonElement).style.backgroundColor =
511
- buttonHoverBg;
512
- }
513
- }}
514
- onMouseLeave={(e) => {
515
- (e.currentTarget as HTMLButtonElement).style.backgroundColor =
516
- buttonBg;
517
- }}
518
- >
519
- {isSubmitting ? 'Gönderiliyor...' : buttonText}
520
- </button>
521
- </div>
522
-
523
- {/* Server-side errors */}
524
- {submitError && (
525
- <div style={{ color: '#ef4444', fontSize: '13px', marginTop: '4px' }}>
526
- {'data' in submitError &&
527
- submitError.data &&
528
- typeof submitError.data === 'object'
529
- ? Object.entries(submitError.data as Record<string, unknown>).map(
530
- ([field, messages]) => (
531
- <div key={field}>
532
- {Array.isArray(messages)
533
- ? messages.join(', ')
534
- : String(messages)}
535
- </div>
536
- )
537
- )
538
- : errorMessageText}
539
- </div>
540
- )}
541
- </form>
542
- </div>
543
- </div>
544
-
545
- <Modal
546
- portalId={`contact-form-feedback-${section.id}`}
547
- open={feedbackModal.open}
548
- setOpen={(open) =>
549
- setFeedbackModal((prev) => ({ ...prev, open }))
550
- }
551
- title={feedbackModal.title}
552
- showCloseButton={true}
553
- className="w-[92vw] max-w-[420px] rounded"
554
- >
555
- <div className="p-6 text-sm leading-6 text-center text-black">
556
- {feedbackModal.message}
557
- </div>
558
- </Modal>
559
- </>
560
- );
561
- };
562
-
563
- export default ContactFormSection;