import React, { useEffect, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import api from '../utils/api'; import { Card } from '../components/ui/card'; import { Button } from '../components/ui/button'; import { Input } from '../components/ui/input'; import { Label } from '../components/ui/label'; import { Alert, AlertDescription } from '../components/ui/alert'; import { Badge } from '../components/ui/badge'; import { toast } from 'sonner'; import { Loader2, Mail, Shield, CheckCircle, XCircle, Calendar } from 'lucide-react'; const AcceptInvitation = () => { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const { login } = useAuth(); const [token, setToken] = useState(null); const [invitation, setInvitation] = useState(null); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); const [success, setSuccess] = useState(false); const [successUser, setSuccessUser] = useState(null); const [error, setError] = useState(null); const [formData, setFormData] = useState({ password: '', confirmPassword: '', first_name: '', last_name: '', phone: '', address: '', city: '', state: '', zipcode: '', date_of_birth: '' }); const [formErrors, setFormErrors] = useState({}); useEffect(() => { const invitationToken = searchParams.get('token'); if (!invitationToken) { setError('Invalid invitation link. No token provided.'); setLoading(false); return; } setToken(invitationToken); verifyInvitation(invitationToken); }, [searchParams]); const verifyInvitation = async (invitationToken) => { try { const response = await api.get(`/invitations/verify/${invitationToken}`); setInvitation(response.data); // Pre-fill form with invitation data setFormData(prev => ({ ...prev, first_name: response.data.first_name || '', last_name: response.data.last_name || '', phone: response.data.phone || '' })); setLoading(false); } catch (error) { setError(error.response?.data?.detail || 'Invalid or expired invitation token'); setLoading(false); } }; const handleChange = (field, value) => { setFormData(prev => ({ ...prev, [field]: value })); // Clear error when user starts typing if (formErrors[field]) { setFormErrors(prev => ({ ...prev, [field]: null })); } }; const validate = () => { const newErrors = {}; if (!formData.password || formData.password.length < 8) { newErrors.password = 'Password must be at least 8 characters'; } if (formData.password !== formData.confirmPassword) { newErrors.confirmPassword = 'Passwords do not match'; } if (!formData.first_name) { newErrors.first_name = 'First name is required'; } if (!formData.last_name) { newErrors.last_name = 'Last name is required'; } if (!formData.phone) { newErrors.phone = 'Phone is required'; } setFormErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async (e) => { e.preventDefault(); if (!validate()) { return; } setSubmitting(true); try { // Prepare payload const payload = { token, password: formData.password, first_name: formData.first_name, last_name: formData.last_name, phone: formData.phone }; // Add optional fields if provided if (formData.address) payload.address = formData.address; if (formData.city) payload.city = formData.city; if (formData.state) payload.state = formData.state; if (formData.zipcode) payload.zipcode = formData.zipcode; if (formData.date_of_birth) payload.date_of_birth = formData.date_of_birth; // Accept invitation const response = await api.post('/invitations/accept', payload); // Auto-login with returned token const { access_token, user } = response.data; localStorage.setItem('token', access_token); // Call login to update auth context if (login) { await login(invitation.email, formData.password); } // Show success state setSuccessUser(user); setSuccess(true); // Auto-redirect after 3 seconds setTimeout(() => { if (user.role === 'admin' || user.role === 'superadmin') { navigate('/admin'); } else { navigate('/dashboard'); } }, 3000); } catch (error) { const errorMessage = error.response?.data?.detail || 'Failed to accept invitation'; toast.error(errorMessage); } finally { setSubmitting(false); } }; const getRoleBadge = (role) => { const config = { superadmin: { label: 'Superadmin', className: 'bg-brand-purple text-white' }, admin: { label: 'Admin', className: 'bg-[var(--green-light)] text-white' }, member: { label: 'Member', className: 'bg-[var(--neutral-800)] text-[var(--purple-ink)]' } }; const roleConfig = config[role] || { label: role, className: 'bg-gray-500 text-white' }; return ( {roleConfig.label} ); }; if (loading) { return (

Verifying your invitation...

); } if (error) { return (

Invalid Invitation

{error}

); } if (success) { const redirectPath = successUser?.role === 'admin' || successUser?.role === 'superadmin' ? '/admin' : '/dashboard'; return (
{/* Success Animation */}
{/* Success Message */}

Welcome to LOAF! 🎉

Your account has been created successfully.

{/* User Info Card */}

Name

{successUser?.first_name} {successUser?.last_name}

Email

{successUser?.email}

Role

{getRoleBadge(successUser?.role)}

Status

{successUser?.status}
{/* Redirect Info */}

Redirecting you to your dashboard in 3 seconds...

{/* Manual Continue Button */}
); } return (
{/* Header */}

Welcome to LOAF!

Complete your profile to accept the invitation

{/* Invitation Details */}

Email Address

{invitation?.email}

Role

{getRoleBadge(invitation?.role)}

Invitation Expires

{invitation?.expires_at ? new Date(invitation.expires_at).toLocaleString() : 'N/A'}

{/* Form */}
{/* Password Fields */}
handleChange('password', e.target.value)} className="rounded-xl border-2 border-[var(--neutral-800)] focus:border-brand-purple " placeholder="Minimum 8 characters" /> {formErrors.password && (

{formErrors.password}

)}
handleChange('confirmPassword', e.target.value)} className="rounded-xl border-2 border-[var(--neutral-800)] focus:border-brand-purple " placeholder="Re-enter password" /> {formErrors.confirmPassword && (

{formErrors.confirmPassword}

)}
{/* Name Fields */}
handleChange('first_name', e.target.value)} className="rounded-xl border-2 border-[var(--neutral-800)] focus:border-brand-purple " placeholder="Jane" /> {formErrors.first_name && (

{formErrors.first_name}

)}
handleChange('last_name', e.target.value)} className="rounded-xl border-2 border-[var(--neutral-800)] focus:border-brand-purple " placeholder="Doe" /> {formErrors.last_name && (

{formErrors.last_name}

)}
{/* Phone */}
handleChange('phone', e.target.value)} className="rounded-xl border-2 border-[var(--neutral-800)] focus:border-brand-purple " placeholder="(555) 123-4567" /> {formErrors.phone && (

{formErrors.phone}

)}
{/* Optional Fields Section */} {invitation?.role === 'member' && ( <>

Additional Information (Optional)

{/* Address */}
handleChange('address', e.target.value)} className="rounded-xl border-2 border-[var(--neutral-800)] focus:border-brand-purple " placeholder="123 Main St" />
{/* City, State, Zipcode */}
handleChange('city', e.target.value)} className="rounded-xl border-2 border-[var(--neutral-800)] focus:border-brand-purple " placeholder="San Francisco" />
handleChange('state', e.target.value)} className="rounded-xl border-2 border-[var(--neutral-800)] focus:border-brand-purple " placeholder="CA" maxLength={2} />
handleChange('zipcode', e.target.value)} className="rounded-xl border-2 border-[var(--neutral-800)] focus:border-brand-purple " placeholder="94102" />
{/* Date of Birth */}
handleChange('date_of_birth', e.target.value)} className="rounded-xl border-2 border-[var(--neutral-800)] focus:border-brand-purple " />
)}
{/* Submit Button */}
{/* Footer Note */}

Already have an account?{' '}

); }; export default AcceptInvitation;