Files
HealthLog/src/components/settings/security-section/index.tsx
T
Marc-André Bombeck 361505bd31 fix(privacy): de-duplicate trusted devices and handle the summary loading state
Trusted devices was mounted on both the Security section and the Privacy
dashboard. Keep it only on the Privacy dashboard, which already gathers sessions
and security activity, and drop it from the Security section. While here: the
Retention card painted an empty list while the privacy summary was loading and
on error — give it a skeleton placeholder and a one-line fallback, and align the
"What's stored" indentation with the other privacy cards.
2026-06-27 22:23:31 +02:00

73 lines
2.0 KiB
TypeScript

"use client";
import { useQuery } from "@tanstack/react-query";
import { Skeleton } from "@/components/ui/skeleton";
import { useAuth } from "@/hooks/use-auth";
import { queryKeys } from "@/lib/query-keys";
import { apiGet } from "@/lib/api/api-fetch";
import { TotpCard } from "./totp-card";
import { SecurityKeysCard, type WebauthnKeyInfo } from "./security-keys-card";
import { PasskeyListSection } from "./passkey-list-section";
import { PasskeyUpgradeNudge } from "./passkey-upgrade-nudge";
interface MfaStatus {
totp: { enabled: boolean };
recoveryCodesRemaining: number;
webauthn: WebauthnKeyInfo[];
passkeyNudgeDismissed: boolean;
}
interface PasskeyInfo {
id: string;
}
export function SecuritySection() {
const { isAuthenticated } = useAuth();
const { data: status, isLoading } = useQuery({
queryKey: queryKeys.mfaStatus(),
queryFn: async () => apiGet<MfaStatus>("/api/auth/me/mfa"),
enabled: isAuthenticated,
});
// Passkey count drives whether the upgrade nudge shows. Shares the cached
// `passkeys()` key with the list below, so this is not an extra round-trip.
const { data: passkeys } = useQuery({
queryKey: queryKeys.passkeys(),
queryFn: async () => apiGet<PasskeyInfo[]>("/api/auth/passkeys"),
enabled: isAuthenticated,
});
const showNudge =
status != null &&
!status.passkeyNudgeDismissed &&
passkeys != null &&
passkeys.length === 0;
if (isLoading || !status) {
return (
<div className="space-y-4">
<Skeleton className="h-40 w-full rounded-xl" />
<Skeleton className="h-40 w-full rounded-xl" />
<Skeleton className="h-40 w-full rounded-xl" />
</div>
);
}
return (
<div className="space-y-6">
{showNudge && <PasskeyUpgradeNudge />}
<TotpCard
enabled={status.totp.enabled}
recoveryCodesRemaining={status.recoveryCodesRemaining}
/>
<SecurityKeysCard keys={status.webauthn} />
<PasskeyListSection isAuthenticated={isAuthenticated} />
</div>
);
}