Files
fuel-price/resources/js/views/dashboard/DashboardLayout.vue
Ovidiu U 03b0bece2c
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled
feat: add auth guards and server-side logout with postcode search integration
- Add navigation guard requiring authentication for dashboard routes
- Create
2026-04-11 17:08:19 +01:00

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>