From 6e5e62434535d754b3c2f56bbaa38413e693a18c Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Mon, 18 May 2026 12:05:17 +0100 Subject: [PATCH] feat(forms): add FormBlock component for form-builder rendering --- src/components/forms/FormBlock.tsx | 258 +++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 src/components/forms/FormBlock.tsx diff --git a/src/components/forms/FormBlock.tsx b/src/components/forms/FormBlock.tsx new file mode 100644 index 0000000..8372c04 --- /dev/null +++ b/src/components/forms/FormBlock.tsx @@ -0,0 +1,258 @@ +'use client' + +import { useState, useTransition } from 'react' + +const FONT = 'var(--font-montserrat, Montserrat), sans-serif' + +const INPUT_CLS = + 'w-full rounded-[12px] border-2 border-white/20 bg-white/10 px-4 py-3 text-[15px] text-white placeholder-white/40 focus:border-[#f28b4a] focus:outline-none' + +interface FieldOption { + label: string + value: string +} + +interface FormField { + blockType: string + id?: string + name: string + label?: string + required?: boolean + placeholder?: string + defaultValue?: string + options?: FieldOption[] + message?: string + width?: number +} + +interface FormData { + id: string + title?: string + fields?: FormField[] + confirmationType?: 'message' | 'redirect' + confirmationMessage?: unknown + redirect?: { url: string } + submitButtonLabel?: string +} + +interface FormBlockProps { + form: FormData + submitLabel?: string +} + +export function FormBlock({ form, submitLabel }: FormBlockProps) { + const [values, setValues] = useState>({}) + const [success, setSuccess] = useState(false) + const [error, setError] = useState(null) + const [isPending, startTransition] = useTransition() + + function setValue(name: string, value: string) { + setValues((prev) => ({ ...prev, [name]: value })) + } + + function handleSubmit(e: React.FormEvent) { + e.preventDefault() + setError(null) + startTransition(async () => { + try { + const submissionData = Object.entries(values).map(([field, value]) => ({ field, value })) + const res = await fetch('/api/form-submissions', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ form: form.id, submissionData }), + }) + if (!res.ok) { + setError('Щось пішло не так. Спробуйте ще раз.') + return + } + if (form.confirmationType === 'redirect' && form.redirect?.url) { + window.location.href = form.redirect.url + return + } + setSuccess(true) + } catch { + setError("Помилка мережі. Перевірте з'єднання та спробуйте ще раз.") + } + }) + } + + if (success) { + return ( +
+
+

+ Заявку отримано! +

+

+ Менеджер зв'яжеться з вами найближчим часом. +

+
+ ) + } + + const fields = form.fields ?? [] + + return ( +
+
+ {fields.map((field) => { + if (field.blockType === 'message') { + return ( +
+ ) + } + + const isFullWidth = + field.blockType === 'textarea' || (field.width != null && field.width > 50) + + return ( +
+ {field.label && ( + + )} + {renderInput(field, values[field.name] ?? '', setValue)} +
+ ) + })} +
+ + {error && ( +
+ {error} +
+ )} + + + + ) +} + +function renderInput( + field: FormField, + value: string, + setValue: (name: string, val: string) => void +) { + const cls = INPUT_CLS + const font = { fontFamily: FONT } + const { name, required, placeholder } = field + + switch (field.blockType) { + case 'textarea': + return ( +