110 lines
3.8 KiB
TypeScript
110 lines
3.8 KiB
TypeScript
"use server";
|
|
|
|
import { redirect } from "next/navigation";
|
|
import { isRedirectError } from "next/dist/client/components/redirect";
|
|
import { z } from "zod";
|
|
|
|
import { enforceContactRateLimit, verifyTurnstileToken } from "@/lib/contact-guard";
|
|
import { getContactProtectionSettings } from "@/lib/app-config";
|
|
import { getLocalizedPath, resolveLocale } from "@/lib/locale";
|
|
import { sendContactMessage } from "@/lib/mail";
|
|
|
|
const contactFormSchema = z.object({
|
|
locale: z.string().trim().min(1),
|
|
name: z.string().trim().min(2).max(120),
|
|
email: z.string().trim().email(),
|
|
phone: z.string().trim().max(40).optional(),
|
|
company: z.string().trim().max(120).optional(),
|
|
message: z.string().trim().min(10).max(5000),
|
|
turnstileToken: z.string().trim().optional(),
|
|
});
|
|
|
|
const contactErrorMessages = {
|
|
ar: {
|
|
invalid: "يرجى تعبئة كل الحقول بشكل صحيح.",
|
|
failed: "تعذر إرسال الرسالة حالياً.",
|
|
blocked: "تم إرسال عدد كبير من الطلبات. حاول لاحقاً.",
|
|
verification: "يرجى إكمال التحقق قبل الإرسال.",
|
|
},
|
|
en: {
|
|
invalid: "Please fill all fields correctly.",
|
|
failed: "Message could not be sent right now.",
|
|
blocked: "Too many requests. Please try again later.",
|
|
verification: "Please complete the verification before sending.",
|
|
},
|
|
de: {
|
|
invalid: "Bitte alle Felder korrekt ausfuellen.",
|
|
failed: "Nachricht konnte gerade nicht gesendet werden.",
|
|
blocked: "Zu viele Anfragen. Bitte spaeter erneut versuchen.",
|
|
verification: "Bitte die Verifizierung vor dem Senden abschliessen.",
|
|
},
|
|
} as const;
|
|
|
|
function withError(pathname: string, message: string) {
|
|
const params = new URLSearchParams();
|
|
params.set("error", message);
|
|
|
|
return `${pathname}?${params.toString()}`;
|
|
}
|
|
|
|
function getStringValue(formData: FormData, key: string) {
|
|
const value = formData.get(key);
|
|
return typeof value === "string" ? value : "";
|
|
}
|
|
|
|
export async function submitContactFormAction(formData: FormData) {
|
|
const locale = resolveLocale(String(formData.get("locale") ?? ""));
|
|
const contactPath = getLocalizedPath(locale, "/contact");
|
|
|
|
try {
|
|
const protectionSettings = await getContactProtectionSettings();
|
|
const values = contactFormSchema.parse({
|
|
locale,
|
|
name: getStringValue(formData, "name"),
|
|
email: getStringValue(formData, "email"),
|
|
phone: getStringValue(formData, "phone"),
|
|
company: getStringValue(formData, "company"),
|
|
message: getStringValue(formData, "message"),
|
|
turnstileToken: getStringValue(formData, "turnstileToken"),
|
|
});
|
|
|
|
await verifyTurnstileToken(protectionSettings, values.turnstileToken ?? "");
|
|
await enforceContactRateLimit(protectionSettings);
|
|
|
|
await sendContactMessage({
|
|
locale,
|
|
name: values.name,
|
|
email: values.email,
|
|
phone: values.phone,
|
|
company: values.company,
|
|
message: values.message,
|
|
});
|
|
|
|
redirect(getLocalizedPath(locale, "/success"));
|
|
} catch (error) {
|
|
if (isRedirectError(error)) {
|
|
throw error;
|
|
}
|
|
|
|
if (error instanceof z.ZodError) {
|
|
redirect(withError(contactPath, contactErrorMessages[locale].invalid));
|
|
}
|
|
|
|
if (error instanceof Error && error.message === "Too many contact requests. Please try again later.") {
|
|
redirect(withError(contactPath, contactErrorMessages[locale].blocked));
|
|
}
|
|
|
|
if (
|
|
error instanceof Error &&
|
|
(error.message === "Turnstile verification is required." ||
|
|
error.message === "Turnstile verification failed." ||
|
|
error.message === "Turnstile verification request failed.")
|
|
) {
|
|
redirect(withError(contactPath, contactErrorMessages[locale].verification));
|
|
}
|
|
|
|
console.error("Contact form delivery failed.", error);
|
|
redirect(withError(contactPath, contactErrorMessages[locale].failed));
|
|
}
|
|
}
|