'use client'; import { useState, useEffect } from 'react'; import { Box, Typography, Card, CardContent, Button, Dialog, DialogTitle, DialogContent, DialogActions, Alert, CircularProgress, Chip, List, ListItem, ListItemText, ListItemSecondaryAction, IconButton, Divider, Accordion, AccordionSummary, AccordionDetails, } from '@mui/material'; import { Devices, Computer, PhoneAndroid, Tablet, Delete, CheckCircle, ExpandMore, } from '@mui/icons-material'; import { sessionsApi, type SessionInfo } from '@/lib/api/sessions'; import { motion } from 'framer-motion'; import { useLocalizedDate } from '@/hooks/useLocalizedDate'; import { useAuth } from '@/lib/auth/AuthContext'; export function SessionsManagement() { const { formatDistanceToNow } = useLocalizedDate(); const { logout } = useAuth(); const [sessions, setSessions] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [successMessage, setSuccessMessage] = useState(null); // Revoke session dialog const [revokeDialogOpen, setRevokeDialogOpen] = useState(false); const [sessionToRevoke, setSessionToRevoke] = useState(null); const [isRevoking, setIsRevoking] = useState(false); // Revoke all dialog const [revokeAllDialogOpen, setRevokeAllDialogOpen] = useState(false); const [isRevokingAll, setIsRevokingAll] = useState(false); useEffect(() => { loadSessions(); }, []); const loadSessions = async () => { try { setIsLoading(true); const response = await sessionsApi.getSessions(); setSessions(response.sessions); setError(null); } catch (err: any) { console.error('Failed to load sessions:', err); setError('Failed to load sessions'); } finally { setIsLoading(false); } }; const handleRevokeSession = async () => { if (!sessionToRevoke) return; try { setIsRevoking(true); await sessionsApi.revokeSession(sessionToRevoke.id); setSuccessMessage('Session revoked successfully'); setRevokeDialogOpen(false); setSessionToRevoke(null); await loadSessions(); } catch (err: any) { console.error('Failed to revoke session:', err); setError(err.response?.data?.message || 'Failed to revoke session'); } finally { setIsRevoking(false); } }; const handleRevokeAllSessions = async () => { try { setIsRevokingAll(true); const response = await sessionsApi.revokeAllSessions(); setSuccessMessage(`${response.revokedCount} session(s) revoked successfully`); setRevokeAllDialogOpen(false); // Logout the current user since all sessions are revoked // This clears tokens and redirects to login await logout(); } catch (err: any) { console.error('Failed to revoke all sessions:', err); setError(err.response?.data?.message || 'Failed to revoke sessions'); setIsRevokingAll(false); } // Note: Don't set isRevokingAll to false here, as we're logging out }; const getPlatformIcon = (platform?: string) => { if (!platform) return ; const p = platform.toLowerCase(); if (p.includes('android') || p.includes('mobile')) return ; if (p.includes('ios') || p.includes('iphone') || p.includes('ipad')) return ; if (p.includes('tablet')) return ; return ; }; if (isLoading) { return ( }> Active Sessions ); } return ( <> } sx={{ '& .MuiAccordionSummary-content': { margin: '12px 0', }, }} > Active Sessions Manage your active sessions across different devices. You can revoke access from any device. {error && ( setError(null)}> {error} )} {successMessage && ( setSuccessMessage(null)}> {successMessage} )} {sessions.length === 0 ? ( No active sessions found. ) : ( <> {sessions.map((session, index) => ( {index > 0 && } {getPlatformIcon(session.platform)} {session.platform || 'Unknown Platform'} {session.isCurrent && ( } /> )} } secondary={ Device: {session.deviceFingerprint} Last active: {formatDistanceToNow(new Date(session.lastUsed))} ago Created: {formatDistanceToNow(new Date(session.createdAt))} ago } /> {!session.isCurrent && ( { setSessionToRevoke(session); setRevokeDialogOpen(true); }} color="error" > )} ))} {sessions.length > 1 && ( )} )} {/* Revoke Session Dialog */} setRevokeDialogOpen(false)} maxWidth="sm" fullWidth> Revoke Session? This device will be logged out and will need to log in again. {sessionToRevoke && ( Device: {sessionToRevoke.deviceFingerprint} Platform: {sessionToRevoke.platform || 'Unknown'} Last active: {formatDistanceToNow(new Date(sessionToRevoke.lastUsed))} ago )} {/* Revoke All Sessions Dialog */} setRevokeAllDialogOpen(false)} maxWidth="sm" fullWidth> Revoke All Other Sessions? This will log out all devices except your current one. They will need to log in again. You are about to revoke {sessions.filter((s) => !s.isCurrent).length} session(s). ); }