SPB Git forge

spb/fetcha

Public
11commits 1branches 0releases
1.5 MBsize
maindefault branch
16 days agolast push
TypeScript 97.5% SQL 1.4% Python 0.8%
3.5 KB · 75 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import Link from "next/link";4import { useRouter } from "next/navigation";5import { authClient } from "@/lib/auth-client";6import { AuthCard } from "@/components/auth/auth-card";7import { PasswordInput } from "@/components/auth/password-input";8import { Button } from "@/components/ui/button";9import { Input } from "@/components/ui/input";10import { Field, FieldError, Label } from "@/components/ui/label";11import { Alert } from "@/components/ui/alert";1213export function LoginForm({ next, verified, reset }: { next?: string; verified?: boolean; reset?: boolean }) {14  const router = useRouter();15  const [email, setEmail] = React.useState("");16  const [password, setPassword] = React.useState("");17  const [remember, setRemember] = React.useState(true);18  const [error, setError] = React.useState<string | null>(null);19  const [loading, setLoading] = React.useState(false);2021  async function onSubmit(e: React.FormEvent) {22    e.preventDefault();23    setError(null);24    setLoading(true);25    const { error } = await authClient.signIn.email({ email: email.trim(), password, rememberMe: remember, callbackURL: next && next.startsWith("/") ? next : "/dashboard" });26    setLoading(false);27    if (error) {28      setError(error.status === 429 ? "Too many attempts. Wait a minute and try again." : error.message ?? "Invalid email or password.");29      return;30    }31    router.push(next && next.startsWith("/") ? next : "/dashboard");32    router.refresh();33  }3435  return (36    <AuthCard37      title="Welcome back"38      description="Log in to your Fetcha account. Fetcha is a private platform: accounts are created by invitation."39      footer={40        <>41          Invitation only — ask your administrator.{" "}42          <Link href={`/signup${next ? `?next=${encodeURIComponent(next)}` : ""}`} className="font-medium text-fg underline-offset-4 hover:underline">43            Invited? Create your account44          </Link>45        </>46      }47    >48      <form onSubmit={onSubmit} className="grid gap-4" noValidate>49        {verified ? <Alert variant="success">Your email is verified. Log in to continue.</Alert> : null}50        {reset ? <Alert variant="success">Your password was updated. Log in with the new one.</Alert> : null}51        {error ? <Alert variant="danger">{error}</Alert> : null}52        <Field>53          <Label htmlFor="email">Email</Label>54          <Input id="email" name="email" type="email" autoComplete="email" required value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@company.com" autoFocus />55        </Field>56        <Field>57          <div className="flex items-center justify-between">58            <Label htmlFor="password">Password</Label>59            <Link href="/forgot-password" className="text-[12.5px] text-fg-muted underline-offset-4 hover:text-fg hover:underline">Forgot password?</Link>60          </div>61          <PasswordInput id="password" name="password" autoComplete="current-password" required value={password} onChange={(e) => setPassword(e.target.value)} />62          <FieldError />63        </Field>64        <label className="flex items-center gap-2 text-[13px] text-fg-muted">65          <input type="checkbox" checked={remember} onChange={(e) => setRemember(e.target.checked)} className="size-4 rounded border-border accent-[var(--accent)]" />66          Remember this device67        </label>68        <Button type="submit" size="lg" loading={loading} className="mt-1 w-full">69          Log in70        </Button>71      </form>72    </AuthCard>73  );74}75