Files
reui-blocks/blocks/contact28.tsx
T

462 lines
17 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 { zodResolver } from "@hookform/resolvers/zod";
import { Check, ChevronLeft, ChevronRight, LoaderIcon } from "lucide-react";
import { useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
Field,
FieldError,
FieldGroup,
FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
const contactFormSchema = z.object({
inquiryType: z.string().min(1, "لطفاً نوع درخواست را انتخاب کنید"),
budget: z.string().optional(),
name: z.string().min(1, "نام الزامی است"),
email: z
.string()
.min(1, "ایمیل الزامی است")
.email("آدرس ایمیل نامعتبر است"),
company: z.string().optional(),
phone: z.string().optional(),
message: z.string().min(10, "لطفاً جزئیات بیشتری بنویسید"),
});
type ContactFormData = z.infer<typeof contactFormSchema>;
const steps = [
{ id: 1, name: "نوع درخواست" },
{ id: 2, name: "اطلاعات شما" },
{ id: 3, name: "پیام" },
];
interface Contact28Props {
title?: string;
subtitle?: string;
className?: string;
onSubmit?: (data: ContactFormData) => Promise<void>;
}
const Contact28 = ({
title = "شروع کنید",
subtitle = "نیازها و اهداف پروژه خود را شرح دهید تا کارشناسان ما حداکثر طی یک روز کاری با شما تماس بگیرند.",
className,
onSubmit,
}: Contact28Props) => {
const [currentStep, setCurrentStep] = useState(1);
const [isSubmitted, setIsSubmitted] = useState(false);
const form = useForm<ContactFormData>({
resolver: zodResolver(contactFormSchema),
mode: "onSubmit",
reValidateMode: "onSubmit",
defaultValues: {
inquiryType: "",
budget: "",
name: "",
email: "",
company: "",
phone: "",
message: "",
},
});
const validateStep = async (step: number): Promise<boolean> => {
let fieldsToValidate: (keyof ContactFormData)[] = [];
if (step === 1) {
fieldsToValidate = ["inquiryType"];
} else if (step === 2) {
fieldsToValidate = ["name", "email"];
} else if (step === 3) {
fieldsToValidate = ["message"];
}
const result = await form.trigger(fieldsToValidate);
return result;
};
const nextStep = async () => {
const isValid = await validateStep(currentStep);
if (isValid && currentStep < 3) {
setCurrentStep(currentStep + 1);
}
};
const prevStep = () => {
if (currentStep > 1) {
setCurrentStep(currentStep - 1);
}
};
const handleFormSubmit = async (data: ContactFormData) => {
try {
if (onSubmit) {
await onSubmit(data);
} else {
console.log("Form submitted:", data);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
setIsSubmitted(true);
} catch {
form.setError("root", {
message: "مشکلی پیش آمد. لطفاً دوباره تلاش کنید.",
});
}
};
if (isSubmitted) {
return (
<section
dir="rtl"
lang="fa"
className={cn("relative min-h-screen bg-muted/30", className)}
>
<div className="container flex min-h-screen flex-col justify-center py-20">
<div className="mx-auto max-w-xl text-center">
<div className="mx-auto mb-6 flex size-20 items-center justify-center rounded-full bg-green-500/10">
<Check className="size-10 text-green-600" />
</div>
<h2 className="mb-4 text-xl font-medium">درخواست شما ثبت شد</h2>
<p className="text-xl text-muted-foreground">
پیام شما با موفقیت دریافت شد. نتیجه بررسی حداکثر تا یک روز کاری به شما اطلاع داده خواهد شد.
</p>
<Button
className="mt-10"
size="lg"
variant="outline"
onClick={() => {
setIsSubmitted(false);
setCurrentStep(1);
form.reset();
}}
>
ارسال درخواست جدید
</Button>
</div>
</div>
</section>
);
}
return (
<section
dir="rtl"
lang="fa"
className={cn("relative min-h-screen bg-muted/30", className)}
>
<div className="container flex min-h-screen flex-col justify-center py-20">
<div className="grid gap-16 lg:grid-cols-2 lg:gap-24">
<div className="flex flex-col justify-center">
<h1 className="mb-6 text-2xll font-medium tracking-tight md:text-2xll lg:text-2xll">
{title}
</h1>
<p className="mb-12 text-xl text-muted-foreground md:text-xl">
{subtitle}
</p>
<div className="space-y-4">
{steps.map((step) => (
<div
key={step.id}
className={cn(
"flex items-center gap-4 rounded-lg p-4 transition-colors",
currentStep === step.id
? "bg-background shadow-sm"
: currentStep > step.id
? "text-muted-foreground"
: "text-muted-foreground/50",
)}
>
<div
className={cn(
"flex size-10 shrink-0 items-center justify-center rounded-full font-medium transition-colors",
currentStep > step.id
? "bg-primary text-primary-foreground"
: currentStep === step.id
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground",
)}
>
{currentStep > step.id ? (
<Check className="size-5" />
) : (
step.id.toLocaleString("fa-IR")
)}
</div>
<span className="text-lg font-medium">{step.name}</span>
</div>
))}
</div>
</div>
<div className="flex items-center">
<form
onSubmit={form.handleSubmit(handleFormSubmit)}
className="w-full rounded-2xl bg-background p-8 shadow-lg md:p-10"
>
<h2 className="mb-8 text-xl font-semibold">
{steps[currentStep - 1].name}
</h2>
{currentStep === 1 && (
<FieldGroup>
<Controller
control={form.control}
name="inquiryType"
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>
نوع درخواست{" "}
<span className="text-destructive">*</span>
</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<SelectTrigger
id={field.name}
aria-invalid={fieldState.invalid}
>
<SelectValue placeholder="موضوع درخواست را انتخاب کنید" />
</SelectTrigger>
<SelectContent>
<SelectItem value="general">پرسش عمومی</SelectItem>
<SelectItem value="sales">فروش و قیمت‌گذاری</SelectItem>
<SelectItem value="support">پشتیبانی فنی</SelectItem>
<SelectItem value="partnership">همکاری</SelectItem>
<SelectItem value="demo">درخواست دمو</SelectItem>
</SelectContent>
</Select>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
<Controller
control={form.control}
name="budget"
render={({ field }) => (
<Field>
<FieldLabel htmlFor={field.name}>
بودجه پروژه
</FieldLabel>
<Select
onValueChange={field.onChange}
value={field.value}
>
<SelectTrigger id={field.name}>
<SelectValue placeholder="انتخاب بازه بودجه" />
</SelectTrigger>
<SelectContent>
<SelectItem value="<5k">
کمتر از ۵ هزار دلار
</SelectItem>
<SelectItem value="5k-15k">
۵ تا ۱۵ هزار دلار
</SelectItem>
<SelectItem value="15k-50k">
۱۵ تا ۵۰ هزار دلار
</SelectItem>
<SelectItem value="50k-100k">
۵۰ تا ۱۰۰ هزار دلار
</SelectItem>
<SelectItem value=">100k">
بیش از ۱۰۰ هزار دلار
</SelectItem>
</SelectContent>
</Select>
</Field>
)}
/>
</FieldGroup>
)}
{currentStep === 2 && (
<FieldGroup>
<div className="grid gap-4 sm:grid-cols-2">
<Controller
control={form.control}
name="name"
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>
نام و نام خانوادگی <span className="text-destructive">*</span>
</FieldLabel>
<Input
{...field}
id={field.name}
aria-invalid={fieldState.invalid}
placeholder="نام و نام خانوادگی"
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
<Controller
control={form.control}
name="email"
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>
ایمیل کاری <span className="text-destructive">*</span>
</FieldLabel>
<Input
{...field}
id={field.name}
type="email"
aria-invalid={fieldState.invalid}
placeholder="example@email.com"
dir="rtl"
className="text-start"
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<Controller
control={form.control}
name="company"
render={({ field }) => (
<Field>
<FieldLabel htmlFor={field.name}>نام شرکت یا سازمان</FieldLabel>
<Input
{...field}
id={field.name}
placeholder="نام شرکت یا سازمان"
/>
</Field>
)}
/>
<Controller
control={form.control}
name="phone"
render={({ field }) => (
<Field>
<FieldLabel htmlFor={field.name}>
شماره تلفن
</FieldLabel>
<Input
{...field}
id={field.name}
type="tel"
placeholder="۰۹۱۲ ۳۴۵ ۶۷۸۹"
dir="rtl"
className="text-start"
/>
</Field>
)}
/>
</div>
</FieldGroup>
)}
{currentStep === 3 && (
<FieldGroup>
<Controller
control={form.control}
name="message"
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>
چگونه می‌توانیم به شما کمک کنیم؟{" "}
<span className="text-destructive">*</span>
</FieldLabel>
<Textarea
{...field}
id={field.name}
aria-invalid={fieldState.invalid}
placeholder="جزئیات پروژه، نیازها یا پرسش‌های خود را با ما در میان بگذارید."
rows={6}
/>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
{form.formState.errors.root && (
<p className="text-sm text-destructive">
{form.formState.errors.root.message}
</p>
)}
</FieldGroup>
)}
<div className="mt-8 flex items-center justify-between">
<Button
type="button"
variant="ghost"
className="gap-1"
onClick={prevStep}
disabled={currentStep === 1}
>
<ChevronRight className="size-4" />
بازگشت
</Button>
{currentStep < 3 ? (
<Button
type="button"
size="lg"
className="gap-1"
onClick={nextStep}
>
مرحله بعد
<ChevronLeft className="size-4" />
</Button>
) : (
<Button
type="submit"
size="lg"
className="gap-2"
disabled={form.formState.isSubmitting}
>
{form.formState.isSubmitting ? (
<>
<LoaderIcon className="size-4 animate-spin" />
در حال ارسال...
</>
) : (
"ارسال"
)}
</Button>
)}
</div>
</form>
</div>
</div>
</div>
</section>
);
};
export { Contact28 };