145 lines
4.5 KiB
TypeScript
145 lines
4.5 KiB
TypeScript
"use client";
|
||
|
||
import { useState } from "react";
|
||
import Link from "next/link";
|
||
import { useRouter } from "next/navigation";
|
||
import { UserPlusIcon } from "lucide-react";
|
||
import { authClient } from "@/lib/auth-client";
|
||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@/components/ui/field";
|
||
import { Input } from "@/components/ui/input";
|
||
import { Spinner } from "@/components/ui/spinner";
|
||
|
||
export function SignUpForm({
|
||
callbackURL,
|
||
requireInvite,
|
||
initialCode = "",
|
||
}: {
|
||
callbackURL: string;
|
||
requireInvite: boolean;
|
||
initialCode?: string;
|
||
}) {
|
||
const router = useRouter();
|
||
const [name, setName] = useState("");
|
||
const [email, setEmail] = useState("");
|
||
const [password, setPassword] = useState("");
|
||
const [inviteCode, setInviteCode] = useState(initialCode);
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
async function submit(event: React.FormEvent<HTMLFormElement>) {
|
||
event.preventDefault();
|
||
if (requireInvite && !inviteCode.trim()) {
|
||
setError("An invite code is required");
|
||
return;
|
||
}
|
||
setLoading(true);
|
||
setError(null);
|
||
const result = await authClient.signUp.email({
|
||
name,
|
||
email,
|
||
password,
|
||
callbackURL,
|
||
});
|
||
if (result.error) {
|
||
setError(result.error.message ?? "Unable to create account");
|
||
setLoading(false);
|
||
return;
|
||
}
|
||
if (inviteCode.trim()) {
|
||
router.push(`/invitations/${encodeURIComponent(inviteCode.trim())}`);
|
||
router.refresh();
|
||
return;
|
||
}
|
||
router.push(callbackURL);
|
||
router.refresh();
|
||
}
|
||
|
||
return (
|
||
<form className="flex flex-col gap-5" onSubmit={submit}>
|
||
<FieldGroup>
|
||
<Field>
|
||
<FieldLabel htmlFor="name">Name</FieldLabel>
|
||
<Input
|
||
id="name"
|
||
autoComplete="name"
|
||
required
|
||
value={name}
|
||
onChange={(event) => setName(event.target.value)}
|
||
className="tap-target"
|
||
/>
|
||
</Field>
|
||
<Field>
|
||
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||
<Input
|
||
id="email"
|
||
type="email"
|
||
autoComplete="email"
|
||
required
|
||
value={email}
|
||
onChange={(event) => setEmail(event.target.value)}
|
||
className="tap-target"
|
||
/>
|
||
</Field>
|
||
<Field>
|
||
<FieldLabel htmlFor="password">Password</FieldLabel>
|
||
<Input
|
||
id="password"
|
||
type="password"
|
||
autoComplete="new-password"
|
||
minLength={process.env.NODE_ENV === "production" ? 10 : 5}
|
||
required
|
||
value={password}
|
||
onChange={(event) => setPassword(event.target.value)}
|
||
className="tap-target"
|
||
/>
|
||
<FieldDescription>Use at least 10 characters in production.</FieldDescription>
|
||
</Field>
|
||
{initialCode.length > 4 ? <p className="text-sm text-muted-foreground">Your invitation is attached. You’ll confirm your access after creating your account.</p> : <Field>
|
||
<FieldLabel htmlFor="invite">
|
||
Invite code {requireInvite ? "" : "(optional)"}
|
||
</FieldLabel>
|
||
<Input
|
||
id="invite"
|
||
value={inviteCode}
|
||
onChange={(event) =>
|
||
setInviteCode(event.target.value.toUpperCase().replace(/[^A-Z0-9]/g, ""))
|
||
}
|
||
placeholder="AB12"
|
||
minLength={4}
|
||
maxLength={4}
|
||
pattern="[A-Za-z0-9]{4}"
|
||
autoCapitalize="characters"
|
||
autoCorrect="off"
|
||
className="tap-target"
|
||
required={requireInvite}
|
||
/>
|
||
</Field>}
|
||
</FieldGroup>
|
||
{error ? (
|
||
<Alert variant="destructive">
|
||
<AlertDescription>{error}</AlertDescription>
|
||
</Alert>
|
||
) : null}
|
||
<Button type="submit" size="lg" className="tap-target w-full" disabled={loading}>
|
||
{loading ? (
|
||
<Spinner data-icon="inline-start" />
|
||
) : (
|
||
<UserPlusIcon data-icon="inline-start" />
|
||
)}
|
||
Create account
|
||
</Button>
|
||
<p className="text-center text-sm text-muted-foreground">
|
||
Already have an account?{" "}
|
||
<Link
|
||
href={`/sign-in?callbackURL=${encodeURIComponent(callbackURL)}`}
|
||
className="font-medium text-primary hover:underline"
|
||
>
|
||
Sign in
|
||
</Link>
|
||
</p>
|
||
</form>
|
||
);
|
||
}
|