import React, { useEffect, useState } from 'react'; import api from '../utils/api'; import { Card } from './ui/card'; import { Button } from './ui/button'; import { Badge } from './ui/badge'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from './ui/table'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from './ui/alert-dialog'; import { toast } from 'sonner'; import { Mail, Trash2, MailCheck, Clock } from 'lucide-react'; const PendingInvitationsTable = () => { const [invitations, setInvitations] = useState([]); const [loading, setLoading] = useState(true); const [revokeDialog, setRevokeDialog] = useState({ open: false, invitation: null }); const [resending, setResending] = useState(null); useEffect(() => { fetchInvitations(); }, []); const fetchInvitations = async () => { try { const response = await api.get('/admin/users/invitations?status=pending'); setInvitations(response.data); } catch (error) { toast.error('Failed to fetch invitations'); } finally { setLoading(false); } }; const handleResend = async (invitationId) => { setResending(invitationId); try { await api.post(`/admin/users/invitations/${invitationId}/resend`); toast.success('Invitation resent successfully'); fetchInvitations(); // Refresh list to show new expiry date } catch (error) { toast.error(error.response?.data?.detail || 'Failed to resend invitation'); } finally { setResending(null); } }; const handleRevoke = async () => { if (!revokeDialog.invitation) return; try { await api.delete(`/admin/users/invitations/${revokeDialog.invitation.id}`); toast.success('Invitation revoked'); setRevokeDialog({ open: false, invitation: null }); fetchInvitations(); // Refresh list } catch (error) { toast.error(error.response?.data?.detail || 'Failed to revoke invitation'); } }; 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} ); }; const isExpiringSoon = (expiresAt) => { const expiry = new Date(expiresAt); const now = new Date(); const hoursDiff = (expiry - now) / (1000 * 60 * 60); return hoursDiff < 24 && hoursDiff > 0; }; const formatDate = (dateString) => { const date = new Date(dateString); const now = new Date(); const hoursDiff = (date - now) / (1000 * 60 * 60); if (hoursDiff < 0) { return 'Expired'; } else if (hoursDiff < 24) { return `Expires in ${Math.round(hoursDiff)} hours`; } else { const daysDiff = Math.round(hoursDiff / 24); return `Expires in ${daysDiff} day${daysDiff > 1 ? 's' : ''}`; } }; if (loading) { return (

Loading invitations...

); } if (invitations.length === 0) { return (

No Pending Invitations

All invitations have been accepted or expired

); } return ( <> Email Name Role Invited Expires Actions {invitations.map((invitation) => ( {invitation.email} {invitation.first_name && invitation.last_name ? `${invitation.first_name} ${invitation.last_name}` : '-'} {getRoleBadge(invitation.role)} {new Date(invitation.invited_at).toLocaleDateString()}
{formatDate(invitation.expires_at)}
))}
{/* Revoke Confirmation Dialog */} setRevokeDialog({ open, invitation: null })}> Revoke Invitation Are you sure you want to revoke the invitation for{' '} {revokeDialog.invitation?.email}? This action cannot be undone. Cancel Revoke ); }; export default PendingInvitationsTable;