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.9 KB · 151 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import Link from "next/link";4import { 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 { PasswordInput } from "./password-input";11import { PasswordStrength, scorePassword } from "./password-strength";12import { ResendVerification, RESEND_COOLDOWN_S } from "./resend-verification";13import { humanAuthError, isValidEmail, passwordLengthError, type AuthClientError } from "./auth-errors";1415export function SignupForm() {16  const [name, setName] = React.useState("");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<{ name?: string; email?: string; password?: string }>({});22  const [sentTo, setSentTo] = React.useState<string | null>(null);2324  async function onSubmit(e: React.FormEvent<HTMLFormElement>) {25    e.preventDefault();26    setError(null);27    const fe: typeof fieldErrors = {};28    if (name.trim().length < 1) fe.name = "Tell us what to call you.";29    if (name.trim().length > 80) fe.name = "Keep it under 80 characters.";30    if (!isValidEmail(email)) fe.email = "Enter a valid email address.";31    const pwErr = passwordLengthError(password);32    if (pwErr) fe.password = pwErr;33    else if (scorePassword(password).score < 2) fe.password = "That password is too easy to guess. Try a longer or less common one.";34    setFieldErrors(fe);35    if (Object.keys(fe).length) return;3637    setLoading(true);38    try {39      const { error: err } = await authClient.signUp.email({ name: name.trim(), email: email.trim(), password, callbackURL: "/verify-email" });40      if (err) {41        setError(humanAuthError(err as AuthClientError, "signup"));42        return;43      }44      toast.success("Account created", "Check your inbox to verify your email.");45      setSentTo(email.trim());46    } catch {47      setError("We couldn't reach the server. Check your connection and try again.");48    } finally {49      setLoading(false);50    }51  }5253  if (sentTo) {54    return (55      <AuthCard56        icon={<MailCheck />}57        title="Check your inbox"58        description={59          <>60            We sent a verification link to <span className="font-medium text-fg">{sentTo}</span>. Open it to activate your account — the link is valid for 24 hours.61          </>62        }63        footer={64          <>65            Wrong address?{" "}66            <button type="button" className="font-medium text-accent underline-offset-4 hover:underline" onClick={() => setSentTo(null)}>67              Edit and try again68            </button>69          </>70        }71      >72        <div className="space-y-3">73          <ResendVerification email={sentTo} initialCooldown={RESEND_COOLDOWN_S} className="w-full" />74          <Button asChild variant="ghost" className="w-full">75            <Link href="/login">Back to sign in</Link>76          </Button>77          <p className="text-center text-xs text-fg-subtle">Nothing after a minute? Check your spam folder or try resending.</p>78        </div>79      </AuthCard>80    );81  }8283  return (84    <AuthCard85      title="Create your account"86      description="Free to use. You bring the API keys; we bring the workspace."87      footer={88        <>89          Already have an account?{" "}90          <Link href="/login" className="font-medium text-accent underline-offset-4 hover:underline">91            Sign in92          </Link>93        </>94      }95    >96      <form onSubmit={onSubmit} noValidate className="space-y-4">97        <FormError id="signup-error">{error}</FormError>98        <Field label="Name" htmlFor="signup-name" error={fieldErrors.name}>99          <Input id="signup-name" name="name" type="text" autoComplete="name" required maxLength={80} value={name} onChange={(e) => setName(e.target.value)} placeholder="Ada Lovelace" aria-invalid={!!fieldErrors.name || undefined} autoFocus />100        </Field>101        <Field label="Email" htmlFor="signup-email" error={fieldErrors.email}>102          <Input103            id="signup-email"104            name="email"105            type="email"106            inputMode="email"107            autoComplete="email"108            autoCapitalize="none"109            spellCheck={false}110            required111            value={email}112            onChange={(e) => setEmail(e.target.value)}113            placeholder="you@company.com"114            aria-invalid={!!fieldErrors.email || undefined}115          />116        </Field>117        <Field label="Password" htmlFor="signup-password" error={fieldErrors.password}>118          <PasswordInput119            id="signup-password"120            name="new-password"121            autoComplete="new-password"122            required123            minLength={10}124            maxLength={128}125            value={password}126            onChange={(e) => setPassword(e.target.value)}127            placeholder="10 to 128 characters"128            invalid={!!fieldErrors.password}129            aria-describedby="signup-password-strength"130          />131          <PasswordStrength password={password} id="signup-password-strength" />132        </Field>133        <Button type="submit" size="lg" className="mt-2 w-full" loading={loading}>134          Create account135        </Button>136        <p className="text-center text-xs leading-5 text-fg-subtle">137          By creating an account you agree to the{" "}138          <Link href="/terms" className="underline underline-offset-4 hover:text-fg">139            Terms140          </Link>{" "}141          and{" "}142          <Link href="/privacy" className="underline underline-offset-4 hover:text-fg">143            Privacy Policy144          </Link>145          .146        </p>147      </form>148    </AuthCard>149  );150}151