SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
4.2 KB · 122 lines tsx
Raw Blame History
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&apos;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