Files

275 lines
10 KiB
TypeScript
Raw Permalink 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 React, { useState } from 'react'
import { CheckCircle2, AlertCircle, ArrowRight, RotateCcw } from 'lucide-react'
type FormState = 'idle' | 'loading' | 'success' | 'error'
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
const ACCENT = '#E61919'
const ACCENT_DARK = '#B8140F'
const ACCENT_SOFT = '#FDEEEC'
export default function ContactForm() {
const [state, setState] = useState<FormState>('idle')
const [errorMessage, setErrorMessage] = useState<string>('')
const [form, setForm] = useState({ name: '', org: '', email: '', phone: '', _hp: '' })
const [touched, setTouched] = useState<{ [key: string]: boolean }>({})
const [fieldErrors, setFieldErrors] = useState<{ [key: string]: string }>({})
const validate = () => {
const errors: { [key: string]: string } = {}
if (!form.name.trim() || form.name.trim().length < 2) {
errors.name = 'Lütfen ad ve soyadınızı belirtin (en az 2 karakter).'
}
if (!form.email.trim() || !EMAIL_REGEX.test(form.email.trim())) {
errors.email = 'Lütfen geçerli bir e-posta adresi girin.'
}
setFieldErrors(errors)
return Object.keys(errors).length === 0
}
const handleBlur = (field: string) => () => {
setTouched(prev => ({ ...prev, [field]: true }))
validate()
}
const set = (k: keyof typeof form) => (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setForm(p => ({ ...p, [k]: value }))
if (fieldErrors[k]) {
setFieldErrors(prev => ({ ...prev, [k]: '' }))
}
}
const resetForm = () => {
setForm({ name: '', org: '', email: '', phone: '', _hp: '' })
setTouched({})
setFieldErrors({})
setState('idle')
setErrorMessage('')
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setTouched({ name: true, org: true, email: true, phone: true })
if (form._hp) return
if (!validate()) {
return
}
setState('loading')
setErrorMessage('')
try {
const res = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: form.name.trim(),
email: form.email.trim(),
org: form.org.trim(),
phone: form.phone.trim(),
_hp: form._hp,
}),
})
const data = await res.json().catch(() => ({}))
if (res.ok && data.success) {
setState('success')
} else {
setState('error')
setErrorMessage(data.error || 'Talep iletilirken bir sorun oluştu. Lütfen tekrar deneyin.')
}
} catch {
setState('error')
setErrorMessage('Bağlantı hatası oluştu. Lütfen internet bağlantınızı kontrol edip tekrar deneyin.')
}
}
if (state === 'success') {
return (
<div
role="status"
aria-live="polite"
className="rounded-3xl border border-zinc-200/80 p-8 sm:p-12 text-center shadow-[0_20px_60px_-30px_rgba(24,24,27,0.15)] bg-white"
>
<div
className="mx-auto mb-5 flex h-14 w-14 items-center justify-center rounded-full text-white shadow-sm"
style={{ background: ACCENT }}
>
<CheckCircle2 size={26} strokeWidth={2.5} aria-hidden="true" />
</div>
<h3 className="text-2xl font-extrabold tracking-tight text-zinc-950">
Demo Talebiniz Alındı
</h3>
<p className="mt-3 max-w-[44ch] mx-auto text-sm leading-relaxed text-zinc-600">
Kurucu ekibimiz 24 saat içinde sizinle iletişime geçerek 15 dakikalık birebir canlı demo oturumunuzu planlayacaktır.
</p>
<button
type="button"
onClick={resetForm}
className="mt-7 inline-flex items-center gap-2 rounded-full border border-zinc-200 bg-white px-5 py-2.5 text-sm font-semibold text-zinc-800 transition-colors hover:bg-zinc-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-950"
>
<RotateCcw size={14} aria-hidden="true" /> Yeni Talep Gönder
</button>
</div>
)
}
const baseInput =
'w-full min-h-[46px] rounded-xl border bg-white px-4 py-2.5 text-sm text-zinc-900 placeholder:text-zinc-400 outline-none transition-all'
return (
<form
onSubmit={handleSubmit}
className="grid grid-cols-1 gap-5 rounded-3xl border border-zinc-200/80 bg-white p-6 sm:p-10 shadow-[0_20px_60px_-30px_rgba(24,24,27,0.2)] sm:grid-cols-2"
noValidate
>
<div aria-hidden="true" className="hidden">
<input
type="text"
name="_hp"
value={form._hp}
onChange={set('_hp')}
autoComplete="off"
tabIndex={-1}
/>
</div>
<div className="sm:col-span-1">
<label className="mb-1.5 block text-xs font-bold text-zinc-900" htmlFor="af_name">
Ad Soyad <span style={{ color: ACCENT }}>*</span>
</label>
<input
id="af_name"
type="text"
required
autoComplete="name"
placeholder="Av. Ayşe Yılmaz"
value={form.name}
onChange={set('name')}
onBlur={handleBlur('name')}
aria-invalid={!!fieldErrors.name}
aria-describedby={fieldErrors.name ? 'af_name_error' : undefined}
className={`${baseInput} ${
fieldErrors.name && touched.name
? 'border-red-500 focus-visible:border-red-600 focus-visible:ring-2 focus-visible:ring-red-500/20'
: 'border-zinc-200 hover:border-zinc-300 focus-visible:border-[#E61919] focus-visible:ring-2 focus-visible:ring-[#E61919]/20'
}`}
/>
{fieldErrors.name && touched.name && (
<p id="af_name_error" className="mt-1.5 text-xs font-semibold text-red-600 flex items-center gap-1">
<AlertCircle size={12} aria-hidden="true" /> {fieldErrors.name}
</p>
)}
</div>
<div>
<label className="mb-1.5 block text-xs font-bold text-zinc-900" htmlFor="af_org">
Büro / Kurum
</label>
<input
id="af_org"
type="text"
autoComplete="organization"
placeholder="Yılmaz Hukuk Bürosu"
value={form.org}
onChange={set('org')}
className={`${baseInput} border-zinc-200 hover:border-zinc-300 focus-visible:border-[#E61919] focus-visible:ring-2 focus-visible:ring-[#E61919]/20`}
/>
</div>
<div>
<label className="mb-1.5 block text-xs font-bold text-zinc-900" htmlFor="af_email">
E-posta <span style={{ color: ACCENT }}>*</span>
</label>
<input
id="af_email"
type="email"
required
autoComplete="email"
placeholder="avukat@buro.com"
value={form.email}
onChange={set('email')}
onBlur={handleBlur('email')}
aria-invalid={!!fieldErrors.email}
aria-describedby={fieldErrors.email ? 'af_email_error' : undefined}
className={`${baseInput} ${
fieldErrors.email && touched.email
? 'border-red-500 focus-visible:border-red-600 focus-visible:ring-2 focus-visible:ring-red-500/20'
: 'border-zinc-200 hover:border-zinc-300 focus-visible:border-[#E61919] focus-visible:ring-2 focus-visible:ring-[#E61919]/20'
}`}
/>
{fieldErrors.email && touched.email && (
<p id="af_email_error" className="mt-1.5 text-xs font-semibold text-red-600 flex items-center gap-1">
<AlertCircle size={12} aria-hidden="true" /> {fieldErrors.email}
</p>
)}
</div>
<div>
<label className="mb-1.5 block text-xs font-bold text-zinc-900" htmlFor="af_phone">
Telefon <span className="text-zinc-500 font-normal">(opsiyonel)</span>
</label>
<input
id="af_phone"
type="tel"
autoComplete="tel"
placeholder="0532 000 00 00"
value={form.phone}
onChange={set('phone')}
className={`${baseInput} border-zinc-200 hover:border-zinc-300 focus-visible:border-[#E61919] focus-visible:ring-2 focus-visible:ring-[#E61919]/20`}
/>
</div>
{state === 'error' && (
<div
role="alert"
aria-live="assertive"
className="rounded-xl border border-red-200 bg-red-50/80 p-3.5 text-sm font-medium text-red-800 sm:col-span-2 flex items-start gap-2.5"
>
<AlertCircle size={17} className="text-red-600 shrink-0 mt-0.5" aria-hidden="true" />
<div>
<span>{errorMessage}</span>
<span className="block mt-1 text-xs text-red-700">
Alternatif olarak doğrudan{' '}
<a href="mailto:info@ayris.tech" className="underline font-semibold hover:text-red-900">
info@ayris.tech
</a>{' '}
adresine e-posta gönderebilirsiniz.
</span>
</div>
</div>
)}
<button
type="submit"
disabled={state === 'loading'}
aria-busy={state === 'loading'}
className="mt-2 flex items-center justify-center gap-2 rounded-full py-4 text-sm font-bold text-white transition-all disabled:cursor-not-allowed disabled:opacity-60 sm:col-span-2 focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-[#E61919]/40 active:scale-[0.99]"
style={{ background: ACCENT }}
onMouseEnter={e => {
if (state !== 'loading') (e.currentTarget as HTMLElement).style.background = ACCENT_DARK
}}
onMouseLeave={e => {
if (state !== 'loading') (e.currentTarget as HTMLElement).style.background = ACCENT
}}
>
{state === 'loading' ? (
<span className="inline-flex items-center gap-2">
<span className="h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" />
Talebiniz İletiliyor
</span>
) : (
<span className="inline-flex items-center gap-2">
15 Dk Canlı Demo Talep Edin <ArrowRight size={16} strokeWidth={2.5} aria-hidden="true" />
</span>
)}
</button>
</form>
)
}