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 { 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