Initial commit of Vellum, an event photo product for guest uploads, host moderation, and original-quality galleries.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
"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>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="invite">
|
||||
Invite code {requireInvite ? "" : "(optional)"}
|
||||
</FieldLabel>
|
||||
<Input
|
||||
id="invite"
|
||||
value={inviteCode}
|
||||
onChange={(event) => setInviteCode(event.target.value)}
|
||||
placeholder="VELLUM-…"
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user