Private
Public Access
1
0

Condense nav: profile dropdown replaces inline nav items

Desktop nav now shows only: theme toggle, notification bell, and
avatar+name. Clicking avatar opens a dropdown menu with My Work,
Profile, Admin, Site Admin, and Logout. Logged-out users see only
the theme toggle and a Login button.

Mobile nav mirrors the same menu items in the hamburger dropdown.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-27 14:47:13 -05:00
parent ecd037ce5c
commit b1d994c9e4
+90 -77
View File
@@ -13,6 +13,7 @@
let { children, data }: { children: Snippet; data: { user: any; tenant: any; theme?: string } } = $props();
let mobileMenuOpen = $state(false);
let profileMenuOpen = $state(false);
const theme = $derived(getTheme());
onMount(() => {
@@ -22,8 +23,16 @@
return () => disconnectSocket();
}
});
function handleProfileClickOutside(e: MouseEvent) {
if (profileMenuOpen && !(e.target as HTMLElement).closest('.profile-menu')) {
profileMenuOpen = false;
}
}
</script>
<svelte:window onclick={handleProfileClickOutside} />
<div class="min-h-screen layout-page-bg">
<nav class="shadow-sm layout-nav-bg">
<div class="px-4 sm:px-6 lg:px-8">
@@ -54,15 +63,7 @@
</div>
<!-- Right: Nav items -->
<div class="flex items-center gap-3">
{#if data.user}
<a
href="/my-work"
class="rounded px-2 py-1 text-sm text-white/80 hover:text-white hover:bg-white/20 transition"
>
My Work
</a>
{/if}
<div class="flex items-center gap-2">
<button
onclick={toggleTheme}
class="rounded p-1.5 text-white/80 hover:text-white hover:bg-white/20 transition"
@@ -80,36 +81,83 @@
{/if}
</button>
{#if data.user}
{#if data.user.globalRole === 'SITE_ADMIN'}
<a
href="/admin/site"
class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition"
<NotificationBell />
<!-- Profile dropdown -->
<div class="relative profile-menu">
<button
onclick={(e) => { e.stopPropagation(); profileMenuOpen = !profileMenuOpen; }}
class="flex items-center gap-1.5 rounded px-2 py-1 text-white/80 hover:text-white hover:bg-white/20 transition"
aria-label="User menu"
>
Site Admin
<Avatar name={data.user.name} avatarUrl={data.user.avatarUrl} size="xs" class="border border-white/30" />
<span class="text-sm">{data.user.name}</span>
<svg class="w-3.5 h-3.5 transition-transform {profileMenuOpen ? 'rotate-180' : ''}" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
{#if profileMenuOpen}
<div class="absolute right-0 top-full mt-1 z-50 w-48 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-xl py-1">
<a
href="/my-work"
class="flex items-center gap-2 px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition"
onclick={() => (profileMenuOpen = false)}
>
<svg class="w-4 h-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd" />
</svg>
My Work
</a>
<a
href="/account"
class="flex items-center gap-2 px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition"
onclick={() => (profileMenuOpen = false)}
>
<svg class="w-4 h-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd" />
</svg>
Profile
</a>
{/if}
{#if data.user.tenantRole === 'ADMIN' || data.user.globalRole === 'SITE_ADMIN'}
<a
href="/admin"
class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition"
class="flex items-center gap-2 px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition"
onclick={() => (profileMenuOpen = false)}
>
<svg class="w-4 h-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
</svg>
Admin
</a>
{/if}
<a href="/account" class="flex items-center gap-1.5 text-white/80 text-sm hover:text-white transition">
<Avatar name={data.user.name} avatarUrl={data.user.avatarUrl} size="xs" class="border border-white/30" />
{data.user.name}
{#if data.user.globalRole === 'SITE_ADMIN'}
<a
href="/admin/site"
class="flex items-center gap-2 px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition"
onclick={() => (profileMenuOpen = false)}
>
<svg class="w-4 h-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M2 5a2 2 0 012-2h12a2 2 0 012 2v10a2 2 0 01-2 2H4a2 2 0 01-2-2V5zm3.293 1.293a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 01-1.414-1.414L7.586 10 5.293 7.707a1 1 0 010-1.414zM11 12a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd" />
</svg>
Site Admin
</a>
<NotificationBell />
{/if}
<div class="border-t border-gray-100 dark:border-gray-700 my-1"></div>
<button
onclick={async () => {
profileMenuOpen = false;
await fetch('/api/auth/logout', { method: 'POST' });
window.location.href = '/';
}}
class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition"
class="flex items-center gap-2 w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition"
>
<svg class="w-4 h-4 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M3 3a1 1 0 00-1 1v12a1 1 0 102 0V4a1 1 0 00-1-1zm10.293 9.293a1 1 0 001.414 1.414l3-3a1 1 0 000-1.414l-3-3a1 1 0 10-1.414 1.414L14.586 9H7a1 1 0 100 2h7.586l-1.293 1.293z" clip-rule="evenodd" />
</svg>
Logout
</button>
</div>
{/if}
</div>
{:else}
<a
href="/auth/login"
@@ -117,12 +165,6 @@
>
Login
</a>
<a
href="/auth/register"
class="rounded bg-white px-3 py-1.5 text-sm text-[var(--color-primary)] font-medium hover:bg-white/90 transition"
>
Register
</a>
{/if}
</div>
</div>
@@ -160,7 +202,6 @@
</button>
{#if data.user}
<NotificationBell />
{/if}
<button
onclick={() => (mobileMenuOpen = !mobileMenuOpen)}
class="rounded p-1.5 text-white/80 hover:text-white hover:bg-white/20 transition"
@@ -174,54 +215,44 @@
{/if}
</svg>
</button>
{:else}
<a
href="/auth/login"
class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition"
>
Login
</a>
{/if}
</div>
</div>
</div>
<!-- Mobile dropdown -->
{#if mobileMenuOpen}
{#if mobileMenuOpen && data.user}
<div class="sm:hidden border-t border-white/20 px-4 py-3 space-y-2">
{#if data.user}
<div class="w-full">
<SearchBar />
</div>
<div class="flex flex-col gap-1">
<a href="/account" class="flex items-center gap-1.5 text-white/80 text-sm py-1 hover:text-white transition" onclick={() => (mobileMenuOpen = false)}>
<Avatar name={data.user.name} avatarUrl={data.user.avatarUrl} size="xs" class="border border-white/30" />
{data.user.name}
<a href="/my-work" class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition text-center" onclick={() => (mobileMenuOpen = false)}>
My Work
</a>
{#if data.user.globalRole === 'SITE_ADMIN'}
<a
href="/admin/site"
class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition text-center"
onclick={() => (mobileMenuOpen = false)}
>
Site Admin
<a href="/boards" class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition text-center" onclick={() => (mobileMenuOpen = false)}>
Boards
</a>
<a href="/account" class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition text-center" onclick={() => (mobileMenuOpen = false)}>
Profile
</a>
{/if}
{#if data.user.tenantRole === 'ADMIN' || data.user.globalRole === 'SITE_ADMIN'}
<a
href="/admin"
class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition text-center"
onclick={() => (mobileMenuOpen = false)}
>
<a href="/admin" class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition text-center" onclick={() => (mobileMenuOpen = false)}>
Admin
</a>
{/if}
<a
href="/my-work"
class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition text-center"
onclick={() => (mobileMenuOpen = false)}
>
My Work
</a>
<a
href="/boards"
class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition text-center"
onclick={() => (mobileMenuOpen = false)}
>
Boards
{#if data.user.globalRole === 'SITE_ADMIN'}
<a href="/admin/site" class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition text-center" onclick={() => (mobileMenuOpen = false)}>
Site Admin
</a>
{/if}
<button
onclick={async () => {
await fetch('/api/auth/logout', { method: 'POST' });
@@ -232,24 +263,6 @@
Logout
</button>
</div>
{:else}
<div class="flex flex-col gap-1">
<a
href="/auth/login"
class="rounded bg-white/20 px-3 py-1.5 text-sm text-white hover:bg-white/30 transition text-center"
onclick={() => (mobileMenuOpen = false)}
>
Login
</a>
<a
href="/auth/register"
class="rounded bg-white px-3 py-1.5 text-sm text-[var(--color-primary)] font-medium hover:bg-white/90 transition text-center"
onclick={() => (mobileMenuOpen = false)}
>
Register
</a>
</div>
{/if}
</div>
{/if}
</nav>