CI / quality (push) Waiting to run
Phase 1 cleanup of the personal-site revamp. Backend/architecture untouched; changes are limited to removing unused complexity and restoring feedback. Removals - Toast system: delete react-hot-toast, Toaster, QueryToastBridge, lib/toast, the toggle/easter-egg calls, related i18n keys and the dependency. - Contact protection: remove Turnstile + per-IP rate limiting (lib/contact-guard, lib/contact-protection, admin screen, form widget, app-config wiring, nav entry, test). - Speculative specs: delete orders, products, downloads, project-inquiry. Inline feedback (replaces toast, no new deps) - Add lib/admin-feedback (withFlash/readFlash) and components/admin/admin-flash, rendered centrally by AdminDashboardShell. - Emit success/error messages for media, site-settings, portfolio, smtp, marquee and maintenance actions; pages read them via searchParams. - Contact form shows validation/delivery errors inline; success still redirects to /success. Docs - Fix stale paths in frontend-system-* (components/root -> components/admin, lib/root-navigation -> lib/admin-navigation, drop phantom src/) and remove contact-protection references from docs and CLAUDE.md. - Add docs/PHASE0_DIAGNOSIS.md (diagnosis report). Note: proxy.ts self-fetch kept intentionally; it also drives maintenance mode.
72 lines
2.4 KiB
TypeScript
72 lines
2.4 KiB
TypeScript
"use server";
|
|
|
|
import { redirect } from "next/navigation";
|
|
import { isRedirectError } from "next/dist/client/components/redirect-error";
|
|
import { z } from "zod";
|
|
|
|
import { getSiteSettings } 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),
|
|
});
|
|
|
|
const contactErrorMessages = {
|
|
ar: "تعذّر إرسال الرسالة. تأكد من تعبئة الحقول بشكل صحيح وحاول مجدداً.",
|
|
en: "Your message could not be sent. Please check the fields and try again.",
|
|
de: "Nachricht konnte nicht gesendet werden. Bitte Eingaben pruefen und erneut versuchen.",
|
|
} as const;
|
|
|
|
function getStringValue(formData: FormData, key: string) {
|
|
const value = formData.get(key);
|
|
return typeof value === "string" ? value : "";
|
|
}
|
|
|
|
function withContactError(pathname: string, message: string) {
|
|
const params = new URLSearchParams();
|
|
params.set("error", message);
|
|
|
|
return `${pathname}?${params.toString()}`;
|
|
}
|
|
|
|
export async function submitContactFormAction(formData: FormData) {
|
|
const siteSettings = await getSiteSettings();
|
|
const locale = resolveLocale(String(formData.get("locale") ?? ""), siteSettings.defaultLocale);
|
|
const contactPath = getLocalizedPath(locale, "/contact", siteSettings.defaultLocale);
|
|
|
|
try {
|
|
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"),
|
|
});
|
|
|
|
await sendContactMessage({
|
|
locale,
|
|
name: values.name,
|
|
email: values.email,
|
|
phone: values.phone,
|
|
company: values.company,
|
|
message: values.message,
|
|
});
|
|
|
|
redirect(getLocalizedPath(locale, "/success", siteSettings.defaultLocale));
|
|
} catch (error) {
|
|
if (isRedirectError(error)) {
|
|
throw error;
|
|
}
|
|
|
|
console.error("Contact form delivery failed.", error);
|
|
redirect(withContactError(contactPath, contactErrorMessages[locale]));
|
|
}
|
|
}
|