import { ClockIcon, MonitorIcon, SmartphoneIcon, TabletIcon, TrashIcon, WifiIcon } from "lucide-react"; import { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import ConfirmDialog from "@/components/ConfirmDialog"; import { Button } from "@/components/ui/button"; import { userServiceClient } from "@/grpcweb"; import useCurrentUser from "@/hooks/useCurrentUser"; import { UserSession } from "@/types/proto/api/v1/user_service"; import { useTranslate } from "@/utils/i18n"; const listUserSessions = async (parent: string) => { const { sessions } = await userServiceClient.listUserSessions({ parent }); return sessions.sort((a, b) => (b.lastAccessedTime?.getTime() ?? 0) - (a.lastAccessedTime?.getTime() ?? 0)); }; const UserSessionsSection = () => { const t = useTranslate(); const currentUser = useCurrentUser(); const [userSessions, setUserSessions] = useState([]); const [revokeTarget, setRevokeTarget] = useState(undefined); useEffect(() => { listUserSessions(currentUser.name).then((sessions) => { setUserSessions(sessions); }); }, []); const handleRevokeSession = async (userSession: UserSession) => { setRevokeTarget(userSession); }; const confirmRevokeSession = async () => { if (!revokeTarget) return; await userServiceClient.revokeUserSession({ name: revokeTarget.name }); setUserSessions(userSessions.filter((session) => session.sessionId !== revokeTarget.sessionId)); toast.success(t("setting.user-sessions-section.session-revoked")); setRevokeTarget(undefined); }; const getFormattedSessionId = (sessionId: string) => { return `${sessionId.slice(0, 8)}...${sessionId.slice(-8)}`; }; const getDeviceIcon = (deviceType: string) => { switch (deviceType?.toLowerCase()) { case "mobile": return ; case "tablet": return ; case "desktop": default: return ; } }; const formatDeviceInfo = (clientInfo: UserSession["clientInfo"]) => { if (!clientInfo) return "Unknown Device"; const parts = []; if (clientInfo.os) parts.push(clientInfo.os); if (clientInfo.browser) parts.push(clientInfo.browser); return parts.length > 0 ? parts.join(" • ") : "Unknown Device"; }; const isCurrentSession = (session: UserSession) => { // A simple heuristic: the most recently accessed session is likely the current one if (userSessions.length === 0) return false; const mostRecent = userSessions[0]; return session.sessionId === mostRecent.sessionId; }; return (

{t("setting.user-sessions-section.title")}

{t("setting.user-sessions-section.description")}

{userSessions.map((userSession) => ( ))}
{t("setting.user-sessions-section.device")} {t("setting.user-sessions-section.last-active")} {t("common.delete")}
{getDeviceIcon(userSession.clientInfo?.deviceType || "")}
{formatDeviceInfo(userSession.clientInfo)} {isCurrentSession(userSession) && ( {t("setting.user-sessions-section.current")} )} {getFormattedSessionId(userSession.sessionId)}
{userSession.lastAccessedTime?.toLocaleString()}
{userSessions.length === 0 && (
{t("setting.user-sessions-section.no-sessions")}
)}
!open && setRevokeTarget(undefined)} title={ revokeTarget ? t("setting.user-sessions-section.session-revocation", { sessionId: getFormattedSessionId(revokeTarget.sessionId), }) : "" } description={revokeTarget ? t("setting.user-sessions-section.session-revocation-description") : ""} confirmLabel={t("setting.user-sessions-section.revoke-session-button")} cancelLabel={t("common.cancel")} onConfirm={confirmRevokeSession} confirmVariant="destructive" />
); }; export default UserSessionsSection;