2026-01-24 22:59:20 +00:00
|
|
|
import { createRouter, createWebHistory } from 'vue-router'
|
|
|
|
|
import { useAppStore } from '../stores/app'
|
|
|
|
|
|
|
|
|
|
const router = createRouter({
|
|
|
|
|
history: createWebHistory(),
|
|
|
|
|
routes: [
|
|
|
|
|
{
|
|
|
|
|
path: '/',
|
|
|
|
|
component: () => import('../views/OnboardingWrapper.vue'),
|
|
|
|
|
meta: { public: true },
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
path: '',
|
2026-02-01 06:04:36 +00:00
|
|
|
redirect: (_to) => {
|
2026-01-24 22:59:20 +00:00
|
|
|
// Initial routing logic - determines first screen after splash
|
|
|
|
|
const devMode = import.meta.env.VITE_DEV_MODE
|
|
|
|
|
const seenOnboarding = localStorage.getItem('neode_onboarding_complete') === '1'
|
2026-02-01 06:04:36 +00:00
|
|
|
// const isSetup = localStorage.getItem('neode_setup_complete') === '1'
|
2026-01-24 22:59:20 +00:00
|
|
|
|
|
|
|
|
// Setup mode: go directly to login (original StartOS setup)
|
|
|
|
|
if (devMode === 'setup') {
|
2026-01-24 23:09:46 +00:00
|
|
|
return '/login'
|
2026-01-24 22:59:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Onboarding mode: go to experimental onboarding flow
|
|
|
|
|
if (devMode === 'onboarding') {
|
|
|
|
|
return seenOnboarding ? '/login' : '/onboarding/intro'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Existing user mode: go to login
|
|
|
|
|
if (devMode === 'existing') {
|
|
|
|
|
return '/login'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Default: check if user has completed onboarding
|
|
|
|
|
return seenOnboarding ? '/login' : '/onboarding/intro'
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'login',
|
|
|
|
|
name: 'login',
|
|
|
|
|
component: () => import('../views/Login.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'onboarding/intro',
|
|
|
|
|
name: 'onboarding-intro',
|
|
|
|
|
component: () => import('../views/OnboardingIntro.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'onboarding/options',
|
|
|
|
|
name: 'onboarding-options',
|
|
|
|
|
component: () => import('../views/OnboardingOptions.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'onboarding/path',
|
|
|
|
|
name: 'onboarding-path',
|
|
|
|
|
component: () => import('../views/OnboardingPath.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'onboarding/did',
|
|
|
|
|
name: 'onboarding-did',
|
|
|
|
|
component: () => import('../views/OnboardingDid.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'onboarding/backup',
|
|
|
|
|
name: 'onboarding-backup',
|
|
|
|
|
component: () => import('../views/OnboardingBackup.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'onboarding/verify',
|
|
|
|
|
name: 'onboarding-verify',
|
|
|
|
|
component: () => import('../views/OnboardingVerify.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'onboarding/done',
|
|
|
|
|
name: 'onboarding-done',
|
|
|
|
|
component: () => import('../views/OnboardingDone.vue'),
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: '/dashboard',
|
|
|
|
|
component: () => import('../views/Dashboard.vue'),
|
|
|
|
|
children: [
|
|
|
|
|
{
|
|
|
|
|
path: '',
|
|
|
|
|
name: 'home',
|
|
|
|
|
component: () => import('../views/Home.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'apps',
|
|
|
|
|
name: 'apps',
|
|
|
|
|
component: () => import('../views/Apps.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'apps/:id',
|
|
|
|
|
name: 'app-details',
|
|
|
|
|
component: () => import('../views/AppDetails.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'marketplace',
|
|
|
|
|
name: 'marketplace',
|
|
|
|
|
component: () => import('../views/Marketplace.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'marketplace/:id',
|
|
|
|
|
name: 'marketplace-app-detail',
|
|
|
|
|
component: () => import('../views/MarketplaceAppDetails.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'cloud',
|
|
|
|
|
name: 'cloud',
|
|
|
|
|
component: () => import('../views/Cloud.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'cloud/:folderId',
|
|
|
|
|
name: 'cloud-folder',
|
|
|
|
|
component: () => import('../views/CloudFolder.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'server',
|
|
|
|
|
name: 'server',
|
|
|
|
|
component: () => import('../views/Server.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'web5',
|
|
|
|
|
name: 'web5',
|
|
|
|
|
component: () => import('../views/Web5.vue'),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
path: 'settings',
|
|
|
|
|
name: 'settings',
|
|
|
|
|
component: () => import('../views/Settings.vue'),
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Navigation Guard
|
|
|
|
|
* Handles authentication and onboarding flow routing
|
|
|
|
|
*/
|
|
|
|
|
router.beforeEach(async (to, _from, next) => {
|
|
|
|
|
const store = useAppStore()
|
|
|
|
|
const isPublic = to.meta.public
|
|
|
|
|
|
|
|
|
|
// Allow all public routes (login, onboarding) without auth check
|
|
|
|
|
if (isPublic) {
|
|
|
|
|
// If already authenticated and trying to access login, redirect to dashboard
|
|
|
|
|
if (to.path === '/login' && store.isAuthenticated) {
|
|
|
|
|
next('/dashboard')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
next()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Protected routes require authentication
|
|
|
|
|
// Check session if not already authenticated
|
|
|
|
|
if (!store.isAuthenticated) {
|
|
|
|
|
const hasSession = await store.checkSession()
|
|
|
|
|
if (hasSession) {
|
|
|
|
|
next()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No valid session - redirect to login
|
|
|
|
|
next('/login')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Authenticated user accessing protected route
|
|
|
|
|
next()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
export default router
|
|
|
|
|
|