archy/neode-ui/src/App.vue

108 lines
3.2 KiB
Vue
Raw Normal View History

2026-01-24 22:59:20 +00:00
<template>
<div id="app">
<!-- Splash Screen (only on first visit) -->
<SplashScreen v-if="showSplash" @complete="handleSplashComplete" />
<!-- Main App Content - only show after splash and routing is complete -->
<RouterView v-if="!showSplash && isReady" />
<!-- Spotlight command palette (Cmd+K / Ctrl+K) -->
<SpotlightSearch />
<!-- Help guide modal (from spotlight) -->
<HelpGuideModal
:show="spotlightStore.helpModal.show"
:title="spotlightStore.helpModal.title"
:content="spotlightStore.helpModal.content"
:related-path="spotlightStore.helpModal.relatedPath"
@close="spotlightStore.closeHelpModal()"
/>
2026-01-24 22:59:20 +00:00
<!-- PWA Update Prompt -->
<PWAUpdatePrompt />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
2026-01-24 22:59:20 +00:00
import { useRouter, useRoute } from 'vue-router'
import SplashScreen from './components/SplashScreen.vue'
import PWAUpdatePrompt from './components/PWAUpdatePrompt.vue'
import SpotlightSearch from './components/SpotlightSearch.vue'
import HelpGuideModal from './components/HelpGuideModal.vue'
import { useControllerNav } from '@/composables/useControllerNav'
import { useSpotlightStore } from '@/stores/spotlight'
2026-01-24 22:59:20 +00:00
const router = useRouter()
const spotlightStore = useSpotlightStore()
useControllerNav()
function onKeyDown(e: KeyboardEvent) {
const isMac = navigator.platform.toUpperCase().includes('MAC')
const mod = isMac ? e.metaKey : e.ctrlKey
if (mod && e.key === 'k') {
e.preventDefault()
spotlightStore.toggle()
}
}
2026-01-24 22:59:20 +00:00
const route = useRoute()
const showSplash = ref(true)
const isReady = ref(false)
/**
* Determine if splash screen should be shown
* Splash is skipped if:
* - User has already seen the intro
* - User is on a direct route (refresh/bookmark)
*/
onMounted(() => {
window.addEventListener('keydown', onKeyDown)
2026-01-24 22:59:20 +00:00
const seenIntro = localStorage.getItem('neode_intro_seen') === '1'
const isDirectRoute = route.path !== '/'
if (seenIntro || isDirectRoute) {
showSplash.value = false
document.body.classList.add('splash-complete')
// Set isReady immediately for direct routes or when intro is already seen
// Router will handle navigation
2026-01-24 22:59:20 +00:00
isReady.value = true
}
// If splash should show, wait for it to complete
// SplashScreen will emit 'complete' which calls handleSplashComplete
2026-01-24 22:59:20 +00:00
})
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKeyDown)
})
2026-01-24 22:59:20 +00:00
/**
* Handle splash screen completion
* Routes user directly to appropriate screen based on onboarding status (from backend)
2026-01-24 22:59:20 +00:00
*/
async function handleSplashComplete() {
2026-01-24 22:59:20 +00:00
showSplash.value = false
document.body.classList.add('splash-complete')
isReady.value = true
const devMode = import.meta.env.VITE_DEV_MODE
if (devMode === 'setup' || devMode === 'existing') {
router.push('/login').catch(() => {})
return
}
try {
const { isOnboardingComplete } = await import('@/composables/useOnboarding')
const seenOnboarding = await isOnboardingComplete()
const destination = seenOnboarding ? '/login' : '/onboarding/intro'
router.push(destination).catch(() => {})
} catch {
router.push('/onboarding/intro').catch(() => {})
}
2026-01-24 22:59:20 +00:00
}
</script>
<style>
/* Global styles are in style.css */
</style>