Ship product gaps: register/vote pages, friend add/accept/decline/remove, email verify TTL, captcha on login/forgot, soft-fail user actions, SSE abort/shared client, Commando Centrum error toasts, admin delete for events/polls, and IT/NL i18n fills. Co-authored-by: Cursor <[email protected]>
72 lines
1.9 KiB
TypeScript
72 lines
1.9 KiB
TypeScript
import { headers } from "next/headers";
|
|
import Link from "next/link";
|
|
import { getTranslations } from "next-intl/server";
|
|
import { requestReset } from "@/actions/password-reset";
|
|
import { CaptchaWidget } from "@/components/auth/captcha-widget";
|
|
import { ContentCard } from "@/components/public/ui";
|
|
import { captchaConfig } from "@/lib/services/captcha";
|
|
|
|
export default async function ForgotPage({
|
|
searchParams,
|
|
}: {
|
|
searchParams: Promise<{ sent?: string; error?: string }>;
|
|
}) {
|
|
const t = await getTranslations("pages.forgot");
|
|
const { sent, error } = await searchParams;
|
|
const cfg = await captchaConfig();
|
|
const nonce = (await headers()).get("x-nonce") ?? undefined;
|
|
|
|
return (
|
|
<main style={{ maxWidth: 420, margin: "2rem auto" }}>
|
|
<ContentCard icon="🔑" title={t("title")} subtitle={t("subtitle")}>
|
|
{sent ? (
|
|
<p className="muted" style={{ textAlign: "center", margin: 0 }}>
|
|
{t("sentNotice")}
|
|
</p>
|
|
) : (
|
|
<form
|
|
action={requestReset}
|
|
style={{ display: "grid", gap: "0.7rem" }}
|
|
>
|
|
<input
|
|
name="email"
|
|
type="email"
|
|
placeholder={t("emailPlaceholder")}
|
|
autoComplete="email"
|
|
required
|
|
/>
|
|
<CaptchaWidget
|
|
captcha={{
|
|
provider: cfg.provider,
|
|
siteKey: cfg.siteKey || undefined,
|
|
field: cfg.field || undefined,
|
|
}}
|
|
nonce={nonce}
|
|
/>
|
|
{error === "captcha" ? (
|
|
<p
|
|
style={{
|
|
color: "var(--color-danger)",
|
|
textAlign: "center",
|
|
margin: 0,
|
|
}}
|
|
>
|
|
{t("errorCaptcha")}
|
|
</p>
|
|
) : null}
|
|
<button type="submit" className="btn btn-primary">
|
|
{t("sendResetLink")}
|
|
</button>
|
|
</form>
|
|
)}
|
|
<p
|
|
className="muted"
|
|
style={{ textAlign: "center", marginBottom: 0, marginTop: "1rem" }}
|
|
>
|
|
<Link href="/login">{t("backToLogin")}</Link>
|
|
</p>
|
|
</ContentCard>
|
|
</main>
|
|
);
|
|
}
|