import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { securityApi } from '@/api/securityApi';
import { Monitor, Smartphone, Tablet, CircleDot, XCircle, Loader2, ShieldAlert } from 'lucide-react';
export default function ActiveSessions() {
const queryClient = useQueryClient();
const { data: sessions, isLoading } = useQuery({
queryKey: ['security-sessions'],
queryFn: securityApi.getSessions,
refetchOnWindowFocus: false,
});
const revokeMutation = useMutation({
mutationFn: (sessionId: string) => securityApi.revokeSession(sessionId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['security-sessions'] });
},
});
const revokeOtherMutation = useMutation({
mutationFn: () => securityApi.revokeOtherSessions(),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['security-sessions'] });
},
});
const inputClass =
'w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm';
if (isLoading) {
return (
<div className="bg-white rounded-xl border border-gray-200 p-6">
<div className="flex items-center gap-3 mb-4">
<Monitor className="h-5 w-5 text-gray-400" />
<h3 className="text-lg font-semibold text-gray-900">Active Sessions</h3>
</div>
<div className="flex items-center gap-2 text-gray-400">
<Loader2 className="h-4 w-4 animate-spin" />
<span className="text-sm">Loading sessions...</span>
</div>
</div>
);
}
const sessionList = sessions?.data || [];
const otherCount = sessionList.filter((s: any) => !s.is_current).length;
const getDeviceIcon = (ua: string) => {
const lower = ua.toLowerCase();
if (/mobile|android|iphone|ipad/.test(lower)) return <Smartphone className="h-4 w-4 text-gray-500" />;
if (/tablet/.test(lower)) return <Tablet className="h-4 w-4 text-gray-500" />;
return <Monitor className="h-4 w-4 text-gray-500" />;
};
const timeAgo = (iso: string) => {
const now = new Date();
const then = new Date(iso);
const diff = Math.floor((now.getTime() - then.getTime()) / 1000);
if (diff < 60) return 'just now';
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
return `${Math.floor(diff / 86400)}d ago`;
};
return (
<div className="bg-white rounded-xl border border-gray-200 p-6">
{/* Header */}
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<Monitor className="h-5 w-5 text-gray-600" />
<h3 className="text-lg font-semibold text-gray-900">Active Sessions</h3>
</div>
{otherCount > 0 && (
<button
onClick={() => revokeOtherMutation.mutate()}
disabled={revokeOtherMutation.isPending}
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-red-700 bg-red-50 hover:bg-red-100 rounded-lg transition-colors disabled:opacity-50"
>
{revokeOtherMutation.isPending ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<ShieldAlert className="h-3.5 w-3.5" />
)}
Sign out other sessions ({otherCount})
</button>
)}
</div>
<p className="text-sm text-gray-500 mb-4">
Manage your active sessions across devices.
</p>
{/* Session list */}
<div className="space-y-3">
{sessionList.map((session: any) => (
<div
key={session.id}
className={`flex items-center justify-between p-3 rounded-lg border ${
session.is_current
? 'border-blue-200 bg-blue-50'
: 'border-gray-200 bg-white'
}`}
>
<div className="flex items-center gap-3 min-w-0">
{getDeviceIcon(session.user_agent || '')}
<div className="min-w-0">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-900 truncate">
{session.device_info || 'Unknown device'}
</span>
{session.is_current && (
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-700 flex-shrink-0">
<CircleDot className="h-2.5 w-2.5" />
Current
</span>
)}
</div>
<div className="flex items-center gap-2 text-xs text-gray-500 mt-0.5">
<span>{session.ip_address}</span>
{session.last_seen && <span>· {timeAgo(session.last_seen)}</span>}
</div>
</div>
</div>
{!session.is_current && (
<button
onClick={() => revokeMutation.mutate(session.session_id)}
disabled={revokeMutation.isPending}
className="ml-2 p-1.5 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-md transition-colors flex-shrink-0"
title="Revoke session"
>
{revokeMutation.isPending ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<XCircle className="h-4 w-4" />
)}
</button>
)}
</div>
))}
{sessionList.length === 0 && (
<div className="text-center py-8 text-gray-400">
<Monitor className="h-8 w-8 mx-auto mb-2 opacity-50" />
<p className="text-sm">No active sessions tracked yet.</p>
<p className="text-xs mt-1">Sessions will appear here after your next login.</p>
</div>
)}
</div>
</div>
);
}