"use client"; import * as React from "react"; import { LogOut, MonitorSmartphone } from "lucide-react"; import { authClient, useSession } from "@/lib/auth-client"; import { recordAuditFromClient } from "@/actions/account"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Alert } from "@/components/ui/alert"; import { Skeleton } from "@/components/ui/skeleton"; import { EmptyState } from "@/components/ui/empty-state"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { formatDate, timeAgo, truncate } from "@/lib/format"; interface SessionRow { id: string; token: string; ipAddress?: string | null; userAgent?: string | null; createdAt: Date | string; expiresAt: Date | string; } /** Turn a UA string into something a human can recognise, without a UA-parser dependency. */ function describeAgent(ua: string | null | undefined): string { if (!ua) return "Unknown device"; const browser = /Edg\//.test(ua) ? "Edge" : /OPR\//.test(ua) ? "Opera" : /Chrome\//.test(ua) ? "Chrome" : /Firefox\//.test(ua) ? "Firefox" : /Safari\//.test(ua) ? "Safari" : /curl|python|node|okhttp/i.test(ua) ? "API client" : "Browser"; const os = /iPhone|iPad/.test(ua) ? "iOS" : /Android/.test(ua) ? "Android" : /Mac OS X/.test(ua) ? "macOS" : /Windows/.test(ua) ? "Windows" : /Linux/.test(ua) ? "Linux" : ""; return os ? `${browser} · ${os}` : browser; } export function ActiveSessions() { const { data: current } = useSession(); const [sessions, setSessions] = React.useState(null); const [error, setError] = React.useState(null); const [busyToken, setBusyToken] = React.useState(null); const [busyAll, setBusyAll] = React.useState(false); const [reloadKey, setReloadKey] = React.useState(0); const load = () => setReloadKey((k) => k + 1); React.useEffect(() => { let cancelled = false; authClient.listSessions().then(({ data, error }) => { if (cancelled) return; if (error) { setError(error.message ?? "Could not load sessions."); setSessions([]); return; } const rows = ((data ?? []) as SessionRow[]).slice().sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); setSessions(rows); }); return () => { cancelled = true; }; }, [reloadKey]); const currentToken = current?.session?.token; async function revoke(token: string) { setError(null); setBusyToken(token); const { error } = await authClient.revokeSession({ token }); if (error) setError(error.message ?? "Could not revoke the session."); else await recordAuditFromClient("session.revoked", { scope: "one" }).catch(() => {}); setBusyToken(null); load(); } async function revokeOthers() { setError(null); setBusyAll(true); const { error } = await authClient.revokeOtherSessions(); if (error) setError(error.message ?? "Could not sign out other devices."); else await recordAuditFromClient("session.revoked", { scope: "others" }).catch(() => {}); setBusyAll(false); load(); } const others = sessions?.filter((s) => s.token !== currentToken).length ?? 0; return (
{error ? {error} : null}

{sessions ? ( <> {sessions.length} active {sessions.length === 1 ? "session" : "sessions"} {others > 0 ? <> · {others} on other devices : null} ) : ( "Loading sessions…" )}

{sessions === null ? (
) : sessions.length === 0 ? ( ) : (
Device IP address Signed in Expires Actions {sessions.map((s) => { const isCurrent = s.token === currentToken; return (
{describeAgent(s.userAgent)} {isCurrent ? ( This device ) : null} {s.userAgent ? truncate(s.userAgent, 64) : "—"}
{s.ipAddress || "—"} {timeAgo(s.createdAt)} {formatDate(s.expiresAt, { timeStyle: undefined })} {!isCurrent ? ( ) : null}
); })}
)}
); }