So many things

This commit is contained in:
2026-02-17 21:52:59 -06:00
parent 8e225aa254
commit a99c9f5102
27 changed files with 5398 additions and 123 deletions
+105
View File
@@ -0,0 +1,105 @@
<script lang="ts">
import { page } from "$app/stores";
import "../../styles/admin.css";
import type { PermissionMap } from "$lib/permissions";
export let data: {
user: {
id: string;
name?: string;
email?: string;
[key: string]: unknown;
} | null;
permissions: PermissionMap;
};
$: permissions = data.permissions;
$: userName = data.user?.name || "Admin";
// Tab definitions — each gated by a permission
const allTabs = [
{ label: "Overview", href: "/admin", exact: true, permission: null },
{
label: "Users",
href: "/admin/users",
exact: false,
permission: "admin.users.view",
},
{
label: "Roles",
href: "/admin/roles",
exact: false,
permission: "admin.roles.view",
},
{
label: "Credential Types",
href: "/admin/credential-types",
exact: false,
permission: "admin.credential-types.view",
},
] as const;
// Only show tabs the user has permission for
$: visibleTabs = allTabs.filter(
(t) => t.permission === null || permissions[t.permission] === true,
);
function isActive(
tab: { href: string; exact?: boolean },
pathname: string,
): boolean {
if (tab.exact) return pathname === tab.href;
return pathname.startsWith(tab.href);
}
</script>
<svelte:head>
<title>Admin — Project Optima</title>
</svelte:head>
<div class="admin-page">
<div class="admin-pane">
<!-- Pane header -->
<div class="admin-header">
<div class="admin-header-left">
<svg
class="admin-header-icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="3" />
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"
/>
</svg>
<h2 class="admin-title">Administration</h2>
<span class="admin-subtitle">Welcome back, {userName}</span>
</div>
</div>
<!-- Tab bar -->
<div class="tab-bar" role="tablist">
{#each visibleTabs as tab}
<a
href={tab.href}
class="tab-btn"
class:active={isActive(tab, $page.url.pathname)}
role="tab"
aria-selected={isActive(tab, $page.url.pathname)}
>
{tab.label}
</a>
{/each}
</div>
<!-- Tab content -->
<div class="admin-body">
<slot />
</div>
</div>
</div>