Update account settings layout to match org settings

Add left nav with descriptions on desktop and horizontal tabs on mobile,
consistent with the organization settings layout pattern.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
igm
2026-01-10 19:31:16 +08:00
parent 730021a5ea
commit cf71cb63d7
4 changed files with 155 additions and 11 deletions

View File

@@ -0,0 +1,148 @@
<script lang="ts">
import type { Component, Snippet } from "svelte";
import ClockIcon from "@lucide/svelte/icons/clock";
import KeyRoundIcon from "@lucide/svelte/icons/key-round";
import MonitorIcon from "@lucide/svelte/icons/monitor";
import ShieldCheckIcon from "@lucide/svelte/icons/shield-check";
import UserIcon from "@lucide/svelte/icons/user";
import { createQuery } from "@tanstack/svelte-query";
import { page } from "$app/stores";
import { api } from "$lib/api/client";
import { DashboardLayout } from "$lib/components/layout";
import { cn } from "$lib/utils.js";
interface Props {
children: Snippet;
}
let { children }: Props = $props();
// Fetch current user to check superuser status
const userQuery = createQuery(() => ({
queryKey: ["me"],
queryFn: () => api.me.get(),
}));
interface NavItem {
href: string;
icon: Component;
label: string;
description: string;
}
const baseNavItems: NavItem[] = [
{
href: "/account",
icon: UserIcon,
label: "Profile",
description: "Your personal information and avatar",
},
{
href: "/account/auth",
icon: ShieldCheckIcon,
label: "Authentication",
description: "Passwords, passkeys, and login methods",
},
{
href: "/account/devices",
icon: MonitorIcon,
label: "Devices",
description: "Manage your trusted devices",
},
{
href: "/account/sessions",
icon: ClockIcon,
label: "Sessions",
description: "Active sessions and login history",
},
];
// Add API Tokens link for superusers only
const navItems = $derived(
userQuery.data?.isSuperuser
? [
...baseNavItems,
{
href: "/account/api-tokens",
icon: KeyRoundIcon,
label: "API Tokens",
description: "Manage API access tokens",
},
]
: baseNavItems,
);
// Determine active item
const activeHref = $derived($page.url.pathname);
function isActive(href: string): boolean {
// Exact match for base account path
if (href === "/account") {
return activeHref === "/account";
}
// Prefix match for sub-pages
return activeHref.startsWith(href);
}
</script>
<DashboardLayout title="Account Settings">
<div class="flex flex-col gap-6 lg:flex-row lg:gap-8">
<!-- Account Navigation -->
<nav class="w-full shrink-0 lg:w-64">
<!-- Mobile: horizontal scroll -->
<div class="flex gap-2 overflow-x-auto pb-2 lg:hidden">
{#each navItems as item}
{@const active = isActive(item.href)}
<a
href={item.href}
class={cn(
"flex shrink-0 items-center gap-2 rounded-lg border px-3 py-2 text-sm font-medium transition-colors",
active
? "border-primary bg-primary/5 text-primary"
: "border-transparent bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground",
)}
>
<item.icon class="h-4 w-4" />
{item.label}
</a>
{/each}
</div>
<!-- Desktop: vertical list -->
<div class="hidden space-y-1 lg:block">
{#each navItems as item}
{@const active = isActive(item.href)}
<a
href={item.href}
class={cn(
"group flex items-start gap-3 rounded-lg px-3 py-2.5 transition-colors",
active
? "bg-primary/5 text-foreground"
: "text-muted-foreground hover:bg-muted/50 hover:text-foreground",
)}
>
<div
class={cn(
"mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg transition-colors",
active
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground group-hover:bg-muted-foreground/20",
)}
>
<item.icon class="h-4 w-4" />
</div>
<div class="flex-1 space-y-0.5">
<p class="text-sm font-medium">{item.label}</p>
<p class="text-xs text-muted-foreground">{item.description}</p>
</div>
</a>
{/each}
</div>
</nav>
<!-- Content -->
<div class="min-w-0 flex-1">
{@render children()}
</div>
</div>
</DashboardLayout>

View File

@@ -0,0 +1 @@
export { default as AccountSettingsLayout } from "./account-settings-layout.svelte";

View File

@@ -1,3 +1,5 @@
// Account layout components
export { AccountSettingsLayout } from "./account/index.js";
// Admin layout components // Admin layout components
export { export {
AdminHeader, AdminHeader,

View File

@@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
import { AccountNav } from "$lib/components/account"; import { AccountSettingsLayout } from "$lib/components/layout";
import { DashboardLayout } from "$lib/components/layout";
interface Props { interface Props {
children: Snippet; children: Snippet;
@@ -14,12 +13,6 @@ let { children }: Props = $props();
<title>Account Settings - Publisher Dashboard</title> <title>Account Settings - Publisher Dashboard</title>
</svelte:head> </svelte:head>
<DashboardLayout title="Account Settings"> <AccountSettingsLayout>
<div class="space-y-6">
<AccountNav />
<div class="max-w-2xl">
{@render children()} {@render children()}
</div> </AccountSettingsLayout>
</div>
</DashboardLayout>