First-run setup wizard (/setup)

- setup_complete site setting; a fresh admin is steered to /setup until it is
  finished (skippable for the session).
- SetupWizard.vue: Site (facility/base-url/access-domain), Features (plugin
  enable/disable), Floor Map dimensions, Starter Data (seed common vendors),
  Finish. Reuses the settings + plugins APIs.
- setup blueprint: POST /setup/seed-starter (idempotent common vendors) and
  POST /setup/complete. setupState composable drives the router redirect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-10 07:47:01 -04:00
parent ec2de635ed
commit 843b225a47
8 changed files with 379 additions and 8 deletions

View File

@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '../stores/auth'
import AppLayout from '../views/AppLayout.vue'
import SettingsLayout from '../views/settings/SettingsLayout.vue'
import { setupComplete, setupSkipped, isSetupLoaded, refreshSetupState } from '../composables/setupState'
// Auto-discover all route modules from routes/ directory
const routeModules = import.meta.glob('./routes/*.js', { eager: true })
@@ -42,6 +43,13 @@ const routes = [
component: () => import('../views/Login.vue'),
meta: { guest: true }
},
// First-run setup wizard (standalone, admin-only)
{
path: '/setup',
name: 'setup',
component: () => import('../views/SetupWizard.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
// Standalone full-screen dashboards (no sidebar, no auth required)
{
path: '/shopfloor',
@@ -87,18 +95,30 @@ const router = createRouter({
})
// Navigation guard
router.beforeEach((to, from, next) => {
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore()
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
next('/login')
} else if (to.meta.requiresAdmin && !authStore.isAdmin) {
next('/')
} else if (to.meta.guest && authStore.isAuthenticated) {
next('/')
} else {
next()
return next('/login')
}
if (to.meta.requiresAdmin && !authStore.isAdmin) {
return next('/')
}
if (to.meta.guest && authStore.isAuthenticated) {
return next('/')
}
// First-run: steer a fresh admin into the setup wizard (once, until finished).
if (authStore.isAuthenticated && authStore.isAdmin && to.path !== '/setup') {
if (!isSetupLoaded()) {
await refreshSetupState()
}
if (!setupComplete.value && !setupSkipped.value) {
return next('/setup')
}
}
next()
})
export default router