Next.js (App Router) clone of negindentalclinic.ir, wired to the Meshkee Website API (blog, contact form, favicon), with on-page SEO and a scroll-reveal animation sequence on the homepage. See context.md for full project context. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
132 lines
3.8 KiB
JavaScript
132 lines
3.8 KiB
JavaScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { submitContact } from "@/lib/meshkee";
|
|
import styles from "./contact.module.css";
|
|
|
|
const EMPTY = { name: "", email: "", phone: "", subject: "", message: "" };
|
|
|
|
export default function ContactForm() {
|
|
const [values, setValues] = useState(EMPTY);
|
|
const [status, setStatus] = useState("idle"); // idle | sending | error
|
|
const [error, setError] = useState("");
|
|
const [toast, setToast] = useState(null); // { text, kind } | null
|
|
const toastTimer = useRef(null);
|
|
|
|
useEffect(() => () => clearTimeout(toastTimer.current), []);
|
|
|
|
const showToast = (text, kind = "success") => {
|
|
setToast({ text, kind });
|
|
clearTimeout(toastTimer.current);
|
|
toastTimer.current = setTimeout(() => setToast(null), 4500);
|
|
};
|
|
|
|
const onChange = (e) =>
|
|
setValues((v) => ({ ...v, [e.target.name]: e.target.value }));
|
|
|
|
const onSubmit = async (e) => {
|
|
e.preventDefault();
|
|
setStatus("sending");
|
|
setError("");
|
|
try {
|
|
await submitContact({
|
|
title: values.subject,
|
|
name: values.name,
|
|
text: values.message,
|
|
email: values.email || undefined,
|
|
cellNumber: values.phone || undefined,
|
|
});
|
|
setValues(EMPTY);
|
|
setStatus("idle");
|
|
showToast("پیام شما با موفقیت ارسال شد. همکاران ما در اسرع وقت با شما تماس خواهند گرفت.");
|
|
} catch (err) {
|
|
setStatus("error");
|
|
setError(err.message || "ارسال پیام با خطا مواجه شد. لطفاً دوباره تلاش کنید.");
|
|
showToast("ارسال پیام ناموفق بود. لطفاً دوباره تلاش کنید.", "error");
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<form className={styles.form} onSubmit={onSubmit} noValidate>
|
|
<div className={styles.row}>
|
|
<label>
|
|
<span>نام و نام خانوادگی</span>
|
|
<input
|
|
name="name"
|
|
value={values.name}
|
|
onChange={onChange}
|
|
required
|
|
autoComplete="name"
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span>آدرس الکترونیکی</span>
|
|
<input
|
|
type="email"
|
|
name="email"
|
|
value={values.email}
|
|
onChange={onChange}
|
|
autoComplete="email"
|
|
dir="ltr"
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div className={styles.row}>
|
|
<label>
|
|
<span>شماره تماس</span>
|
|
<input
|
|
name="phone"
|
|
value={values.phone}
|
|
onChange={onChange}
|
|
autoComplete="tel"
|
|
inputMode="tel"
|
|
dir="ltr"
|
|
/>
|
|
</label>
|
|
<label>
|
|
<span>موضوع</span>
|
|
<input
|
|
name="subject"
|
|
value={values.subject}
|
|
onChange={onChange}
|
|
required
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<label>
|
|
<span>پیام شما</span>
|
|
<textarea
|
|
name="message"
|
|
rows={6}
|
|
value={values.message}
|
|
onChange={onChange}
|
|
required
|
|
/>
|
|
</label>
|
|
|
|
<button type="submit" className="btn" disabled={status === "sending"}>
|
|
{status === "sending" ? "در حال ارسال…" : "ارسال پیام"}
|
|
</button>
|
|
|
|
{status === "error" && error ? (
|
|
<p className={styles.formError} role="alert">
|
|
{error}
|
|
</p>
|
|
) : null}
|
|
</form>
|
|
|
|
{toast ? (
|
|
<div
|
|
className={`${styles.toast} ${toast.kind === "error" ? styles.toastError : ""}`}
|
|
role="status"
|
|
>
|
|
{toast.text}
|
|
</div>
|
|
) : null}
|
|
</>
|
|
);
|
|
}
|