diff --git a/src/api/contact.ts b/src/api/contact.ts new file mode 100644 index 0000000..fb7c9d0 --- /dev/null +++ b/src/api/contact.ts @@ -0,0 +1,41 @@ +import { buildApiError, buildApiUrl } from "./client" + +const normalizeDigits = (value: string) => + value + .replace(/[۰-۹]/g, (digit) => String("۰۱۲۳۴۵۶۷۸۹".indexOf(digit))) + .replace(/[٠-٩]/g, (digit) => String("٠١٢٣٤٥٦٧٨٩".indexOf(digit))) + +export interface ContactSubmissionPayload { + first_name: string + last_name: string + email: string + mobile: string + message: string +} + +export interface ContactSubmissionResponse extends ContactSubmissionPayload { + id: string + status: string + created_at: string +} + +export const submitContactForm = async ( + payload: ContactSubmissionPayload, +): Promise => { + const response = await fetch(buildApiUrl("/api/contact/"), { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + ...payload, + mobile: normalizeDigits(payload.mobile), + }), + }) + + if (!response.ok) { + throw await buildApiError(response) + } + + return response.json() +} diff --git a/src/content/about.json b/src/content/about.json index 182f0ce..6ad534d 100644 --- a/src/content/about.json +++ b/src/content/about.json @@ -67,7 +67,7 @@ "contact": { "eyebrow": "Contact", "title": "Need help or want to talk about Qlockify?", - "description": "Send a note or reach out through the support channels below. The form opens an email draft with your message so you can review it before sending.", + "description": "Send a note or reach out through the support channels below. Contact form submissions are stored securely so the team can review and follow up.", "formTitle": "Send a note", "fields": { "firstName": "First name", @@ -83,7 +83,10 @@ "mobile": "09...", "message": "Tell us what you need help with..." }, - "submit": "Prepare email", + "submit": "Send message", + "submitting": "Sending...", + "success": "Your message was saved. We will contact you soon.", + "error": "Could not send your message. Please try again.", "channels": [ { "label": "Telegram support", @@ -181,7 +184,7 @@ "contact": { "eyebrow": "تماس", "title": "کمک می‌خواهید یا می‌خواهید درباره Qlockify صحبت کنیم؟", - "description": "یک پیام بفرستید یا از مسیرهای پشتیبانی زیر با ما در ارتباط باشید. فرم یک پیش‌نویس ایمیل با پیام شما آماده می‌کند تا قبل از ارسال آن را بررسی کنید.", + "description": "یک پیام بفرستید یا از مسیرهای پشتیبانی زیر با ما در ارتباط باشید. پیام‌های فرم تماس ذخیره می‌شوند تا تیم بتواند آن‌ها را بررسی و پیگیری کند.", "formTitle": "ارسال پیام", "fields": { "firstName": "نام", @@ -197,7 +200,10 @@ "mobile": "09...", "message": "بگویید برای چه چیزی به کمک نیاز دارید..." }, - "submit": "آماده‌سازی ایمیل", + "submit": "ارسال پیام", + "submitting": "در حال ارسال...", + "success": "پیام شما ثبت شد. به‌زودی با شما تماس می‌گیریم.", + "error": "ارسال پیام انجام نشد. لطفا دوباره تلاش کنید.", "channels": [ { "label": "پشتیبانی تلگرام", diff --git a/src/pages/About.tsx b/src/pages/About.tsx index b1ca037..636fb5e 100644 --- a/src/pages/About.tsx +++ b/src/pages/About.tsx @@ -1,5 +1,6 @@ import { useState, type FormEvent } from "react" import { Link, useNavigate } from "react-router-dom" +import { toast } from "sonner" import { ArrowLeft, ArrowRight, @@ -29,6 +30,7 @@ import { Input } from "../components/ui/input" import { TextAreaInput } from "../components/ui/TextAreaInput" import { useTheme } from "../components/ThemeProvider" import { useTranslation } from "../hooks/useTranslation" +import { submitContactForm } from "../api/contact" import aboutContent from "../content/about.json" import { cn } from "../lib/utils" @@ -50,6 +52,7 @@ export default function About() { mobile: "", message: "", }) + const [isContactSubmitting, setIsContactSubmitting] = useState(false) const content = aboutContent[lang] as AboutContent const isAuthenticated = typeof window !== "undefined" && !!localStorage.getItem("accessToken") @@ -62,21 +65,30 @@ export default function About() { setContactForm((current) => ({ ...current, [field]: value })) } - const submitContactForm = (event: FormEvent) => { + const handleContactSubmit = async (event: FormEvent) => { event.preventDefault() - const subject = encodeURIComponent("Qlockify contact request") - const body = encodeURIComponent( - [ - `First name: ${contactForm.firstName}`, - `Last name: ${contactForm.lastName}`, - `Email: ${contactForm.email}`, - `Mobile: ${contactForm.mobile}`, - "", - "Message:", - contactForm.message, - ].join("\n"), - ) - window.location.href = `mailto:qlockify@gmail.com?subject=${subject}&body=${body}` + setIsContactSubmitting(true) + try { + await submitContactForm({ + first_name: contactForm.firstName.trim(), + last_name: contactForm.lastName.trim(), + email: contactForm.email.trim(), + mobile: contactForm.mobile.trim(), + message: contactForm.message.trim(), + }) + setContactForm({ + firstName: "", + lastName: "", + email: "", + mobile: "", + message: "", + }) + toast.success(content.contact.success) + } catch (error) { + toast.error(error instanceof Error ? error.message : content.contact.error) + } finally { + setIsContactSubmitting(false) + } } return ( @@ -377,7 +389,7 @@ export default function About() {
@@ -446,10 +458,11 @@ export default function About() {