From 0dc87469149f37209b247846443699331c0c98a8 Mon Sep 17 00:00:00 2001 From: saopig1 <4x7sw862st@gmail.com> Date: Wed, 27 May 2026 13:52:49 +0800 Subject: [PATCH] feat(web): add /first-run + /login routes with auth guard Co-Authored-By: Claude Sonnet 4.6 --- web/src/router/index.ts | 82 ++++++++++++++++----------------- web/src/views/FirstRunSetup.vue | 75 ++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 41 deletions(-) create mode 100644 web/src/views/FirstRunSetup.vue diff --git a/web/src/router/index.ts b/web/src/router/index.ts index d62afcd..3965ceb 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -1,23 +1,12 @@ import { createRouter, createWebHistory } from 'vue-router'; +import { useSession } from '../composables/useSession.js'; const router = createRouter({ history: createWebHistory(), routes: [ - { - path: '/', - name: 'home', - component: () => import('../views/Home.vue'), - }, - { - path: '/search', - name: 'search', - component: () => import('../views/Search.vue'), - }, - { - path: '/library', - name: 'library', - component: () => import('../views/Library.vue'), - }, + { path: '/', name: 'home', component: () => import('../views/Home.vue') }, + { path: '/search', name: 'search', component: () => import('../views/Search.vue') }, + { path: '/library', name: 'library', component: () => import('../views/Library.vue') }, { path: '/playlist/:id', name: 'playlist', @@ -30,33 +19,44 @@ const router = createRouter({ component: () => import('../views/Playlist.vue'), meta: { kind: 'album' }, }, - { - path: '/lyrics', - name: 'lyrics', - component: () => import('../views/Lyrics.vue'), - }, - { - path: '/history', - name: 'history', - component: () => import('../views/History.vue'), - }, - { - path: '/settings', - name: 'settings', - component: () => import('../views/Settings.vue'), - }, - { - path: '/setup', - name: 'setup', - component: () => import('../views/Setup.vue'), - }, - { - // Per-bot URL: /bot/:id — sets active bot then redirects to home - path: '/bot/:id', - name: 'bot', - component: () => import('../views/BotRedirect.vue'), - }, + { path: '/lyrics', name: 'lyrics', component: () => import('../views/Lyrics.vue') }, + { path: '/history', name: 'history', component: () => import('../views/History.vue') }, + { path: '/settings', name: 'settings', component: () => import('../views/Settings.vue') }, + { path: '/setup', name: 'setup', component: () => import('../views/Setup.vue') }, + { path: '/bot/:id', name: 'bot', component: () => import('../views/BotRedirect.vue') }, + + // Auth views + { path: '/login', name: 'login', component: () => import('../views/Login.vue'), meta: { public: true } }, + { path: '/first-run', name: 'first-run', component: () => import('../views/FirstRunSetup.vue'), meta: { public: true } }, ], }); +const PUBLIC_NAMES = new Set(['login', 'first-run']); + +router.beforeEach(async (to) => { + const session = useSession(); + if (!session.ready.value) { + await session.refresh(); + } + + if (session.needsSetup.value && to.name !== 'first-run') { + return { name: 'first-run' }; + } + if (!session.needsSetup.value && to.name === 'first-run') { + return { name: 'home' }; + } + + if (PUBLIC_NAMES.has(to.name as string)) { + if (to.name === 'login' && session.isAuthenticated.value) { + return { name: 'home' }; + } + return true; + } + + if (!session.isAuthenticated.value) { + return { name: 'login', query: { next: to.fullPath } }; + } + return true; +}); + export default router; diff --git a/web/src/views/FirstRunSetup.vue b/web/src/views/FirstRunSetup.vue new file mode 100644 index 0000000..24664d6 --- /dev/null +++ b/web/src/views/FirstRunSetup.vue @@ -0,0 +1,75 @@ + + + + +