Files
fuel-price/resources/js/views/dashboard/DashboardLayout.vue
Ovidiu U 3895356b0d fix: replace Alpine dropdown with Vue reactive state in DashboardLayout
Alpine.js is not loaded in the Vue SPA bundle, causing the avatar dropdown
to never open and making Settings and Log out inaccessible. Replaced x-data/
x-show/x-transition/@click.away with Vue refs, onMounted/onUnmounted click-
outside listener, and Vue's built-in <Transition> component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 13:21:27 +01:00

152 lines
6.4 KiB
Vue

<template>
<div class="min-h-screen bg-[#f5ede5] flex flex-col">
<!-- Top nav -->
<nav class="fixed top-0 w-full z-50 bg-[#faf6f3] border-b border-[#e5ded7] 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-[#bb5b3e] 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-[#bb5b3e]">FuelAlert</span>
</RouterLink>
<div class="flex items-center gap-4">
<RouterLink to="/" class="text-sm font-bold text-[#89726c] hover:text-[#4a3f3b]">
Find fuel
</RouterLink>
<!-- User dropdown -->
<div ref="dropdownRef" class="relative">
<button
@click="dropdownOpen = !dropdownOpen"
class="w-9 h-9 rounded-full bg-[#bb5b3e] flex items-center justify-center text-white text-sm font-black hover:bg-[#a34a31] transition-colors"
:aria-expanded="dropdownOpen"
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-[#e5ded7] rounded-2xl shadow-lg overflow-hidden z-50"
>
<div class="px-4 py-3 border-b border-[#e5ded7]">
<p class="text-sm font-black text-[#4a3f3b]">{{ user?.name }}</p>
<p class="text-xs text-[#89726c] 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-[#89726c] hover:bg-[#faf6f3] hover:text-[#4a3f3b] 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-[#89726c] hover:bg-[#faf6f3] hover:text-[#4a3f3b] 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-[#bb5b3e] text-white'
: 'text-[#89726c] hover:bg-white hover:text-[#4a3f3b]'"
>
<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, logout } = 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()
})
async function handleLogout() {
dropdownOpen.value = false
await logout()
router.push('/')
}
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>