Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 0 additions & 55 deletions bun.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
"@aws-sdk/client-s3": "^3.500.0",
"@getgitops/gitdb": "^0.2.0",
"@google-cloud/storage": "^7.21.0",
"better-sqlite3": "^12.11.1",
"lucide-svelte": "^0.320.0"
}
}
2 changes: 1 addition & 1 deletion src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const handle: Handle = async ({ event, resolve }) => {

if (event.url.pathname.startsWith('/settings') || event.url.pathname.startsWith('/api/system')) {
if (!canAccessAdminArea(currentUser)) {
return new Response(null, { status: 302, headers: { location: '/vault' } });
return new Response(null, { status: 302, headers: { location: '/' } });
}
}

Expand Down
134 changes: 106 additions & 28 deletions src/lib/components/AppNavbar.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
<script lang="ts">
import { ChevronDown, LogOut, UserRound } from 'lucide-svelte';
import { ChevronDown, FolderKanban, LogOut, UserRound } from 'lucide-svelte';

export let user: { username: string; role: string } | null = null;
export let projects: { id: string; name: string; slug: string }[] = [];
export let organizationSlug = 'gitops';
export let projectSlug = '';

let showUserDropdown = false;
let showProjectDropdown = false;

let userMenuRef: HTMLDivElement | undefined;
let projectMenuRef: HTMLDivElement | undefined;

function toggleUserDropdown() {
showUserDropdown = !showUserDropdown;
Expand All @@ -13,55 +20,126 @@
showUserDropdown = false;
}

function toggleProjectDropdown() {
showProjectDropdown = !showProjectDropdown;
}

function closeProjectDropdown() {
showProjectDropdown = false;
}

// header uses backdrop-blur, which traps fixed-position overlays inside it, so a viewport-wide
// backdrop can't be used to detect outside clicks — listen on the window instead.
function handleWindowClick(event: MouseEvent) {
const target = event.target as Node;

if (showUserDropdown && userMenuRef && !userMenuRef.contains(target)) {
showUserDropdown = false;
}

if (showProjectDropdown && projectMenuRef && !projectMenuRef.contains(target)) {
showProjectDropdown = false;
}
}

$: currentProject = projects.find((project) => project.slug === projectSlug) ?? null;
</script>

<svelte:window on:click={handleWindowClick} />

<header class="sticky top-0 z-30 border-b border-slate-200/80 bg-white/90 backdrop-blur-xl">
<div class="mx-auto flex h-16 w-full items-center justify-between gap-4 px-4 sm:px-6">
<div class="mx-auto flex h-16 w-full items-center gap-4 px-4 sm:px-6">
<a href="/" class="flex items-center">
<img src="/gitops_logo.png" alt="GitOps" class="h-14 w-auto sm:h-16" />
</a>

<a
href="/how-to"
class="hidden rounded-full border border-slate-200 px-3 py-1.5 text-sm font-medium text-slate-600 transition-colors hover:border-slate-300 hover:text-slate-900 md:inline-flex"
>
How To
</a>
{#if user}
<div class="relative hidden md:block" bind:this={projectMenuRef}>
<button
type="button"
on:click={toggleProjectDropdown}
class="btn-secondary flex max-w-55 items-center gap-2 rounded-2xl px-3 py-1.5 text-sm font-medium text-slate-700"
>
<FolderKanban class="h-4 w-4 shrink-0 text-slate-500" />
<span class="truncate">{currentProject ? currentProject.name : 'Select project'}</span>
<ChevronDown class="h-3.5 w-3.5 shrink-0 text-slate-500" />
</button>

{#if showProjectDropdown}
<div
class="absolute left-0 z-50 mt-2 w-64 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-xl"
>
{#if projects.length === 0}
<p class="px-4 py-3 text-sm text-slate-500">No hay proyectos activos.</p>
{:else}
<div class="max-h-72 overflow-y-auto py-1">
{#each projects as project (project.id)}
<a
href={`/org/${organizationSlug}/projects/${project.slug}/overview`}
on:click={closeProjectDropdown}
class="flex items-center gap-2 px-4 py-2.5 text-sm font-medium transition-colors hover:bg-slate-50 {currentProject?.id ===
project.id
? 'text-slate-900'
: 'text-slate-600'}"
>
<FolderKanban class="h-3.5 w-3.5 shrink-0" />
<span class="truncate">{project.name}</span>
</a>
{/each}
</div>
{/if}

<a
href={`/org/${organizationSlug}/overview`}
on:click={closeProjectDropdown}
class="block border-t border-slate-100 bg-slate-50 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-100"
>
View all projects
</a>
</div>
{/if}
</div>
{/if}

<div class="ml-auto flex items-center gap-3 sm:gap-4">
<a
href="/how-to"
class="hidden rounded-full border border-slate-200 px-3 py-1.5 text-sm font-medium text-slate-600 transition-colors hover:border-slate-300 hover:text-slate-900 md:inline-flex"
>
How To
</a>

<div class="flex items-center gap-3 sm:gap-4">
{#if user}
<div class="relative ml-1">
<div class="relative ml-1" bind:this={userMenuRef}>
<button
on:click={toggleUserDropdown}
class="btn-secondary flex items-center gap-2 rounded-2xl px-2 py-1.5 text-left"
>
<div class="flex h-8 w-8 items-center justify-center rounded-xl bg-slate-900 text-xs font-bold uppercase text-white">
<div
class="flex h-8 w-8 items-center justify-center rounded-xl bg-slate-900 text-xs font-bold uppercase text-white"
>
{user.username.slice(0, 2)}
</div>
<ChevronDown class="h-3.5 w-3.5 text-slate-500" />
</button>

{#if showUserDropdown}
<button
type="button"
class="fixed inset-0 z-40 cursor-default"
on:click={closeUserDropdown}
aria-label="Close user menu"
></button>

<div class="absolute right-0 z-50 mt-2 w-56 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-xl">
<div
class="absolute right-0 z-50 mt-2 w-56 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-xl"
>
<div class="border-b border-slate-100 bg-slate-50 px-4 py-3">
<p class="truncate text-sm font-semibold text-slate-900">{user.username}</p>
<p class="truncate text-xs text-slate-500 capitalize">{user.role.name} Account</p>
</div>
<a
href="/profile"
class="flex items-center gap-2 px-4 py-3 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50"
>
<UserRound class="h-4 w-4" />
Profile
</a>
<form action="/api/auth/logout" method="POST">
<a
href="/profile"
on:click={closeUserDropdown}
class="flex items-center gap-2 px-4 py-3 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50"
>
<UserRound class="h-4 w-4" />
Profile
</a>
<form action="/api/auth/logout" method="POST" on:submit={closeUserDropdown}>
<button
type="submit"
class="btn-ghost flex w-full items-center gap-2 px-4 py-3 text-left text-sm font-medium text-red-600 hover:bg-red-50"
Expand All @@ -76,4 +154,4 @@
{/if}
</div>
</div>
</header>
</header>
Loading
Loading