<template>
<aside class="app-sidebar" :class="{ 'is-mobile-open': props.mobileOpen }">
<div class="sidebar-header">
<div class="sidebar-logo">
<img src="/images/logo.svg" alt="Navi" />
<span>Navi</span>
<button class="btn-icon sidebar-close-btn" title="Close" @click="emit('close')">
<i class="ph ph-x"></i>
</button>
</div>
<div class="sidebar-controls-row">
<div class="sidebar-profile-select">
<select
:value="profilesStore.selectedProfileId"
@change="profilesStore.selectedProfileId = $event.target.value"
>
<option
v-for="p in profilesStore.profiles"
:key="p.id"
:value="p.id"
>{{ p.name }}</option>
</select>
</div>
<button class="btn btn-primary with-icon" @click="handleNewSession">
<i class="ph ph-plus"></i>
New Chat
</button>
</div>
</div>
<div class="sidebar-sessions">
<div class="sessions-label">Conversations</div>
<SessionList @select="handleSelect" />
</div>
<div class="sidebar-footer">
<template v-if="authStore.isAuthenticated">
<a
class="user-info"
:href="authStore.user?.profile_url"
target="_blank"
rel="noopener noreferrer"
title="Open profile in gnexus-auth"
>
<img
v-if="authStore.user?.avatar_url"
class="user-avatar"
:src="authStore.user.avatar_url"
alt="avatar"
/>
<div v-else class="user-avatar user-avatar-fallback">
{{ (authStore.user?.display_name || authStore.user?.email || '?').charAt(0).toUpperCase() }}
</div>
<span class="user-name">{{ authStore.user?.display_name || authStore.user?.email }}</span>
</a>
<div class="sidebar-footer-actions">
<a
v-if="authStore.isAdmin"
href="/admin"
class="btn btn-secondary with-icon admin-link"
>
<i class="ph ph-shield-check"></i>
Admin
</a>
<button class="btn btn-primary with-icon" @click="authStore.logout">
<i class="ph ph-sign-out"></i>
Logout
</button>
</div>
</template>
<template v-else>
<button class="btn btn-primary with-icon" @click="authStore.login">
<i class="ph ph-sign-in"></i>
Login
</button>
</template>
</div>
</aside>
</template>
<script setup>
import { watch } from 'vue'
import { useProfilesStore } from '@/stores/profiles.js'
import { useSessionsStore } from '@/stores/sessions.js'
import { useChatStore } from '@/stores/chat.js'
import { useAuthStore } from '@/stores/auth.js'
import SessionList from './SessionList.vue'
const props = defineProps({
mobileOpen: Boolean
})
const emit = defineEmits(['close'])
const profilesStore = useProfilesStore()
const sessionsStore = useSessionsStore()
const chatStore = useChatStore()
const authStore = useAuthStore()
// Keep selector in sync with the active session's profile
watch(
() => chatStore.currentProfileId,
(id) => { if (id) profilesStore.selectedProfileId = id }
)
async function handleNewSession() {
const session = await sessionsStore.createSession(profilesStore.selectedProfileId)
await chatStore.loadSession(session.session_id)
emit('close')
}
function handleSelect() {
emit('close')
}
</script>
<style scoped>
.sidebar-close-btn {
display: none;
@media (max-width: 1280px) {
display: flex;
margin-left: auto;
}
}
.sidebar-footer {
padding: 12px 16px;
border-top: 1px solid var(--color-border);
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
text-decoration: none;
color: inherit;
flex: 1;
min-width: 0;
cursor: pointer;
}
.user-info:hover .user-name {
color: var(--color-text-primary);
}
.user-avatar {
width: 28px;
height: 28px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.user-avatar-fallback {
display: flex;
align-items: center;
justify-content: center;
background: var(--color-primary);
color: #fff;
font-size: 12px;
font-weight: 600;
}
.user-name {
font-size: 13px;
color: var(--color-text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sidebar-footer-actions {
display: flex;
align-items: center;
gap: 8px;
}
.admin-link {
text-decoration: none;
}
</style>