import React, { useState, useEffect } from 'react';
import { getAuthToken } from '@/lib/serverComm';
import { getApiBaseUrl } from '@/lib/api-url';
import {
RefreshCcw,
CheckCircle,
XCircle,
AlertTriangle,
Clock,
Loader2,
Mail,
MoreVertical,
Send,
} from 'lucide-react';
import { ic } from '@/hooks/useNavItems';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { toast } from '@/lib/toast';
import { formatDistanceToNow } from 'date-fns';
export default function PlatformInvitations() {
const [invitations, setInvitations] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [resending, setResending] = useState<string | null>(null);
const [selectedTab, setSelectedTab] = useState('pending');
const [currentPage, setCurrentPage] = useState(1);
const [pagination, setPagination] = useState({ total: 0, totalPages: 1, limit: 50 });
const [serverStats, setServerStats] = useState({ pending: 0, accepted: 0, expired: 0 });
useEffect(() => {
fetchInvitations(1, selectedTab);
}, [selectedTab]);
const getAuthHeaders = () => {
const token = getAuthToken();
return {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
};
};
const fetchInvitations = async (page = currentPage, status = selectedTab) => {
try {
setLoading(true);
const apiUrl = getApiBaseUrl() || '';
const response = await fetch(
`${apiUrl}/api/v1/protected/admin/invitations?page=${page}&status=${status}&limit=50`,
{ headers: getAuthHeaders() }
);
if (!response.ok) throw new Error('Failed to fetch invitations');
const data = await response.json();
if (data.invitations) {
setInvitations(data.invitations);
setPagination(data.pagination);
setServerStats(data.stats);
setCurrentPage(data.pagination.page);
} else {
setInvitations(Array.isArray(data) ? data : []);
}
} catch (error) {
console.error('Failed to fetch invitations:', error);
toast.error('Failed to load invitations');
} finally {
setLoading(false);
}
};
const resendInvitation = async (invitation: any) => {
try {
setResending(invitation.id);
const apiUrl = getApiBaseUrl() || '';
const response = await fetch(
`${apiUrl}/api/v1/protected/admin/invitations/${invitation.id}/resend`,
{ method: 'POST', headers: getAuthHeaders() }
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Failed to resend invitation');
}
toast.success(`Invitation resent to ${invitation.email}`);
fetchInvitations();
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Failed to resend invitation');
} finally {
setResending(null);
}
};
const getStatusBadge = (invitation: any) => {
if (invitation.status === 'accepted') {
return (
<Badge className="bg-emerald-500/5 text-emerald-600 border-emerald-500/10">
{ic(CheckCircle, 'h-3 w-3 mr-1')}Accepted
</Badge>
);
}
if (invitation.status === 'cancelled') {
return (
<Badge variant="secondary" className="bg-zinc-500/5 text-zinc-500">
{ic(XCircle, 'h-3 w-3 mr-1')}Cancelled
</Badge>
);
}
if (invitation.isExpired) {
return (
<Badge variant="destructive" className="bg-red-500/5 text-red-600 border-red-200">
{ic(AlertTriangle, 'h-3 w-3 mr-1')}Expired
</Badge>
);
}
return (
<Badge className="bg-amber-500/5 text-amber-600 border-amber-500/10">
{ic(Clock, 'h-3 w-3 mr-1')}Pending
</Badge>
);
};
const handlePageChange = (newPage: number) => {
if (newPage >= 1 && newPage <= pagination.totalPages) {
fetchInvitations(newPage, selectedTab);
}
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold tracking-tight text-foreground">Platform Invitations</h2>
<p className="text-muted-foreground">View and manage all staff invitations across all clinics</p>
</div>
<Button variant="outline" onClick={() => fetchInvitations(1)} disabled={loading} className="gap-2">
{ic(RefreshCcw, `h-4 w-4 ${loading ? 'animate-spin' : ''}`)}
Refresh
</Button>
</div>
<div className="grid gap-4 md:grid-cols-3">
<Card className="cursor-pointer hover:border-amber-500/50" onClick={() => setSelectedTab('pending')}>
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">Pending</p>
<p className="text-2xl font-bold text-foreground">{serverStats.pending}</p>
</div>
{ic(Clock, 'h-8 w-8 text-amber-500')}
</div>
</CardContent>
</Card>
<Card className="cursor-pointer hover:border-green-500/50" onClick={() => setSelectedTab('accepted')}>
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">Accepted</p>
<p className="text-2xl font-bold text-foreground">{serverStats.accepted}</p>
</div>
{ic(CheckCircle, 'h-8 w-8 text-green-500')}
</div>
</CardContent>
</Card>
<Card className="cursor-pointer hover:border-red-500/50" onClick={() => setSelectedTab('expired')}>
<CardContent className="pt-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">Expired</p>
<p className="text-2xl font-bold text-foreground">{serverStats.expired}</p>
</div>
{ic(AlertTriangle, 'h-8 w-8 text-red-500')}
</div>
</CardContent>
</Card>
</div>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
{ic(Mail, 'h-5 w-5')}All Invitations
</CardTitle>
<CardDescription>Invitations from all clinics on the platform</CardDescription>
</CardHeader>
<CardContent>
<Tabs value={selectedTab} onValueChange={setSelectedTab}>
<TabsList className="mb-4">
<TabsTrigger value="pending">Pending ({serverStats.pending})</TabsTrigger>
<TabsTrigger value="accepted">Accepted</TabsTrigger>
<TabsTrigger value="expired">Expired</TabsTrigger>
<TabsTrigger value="all">All</TabsTrigger>
</TabsList>
{['pending', 'accepted', 'expired', 'all'].map((tab) => (
<TabsContent key={tab} value={tab}>
{loading ? (
<div className="flex justify-center py-12">
{ic(Loader2, 'h-8 w-8 animate-spin text-muted-foreground')}
</div>
) : invitations.length === 0 ? (
<div className="text-center py-12">
{ic(Mail, 'h-12 w-12 text-muted-foreground mx-auto mb-4')}
<p className="text-muted-foreground">No invitations</p>
</div>
) : (
<>
<Table>
<TableHeader>
<TableRow>
<TableHead>Recipient</TableHead>
<TableHead>Email</TableHead>
<TableHead>Clinic</TableHead>
<TableHead>Role</TableHead>
<TableHead>Status</TableHead>
<TableHead>Sent</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{invitations.map((inv) => (
<TableRow key={inv.id}>
<TableCell className="font-medium text-foreground">
{inv.firstName} {inv.lastName}
</TableCell>
<TableCell className="text-muted-foreground">{inv.email}</TableCell>
<TableCell>
<Badge variant="outline">{inv.clinicName || 'Unknown'}</Badge>
</TableCell>
<TableCell><Badge>{inv.role}</Badge></TableCell>
<TableCell>{getStatusBadge(inv)}</TableCell>
<TableCell className="text-muted-foreground text-sm">
{formatDistanceToNow(new Date(inv.createdAt), { addSuffix: true })}
</TableCell>
<TableCell className="text-right">
{(inv.status === 'pending' || inv.isExpired) && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
{ic(MoreVertical, 'h-4 w-4')}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => resendInvitation(inv)}
disabled={resending === inv.id}
>
{resending === inv.id
? ic(Loader2, 'h-4 w-4 mr-2 animate-spin')
: ic(Send, 'h-4 w-4 mr-2')}
Resend Invitation
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{pagination.totalPages > 1 && (
<div className="flex items-center justify-between px-2 py-4 border-t mt-4">
<p className="text-sm text-muted-foreground">
Showing {(currentPage - 1) * pagination.limit + 1} to{' '}
{Math.min(currentPage * pagination.limit, pagination.total)} of{' '}
{pagination.total} invitations
</p>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1 || loading}
>
Previous
</Button>
<span className="text-sm font-medium px-2">
Page {currentPage} of {pagination.totalPages}
</span>
<Button
variant="outline"
size="sm"
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === pagination.totalPages || loading}
>
Next
</Button>
</div>
</div>
)}
</>
)}
</TabsContent>
))}
</Tabs>
</CardContent>
</Card>
</div>
);
}