mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
Collapse the active-sessions and security-activity lists by default
Both account-security lists now render behind a disclosure control and open only on click. The trigger carries aria-expanded and aria-controls and is keyboard-operable; the region is gated with hidden. State is UI-only and not persisted.
This commit is contained in:
1 parent
d15dd26ebf
commit
cab0e50fff
2 files changed
+67
-17
No files matched your search
@@ -8,14 +8,15 @@
|
||||
* shared `GET /api/auth/me/security-activity` endpoint. Each row shows a
|
||||
* readable action label, the resolved location, the masked IP, and the time.
|
||||
*/
|
||||
import { useId, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Loader2, ShieldCheck } from "lucide-react";
|
||||
import { ChevronDown, Loader2, ShieldCheck } from "lucide-react";
|
||||
|
||||
import { SettingsCard } from "@/components/settings/settings-card";
|
||||
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { apiGet } from "@/lib/api/api-fetch";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface ActivityRow {
|
||||
action: string;
|
||||
@@ -53,6 +54,10 @@ export function SecurityActivityCard({
|
||||
isAuthenticated: boolean;
|
||||
}) {
|
||||
const { t } = useTranslations();
|
||||
// Collapsed by default — the feed opens only on demand, keeping the security
|
||||
// surface skimmable. UI-only state; nothing is persisted across reloads.
|
||||
const [open, setOpen] = useState(false);
|
||||
const regionId = useId();
|
||||
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey: queryKeys.securityActivity(),
|
||||
@@ -65,12 +70,32 @@ export function SecurityActivityCard({
|
||||
|
||||
return (
|
||||
<SettingsCard data-slot="settings-security-activity-card">
|
||||
<SettingsCardHeader
|
||||
icon={ShieldCheck}
|
||||
title={t("settings.security.activityTitle")}
|
||||
className="mb-4"
|
||||
/>
|
||||
<div className="space-y-4 pl-7">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
aria-expanded={open}
|
||||
aria-controls={regionId}
|
||||
data-slot="settings-security-activity-toggle"
|
||||
className="hover:bg-muted/40 focus-visible:ring-ring/50 -m-2 mb-2 flex w-full items-start gap-2 rounded-md p-2 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none"
|
||||
>
|
||||
<ShieldCheck
|
||||
className="text-muted-foreground mt-0.5 h-5 w-5 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="flex min-w-0 flex-1 items-center justify-between gap-2">
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t("settings.security.activityTitle")}
|
||||
</h2>
|
||||
<ChevronDown
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"text-muted-foreground h-4 w-4 shrink-0 transition-transform",
|
||||
open && "rotate-180",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
<div id={regionId} hidden={!open} className="space-y-4 pl-7">
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{t("settings.security.activityDescription")}
|
||||
</p>
|
||||
|
||||
@@ -9,16 +9,16 @@
|
||||
* session (and native device logins) while keeping the current one. All reads
|
||||
* + writes route through the centralised query-key factory.
|
||||
*/
|
||||
import { useState } from "react";
|
||||
import { useId, useState } from "react";
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { Loader2, MonitorSmartphone } from "lucide-react";
|
||||
import { ChevronDown, Loader2, MonitorSmartphone } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { SettingsCard } from "@/components/settings/settings-card";
|
||||
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { apiGet, apiDelete } from "@/lib/api/api-fetch";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SessionRow {
|
||||
id: string;
|
||||
@@ -38,6 +38,11 @@ export function SecuritySessionsCard({
|
||||
const { t } = useTranslations();
|
||||
const queryClient = useQueryClient();
|
||||
const [status, setStatus] = useState<string | null>(null);
|
||||
// Collapsed by default — the list opens only when the user asks for it, so
|
||||
// the security surface stays calm and skimmable on first paint. UI-only
|
||||
// state; nothing is persisted across reloads.
|
||||
const [open, setOpen] = useState(false);
|
||||
const regionId = useId();
|
||||
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey: queryKeys.sessions(),
|
||||
@@ -79,12 +84,32 @@ export function SecuritySessionsCard({
|
||||
|
||||
return (
|
||||
<SettingsCard data-slot="settings-security-sessions-card">
|
||||
<SettingsCardHeader
|
||||
icon={MonitorSmartphone}
|
||||
title={t("settings.security.sessionsTitle")}
|
||||
className="mb-4"
|
||||
/>
|
||||
<div className="space-y-4 pl-7">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
aria-expanded={open}
|
||||
aria-controls={regionId}
|
||||
data-slot="settings-security-sessions-toggle"
|
||||
className="hover:bg-muted/40 focus-visible:ring-ring/50 -m-2 mb-2 flex w-full items-start gap-2 rounded-md p-2 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none"
|
||||
>
|
||||
<MonitorSmartphone
|
||||
className="text-muted-foreground mt-0.5 h-5 w-5 shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="flex min-w-0 flex-1 items-center justify-between gap-2">
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t("settings.security.sessionsTitle")}
|
||||
</h2>
|
||||
<ChevronDown
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"text-muted-foreground h-4 w-4 shrink-0 transition-transform",
|
||||
open && "rotate-180",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
<div id={regionId} hidden={!open} className="space-y-4 pl-7">
|
||||
<p className="text-muted-foreground text-xs">
|
||||
{t("settings.security.sessionsDescription")}
|
||||
</p>
|
||||
|
||||
Reference in new issue
Block a user