feat(about): submit contact form to api
This commit is contained in:
41
src/api/contact.ts
Normal file
41
src/api/contact.ts
Normal file
@@ -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<ContactSubmissionResponse> => {
|
||||||
|
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()
|
||||||
|
}
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
"contact": {
|
"contact": {
|
||||||
"eyebrow": "Contact",
|
"eyebrow": "Contact",
|
||||||
"title": "Need help or want to talk about Qlockify?",
|
"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",
|
"formTitle": "Send a note",
|
||||||
"fields": {
|
"fields": {
|
||||||
"firstName": "First name",
|
"firstName": "First name",
|
||||||
@@ -83,7 +83,10 @@
|
|||||||
"mobile": "09...",
|
"mobile": "09...",
|
||||||
"message": "Tell us what you need help with..."
|
"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": [
|
"channels": [
|
||||||
{
|
{
|
||||||
"label": "Telegram support",
|
"label": "Telegram support",
|
||||||
@@ -181,7 +184,7 @@
|
|||||||
"contact": {
|
"contact": {
|
||||||
"eyebrow": "تماس",
|
"eyebrow": "تماس",
|
||||||
"title": "کمک میخواهید یا میخواهید درباره Qlockify صحبت کنیم؟",
|
"title": "کمک میخواهید یا میخواهید درباره Qlockify صحبت کنیم؟",
|
||||||
"description": "یک پیام بفرستید یا از مسیرهای پشتیبانی زیر با ما در ارتباط باشید. فرم یک پیشنویس ایمیل با پیام شما آماده میکند تا قبل از ارسال آن را بررسی کنید.",
|
"description": "یک پیام بفرستید یا از مسیرهای پشتیبانی زیر با ما در ارتباط باشید. پیامهای فرم تماس ذخیره میشوند تا تیم بتواند آنها را بررسی و پیگیری کند.",
|
||||||
"formTitle": "ارسال پیام",
|
"formTitle": "ارسال پیام",
|
||||||
"fields": {
|
"fields": {
|
||||||
"firstName": "نام",
|
"firstName": "نام",
|
||||||
@@ -197,7 +200,10 @@
|
|||||||
"mobile": "09...",
|
"mobile": "09...",
|
||||||
"message": "بگویید برای چه چیزی به کمک نیاز دارید..."
|
"message": "بگویید برای چه چیزی به کمک نیاز دارید..."
|
||||||
},
|
},
|
||||||
"submit": "آمادهسازی ایمیل",
|
"submit": "ارسال پیام",
|
||||||
|
"submitting": "در حال ارسال...",
|
||||||
|
"success": "پیام شما ثبت شد. بهزودی با شما تماس میگیریم.",
|
||||||
|
"error": "ارسال پیام انجام نشد. لطفا دوباره تلاش کنید.",
|
||||||
"channels": [
|
"channels": [
|
||||||
{
|
{
|
||||||
"label": "پشتیبانی تلگرام",
|
"label": "پشتیبانی تلگرام",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, type FormEvent } from "react"
|
import { useState, type FormEvent } from "react"
|
||||||
import { Link, useNavigate } from "react-router-dom"
|
import { Link, useNavigate } from "react-router-dom"
|
||||||
|
import { toast } from "sonner"
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
@@ -29,6 +30,7 @@ import { Input } from "../components/ui/input"
|
|||||||
import { TextAreaInput } from "../components/ui/TextAreaInput"
|
import { TextAreaInput } from "../components/ui/TextAreaInput"
|
||||||
import { useTheme } from "../components/ThemeProvider"
|
import { useTheme } from "../components/ThemeProvider"
|
||||||
import { useTranslation } from "../hooks/useTranslation"
|
import { useTranslation } from "../hooks/useTranslation"
|
||||||
|
import { submitContactForm } from "../api/contact"
|
||||||
import aboutContent from "../content/about.json"
|
import aboutContent from "../content/about.json"
|
||||||
import { cn } from "../lib/utils"
|
import { cn } from "../lib/utils"
|
||||||
|
|
||||||
@@ -50,6 +52,7 @@ export default function About() {
|
|||||||
mobile: "",
|
mobile: "",
|
||||||
message: "",
|
message: "",
|
||||||
})
|
})
|
||||||
|
const [isContactSubmitting, setIsContactSubmitting] = useState(false)
|
||||||
|
|
||||||
const content = aboutContent[lang] as AboutContent
|
const content = aboutContent[lang] as AboutContent
|
||||||
const isAuthenticated = typeof window !== "undefined" && !!localStorage.getItem("accessToken")
|
const isAuthenticated = typeof window !== "undefined" && !!localStorage.getItem("accessToken")
|
||||||
@@ -62,21 +65,30 @@ export default function About() {
|
|||||||
setContactForm((current) => ({ ...current, [field]: value }))
|
setContactForm((current) => ({ ...current, [field]: value }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitContactForm = (event: FormEvent<HTMLFormElement>) => {
|
const handleContactSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const subject = encodeURIComponent("Qlockify contact request")
|
setIsContactSubmitting(true)
|
||||||
const body = encodeURIComponent(
|
try {
|
||||||
[
|
await submitContactForm({
|
||||||
`First name: ${contactForm.firstName}`,
|
first_name: contactForm.firstName.trim(),
|
||||||
`Last name: ${contactForm.lastName}`,
|
last_name: contactForm.lastName.trim(),
|
||||||
`Email: ${contactForm.email}`,
|
email: contactForm.email.trim(),
|
||||||
`Mobile: ${contactForm.mobile}`,
|
mobile: contactForm.mobile.trim(),
|
||||||
"",
|
message: contactForm.message.trim(),
|
||||||
"Message:",
|
})
|
||||||
contactForm.message,
|
setContactForm({
|
||||||
].join("\n"),
|
firstName: "",
|
||||||
)
|
lastName: "",
|
||||||
window.location.href = `mailto:qlockify@gmail.com?subject=${subject}&body=${body}`
|
email: "",
|
||||||
|
mobile: "",
|
||||||
|
message: "",
|
||||||
|
})
|
||||||
|
toast.success(content.contact.success)
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(error instanceof Error ? error.message : content.contact.error)
|
||||||
|
} finally {
|
||||||
|
setIsContactSubmitting(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -377,7 +389,7 @@ export default function About() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
onSubmit={submitContactForm}
|
onSubmit={handleContactSubmit}
|
||||||
className="animate-landing-rise rounded-[2rem] border border-white/70 bg-white/85 p-7 shadow-[0_30px_80px_-50px_rgba(15,23,42,0.6)] backdrop-blur-xl dark:border-white/10 dark:bg-slate-950/70"
|
className="animate-landing-rise rounded-[2rem] border border-white/70 bg-white/85 p-7 shadow-[0_30px_80px_-50px_rgba(15,23,42,0.6)] backdrop-blur-xl dark:border-white/10 dark:bg-slate-950/70"
|
||||||
>
|
>
|
||||||
<div className="mb-6 flex items-center gap-3">
|
<div className="mb-6 flex items-center gap-3">
|
||||||
@@ -446,10 +458,11 @@ export default function About() {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
disabled={isContactSubmitting}
|
||||||
className="mt-6 h-14 w-full rounded-full bg-slate-950 px-7 text-base text-white hover:bg-slate-800 dark:bg-cyan-400 dark:text-slate-950 dark:hover:bg-cyan-300"
|
className="mt-6 h-14 w-full rounded-full bg-slate-950 px-7 text-base text-white hover:bg-slate-800 dark:bg-cyan-400 dark:text-slate-950 dark:hover:bg-cyan-300"
|
||||||
>
|
>
|
||||||
<Send className="me-2 h-4 w-4" />
|
<Send className="me-2 h-4 w-4" />
|
||||||
{content.contact.submit}
|
{isContactSubmitting ? content.contact.submitting : content.contact.submit}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user