<template>
<div class="app-shell">
<!-- Mobile sidebar backdrop -->
<div
v-if="sidebarOpen"
class="sidebar-backdrop"
@click="closeSidebar"
/>
<Transition name="backdrop-fade">
<div
v-if="artifactsOpen"
class="artifacts-backdrop"
@click="closeArtifacts"
/>
</Transition>
<!-- Sidebar (desktop: always visible; mobile: drawer) -->
<AppSidebar
:mobile-open="sidebarOpen"
:class="{ 'is-hidden-by-artifacts': artifactsOpen }"
@close="closeSidebar"
/>
<!-- Global confirm dialog -->
<ConfirmDialog />
<!-- Global image lightbox -->
<ImageLightbox />
<!-- Selection reply toolbar -->
<SelectionToolbar />
<!-- Main content -->
<div class="app-main">
<router-view @toggle-sidebar="toggleSidebar" @toggle-artifacts="toggleArtifacts" />
</div>
<ArtifactsPanel
:open="artifactsOpen"
@close="closeArtifacts"
/>
<!-- Login overlay: blocks the UI when auth is required but user is not logged in -->
<LoginScreen
v-if="authStore.authConfigured && !authStore.isAuthenticated && !authStore.loading"
/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useSessionsStore } from '@/stores/sessions'
import { useProfilesStore } from '@/stores/profiles'
import { useChatStore } from '@/stores/chat'
import { useAuthStore } from '@/stores/auth'
import AppSidebar from '@/components/sidebar/AppSidebar.vue'
import ConfirmDialog from '@/components/ui/ConfirmDialog.vue'
import ImageLightbox from '@/components/ui/ImageLightbox.vue'
import SelectionToolbar from '@/components/ui/SelectionToolbar.vue'
import ArtifactsPanel from '@/components/artifacts/ArtifactsPanel.vue'
import LoginScreen from '@/components/ui/LoginScreen.vue'
const sessionsStore = useSessionsStore()
const profilesStore = useProfilesStore()
const chatStore = useChatStore()
const authStore = useAuthStore()
const sidebarOpen = ref(false)
const artifactsOpen = ref(false)
function closeSidebar() {
sidebarOpen.value = false
}
function closeArtifacts() {
artifactsOpen.value = false
}
function toggleSidebar() {
const next = !sidebarOpen.value
if (next) artifactsOpen.value = false
sidebarOpen.value = next
}
function toggleArtifacts() {
const next = !artifactsOpen.value
if (next) sidebarOpen.value = false
artifactsOpen.value = next
}
onMounted(async () => {
// Check auth configuration first, before any protected API calls
console.log('[app] fetchStatus start')
await authStore.fetchStatus()
console.log('[app] fetchStatus done, authConfigured=', authStore.authConfigured)
// Resolve auth state
try {
console.log('[app] fetchMe start')
await authStore.fetchMe()
console.log('[app] fetchMe done, isAuthenticated=', authStore.isAuthenticated)
} catch {
console.log('[app] fetchMe failed (unauthenticated)')
}
console.log('[app] should show login?', authStore.authConfigured, !authStore.isAuthenticated, !authStore.loading)
// Load app data only if authenticated or auth is not configured
if (!authStore.authConfigured || authStore.isAuthenticated) {
try {
await profilesStore.fetchProfiles()
await sessionsStore.fetchSessions(profilesStore.selectedProfileId)
} catch (err) {
console.log('[app] data fetch error', err)
}
}
})
// Legacy hash-based routing for session IDs that do NOT use the vue-router #/ prefix.
window.addEventListener('hashchange', () => {
const hash = location.hash
if (hash.startsWith('#/')) return // vue-router managed
const id = hash.slice(1)
if (id && id !== chatStore.currentId) {
chatStore.loadSession(id)
}
})
</script>
<style scoped>
.app-shell {
display: flex;
flex: 1 1 0;
min-height: 0;
min-width: 0;
overflow: hidden;
}
.sidebar-backdrop {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 99;
@media (max-width: 1280px) {
display: block;
}
}
.artifacts-backdrop {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.45);
z-index: 119;
@media (max-width: 980px) {
display: block;
}
}
.backdrop-fade-enter-active,
.backdrop-fade-leave-active {
transition: opacity 0.18s ease;
}
.backdrop-fade-enter-from,
.backdrop-fade-leave-to {
opacity: 0;
}
</style>