106 lines
3.0 KiB
Svelte
106 lines
3.0 KiB
Svelte
<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>
|