Newer
Older
navi-1 / webclient / src / App.vue
<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>