init: initial commit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
88
src/password-reset-request-form.tsx
Normal file
88
src/password-reset-request-form.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
"use client";
|
||||
|
||||
import { useState, type CSSProperties, type FC, type ReactNode } from "react";
|
||||
import { AuthCard, ForgotPasswordForm } from "@crema/auth-ui";
|
||||
import { ArcadiaError, useArcadiaClient } from "@crema/arcadia-client";
|
||||
|
||||
export interface PasswordResetRequestFormProps {
|
||||
/** Where to POST. Defaults to "/api/v1/password-reset/request". */
|
||||
requestPath?: string;
|
||||
onSuccess?: (email: string) => void | Promise<void>;
|
||||
onBack?: () => void;
|
||||
brand?: ReactNode;
|
||||
heading?: string;
|
||||
subhead?: ReactNode;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const PasswordResetRequestForm: FC<PasswordResetRequestFormProps> = ({
|
||||
requestPath = "/api/v1/password-reset/request",
|
||||
onSuccess,
|
||||
onBack,
|
||||
brand,
|
||||
heading = "Reset your password",
|
||||
subhead = "Enter the email associated with your account and we'll send you a reset link.",
|
||||
style,
|
||||
}) => {
|
||||
const arcadia = useArcadiaClient();
|
||||
const [sentTo, setSentTo] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
async function handleSubmit(email: string) {
|
||||
setError(null);
|
||||
try {
|
||||
await arcadia.POST(requestPath, { body: { email } });
|
||||
setSentTo(email);
|
||||
await onSuccess?.(email);
|
||||
} catch (err) {
|
||||
// Most deployments respond 200 even for unknown emails (anti-enumeration).
|
||||
// We only land here on hard failures (rate limit, validation, server).
|
||||
if (err instanceof ArcadiaError) setError(err.message);
|
||||
else setError("Something went wrong. Please try again.");
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthCard
|
||||
logo={brand}
|
||||
title={heading}
|
||||
description={subhead}
|
||||
footer={
|
||||
onBack ? (
|
||||
<button
|
||||
type="button"
|
||||
data-action="auth-pwreset-back"
|
||||
onClick={onBack}
|
||||
className="text-sm text-primary underline underline-offset-2"
|
||||
>
|
||||
Back to sign in
|
||||
</button>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<div data-action="auth-pwreset-request" style={style}>
|
||||
{sentTo ? (
|
||||
<div
|
||||
role="status"
|
||||
className="rounded-md border border-success/30 bg-success/10 px-3 py-2 text-sm"
|
||||
style={{ color: "var(--success, currentColor)" }}
|
||||
>
|
||||
If an account exists for <strong>{sentTo}</strong>, we've sent a reset link.
|
||||
</div>
|
||||
) : (
|
||||
<ForgotPasswordForm onSubmit={handleSubmit} onBack={onBack} />
|
||||
)}
|
||||
{error ? (
|
||||
<div
|
||||
role="alert"
|
||||
className="mt-3 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive"
|
||||
data-action="auth-pwreset-error"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</AuthCard>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user