- Updated logout handler to return user to current page instead of login - Consistent with login behavior which redirects back to original page - Allows users to continue browsing after logout without forced redirect - Users who accidentally click logout can easily log back in and continue This improves UX by keeping users on the page they were viewing, rather than forcing them to the login page when they may have just wanted to log out temporarily. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
175 lines
5.1 KiB
Vue
175 lines
5.1 KiB
Vue
<template>
|
|
<div class="relative" ref="menuContainer">
|
|
<!-- Hamburger Button -->
|
|
<button
|
|
@click="toggleMenu"
|
|
:disabled="isToggling"
|
|
class="p-2 text-gray-700 hover:bg-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
|
|
aria-label="Menu"
|
|
>
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
v-if="!isOpen"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M4 6h16M4 12h16M4 18h16"
|
|
/>
|
|
<path
|
|
v-else
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Dropdown Menu -->
|
|
<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-if="isOpen"
|
|
class="absolute right-0 mt-2 w-56 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 z-50"
|
|
>
|
|
<div class="py-1">
|
|
<!-- Home Link -->
|
|
<NuxtLink
|
|
v-if="showHome"
|
|
to="/"
|
|
@click="isOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
>
|
|
🏠 Home
|
|
</NuxtLink>
|
|
|
|
<!-- Authentication Links -->
|
|
<ClientOnly>
|
|
<template v-if="isAuthenticated">
|
|
<!-- Profile Link -->
|
|
<NuxtLink
|
|
to="/profile"
|
|
@click="isOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
>
|
|
👤 Edit Profile
|
|
</NuxtLink>
|
|
|
|
<!-- Admin Links -->
|
|
<template v-if="isAdmin">
|
|
<NuxtLink
|
|
to="/admin"
|
|
@click="isOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
>
|
|
📝 Manage Sermons
|
|
</NuxtLink>
|
|
<NuxtLink
|
|
to="/users"
|
|
@click="isOpen = false"
|
|
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
>
|
|
👥 Manage Users
|
|
</NuxtLink>
|
|
</template>
|
|
|
|
<!-- Logout -->
|
|
<button
|
|
@click="handleLogout"
|
|
class="block w-full text-left px-4 py-2 text-sm text-red-700 hover:bg-red-50"
|
|
>
|
|
🚪 Log Out
|
|
</button>
|
|
</template>
|
|
|
|
<!-- Not authenticated -->
|
|
<template v-else>
|
|
<NuxtLink
|
|
:to="loginRedirectUrl"
|
|
@click="isOpen = false"
|
|
class="block px-4 py-2 text-sm text-green-700 hover:bg-green-50 font-medium"
|
|
>
|
|
🔑 Log In
|
|
</NuxtLink>
|
|
<NuxtLink
|
|
to="/register"
|
|
@click="isOpen = false"
|
|
class="block px-4 py-2 text-sm text-blue-700 hover:bg-blue-50 font-medium"
|
|
>
|
|
✨ Register
|
|
</NuxtLink>
|
|
</template>
|
|
</ClientOnly>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
const props = defineProps<{
|
|
isAuthenticated?: boolean
|
|
isAdmin?: boolean
|
|
showHome?: boolean
|
|
currentPath?: string
|
|
}>()
|
|
|
|
const isOpen = ref(false)
|
|
const isToggling = ref(false)
|
|
const menuContainer = ref<HTMLElement | null>(null)
|
|
const route = useRoute()
|
|
|
|
const loginRedirectUrl = computed(() => {
|
|
const redirectPath = props.currentPath || route.fullPath
|
|
return `/login?redirect=${encodeURIComponent(redirectPath)}`
|
|
})
|
|
|
|
// Toggle menu with debounce to prevent accidental double-taps
|
|
const toggleMenu = () => {
|
|
if (isToggling.value) return
|
|
|
|
isToggling.value = true
|
|
isOpen.value = !isOpen.value
|
|
|
|
// Re-enable after 500ms
|
|
setTimeout(() => {
|
|
isToggling.value = false
|
|
}, 500)
|
|
}
|
|
|
|
const handleLogout = async () => {
|
|
isOpen.value = false
|
|
const currentPath = props.currentPath || route.fullPath
|
|
await $fetch('/api/auth/logout', { method: 'POST' })
|
|
|
|
// Redirect back to the current page so user can continue browsing
|
|
await navigateTo(currentPath)
|
|
}
|
|
|
|
// Close menu when clicking outside
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (!isOpen.value || !menuContainer.value) return
|
|
|
|
const target = event.target as HTMLElement
|
|
// Check if click is outside the menu container
|
|
if (!menuContainer.value.contains(target)) {
|
|
isOpen.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
// Use capture phase to ensure we catch the event before other handlers
|
|
document.addEventListener('click', handleClickOutside, true)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
document.removeEventListener('click', handleClickOutside, true)
|
|
})
|
|
</script>
|