228 lines
7.4 KiB
TypeScript
228 lines
7.4 KiB
TypeScript
'use client'
|
||||
|
|
|
|||
|
|
import React, { useState } from 'react'
|
|||
|
|
import { useForm } from 'react-hook-form'
|
|||
|
|
import { z } from 'zod'
|
|||
|
|
import { zodResolver } from '@hookform/resolvers/zod'
|
|||
|
|
|
|||
|
|
import { Button } from '@/components/ui/button'
|
|||
|
|
import { Input } from '@/components/ui/input'
|
|||
|
|
import { Label } from '@/components/ui/label'
|
|||
|
|
import {
|
|||
|
|
Select,
|
|||
|
|
SelectContent,
|
|||
|
|
SelectItem,
|
|||
|
|
SelectTrigger,
|
|||
|
|
SelectValue,
|
|||
|
|
} from '@/components/ui/select'
|
|||
|
|
import { Textarea } from '@/components/ui/textarea'
|
|||
|
|
|
|||
|
|
import { submitDemoRequest } from '@/app/demo/actions'
|
|||
|
|
import type { DemoRequestFormData } from '@/app/demo/actions'
|
|||
|
|
|
|||
|
|
const PRODUCT_OPTIONS = [
|
|||
|
|
{ value: 'آبشده همکار', label: 'آبشده همکار' },
|
|||
|
|
{ value: 'آبشده خانگی', label: 'آبشده خانگی' },
|
|||
|
|
{ value: 'فروشگاهساز طلا', label: 'فروشگاهساز طلا' },
|
|||
|
|
{ value: 'قیمتدهی طلا', label: 'قیمتدهی طلا' },
|
|||
|
|
{ value: 'سایر', label: 'سایر' },
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
const schema = z.object({
|
|||
|
|
name: z.string().min(2, { message: 'نام و نام خانوادگی حداقل ۲ کاراکتر باشد.' }),
|
|||
|
|
phone: z
|
|||
|
|
.string()
|
|||
|
|
.min(10, { message: 'شماره تماس معتبر وارد کنید.' })
|
|||
|
|
.regex(/^[\d\s۰-۹]+$/, { message: 'فقط اعداد وارد کنید.' }),
|
|||
|
|
product: z
|
|||
|
|
.string()
|
|||
|
|
.refine((v) => v && v !== '__empty__', { message: 'نرمافزار مورد نظر را انتخاب کنید.' }),
|
|||
|
|
email: z.string().email({ message: 'ایمیل معتبر وارد کنید.' }).optional().or(z.literal('')),
|
|||
|
|
message: z.string().max(1000).optional(),
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
type FormValues = z.infer<typeof schema>
|
|||
|
|
|
|||
|
|
export function DemoRequestForm() {
|
|||
|
|
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
|
|||
|
|
const [errorMessage, setErrorMessage] = useState<string | null>(null)
|
|||
|
|
|
|||
|
|
const {
|
|||
|
|
register,
|
|||
|
|
handleSubmit,
|
|||
|
|
formState: { errors },
|
|||
|
|
setValue,
|
|||
|
|
watch,
|
|||
|
|
reset,
|
|||
|
|
} = useForm<FormValues>({
|
|||
|
|
resolver: zodResolver(schema),
|
|||
|
|
defaultValues: {
|
|||
|
|
name: '',
|
|||
|
|
phone: '',
|
|||
|
|
product: '',
|
|||
|
|
email: '',
|
|||
|
|
message: '',
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const productValue = watch('product')
|
|||
|
|
|
|||
|
|
const onSubmit = async (data: FormValues) => {
|
|||
|
|
setStatus('loading')
|
|||
|
|
setErrorMessage(null)
|
|||
|
|
const product = data.product?.trim() && data.product !== '__empty__' ? data.product : ''
|
|||
|
|
const payload: DemoRequestFormData = {
|
|||
|
|
name: data.name.trim(),
|
|||
|
|
phone: data.phone.replace(/\s/g, ''),
|
|||
|
|
product,
|
|||
|
|
email: data.email?.trim() || undefined,
|
|||
|
|
message: data.message?.trim() || undefined,
|
|||
|
|
}
|
|||
|
|
const result = await submitDemoRequest(payload)
|
|||
|
|
if (result.success) {
|
|||
|
|
setStatus('success')
|
|||
|
|
reset()
|
|||
|
|
} else {
|
|||
|
|
setStatus('error')
|
|||
|
|
setErrorMessage(result.error ?? 'خطایی رخ داد.')
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (status === 'success') {
|
|||
|
|
return (
|
|||
|
|
<div
|
|||
|
|
className="rounded-xl border border-primary/20 bg-primary/5 p-8 text-center"
|
|||
|
|
role="alert"
|
|||
|
|
>
|
|||
|
|
<h3 className="text-heading-3 font-semibold text-foreground">
|
|||
|
|
درخواست دمو شما ثبت شد
|
|||
|
|
</h3>
|
|||
|
|
<p className="mt-2 text-body text-muted-foreground">
|
|||
|
|
کارشناسان مستر دکور در اسرع وقت با شما تماس میگیرند تا زمان جلسه دمو و نمایش امکانات سامانه را هماهنگ کنند.
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<form
|
|||
|
|
onSubmit={handleSubmit(onSubmit)}
|
|||
|
|
className="mx-auto max-w-xl space-y-6 rounded-xl border bg-card p-6 shadow-sm md:p-8"
|
|||
|
|
noValidate
|
|||
|
|
>
|
|||
|
|
<div className="grid gap-2 md:grid-cols-2">
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="demo-name" className="text-body-sm font-medium">
|
|||
|
|
نام و نام خانوادگی <span className="text-destructive">*</span>
|
|||
|
|
</Label>
|
|||
|
|
<Input
|
|||
|
|
id="demo-name"
|
|||
|
|
type="text"
|
|||
|
|
placeholder="مثال: علی محمدی"
|
|||
|
|
className="w-full"
|
|||
|
|
autoComplete="name"
|
|||
|
|
aria-invalid={!!errors.name}
|
|||
|
|
{...register('name')}
|
|||
|
|
/>
|
|||
|
|
{errors.name && (
|
|||
|
|
<p className="text-caption text-destructive">{errors.name.message}</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="demo-phone" className="text-body-sm font-medium">
|
|||
|
|
شماره تماس <span className="text-destructive">*</span>
|
|||
|
|
</Label>
|
|||
|
|
<Input
|
|||
|
|
id="demo-phone"
|
|||
|
|
type="tel"
|
|||
|
|
placeholder="۰۹۱۲۳۴۵۶۷۸۹"
|
|||
|
|
className="w-full"
|
|||
|
|
autoComplete="tel"
|
|||
|
|
aria-invalid={!!errors.phone}
|
|||
|
|
{...register('phone')}
|
|||
|
|
/>
|
|||
|
|
{errors.phone && (
|
|||
|
|
<p className="text-caption text-destructive">{errors.phone.message}</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label className="text-body-sm font-medium">
|
|||
|
|
نرمافزار مورد نظر <span className="text-destructive">*</span>
|
|||
|
|
</Label>
|
|||
|
|
<Select
|
|||
|
|
value={productValue || '__empty__'}
|
|||
|
|
onValueChange={(v) => setValue('product', v === '__empty__' ? '' : v)}
|
|||
|
|
>
|
|||
|
|
<SelectTrigger className="w-full">
|
|||
|
|
<SelectValue placeholder="انتخاب نرمافزار" />
|
|||
|
|
</SelectTrigger>
|
|||
|
|
<SelectContent>
|
|||
|
|
<SelectItem value="__empty__">انتخاب نرمافزار</SelectItem>
|
|||
|
|
{PRODUCT_OPTIONS.map((opt) => (
|
|||
|
|
<SelectItem key={opt.value} value={opt.value}>
|
|||
|
|
{opt.label}
|
|||
|
|
</SelectItem>
|
|||
|
|
))}
|
|||
|
|
</SelectContent>
|
|||
|
|
</Select>
|
|||
|
|
{errors.product && (
|
|||
|
|
<p className="text-caption text-destructive">{errors.product.message}</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="demo-email" className="text-body-sm font-medium">
|
|||
|
|
ایمیل (اختیاری)
|
|||
|
|
</Label>
|
|||
|
|
<Input
|
|||
|
|
id="demo-email"
|
|||
|
|
type="email"
|
|||
|
|
placeholder="نمونه@example.com"
|
|||
|
|
className="w-full"
|
|||
|
|
autoComplete="email"
|
|||
|
|
aria-invalid={!!errors.email}
|
|||
|
|
{...register('email')}
|
|||
|
|
/>
|
|||
|
|
{errors.email && (
|
|||
|
|
<p className="text-caption text-destructive">{errors.email.message}</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div className="space-y-2">
|
|||
|
|
<Label htmlFor="demo-message" className="text-body-sm font-medium">
|
|||
|
|
توضیحات (اختیاری)
|
|||
|
|
</Label>
|
|||
|
|
<Textarea
|
|||
|
|
id="demo-message"
|
|||
|
|
placeholder="در صورت نیاز توضیح دهید؛ مثلاً تعداد شعبه، نوع استفاده یا سوال خاص."
|
|||
|
|
rows={4}
|
|||
|
|
className="resize-none"
|
|||
|
|
aria-invalid={!!errors.message}
|
|||
|
|
{...register('message')}
|
|||
|
|
/>
|
|||
|
|
{errors.message && (
|
|||
|
|
<p className="text-caption text-destructive">{errors.message.message}</p>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{status === 'error' && errorMessage && (
|
|||
|
|
<p className="rounded-md bg-destructive/10 p-3 text-body-sm text-destructive">
|
|||
|
|
{errorMessage}
|
|||
|
|
</p>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
<Button
|
|||
|
|
type="submit"
|
|||
|
|
size="lg"
|
|||
|
|
variant="primary"
|
|||
|
|
className="w-full"
|
|||
|
|
disabled={status === 'loading'}
|
|||
|
|
>
|
|||
|
|
{status === 'loading' ? 'در حال ارسال...' : 'ثبت درخواست دمو'}
|
|||
|
|
</Button>
|
|||
|
|
</form>
|
|||
|
|
)
|
|||
|
|
}
|