import React, { useEffect, useState } from "react"; import { Alert, Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, List, ListItem, ListItemSecondaryAction, ListItemText, Paper, Typography, } from "@mui/material"; import DeleteIcon from "@mui/icons-material/Delete"; import { api, getApiErrorMessage } from "../api"; import { useToast } from "../toast"; import { useI18n } from "../i18n/I18nProvider"; import { clearAuthClientState } from "../auth"; type Session = { id: string; deviceLabel: string | null; createdAtUtc: string; lastSeenAtUtc: string; expiresAtUtc: string; isCurrentSession: boolean; }; function apiErrorMessage(e: any, t: (k: any) => string) { return getApiErrorMessage(e, t("twoFactorGenericError")); } export default function SessionsSettingsCard() { const { toast } = useToast(); const { t } = useI18n(); const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [confirmRevokeOthers, setConfirmRevokeOthers] = useState(false); const loadSessions = () => { setLoading(true); setError(null); api .get("/auth/sessions") .then((r) => setSessions(r.data)) .catch((e) => setError(apiErrorMessage(e, t))) .finally(() => setLoading(false)); }; useEffect(() => { loadSessions(); }, []); async function revokeSession(id: string, isCurrentSession: boolean) { try { await api.delete(`/auth/sessions/${id}`); if (isCurrentSession) { // Same pattern as AuthStatusCard's sign-out: clearing local auth state emits // "auth-changed", which App.tsx's listener picks up to refetch /auth/me (now 401, // since the server already deleted the session cookie) and redirect to /login. clearAuthClientState(); return; } toast(t("sessionsRevoked"), "success"); loadSessions(); } catch (e: any) { setError(apiErrorMessage(e, t)); } } async function revokeOthers() { try { await api.post("/auth/sessions/revoke-others"); toast(t("sessionsRevokedOthers"), "success"); setConfirmRevokeOthers(false); loadSessions(); } catch (e: any) { setError(apiErrorMessage(e, t)); } } return ( {t("sessionsSectionTitle")} {error ? {error} : null} {!loading && sessions.length === 0 && !error ? ( {t("sessionsEmpty")} ) : null} {sessions.length > 0 ? ( {sessions.map((s) => ( {s.deviceLabel || t("sessionsUnknownDevice")} {s.isCurrentSession ? ( {t("sessionsCurrentDevice")} ) : null} } secondary={t("sessionsMeta", { lastSeen: new Date(s.lastSeenAtUtc).toLocaleString(), expires: new Date(s.expiresAtUtc).toLocaleDateString(), })} /> revokeSession(s.id, s.isCurrentSession)}> ))} ) : null} {sessions.length > 1 ? ( ) : null} setConfirmRevokeOthers(false)} maxWidth="sm" fullWidth> {t("sessionsRevokeOthersConfirmTitle")} {t("sessionsRevokeOthersConfirmBody")} ); }