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%
5.2 KB · 131 lines tsx
Raw Blame History
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&apos;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