import React, { useState, useEffect } from 'react'; import { useSearchParams, useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useInvitationDetails, useAcceptInvitation, useDeclineInvitation, } from '../hooks/useInvitations'; import { useAuth } from '../hooks/useAuth'; import { Loader2, CheckCircle, XCircle, Building2, Mail, User, Lock, AlertCircle, Eye, EyeOff, } from 'lucide-react'; const AcceptInvitePage: React.FC = () => { const { t } = useTranslation(); const [searchParams] = useSearchParams(); const { token: pathToken } = useParams<{ token: string }>(); const navigate = useNavigate(); // Support both path parameter (/accept-invite/:token) and query parameter (?token=...) const token = pathToken || searchParams.get('token'); const { data: invitation, isLoading, error } = useInvitationDetails(token); const acceptInvitationMutation = useAcceptInvitation(); const declineInvitationMutation = useDeclineInvitation(); const { setTokens } = useAuth(); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [formError, setFormError] = useState(''); const [accepted, setAccepted] = useState(false); const [declined, setDeclined] = useState(false); const handleAccept = async (e: React.FormEvent) => { e.preventDefault(); setFormError(''); // Validate if (!firstName.trim()) { setFormError(t('acceptInvite.firstNameRequired', 'First name is required')); return; } if (!password || password.length < 8) { setFormError(t('acceptInvite.passwordMinLength', 'Password must be at least 8 characters')); return; } if (password !== confirmPassword) { setFormError(t('acceptInvite.passwordsMustMatch', 'Passwords do not match')); return; } try { const result = await acceptInvitationMutation.mutateAsync({ token: token!, firstName: firstName.trim(), lastName: lastName.trim(), password, invitationType: invitation?.invitation_type, }); // Set auth tokens and redirect to dashboard setTokens(result.access, result.refresh); setAccepted(true); // Redirect after a short delay setTimeout(() => { navigate('/'); }, 2000); } catch (err: any) { setFormError(err.response?.data?.error || t('acceptInvite.acceptFailed', 'Failed to accept invitation')); } }; const handleDecline = async () => { if (!confirm(t('acceptInvite.confirmDecline', 'Are you sure you want to decline this invitation?'))) { return; } try { await declineInvitationMutation.mutateAsync({ token: token!, invitationType: invitation?.invitation_type }); setDeclined(true); } catch (err: any) { setFormError(err.response?.data?.error || t('acceptInvite.declineFailed', 'Failed to decline invitation')); } }; // No token provided if (!token) { return (
{t('acceptInvite.noToken', 'This invitation link is invalid. Please check your email for the correct link.')}
{t('acceptInvite.loading', 'Loading invitation...')}
{t( 'acceptInvite.expiredDescription', 'This invitation has expired or is no longer valid. Please contact the person who sent the invitation to request a new one.' )}
{t('acceptInvite.redirecting', 'Your account has been created. Redirecting to dashboard...')}
{t('acceptInvite.declinedDescription', "You've declined this invitation. You can close this page.")}
{t('acceptInvite.subtitle', 'Join the team and start scheduling')}
{t('acceptInvite.business', 'Business')}
{invitation.business_name}
{t('acceptInvite.invitedAs', 'Invited As')}
{invitation.role_display} •{' '} {invitation.email}
{t('acceptInvite.invitedBy', 'Invited by')} {invitation.invited_by}
)}