Files
lib-auth/react/PasswordResetForms.tsx

164 lines
4.2 KiB
TypeScript

import type { FormEvent, ReactNode } from "react";
import {
Alert,
AlertDescription,
AlertIcon,
Button,
FormControl,
FormLabel,
Input,
Spinner,
Stack,
Text
} from "./chakra-compat";
import type { PasswordResetMode, PasswordResetTokenState } from "./types";
type PasswordResetRequestTexts = {
emailLabel: string;
submitLabel: string;
requestSentMessage: string;
};
type PasswordResetRequestFormProps = {
texts: PasswordResetRequestTexts;
helperText: ReactNode;
loading?: boolean;
requestSent?: boolean;
onSubmit: (values: { email: string }) => void | Promise<void>;
emailPlaceholder?: string;
};
type PasswordResetConfirmTexts = {
loadingLabel: string;
passwordLabel: string;
passwordConfirmLabel: string;
invalidLinkLabel: string;
resetSubmitLabel: string;
createSubmitLabel: string;
resetSuccessLabel: string;
createSuccessLabel: string;
};
type PasswordResetConfirmFormProps = {
texts: PasswordResetConfirmTexts;
tokenState: PasswordResetTokenState;
loading?: boolean;
completedMode?: PasswordResetMode | null;
onSubmit: (values: { password: string; passwordConfirm: string }) => void | Promise<void>;
};
export function PasswordResetRequestForm({
texts,
helperText,
loading = false,
requestSent = false,
onSubmit,
emailPlaceholder = "you@example.com"
}: PasswordResetRequestFormProps) {
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const form = new FormData(event.currentTarget);
void onSubmit({
email: String(form.get("email") ?? "")
});
}
return (
<Stack spacing={5}>
{requestSent ? (
<Alert status="success" borderRadius="md">
<AlertIcon />
<AlertDescription>{texts.requestSentMessage}</AlertDescription>
</Alert>
) : null}
<form onSubmit={handleSubmit}>
<Stack spacing={4}>
<FormControl isRequired>
<FormLabel>{texts.emailLabel}</FormLabel>
<Input name="email" type="email" placeholder={emailPlaceholder} />
</FormControl>
<Text fontSize="sm" color="gray.600">
{helperText}
</Text>
<Button type="submit" isLoading={loading}>
{texts.submitLabel}
</Button>
</Stack>
</form>
</Stack>
);
}
export function PasswordResetConfirmForm({
texts,
tokenState,
loading = false,
completedMode = null,
onSubmit
}: PasswordResetConfirmFormProps) {
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const form = new FormData(event.currentTarget);
void onSubmit({
password: String(form.get("password") ?? ""),
passwordConfirm: String(form.get("passwordConfirm") ?? "")
});
}
if (tokenState.status === "loading") {
return (
<Stack align="center" py={6} spacing={3}>
<Spinner />
<Text color="gray.600">{texts.loadingLabel}</Text>
</Stack>
);
}
if (tokenState.status === "invalid") {
return (
<Alert status="error" borderRadius="md">
<AlertIcon />
<AlertDescription>{tokenState.error || texts.invalidLinkLabel}</AlertDescription>
</Alert>
);
}
if (completedMode !== null) {
return (
<Alert status="success" borderRadius="md">
<AlertIcon />
<AlertDescription>{completedMode === "create" ? texts.createSuccessLabel : texts.resetSuccessLabel}</AlertDescription>
</Alert>
);
}
return (
<Stack spacing={4}>
<Text fontSize="sm" color="gray.600">
{tokenState.email}
</Text>
<form onSubmit={handleSubmit}>
<Stack spacing={4}>
<FormControl isRequired>
<FormLabel>{texts.passwordLabel}</FormLabel>
<Input name="password" type="password" minLength={8} />
</FormControl>
<FormControl isRequired>
<FormLabel>{texts.passwordConfirmLabel}</FormLabel>
<Input name="passwordConfirm" type="password" minLength={8} />
</FormControl>
<Button type="submit" isLoading={loading}>
{tokenState.mode === "create" ? texts.createSubmitLabel : texts.resetSubmitLabel}
</Button>
</Stack>
</form>
</Stack>
);
}