diff --git a/web/src/router/index.ts b/web/src/router/index.ts index d6efef5..8c8368b 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -1,5 +1,6 @@ import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router"; import { useAuthStore } from "@/stores/auth"; +import api from "@/api/client"; const routes: RouteRecordRaw[] = [ { @@ -8,6 +9,12 @@ const routes: RouteRecordRaw[] = [ component: () => import("@/views/LoginView.vue"), meta: { requiresAuth: false }, }, + { + path: "/setup", + name: "setup", + component: () => import("@/views/SetupView.vue"), + meta: { requiresAuth: false }, + }, { path: "/", component: () => import("@/components/layout/AppLayout.vue"), @@ -42,8 +49,34 @@ const router = createRouter({ routes, }); -router.beforeEach((to) => { +let setupChecked = false; +let isSetUp = false; + +router.beforeEach(async (to) => { const auth = useAuthStore(); + + // Check setup status once on first navigation + if (!setupChecked) { + try { + const { data } = await api.get("/setup/status"); + isSetUp = data.isSetUp; + } catch { + // If server unreachable, assume setup done + isSetUp = true; + } + setupChecked = true; + } + + // Redirect to setup if not configured (unless already on setup page) + if (!isSetUp && to.name !== "setup") { + return { name: "setup" }; + } + + // After setup is done, don't allow revisiting setup + if (isSetUp && to.name === "setup") { + return { name: "login" }; + } + if (to.meta.requiresAuth !== false && !auth.isAuthenticated) { return { name: "login" }; } @@ -52,4 +85,9 @@ router.beforeEach((to) => { } }); +// Allow marking setup as complete from the setup view +export function markSetupComplete(): void { + isSetUp = true; +} + export default router; diff --git a/web/src/views/SetupView.vue b/web/src/views/SetupView.vue new file mode 100644 index 0000000..8c56481 --- /dev/null +++ b/web/src/views/SetupView.vue @@ -0,0 +1,301 @@ + + +