150 lines
6.3 KiB
Vue
150 lines
6.3 KiB
Vue
<template>
|
|
<div class="min-h-screen bg-zinc-100 flex flex-col">
|
|
<!-- Top nav -->
|
|
<nav class="fixed top-0 w-full z-50 bg-zinc-50 border-b border-zinc-300 px-6 py-4">
|
|
<div class="max-w-7xl mx-auto flex items-center justify-between">
|
|
<RouterLink to="/" class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-lg bg-accent flex items-center justify-center shadow-md">
|
|
<iconify-icon icon="lucide:fuel" class="text-white text-xl"></iconify-icon>
|
|
</div>
|
|
<span class="text-2xl font-black tracking-tighter text-accent">FuelAlert</span>
|
|
</RouterLink>
|
|
<div class="flex items-center gap-4">
|
|
<RouterLink class="text-sm font-bold text-zinc-500 hover:text-zinc-800" to="/">
|
|
← Find fuel
|
|
</RouterLink>
|
|
|
|
<!-- User dropdown -->
|
|
<div ref="dropdownRef" class="relative">
|
|
<button
|
|
@click="dropdownOpen = !dropdownOpen"
|
|
class="w-9 h-9 rounded-full bg-accent flex items-center justify-center text-white text-sm font-black hover:bg-accent-content transition-colors"
|
|
:aria-expanded="dropdownOpen ? 'true' : 'false'"
|
|
aria-haspopup="true"
|
|
>
|
|
{{ userInitials }}
|
|
</button>
|
|
<Transition
|
|
enter-active-class="transition ease-out duration-100"
|
|
enter-from-class="transform opacity-0 scale-95"
|
|
enter-to-class="transform opacity-100 scale-100"
|
|
leave-active-class="transition ease-in duration-75"
|
|
leave-from-class="transform opacity-100 scale-100"
|
|
leave-to-class="transform opacity-0 scale-95"
|
|
>
|
|
<div
|
|
v-show="dropdownOpen"
|
|
class="absolute right-0 top-full mt-2 w-64 bg-white border border-zinc-300 rounded-2xl shadow-lg overflow-hidden z-50"
|
|
>
|
|
<div class="px-4 py-3 border-b border-zinc-300">
|
|
<p class="text-sm font-black text-zinc-800">{{ user?.name }}</p>
|
|
<p class="text-xs text-zinc-500 truncate">{{ user?.email }}</p>
|
|
</div>
|
|
<div class="py-1">
|
|
<RouterLink
|
|
to="/dashboard/settings"
|
|
@click="dropdownOpen = false"
|
|
class="flex items-center gap-3 px-4 py-2.5 text-sm font-bold text-zinc-500 hover:bg-zinc-50 hover:text-zinc-800 transition-colors"
|
|
>
|
|
<iconify-icon icon="lucide:settings"></iconify-icon>
|
|
Settings
|
|
</RouterLink>
|
|
<button
|
|
@click="handleLogout"
|
|
class="w-full flex items-center gap-3 px-4 py-2.5 text-sm font-bold text-zinc-500 hover:bg-zinc-50 hover:text-zinc-800 transition-colors"
|
|
>
|
|
<iconify-icon icon="lucide:log-out"></iconify-icon>
|
|
Log out
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="flex pt-20 max-w-7xl mx-auto w-full px-6 py-8 gap-8">
|
|
<!-- Sidebar -->
|
|
<aside class="w-56 flex-shrink-0 hidden md:block">
|
|
<nav class="space-y-1">
|
|
<RouterLink
|
|
v-for="item in navItems"
|
|
:key="item.to"
|
|
:to="item.to"
|
|
class="flex items-center gap-3 px-4 py-2.5 rounded-xl text-sm font-bold transition-colors"
|
|
:class="isActive(item.to)
|
|
? 'bg-accent text-white'
|
|
: 'text-zinc-500 hover:bg-white hover:text-zinc-800'"
|
|
>
|
|
<iconify-icon :icon="item.icon"></iconify-icon>
|
|
{{ item.label }}
|
|
</RouterLink>
|
|
</nav>
|
|
</aside>
|
|
|
|
<!-- Content -->
|
|
<main class="flex-1 min-w-0">
|
|
<RouterView />
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
import { RouterLink, RouterView, useRoute, useRouter } from 'vue-router'
|
|
import { useAuth } from '../../composables/useAuth.js'
|
|
|
|
const { user } = useAuth()
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const dropdownOpen = ref(false)
|
|
const dropdownRef = ref(null)
|
|
|
|
function handleClickOutside(event) {
|
|
if (dropdownRef.value && !dropdownRef.value.contains(event.target)) {
|
|
dropdownOpen.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
document.addEventListener('click', handleClickOutside)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
document.removeEventListener('click', handleClickOutside)
|
|
})
|
|
|
|
const userInitials = computed(() => {
|
|
if (!user.value?.name) {
|
|
return '?'
|
|
}
|
|
return user.value.name
|
|
.split(' ')
|
|
.slice(0, 2)
|
|
.map((w) => w[0])
|
|
.join('')
|
|
.toUpperCase()
|
|
})
|
|
|
|
function handleLogout() {
|
|
window.location.href = '/logout'
|
|
}
|
|
|
|
function isActive(to) {
|
|
if (to === '/dashboard') {
|
|
return route.path === '/dashboard'
|
|
}
|
|
return route.path.startsWith(to)
|
|
}
|
|
|
|
const navItems = [
|
|
{ to: '/dashboard', label: 'Overview', icon: 'lucide:layout-dashboard' },
|
|
{ to: '/dashboard/saved-stations', label: 'Saved Stations', icon: 'lucide:bookmark' },
|
|
{ to: '/dashboard/preferences', label: 'Preferences', icon: 'lucide:settings' },
|
|
{ to: '/dashboard/settings', label: 'Account', icon: 'lucide:user' },
|
|
]
|
|
</script>
|