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 [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); toast.success('Welcome to LOAF! Your account has been created successfully.'); // Call login to update auth context if (login) { await login(invitation.email, formData.password); } // Redirect based on role if (user.role === 'admin' || user.role === 'superadmin') { navigate('/admin/dashboard'); } else { navigate('/dashboard'); } } 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-[#664fa3] text-white' }, admin: { label: 'Admin', className: 'bg-[#81B29A] text-white' }, member: { label: 'Member', className: 'bg-[#DDD8EB] text-[#422268]' } }; 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}

); } 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-[#ddd8eb] focus:border-[#664fa3]" placeholder="Minimum 8 characters" /> {formErrors.password && (

{formErrors.password}

)}
handleChange('confirmPassword', e.target.value)} className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" placeholder="Re-enter password" /> {formErrors.confirmPassword && (

{formErrors.confirmPassword}

)}
{/* Name Fields */}
handleChange('first_name', e.target.value)} className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" placeholder="John" /> {formErrors.first_name && (

{formErrors.first_name}

)}
handleChange('last_name', e.target.value)} className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" placeholder="Doe" /> {formErrors.last_name && (

{formErrors.last_name}

)}
{/* Phone */}
handleChange('phone', e.target.value)} className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" 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-[#ddd8eb] focus:border-[#664fa3]" placeholder="123 Main St" />
{/* City, State, Zipcode */}
handleChange('city', e.target.value)} className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" placeholder="San Francisco" />
handleChange('state', e.target.value)} className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" placeholder="CA" maxLength={2} />
handleChange('zipcode', e.target.value)} className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" placeholder="94102" />
{/* Date of Birth */}
handleChange('date_of_birth', e.target.value)} className="rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" />
)}
{/* Submit Button */}
{/* Footer Note */}

Already have an account?{' '}

); }; export default AcceptInvitation;