'use client'; import { useEffect, useMemo, useState } from 'react'; import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; import { Video, Loader2, KeyRound, UserPlus } from 'lucide-react'; import { signIn } from 'next-auth/react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { getSafeCallbackUrl } from '@/lib/safe-redirect'; export interface RegisterInvitation { email: string; inviterName: string; roleLabel: string; scopeLabel: string; targetName: string | null; } interface RegisterPageClientProps { requireInviteCode: boolean; googleEnabled: boolean; githubEnabled: boolean; invitation?: RegisterInvitation | null; /** Preview lookup was rate-limited, so `invitation` says nothing about its validity. */ invitationLookupThrottled?: boolean; } export default function RegisterPageClient({ requireInviteCode, googleEnabled, githubEnabled, invitation = null, invitationLookupThrottled = false, }: RegisterPageClientProps) { const router = useRouter(); const searchParams = useSearchParams(); const invitationToken = useMemo(() => searchParams.get('invitationToken') || '', [searchParams]); const invitedEmail = useMemo( () => invitation?.email || searchParams.get('email') || '', [invitation, searchParams] ); // Where to send the user once they are signed in — for invitations this points back // at /invitations/accept so they land on the workspace/project they were invited to // instead of the onboarding wizard. const callbackUrl = useMemo( () => getSafeCallbackUrl(searchParams.get('callbackUrl')), [searchParams] ); const isInvitationFlow = invitationToken.length > 0; const shouldShowInviteCode = requireInviteCode && !isInvitationFlow; const loginHref = callbackUrl === '/dashboard' ? '/login' : `/login?callbackUrl=${encodeURIComponent(callbackUrl)}`; const [isLoading, setIsLoading] = useState(false); const [oauthLoading, setOauthLoading] = useState(null); const [error, setError] = useState(''); const [formData, setFormData] = useState({ name: '', email: '', password: '', confirmPassword: '', inviteCode: '', }); useEffect(() => { if (!invitedEmail) return; setFormData((prev) => ({ ...prev, email: invitedEmail, })); }, [invitedEmail]); const handleChange = (e: React.ChangeEvent) => { setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value, })); setError(''); }; const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); setError(''); setIsLoading(true); if (formData.password !== formData.confirmPassword) { setError('Passwords do not match'); setIsLoading(false); return; } if (formData.password.length < 8) { setError('Password must be at least 8 characters'); setIsLoading(false); return; } try { const response = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: formData.name, email: formData.email, password: formData.password, inviteCode: shouldShowInviteCode ? formData.inviteCode || undefined : undefined, invitationToken: invitationToken || undefined, }), }); const data = await response.json(); if (!response.ok) { setError(data.error || 'Registration failed'); return; } const callbackParam = `&callbackUrl=${encodeURIComponent(callbackUrl)}`; if (data.data?.emailVerificationRequired) { router.push(`/verify-email?email=${encodeURIComponent(formData.email)}${callbackParam}`); } else { router.push(`/login?registered=true${callbackParam}`); } } catch { setError('Something went wrong. Please try again.'); } finally { setIsLoading(false); } }; const handleOAuthSignUp = async (provider: string) => { setOauthLoading(provider); setError(''); await signIn(provider, { callbackUrl }); }; const hasOAuth = googleEnabled || githubEnabled; const anyLoading = isLoading || oauthLoading !== null; return (
); }