import { Fragment, useState } from "react"; import { Form, useNavigation } from "react-router"; import { useTranslation } from "react-i18next"; import { FeatherAlertTriangle, FeatherLogOut, FeatherMonitor, FeatherShieldOff, FeatherSmartphone } from "@subframe/core"; import type { SessionListVM } from "@toolctrl/view-models"; import { Button } from "~/ui/components/Button"; import { BadgeFeedback } from "~/ui/components/BadgeFeedback"; // D-P2 — Settings > Sicherheit nach Davids Subframe-"Session Management"-Design: Header (Titel + "Überall // abmelden") + Geräte-Card (Icon + Label/Standort + "Dieses Gerät"-Badge / "Abmelden") + Confirm-Overlay // (Revoke / Logout-All). Praesentational: VM + csrf werden injiziert, Aktionen laufen ueber die bestehende // security-Action (intents revoke / revoke-all / forget-device / forget-all-devices). Die Trusted-Devices- // Sektion hat kein eigenes David-Design -> konsistent in derselben Card-Optik re-skinned. type TrustedDeviceVM = { id: string; label: string | null; approxLocation: string | null }; type ConfirmState = | { kind: "revoke-all" } | { kind: "revoke"; sessionId: string; label: string } | { kind: "forget-all-devices" } | { kind: "forget-device"; deviceId: string; label: string }; function isMobileDevice(label?: string | null): boolean { if (!label) return false; return /iphone|android|mobile|ipad|tablet/i.test(label); } export default function SecuritySessionsScreen({ vm, trustedDevices, csrf, }: { vm: SessionListVM; trustedDevices: TrustedDeviceVM[]; csrf: string; }) { const { t } = useTranslation(); const navigation = useNavigation(); const submitting = navigation.state !== "idle"; const [confirm, setConfirm] = useState(null); const ready = vm.status === "ready"; const devices = ready ? vm.data.devices : []; const onlyThisDevice = ready && devices.length <= 1 && !vm.data.canRevokeOthers; return (
{/* Header */}
{t("settings.security.title")} {t("settings.security.sessions.subtitle")}
{ready && vm.data.canRevokeOthers ? ( ) : null}
{/* Aktive Sitzungen */}
{!ready ? ( {t("settings.security.sessions.empty")} ) : ( devices.map((device, index) => { const Icon = isMobileDevice(device.deviceLabel) ? FeatherSmartphone : FeatherMonitor; const label = device.deviceLabel ?? t(device.deviceLabelFallbackKey ?? "settings.security.sessions.unknownDevice"); const meta = [device.approxLocation, t(`settings.security.sessions.lastSeen.${device.lastSeenBucket}`, { count: device.lastSeenCount ?? 0 })] .filter(Boolean) .join(" · "); return ( {index > 0 ?
: null}
{label} {meta ? ( {meta} ) : null}
{device.isCurrent ? ( {t("settings.security.sessions.thisDevice")} ) : device.canRevoke ? ( ) : null}
); }) )}
{onlyThisDevice ? ( {t("settings.security.sessions.onlyThisDevice")} ) : null} {/* Vertrauenswürdige Geräte (kein eigenes David-Design -> konsistente Card-Optik) */} {trustedDevices.length > 0 ? (
{t("settings.security.trustedDevices.heading")} {t("settings.security.trustedDevices.description")}
{trustedDevices.map((device, index) => ( {index > 0 ?
: null}
{device.label ?? t("settings.security.trustedDevices.unknownDevice")} {device.approxLocation ? ( {device.approxLocation} ) : null}
))}
) : null}
{confirm ? setConfirm(null)} /> : null}
); } function ConfirmOverlay({ confirm, csrf, submitting, onClose, }: { confirm: ConfirmState; csrf: string; submitting: boolean; onClose: () => void; }) { const { t } = useTranslation(); const copy = { "revoke-all": { title: t("settings.security.sessions.confirmRevokeAll.title"), description: t("settings.security.sessions.confirmRevokeAll.description"), confirm: t("settings.security.sessions.confirmRevokeAll.confirm"), }, revoke: { title: t("settings.security.sessions.confirmRevoke.title"), description: t("settings.security.sessions.confirmRevoke.description", { device: "label" in confirm ? confirm.label : "" }), confirm: t("settings.security.sessions.confirmRevoke.confirm"), }, "forget-all-devices": { title: t("settings.security.trustedDevices.confirmForgetAll.title"), description: t("settings.security.trustedDevices.confirmForgetAll.description"), confirm: t("settings.security.trustedDevices.forgetAll"), }, "forget-device": { title: t("settings.security.trustedDevices.confirmForget.title"), description: t("settings.security.trustedDevices.confirmForget.description", { device: "label" in confirm ? confirm.label : "" }), confirm: t("settings.security.trustedDevices.forget"), }, }[confirm.kind]; return (
{copy.title} {copy.description}
{confirm.kind === "revoke" ? : null} {confirm.kind === "forget-device" ? : null}
); }