This commit is contained in:
Justin Visser 2026-02-17 20:24:12 +01:00
parent efd21a7297
commit 441676be27
97 changed files with 10764 additions and 223 deletions

View file

@ -0,0 +1,35 @@
<script setup lang="ts">
import { computed } from "vue";
import { RouterView } from "vue-router";
import AppHeader from "@/components/layout/AppHeader.vue";
import AppNav from "@/components/layout/AppNav.vue";
import RequestErrorPanel from "@/components/patterns/RequestErrorPanel.vue";
import { useAuthStore } from "@/stores/auth";
const auth = useAuthStore();
const showChrome = computed(() => auth.isAuthenticated);
</script>
<template>
<div class="min-h-screen overflow-x-clip">
<a href="#app-main" class="skip-link">Skip to main content</a>
<RequestErrorPanel />
<template v-if="showChrome">
<AppHeader />
<div
class="grid min-h-[calc(100dvh-4.5rem)] grid-cols-1 lg:h-[calc(100dvh-4.5rem)] lg:min-h-[calc(100dvh-4.5rem)] lg:grid-cols-[17rem_minmax(0,1fr)]"
>
<AppNav class="lg:min-h-0 lg:overflow-y-auto" />
<main id="app-main" class="min-w-0 px-4 py-6 sm:px-6 lg:min-h-0 lg:overflow-y-auto lg:px-8">
<RouterView />
</main>
</div>
</template>
<main id="app-main" v-else class="min-h-screen overflow-x-clip">
<RouterView />
</main>
</div>
</template>

View file

@ -0,0 +1,23 @@
import type { Router } from "vue-router";
import { useAuthStore } from "@/stores/auth";
export function applyRouteGuards(router: Router): void {
router.beforeEach((to) => {
const auth = useAuthStore();
if (to.meta.requiresAuth && !auth.isAuthenticated) {
return { name: "login" };
}
if (to.meta.requiresAdmin && !auth.isAdmin) {
return { name: "dashboard" };
}
if (to.meta.guestOnly && auth.isAuthenticated) {
return { name: "dashboard" };
}
return true;
});
}

View file

@ -0,0 +1,12 @@
import { setCsrfTokenProvider } from "@/lib/api/client";
import { useAuthStore } from "@/stores/auth";
import { useThemeStore } from "@/stores/theme";
export async function bootstrapAppProviders(): Promise<void> {
const theme = useThemeStore();
theme.initialize();
const auth = useAuthStore();
setCsrfTokenProvider(() => auth.csrfToken);
await auth.bootstrapSession();
}

View file

@ -0,0 +1,84 @@
import { createRouter, createWebHistory } from "vue-router";
import { applyRouteGuards } from "@/app/guards";
import LoginPage from "@/pages/LoginPage.vue";
import DashboardPage from "@/pages/DashboardPage.vue";
import ScholarsPage from "@/pages/ScholarsPage.vue";
import PublicationsPage from "@/pages/PublicationsPage.vue";
import RunsPage from "@/pages/RunsPage.vue";
import RunDetailPage from "@/pages/RunDetailPage.vue";
import SettingsPage from "@/pages/SettingsPage.vue";
import AdminUsersPage from "@/pages/AdminUsersPage.vue";
import StyleGuidePage from "@/pages/StyleGuidePage.vue";
export const router = createRouter({
history: createWebHistory(),
routes: [
{
path: "/login",
name: "login",
component: LoginPage,
meta: { guestOnly: true },
},
{
path: "/",
redirect: "/dashboard",
},
{
path: "/dashboard",
name: "dashboard",
component: DashboardPage,
meta: { requiresAuth: true },
},
{
path: "/scholars",
name: "scholars",
component: ScholarsPage,
meta: { requiresAuth: true },
},
{
path: "/publications",
name: "publications",
component: PublicationsPage,
meta: { requiresAuth: true },
},
{
path: "/settings",
name: "settings",
component: SettingsPage,
meta: { requiresAuth: true },
},
{
path: "/admin/style-guide",
name: "style-guide",
component: StyleGuidePage,
meta: { requiresAuth: true, requiresAdmin: true },
},
{
path: "/admin/runs",
name: "runs",
component: RunsPage,
meta: { requiresAuth: true, requiresAdmin: true },
},
{
path: "/admin/runs/:id",
name: "run-detail",
component: RunDetailPage,
meta: { requiresAuth: true, requiresAdmin: true },
},
{
path: "/admin/users",
name: "admin-users",
component: AdminUsersPage,
meta: { requiresAuth: true, requiresAdmin: true },
},
{
path: "/:pathMatch(.*)*",
redirect: "/dashboard",
},
],
});
applyRouteGuards(router);
export default router;