diff --git a/web/src/lib/components/NavBar.svelte b/web/src/lib/components/NavBar.svelte new file mode 100644 index 0000000..c6e2a42 --- /dev/null +++ b/web/src/lib/components/NavBar.svelte @@ -0,0 +1,55 @@ + + + diff --git a/web/src/lib/components/PageTransition.svelte b/web/src/lib/components/PageTransition.svelte index fab59b3..c3c3a19 100644 --- a/web/src/lib/components/PageTransition.svelte +++ b/web/src/lib/components/PageTransition.svelte @@ -4,36 +4,65 @@ import type { Snippet } from "svelte"; import { cubicOut } from "svelte/easing"; import type { TransitionConfig } from "svelte/transition"; -let { key, children }: { key: string; children: Snippet } = $props(); +type Axis = "horizontal" | "vertical"; -const DURATION = 250; +let { + key, + children, + axis = "horizontal", + inDelay = 0, + outDelay = 0, +}: { + key: string; + children: Snippet; + axis?: Axis; + inDelay?: number; + outDelay?: number; +} = $props(); + +const DURATION = 400; const OFFSET = 40; +function translate(axis: Axis, value: number): string { + return axis === "vertical" ? `translateY(${value}px)` : `translateX(${value}px)`; +} + function inTransition(_node: HTMLElement): TransitionConfig { const dir = navigationStore.direction; if (dir === "fade") { - return { duration: DURATION, easing: cubicOut, css: (t: number) => `opacity: ${t}` }; + return { + duration: DURATION, + delay: inDelay, + easing: cubicOut, + css: (t: number) => `opacity: ${t}`, + }; } - const x = dir === "right" ? OFFSET : -OFFSET; + const offset = dir === "right" ? OFFSET : -OFFSET; return { duration: DURATION, + delay: inDelay, easing: cubicOut, - css: (t: number) => `opacity: ${t}; transform: translateX(${(1 - t) * x}px)`, + css: (t: number) => `opacity: ${t}; transform: ${translate(axis, (1 - t) * offset)}`, }; } function outTransition(_node: HTMLElement): TransitionConfig { const dir = navigationStore.direction; - // Outgoing element is positioned absolutely so incoming flows normally const base = "position: absolute; top: 0; left: 0; width: 100%"; if (dir === "fade") { - return { duration: DURATION, easing: cubicOut, css: (t: number) => `${base}; opacity: ${t}` }; + return { + duration: DURATION, + delay: outDelay, + easing: cubicOut, + css: (t: number) => `${base}; opacity: ${t}`, + }; } - const x = dir === "right" ? -OFFSET : OFFSET; + const offset = dir === "right" ? -OFFSET : OFFSET; return { duration: DURATION, + delay: outDelay, easing: cubicOut, - css: (t: number) => `${base}; opacity: ${t}; transform: translateX(${(1 - t) * x}px)`, + css: (t: number) => `${base}; opacity: ${t}; transform: ${translate(axis, (1 - t) * offset)}`, }; } diff --git a/web/src/lib/stores/navigation.svelte.ts b/web/src/lib/stores/navigation.svelte.ts index e71e0f4..c973f46 100644 --- a/web/src/lib/stores/navigation.svelte.ts +++ b/web/src/lib/stores/navigation.svelte.ts @@ -2,15 +2,22 @@ import { beforeNavigate } from "$app/navigation"; export type NavDirection = "left" | "right" | "fade"; -/** Admin sidebar order — indexes determine slide direction for same-depth siblings */ -const ADMIN_NAV_ORDER = ["/admin", "/admin/scrape-jobs", "/admin/audit-log", "/admin/users"]; +/** Sidebar nav order — indexes determine slide direction for same-depth siblings */ +const SIDEBAR_NAV_ORDER = [ + "/profile", + "/settings", + "/admin", + "/admin/jobs", + "/admin/audit", + "/admin/users", +]; function getDepth(path: string): number { return path.replace(/\/$/, "").split("/").filter(Boolean).length; } -function getAdminIndex(path: string): number { - return ADMIN_NAV_ORDER.indexOf(path); +function getSidebarIndex(path: string): number { + return SIDEBAR_NAV_ORDER.indexOf(path); } function computeDirection(from: string, to: string): NavDirection { @@ -20,9 +27,9 @@ function computeDirection(from: string, to: string): NavDirection { if (toDepth > fromDepth) return "right"; if (toDepth < fromDepth) return "left"; - // Same depth — use admin sidebar ordering if both are admin routes - const fromIdx = getAdminIndex(from); - const toIdx = getAdminIndex(to); + // Same depth — use sidebar ordering if both are sidebar routes + const fromIdx = getSidebarIndex(from); + const toIdx = getSidebarIndex(to); if (fromIdx >= 0 && toIdx >= 0) { return toIdx > fromIdx ? "right" : "left"; } diff --git a/web/src/routes/(app)/+layout.svelte b/web/src/routes/(app)/+layout.svelte new file mode 100644 index 0000000..a1a1e65 --- /dev/null +++ b/web/src/routes/(app)/+layout.svelte @@ -0,0 +1,116 @@ + + +{#if authStore.isLoading} +
Loading...
+Redirecting to login...
+{error}
@@ -40,7 +40,7 @@ onMount(async () => { -{error}
diff --git a/web/src/routes/admin/scrape-jobs/+page.svelte b/web/src/routes/(app)/admin/jobs/+page.svelte similarity index 96% rename from web/src/routes/admin/scrape-jobs/+page.svelte rename to web/src/routes/(app)/admin/jobs/+page.svelte index aeaee64..683c9a7 100644 --- a/web/src/routes/admin/scrape-jobs/+page.svelte +++ b/web/src/routes/(app)/admin/jobs/+page.svelte @@ -14,7 +14,7 @@ onMount(async () => { }); -{error}
diff --git a/web/src/routes/admin/users/+page.svelte b/web/src/routes/(app)/admin/users/+page.svelte similarity index 97% rename from web/src/routes/admin/users/+page.svelte rename to web/src/routes/(app)/admin/users/+page.svelte index 6ce37ea..0bb5ea0 100644 --- a/web/src/routes/admin/users/+page.svelte +++ b/web/src/routes/(app)/admin/users/+page.svelte @@ -29,7 +29,7 @@ async function toggleAdmin(user: User) { } -{error}
diff --git a/web/src/routes/(app)/profile/+page.svelte b/web/src/routes/(app)/profile/+page.svelte new file mode 100644 index 0000000..76845a0 --- /dev/null +++ b/web/src/routes/(app)/profile/+page.svelte @@ -0,0 +1,24 @@ + + +Username
+{authStore.user.discordUsername}
+Discord ID
+{authStore.user.discordId}
+Role
+{authStore.isAdmin ? "Admin" : "User"}
+No settings available yet.
+Loading...
-You do not have admin access.
-