Auth Forgot Password
Auth Forgot Password is a React and Tailwind CSS text component from HextaUI, licensed MIT. Copy it and paste it into your project.
HextaUI
MIT
text
What it is
Auth Forgot Password is a React and Tailwind CSS text component from HextaUI, licensed MIT. Copy it and paste it into your project.
How to use it
Open it in the catalogue, press copy, and paste it into your project. The prompt you copy carries the code and the rules that tell your agent to reproduce it without changing anything.
Five free copies a month. No card.
No packages beyond the project baseline
Licence
This component comes from HextaUI and is redistributed under the MIT licence, which permits commercial use. The copyright notice travels with the code.
Auth Forgot Password
"use client";
import { ArrowLeft, CheckCircle2, Loader2, Mail } from "lucide-react";
import { useCallback, useState } from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/registry/new-york/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/registry/new-york/ui/card";
import {
Field,
FieldContent,
FieldDescription,
FieldError,
FieldLabel,
} from "@/registry/new-york/ui/field";
import {
InputGroup,
InputGroupAddon,
InputGroupInput,
} from "@/registry/new-york/ui/input-group";
export interface AuthForgotPasswordProps {
onSubmit?: (email: string) => void;
onBack?: () => void;
className?: string;
defaultEmail?: string;
isLoading?: boolean;
isSuccess?: boolean;
errors?: {
email?: string;
general?: string;
};
successMessage?: string;
}
function validateEmail(value: string): string | undefined {
if (!value.trim()) {
return "Email is required";
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
return "Please enter a valid email address";
}
return;
}
interface ErrorAlertProps {
message: string;
}
function ErrorAlert({ message }: ErrorAlertProps) {
return (
<div
aria-live="polite"
className="rounded-lg border border-destructive/50 bg-destructive/10 p-3 text-destructive text-sm"
role="alert"
>
{message}
</div>
);
}
interface EmailFieldProps {
id: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
error?: string;
}
function EmailField({ id, value, onChange, error }: EmailFieldProps) {
return (
<Field data-invalid={!!error}>
<FieldLabel htmlFor={id}>
Email
<span aria-label="required" className="text-destructive">
*
</span>
</FieldLabel>
<FieldContent>
<InputGroup aria-invalid={!!error}>
<InputGroupAddon>
<Mail aria-hidden="true" className="size-4" />
</InputGroupAddon>
<InputGroupInput
aria-describedby={error ? `${id}-error` : undefined}
aria-invalid={!!error}
autoComplete="email"
id={id}
inputMode="email"
name="email"
onChange={onChange}
placeholder="name@example.com…"
required
type="email"
value={value}
/>
</InputGroup>
{error && <FieldError id={`${id}-error`}>{error}</FieldError>}
<FieldDescription>
We'll send a password reset link to this email address
</FieldDescription>
</FieldContent>
</Field>
);
}
interface SuccessStateProps {
message: string;
onBack?: () => void;
className?: string;
}
function SuccessState({ message, onBack, className }: SuccessStateProps) {
return (
<Card className={cn("w-full max-w-sm shadow-xs", className)}>
<CardHeader>
<CardTitle>Check your email</CardTitle>
<CardDescription>
We've sent you a password reset link
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-6">
<div className="flex flex-col items-center gap-4 rounded-lg border border-primary/20 bg-primary/5 p-6 text-center">
<div className="flex size-12 items-center justify-center rounded-full bg-primary/10">
<CheckCircle2
aria-hidden="true"
className="size-6 text-primary"
/>
</div>
<div className="flex flex-col gap-2">
<p className="font-medium text-sm">{message}</p>
<p className="text-muted-foreground text-sm">
If you don't see the email, check your spam folder.
</p>
</div>
</div>
{onBack && (
<Button
className="min-h-[44px] w-full touch-manipulation"
onClick={onBack}
type="button"
variant="outline"
>
<ArrowLeft aria-hidden="true" className="size-4" />
Back to sign in
</Button>
)}
</div>
</CardContent>
</Card>
);
}
export default function AuthForgotPassword({
onSubmit,
onBack,
className,
defaultEmail = "",
isLoading = false,
isSuccess = false,
errors,
successMessage = "We've sent a password reset link to your email address.",
}: AuthForgotPasswordProps) {
const [email, setEmail] = useState(defaultEmail);
const [localErrors, setLocalErrors] = useState<{
email?: string;
}>({});
const handleSubmit = useCallback(
(e: React.FormEvent) => {
e.preventDefault();
const emailError = validateEmail(email);
if (emailError) {
setLocalErrors({ email: emailError });
return;
}
setLocalErrors({});
onSubmit?.(email.trim());
},
[email, onSubmit]
);
const handleEmailChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setEmail(value);
if (localErrors.email) {
setLocalErrors((prev) => ({ ...prev, email: validateEmail(value) }));
}
},
[localErrors.email]
);
const emailError = errors?.email || localErrors.email;
const generalError = errors?.general;
if (isSuccess) {
return (
<SuccessState
className={className}
message={successMessage}
onBack={onBack}
/>
);
}
return (
<Card className={cn("w-full max-w-sm shadow-xs", className)}>
<CardHeader>
<CardTitle>Reset password</CardTitle>
<CardDescription>
Enter your email address and we'll send you a link to reset your
password
</CardDescription>
</CardHeader>
<CardContent>
<form className="flex flex-col gap-6" onSubmit={handleSubmit}>
{generalError && <ErrorAlert message={generalError} />}
<EmailField
error={emailError}
id="forgot-password-email"
onChange={handleEmailChange}
value={email}
/>
<div className="flex flex-col gap-2">
<Button
aria-busy={isLoading}
className="min-h-[44px] w-full touch-manipulation"
data-loading={isLoading}
disabled={isLoading}
type="submit"
>
{isLoading ? (
<>
<Loader2 aria-hidden="true" className="size-4 animate-spin" />
Sending reset link…
</>
) : (
"Send reset link"
)}
</Button>
{onBack && (
<Button
className="min-h-[44px] w-full touch-manipulation"
onClick={onBack}
type="button"
variant="ghost"
>
<ArrowLeft aria-hidden="true" className="size-4" />
Back to sign in
</Button>
)}
</div>
</form>
</CardContent>
</Card>
);
}