Shumiland/src/components/forms/FormBlock.tsx

258 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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<Record<string, string>>({})
const [success, setSuccess] = useState(false)
const [error, setError] = useState<string | null>(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 (
<div className="flex flex-col items-center gap-4 py-10 text-center">
<div className="text-[48px]"></div>
<h3 className="text-[24px] font-bold text-white" style={{ fontFamily: FONT }}>
Заявку отримано!
</h3>
<p className="text-[16px] text-white/70" style={{ fontFamily: FONT }}>
Менеджер зв&apos;яжеться з вами найближчим часом.
</p>
</div>
)
}
const fields = form.fields ?? []
return (
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
<div className="grid grid-cols-1 gap-5 md:grid-cols-2">
{fields.map((field) => {
if (field.blockType === 'message') {
return (
<div
key={field.id ?? field.name}
className="text-[14px] text-white/60 md:col-span-2"
style={{ fontFamily: FONT }}
dangerouslySetInnerHTML={{ __html: field.message ?? '' }}
/>
)
}
const isFullWidth =
field.blockType === 'textarea' || (field.width != null && field.width > 50)
return (
<div
key={field.id ?? field.name}
className={isFullWidth ? 'flex flex-col gap-2 md:col-span-2' : 'flex flex-col gap-2'}
>
{field.label && (
<label
htmlFor={field.name}
className="text-[14px] font-medium text-white/80"
style={{ fontFamily: FONT }}
>
{field.label}
{field.required && ' *'}
</label>
)}
{renderInput(field, values[field.name] ?? '', setValue)}
</div>
)
})}
</div>
{error && (
<div className="rounded-xl border border-red-400 bg-red-900/30 px-4 py-3 text-[14px] text-red-300">
{error}
</div>
)}
<button
type="submit"
disabled={isPending}
className="mt-2 inline-flex items-center justify-center rounded-[64px] bg-[#f28b4a] px-10 py-4 text-[16px] font-bold text-white transition-shadow hover:shadow-[0_0_20px_0_#f28b4a] disabled:opacity-60"
style={{ fontFamily: FONT }}
>
{isPending
? 'Надсилаємо...'
: (form.submitButtonLabel ?? submitLabel ?? 'Надіслати заявку')}
</button>
</form>
)
}
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 (
<textarea
id={name}
rows={4}
required={required}
value={value}
onChange={(e) => setValue(name, e.target.value)}
placeholder={placeholder}
className={cls + ' resize-none'}
style={font}
/>
)
case 'select':
return (
<select
id={name}
required={required}
value={value}
onChange={(e) => setValue(name, e.target.value)}
className={cls}
style={font}
>
<option value="">Оберіть...</option>
{field.options?.map((opt) => (
<option key={opt.value} value={opt.value}>
{opt.label}
</option>
))}
</select>
)
case 'email':
return (
<input
id={name}
type="email"
required={required}
value={value}
onChange={(e) => setValue(name, e.target.value)}
placeholder={placeholder ?? 'your@email.com'}
className={cls}
style={font}
/>
)
case 'number':
return (
<input
id={name}
type="number"
required={required}
value={value}
onChange={(e) => setValue(name, e.target.value)}
placeholder={placeholder}
className={cls}
style={font}
/>
)
case 'date':
return (
<input
id={name}
type="date"
required={required}
value={value}
onChange={(e) => setValue(name, e.target.value)}
className={cls}
style={font}
/>
)
case 'checkbox':
return (
<input
id={name}
type="checkbox"
required={required}
checked={value === 'true'}
onChange={(e) => setValue(name, String(e.target.checked))}
className="h-5 w-5 rounded accent-[#f28b4a]"
/>
)
default:
return (
<input
id={name}
type="text"
required={required}
value={value}
onChange={(e) => setValue(name, e.target.value)}
placeholder={placeholder}
className={cls}
style={font}
/>
)
}
}