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 { useRouter } from "next/navigation";5import { MailCheck } from "lucide-react";6import { authClient } from "@/lib/auth-client";7import { Button } from "@/components/ui/button";8import { Field, Input } from "@/components/ui/input";9import { toast } from "@/components/ui/toast";10import { AuthCard, FormError, FormNotice } from "./auth-card";11import { PasswordInput } from "./password-input";12import { ResendVerification, RESEND_COOLDOWN_S } from "./resend-verification";13import { humanAuthError, isValidEmail, type AuthClientError } from "./auth-errors";1415export function LoginForm({ next, notice }: { next: string; notice?: "reset" | "verified" | "signed-out" }) {16 const router = useRouter();17 const [email, setEmail] = React.useState("");18 const [password, setPassword] = React.useState("");19 const [loading, setLoading] = React.useState(false);20 const [error, setError] = React.useState<string | null>(null);21 const [fieldErrors, setFieldErrors] = React.useState<{ email?: string; password?: string }>({});22 const [unverified, setUnverified] = React.useState(false);2324 async function onSubmit(e: React.FormEvent<HTMLFormElement>) {25 e.preventDefault();26 setError(null);27 setUnverified(false);28 const fe: typeof fieldErrors = {};29 if (!isValidEmail(email)) fe.email = "Enter a valid email address.";30 if (!password) fe.password = "Enter your password.";31 setFieldErrors(fe);32 if (Object.keys(fe).length) return;3334 setLoading(true);35 try {36 const { error: err } = await authClient.signIn.email({ email: email.trim(), password, callbackURL: "/verify-email" });37 if (err) {38 const ae = err as AuthClientError;39 if (ae.status === 403 || ae.code === "EMAIL_NOT_VERIFIED") {40 setUnverified(true);41 return;42 }43 setError(humanAuthError(ae, "login"));44 return;45 }46 toast.success("Welcome back");47 router.push(next);48 router.refresh();49 } catch {50 setError("We couldn't reach the server. Check your connection and try again.");51 } finally {52 setLoading(false);53 }54 }5556 if (unverified) {57 return (58 <AuthCard59 icon={<MailCheck />}60 title="Verify your email first"61 description={62 <>63 Your account exists but <span className="font-medium text-fg">{email.trim()}</span> hasn't been verified yet. We just sent you a fresh link — open it to finish signing in.64 </>65 }66 footer={67 <button type="button" className="text-accent underline-offset-4 hover:underline" onClick={() => setUnverified(false)}>68 Back to sign in69 </button>70 }71 >72 <div className="space-y-3">73 <ResendVerification email={email.trim()} initialCooldown={RESEND_COOLDOWN_S} className="w-full" />74 <p className="text-center text-xs text-fg-subtle">Check your spam folder if nothing arrives within a minute.</p>75 </div>76 </AuthCard>77 );78 }7980 return (81 <AuthCard82 title="Sign in"83 description="Welcome back. Your keys and conversations are where you left them."84 footer={85 <>86 New to PolyLLM?{" "}87 <Link href="/signup" className="font-medium text-accent underline-offset-4 hover:underline">88 Create an account89 </Link>90 </>91 }92 >93 <form onSubmit={onSubmit} noValidate className="space-y-4">94 {notice === "reset" ? <FormNotice tone="success">Your password was updated. Sign in with the new one.</FormNotice> : null}95 {notice === "verified" ? <FormNotice tone="success">Email verified. You can sign in now.</FormNotice> : null}96 {notice === "signed-out" ? <FormNotice>You have been signed out.</FormNotice> : null}97 <FormError id="login-error">{error}</FormError>98 <Field label="Email" htmlFor="login-email" error={fieldErrors.email}>99 <Input100 id="login-email"101 name="email"102 type="email"103 inputMode="email"104 autoComplete="email"105 autoCapitalize="none"106 spellCheck={false}107 required108 value={email}109 onChange={(e) => setEmail(e.target.value)}110 placeholder="you@company.com"111 aria-invalid={!!fieldErrors.email || undefined}112 aria-describedby={error ? "login-error" : undefined}113 autoFocus114 />115 </Field>116 <Field label="Password" htmlFor="login-password" error={fieldErrors.password}>117 <PasswordInput id="login-password" name="password" autoComplete="current-password" required value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Your password" invalid={!!fieldErrors.password} />118 <div className="flex justify-end">119 <Link href={email ? `/forgot-password?email=${encodeURIComponent(email)}` : "/forgot-password"} className="text-xs text-fg-muted underline-offset-4 hover:text-fg hover:underline">120 Forgot your password?121 </Link>122 </div>123 </Field>124 <Button type="submit" size="lg" className="mt-2 w-full" loading={loading}>125 Sign in126 </Button>127 </form>128 </AuthCard>129 );130}131