"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { Loader2, UserPlus } from "lucide-react"; import { z } from "zod"; import { FormField } from "@/components/ui/form-field"; import { PasswordField } from "@/components/ui/password-field"; import { PasswordStrengthMeter } from "@/components/ui/password-strength-meter"; import { TurnstileWidget, type TurnstileWidgetHandle } from "@/components/ui/turnstile-widget"; import { authErrorMessage, useCurrentUser, useRegister, useSystemStatus } from "@/hooks/use-auth"; const registerSchema = z.object({ displayName: z.string().min(1, "Enter your name"), email: z.string().email("Enter a valid email address"), password: z .string() .min(10, "Must be at least 10 characters") .refine((v) => /[a-zA-Z]/.test(v) && /\d/.test(v), { message: "Must contain at least one letter and one digit", }), }); type RegisterForm = z.infer; export default function RegisterPage() { const router = useRouter(); const registerMutation = useRegister(); const { data: currentUser } = useCurrentUser(); const { data: status } = useSystemStatus(); const turnstileRef = useRef(null); const [turnstileToken, setTurnstileToken] = useState(null); const { register, handleSubmit, watch, formState: { errors }, } = useForm({ resolver: zodResolver(registerSchema) }); // Mirrors the backend's turnstile_required(is_localhost, settings) gate - // skipped on loopback regardless of auth mode. const turnstileRequired = status ? !status.is_localhost : false; useEffect(() => { if (currentUser) { router.replace("/dashboard"); } }, [currentUser, router]); const onSubmit = handleSubmit(async (values) => { try { await registerMutation.mutateAsync({ email: values.email, password: values.password, display_name: values.displayName, turnstile_token: turnstileToken ?? undefined, }); router.push(`/verify-email?email=${encodeURIComponent(values.email)}`); } catch { // Surfaced via registerMutation.isError below. } finally { // Tokens are single-use - always reset after a submit attempt, // success or failure, so a retry never reuses a stale token. turnstileRef.current?.reset(); setTurnstileToken(null); } }); return (

Create your account

Start monitoring your competitors.

{turnstileRequired && ( )} {registerMutation.isError && (

{authErrorMessage(registerMutation.error)}

)}

Already have an account?{" "} Sign in

); }