1"use client";
2
3import { useState } from "react";
4import { useRouter } from "next/navigation";
5import Link from "next/link";
6import { useForm } from "react-hook-form";
7import { zodResolver } from "@hookform/resolvers/zod";
8import { z } from "zod";
9import { authApi } from "@/lib/api";
10import { authStore } from "@/lib/auth-store";
11import { toast } from "@/stores/ui-store";
12import { Field, FormError, Input, SubmitButton } from "@/components/ui/form";
13
14const registerSchema = z
15 .object({
16 name: z.string().min(2, "Name must be at least 2 characters").max(80),
17 email: z.string().email("Enter a valid email address"),
18 password: z
19 .string()
20 .min(10, "Password must be at least 10 characters")
21 .regex(/[a-zA-Z]/, "Must contain a letter")
22 .regex(/[0-9]/, "Must contain a number"),
23 workspaceName: z.string().min(2, "Workspace name must be at least 2 characters").max(50)
24 });
25type RegisterValues = z.infer<typeof registerSchema>;
26
27export default function RegisterPage() {
28 const router = useRouter();
29 const [serverError, setServerError] = useState<string | null>(null);
30
31 const {
32 register,
33 handleSubmit,
34 formState: { errors, isSubmitting }
35 } = useForm<RegisterValues>({
36 resolver: zodResolver(registerSchema),
37 defaultValues: { name: "", email: "", password: "", workspaceName: "" }
38 });
39
40 const onSubmit = async (values: RegisterValues) => {
41 setServerError(null);
42 try {
43
44
45
46 const result = await authApi.register({
47 name: values.name,
48 email: values.email,
49 password: values.password,
50 workspaceName: values.workspaceName
51 });
52 authStore.setUser(result.user, result.accessToken);
53 toast("success", "Workspace created", `Welcome to ${values.workspaceName}!`);
54
55
56
57 router.replace(`/w/${result.workspace.id}`);
58 } catch (err) {
59 setServerError(err instanceof Error ? err.message : "Registration failed. Please try again.");
60 }
61 };
62
63 return (
64 <div className="space-y-5">
65 <div>
66 <h1 className="text-xl font-semibold">Create your account</h1>
67 <p className="mt-1 text-sm text-muted-foreground">
68 You'll also get a private workspace to start in.
69 </p>
70 </div>
71
72 <form onSubmit={handleSubmit(onSubmit)} className="space-y-4" noValidate>
73 <FormError message={serverError} data-testid="auth-error" />
74 <Field label="Your name" htmlFor="name" error={errors.name?.message}>
75 <Input id="name" autoComplete="name" placeholder="Ada Lovelace" {...register("name")} />
76 </Field>
77 <Field label="Email" htmlFor="reg-email" error={errors.email?.message}>
78 <Input
79 id="reg-email"
80 type="email"
81 autoComplete="email"
82 placeholder="you@company.com"
83 data-testid="auth-email"
84 {...register("email")}
85 />
86 </Field>
87 <Field
88 label="Password"
89 htmlFor="password"
90 error={errors.password?.message}
91 hint="At least 10 characters, with a letter and a number."
92 >
93 <Input
94 id="password"
95 type="password"
96 autoComplete="new-password"
97 data-testid="auth-password"
98 {...register("password")}
99 />
100 </Field>
101 <Field label="Workspace name" htmlFor="workspace" error={errors.workspaceName?.message}>
102 <Input id="workspace" placeholder="Acme Inc." {...register("workspaceName")} />
103 </Field>
104 <SubmitButton busy={isSubmitting} data-testid="auth-submit">
105 Create account
106 </SubmitButton>
107 </form>
108
109 <p className="text-center text-sm text-muted-foreground">
110 Already have an account?{" "}
111 <Link href="/login" className="text-foreground underline underline-offset-2">
112 Sign in
113 </Link>
114 </p>
115 </div>
116 );
117}
118