TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1"use client";2import * as React from "react";3import Link from "next/link";4import { KeyRound, MailCheck } from "lucide-react";5import { authClient } from "@/lib/auth-client";6import { Button } from "@/components/ui/button";7import { Field, Input } from "@/components/ui/input";8import { toast } from "@/components/ui/toast";9import { AuthCard, FormError } from "./auth-card";10import { isValidEmail, type AuthClientError } from "./auth-errors";11import { useCooldown, RESEND_COOLDOWN_S } from "./resend-verification";1213export function ForgotForm({ initialEmail = "" }: { initialEmail?: string }) {14 const [email, setEmail] = React.useState(initialEmail);15 const [loading, setLoading] = React.useState(false);16 const [error, setError] = React.useState<string | null>(null);17 const [sent, setSent] = React.useState(false);18 const { left, start } = useCooldown(0);1920 async function request() {21 setError(null);22 if (!isValidEmail(email)) {23 setError("Enter a valid email address.");24 return;25 }26 setLoading(true);27 try {28 const { error: err } = await authClient.requestPasswordReset({ email: email.trim(), redirectTo: "/reset-password" });29 if (err && (err as AuthClientError).status === 429) {30 setError("Too many requests. Please wait a minute and try again.");31 start(60);32 return;33 }34 // Whether or not an account exists, respond identically to avoid leaking who has an account.35 setSent(true);36 start(RESEND_COOLDOWN_S);37 toast.success("Reset link sent", "If an account exists for this email, the link is on its way.");38 } catch {39 setError("We couldn't reach the server. Check your connection and try again.");40 } finally {41 setLoading(false);42 }43 }4445 if (sent) {46 return (47 <AuthCard48 icon={<MailCheck />}49 title="Check your inbox"50 description={51 <>52 If an account exists for <span className="font-medium text-fg">{email.trim()}</span>, we've sent a link to reset your password. It expires in 60 minutes.53 </>54 }55 footer={56 <Link href="/login" className="font-medium text-accent underline-offset-4 hover:underline">57 Back to sign in58 </Link>59 }60 >61 <div className="space-y-3">62 <FormError>{error}</FormError>63 <Button type="button" variant="outline" className="w-full" onClick={request} loading={loading} disabled={left > 0}>64 {left > 0 ? `Send again in ${left}s` : "Send the link again"}65 </Button>66 <button type="button" className="w-full text-center text-xs text-fg-subtle underline-offset-4 hover:text-fg hover:underline" onClick={() => setSent(false)}>67 Use a different email68 </button>69 </div>70 </AuthCard>71 );72 }7374 return (75 <AuthCard76 icon={<KeyRound />}77 title="Reset your password"78 description="Enter the email you signed up with and we'll send you a link to choose a new password."79 footer={80 <>81 Remembered it?{" "}82 <Link href="/login" className="font-medium text-accent underline-offset-4 hover:underline">83 Back to sign in84 </Link>85 </>86 }87 >88 <form89 onSubmit={(e) => {90 e.preventDefault();91 void request();92 }}93 noValidate94 className="space-y-4"95 >96 <FormError id="forgot-error">{error}</FormError>97 <Field label="Email" htmlFor="forgot-email">98 <Input99 id="forgot-email"100 name="email"101 type="email"102 inputMode="email"103 autoComplete="email"104 autoCapitalize="none"105 spellCheck={false}106 required107 value={email}108 onChange={(e) => setEmail(e.target.value)}109 placeholder="you@company.com"110 aria-invalid={!!error || undefined}111 aria-describedby={error ? "forgot-error" : undefined}112 autoFocus113 />114 </Field>115 <Button type="submit" size="lg" className="mt-2 w-full" loading={loading} disabled={left > 0}>116 {left > 0 ? `Try again in ${left}s` : "Send reset link"}117 </Button>118 </form>119 </AuthCard>120 );121}122