197 lines
8.1 KiB
Vue
197 lines
8.1 KiB
Vue
<script setup>
|
|
import { computed, onMounted, ref, watch } from 'vue'
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { useProjectsStore } from '@/stores/projectsStore'
|
|
import { storeToRefs } from 'pinia'
|
|
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const authStore = useAuthStore()
|
|
const projectsStore = useProjectsStore()
|
|
const { projects, currentProject } = storeToRefs(projectsStore)
|
|
|
|
const selectedProject = ref(null)
|
|
|
|
onMounted(async () => {
|
|
// Ensure we have projects
|
|
if (projects.value.length === 0) {
|
|
await projectsStore.fetchProjects()
|
|
}
|
|
// Sync local ref with store
|
|
if (currentProject.value) {
|
|
selectedProject.value = currentProject.value.id
|
|
}
|
|
})
|
|
|
|
// Watch for external changes (like selecting from the list view)
|
|
watch(currentProject, (newVal) => {
|
|
selectedProject.value = newVal ? newVal.id : null
|
|
})
|
|
|
|
const projectOptions = computed(() => {
|
|
return projects.value.map(p => ({ name: p.name, id: p.id }))
|
|
})
|
|
|
|
const getProjectName = (id) => {
|
|
const p = projects.value.find(p => p.id === id)
|
|
return p ? p.name : 'Unknown Project'
|
|
}
|
|
|
|
const isProjectMenuOpen = ref(false)
|
|
|
|
const selectProject = (projectId) => {
|
|
selectedProject.value = projectId
|
|
isProjectMenuOpen.value = false
|
|
|
|
if (projectId) {
|
|
projectsStore.selectProject(projectId)
|
|
} else {
|
|
// Clear selection
|
|
projectsStore.currentProject = null
|
|
localStorage.removeItem('active_project_id')
|
|
}
|
|
// Reload page to ensure all data is refreshed with new context
|
|
window.location.reload()
|
|
}
|
|
|
|
const handleLogout = () => {
|
|
authStore.logout()
|
|
router.push('/login')
|
|
}
|
|
|
|
// Check active route for styling
|
|
const isActive = (path) => {
|
|
// Exact match for home, startsWith for others
|
|
if (path === '/') return route.path === '/'
|
|
return route.path.startsWith(path)
|
|
}
|
|
|
|
const navItems = computed(() => {
|
|
const items = [
|
|
{ path: '/', icon: '🏠', tooltip: 'Home' },
|
|
{ path: '/projects', icon: '📂', tooltip: 'Projects' },
|
|
{ path: '/ideas', icon: '💡', tooltip: 'Ideas' },
|
|
{ path: '/flexible', icon: '🖌️', tooltip: 'Flexible' },
|
|
{ path: '/albums', icon: '🖼️', tooltip: 'Library' },
|
|
{ path: '/characters', icon: '👥', tooltip: 'Characters' }
|
|
]
|
|
|
|
if (authStore.isAdmin()) {
|
|
items.push({ path: '/admin/approvals', icon: '🛡️', tooltip: 'Approvals' })
|
|
}
|
|
|
|
return items
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="contents">
|
|
<!-- Sidebar (Desktop -> Top Bar) -->
|
|
<nav
|
|
class="hidden md:flex glass-panel w-[calc(100%-2rem)] mx-4 mt-4 mb-2 flex-row items-center px-8 py-3 rounded-2xl z-40 border border-white/5 bg-slate-900/50 backdrop-blur-md shrink-0 justify-between">
|
|
|
|
<!-- Logo -->
|
|
<img src="/web-app-manifest-512x512.png" alt="Logo"
|
|
class="w-10 h-10 rounded-xl shadow-lg shadow-violet-500/20 shrink-0" />
|
|
|
|
<!-- Project Switcher -->
|
|
<div class="hidden lg:block ml-4 relative">
|
|
<button @click="isProjectMenuOpen = !isProjectMenuOpen"
|
|
class="flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-white/5 transition-colors text-slate-400 hover:text-slate-200">
|
|
<i v-if="selectedProject" class="pi pi-folder text-violet-400"></i>
|
|
<i v-else class="pi pi-user"></i>
|
|
|
|
<span class="max-w-[150px] truncate font-medium">
|
|
{{ selectedProject ? getProjectName(selectedProject) : 'Personal Workspace' }}
|
|
</span>
|
|
|
|
<i class="pi pi-chevron-down text-xs ml-1 opacity-50"></i>
|
|
</button>
|
|
|
|
<!-- Custom Dropdown Menu -->
|
|
<div v-if="isProjectMenuOpen"
|
|
class="absolute top-full left-0 mt-2 w-56 bg-slate-900 border border-white/10 shadow-xl rounded-xl overflow-hidden z-50 py-1">
|
|
|
|
<!-- Personal Workspace Option -->
|
|
<div @click="selectProject(null)"
|
|
class="flex items-center gap-3 px-4 py-3 hover:bg-white/5 cursor-pointer transition-colors"
|
|
:class="{ 'text-violet-400 bg-white/5': !selectedProject, 'text-slate-300': selectedProject }">
|
|
<i class="pi pi-user"></i>
|
|
<span class="font-medium">Personal Workspace</span>
|
|
<i v-if="!selectedProject" class="pi pi-check ml-auto text-sm"></i>
|
|
</div>
|
|
|
|
<div class="h-px bg-white/5 my-1"></div>
|
|
|
|
<!-- Project Options -->
|
|
<div v-for="project in projects" :key="project.id" @click="selectProject(project.id)"
|
|
class="flex items-center gap-3 px-4 py-3 hover:bg-white/5 cursor-pointer transition-colors"
|
|
:class="{ 'text-violet-400 bg-white/5': selectedProject === project.id, 'text-slate-300': selectedProject !== project.id }">
|
|
<i class="pi pi-folder"></i>
|
|
<span class="truncate">{{ project.name }}</span>
|
|
<i v-if="selectedProject === project.id" class="pi pi-check ml-auto text-sm"></i>
|
|
</div>
|
|
|
|
<div v-if="projects.length === 0" class="px-4 py-3 text-slate-500 text-sm font-italic">
|
|
No projects found
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Backdrop to close on click outside -->
|
|
<div v-if="isProjectMenuOpen" @click="isProjectMenuOpen = false"
|
|
class="fixed inset-0 z-40 bg-transparent"></div>
|
|
</div>
|
|
|
|
<!-- Nav Items -->
|
|
<div class="flex flex-row gap-2 items-center justify-center flex-1 mx-8">
|
|
<div v-for="item in navItems" :key="item.path" :class="[
|
|
'px-4 py-2 flex items-center gap-2 rounded-xl cursor-pointer transition-all duration-300',
|
|
isActive(item.path)
|
|
? 'bg-white/10 text-slate-50 shadow-inner'
|
|
: 'text-slate-400 hover:bg-white/5 hover:text-slate-50'
|
|
]" @click="router.push(item.path)" v-tooltip.bottom="item.tooltip">
|
|
<span class="text-xl">{{ item.icon }}</span>
|
|
<span class="text-sm font-medium hidden lg:block">{{ item.tooltip }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Actions -->
|
|
<div class="flex items-center gap-4 shrink-0">
|
|
<div @click="handleLogout"
|
|
class="w-10 h-10 rounded-xl bg-red-500/10 text-red-400 flex items-center justify-center cursor-pointer hover:bg-red-500/20 transition-all font-bold"
|
|
v-tooltip.bottom="'Logout'">
|
|
<i class="pi pi-power-off"></i>
|
|
</div>
|
|
<!-- Profile Avatar Placeholder -->
|
|
<div class="w-10 h-10 rounded-full bg-slate-800 border-2 border-violet-600 flex items-center justify-center font-bold text-slate-50 cursor-pointer hover:scale-105 transition-all"
|
|
title="Profile">
|
|
U
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Mobile Bottom Nav -->
|
|
<nav
|
|
class="md:hidden fixed bottom-0 left-0 right-0 h-16 bg-slate-900/90 backdrop-blur-xl border-t border-white/10 z-50 flex justify-around items-center px-2">
|
|
<div v-for="item in navItems" :key="item.path" :class="[
|
|
'flex flex-col items-center gap-1 p-2 rounded-xl transition-all',
|
|
isActive(item.path)
|
|
? 'text-white bg-white/10 relative top-[-10px] shadow-lg shadow-violet-500/20 border border-violet-500/30'
|
|
: 'text-slate-400 hover:text-slate-200'
|
|
]" @click="router.push(item.path)">
|
|
<span class="text-xl">{{ item.icon }}</span>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.glass-panel {
|
|
background: rgba(255, 255, 255, 0.03);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
}
|
|
</style>
|