Add Sonner-based notifications and UX polish

This commit is contained in:
MOH
2026-03-10 17:43:26 +01:00
parent eceb0f3a54
commit 77cc9dca87
32 changed files with 279 additions and 310 deletions
+13 -7
View File
@@ -40,9 +40,15 @@ const contactErrorMessages = {
},
} as const;
function withError(pathname: string, message: string) {
const contactSuccessMessages = {
ar: "شكراً على رسالتك.",
en: "Thank you for your message.",
de: "Vielen Dank fuer deine Nachricht.",
} as const;
function withMessage(pathname: string, type: "success" | "error", message: string) {
const params = new URLSearchParams();
params.set("error", message);
params.set(type, message);
return `${pathname}?${params.toString()}`;
}
@@ -80,18 +86,18 @@ export async function submitContactFormAction(formData: FormData) {
message: values.message,
});
redirect(getLocalizedPath(locale, "/success"));
redirect(withMessage(getLocalizedPath(locale, "/success"), "success", contactSuccessMessages[locale]));
} catch (error) {
if (isRedirectError(error)) {
throw error;
}
if (error instanceof z.ZodError) {
redirect(withError(contactPath, contactErrorMessages[locale].invalid));
redirect(withMessage(contactPath, "error", contactErrorMessages[locale].invalid));
}
if (error instanceof Error && error.message === "Too many contact requests. Please try again later.") {
redirect(withError(contactPath, contactErrorMessages[locale].blocked));
redirect(withMessage(contactPath, "error", contactErrorMessages[locale].blocked));
}
if (
@@ -100,10 +106,10 @@ export async function submitContactFormAction(formData: FormData) {
error.message === "Turnstile verification failed." ||
error.message === "Turnstile verification request failed.")
) {
redirect(withError(contactPath, contactErrorMessages[locale].verification));
redirect(withMessage(contactPath, "error", contactErrorMessages[locale].verification));
}
console.error("Contact form delivery failed.", error);
redirect(withError(contactPath, contactErrorMessages[locale].failed));
redirect(withMessage(contactPath, "error", contactErrorMessages[locale].failed));
}
}
+1 -13
View File
@@ -5,7 +5,6 @@ import { getTranslations } from "next-intl/server";
import { Container } from "@/components/layout/container";
import { PageHero } from "@/components/layout/page-hero";
import { MotionFade } from "@/components/motion-fade";
import { FlashMessage } from "@/components/root/flash-message";
import { ContactForm } from "@/components/site/contact-form";
import { buildLocalizedMetadata } from "@/lib/metadata";
import { getPublicContactProtectionSettings } from "@/lib/app-config";
@@ -19,9 +18,6 @@ type ContactPageProps = {
params: {
locale: string;
};
searchParams?: {
error?: string;
};
};
export async function generateMetadata({
@@ -38,10 +34,7 @@ export async function generateMetadata({
});
}
export default async function ContactPage({
params: { locale },
searchParams,
}: ContactPageProps) {
export default async function ContactPage({ params: { locale } }: ContactPageProps) {
const localeKey = resolveLocale(locale);
const [t, protection] = await Promise.all([
getTranslations({ locale: localeKey, namespace: "contactPage" }),
@@ -86,11 +79,6 @@ export default async function ContactPage({
<MotionFade delay={0.05}>
<AppCard>
<CardContent className="pt-6">
{searchParams?.error ? (
<div className="mb-4">
<FlashMessage type="error" message={searchParams.error} />
</div>
) : null}
<ContactForm
action={submitContactFormAction}
locale={localeKey}