feat: Add mobile hamburger menu and Register button
Implemented comprehensive navigation improvements to make registration more discoverable and provide a cleaner mobile experience. ## Mobile Improvements - Created reusable MobileMenu component with hamburger icon - Replaces cluttered button layout with clean dropdown menu - Includes Home, Profile, Admin links, and authentication options - Auto-closes when clicking outside or navigating - Smooth transition animations ## Register Button Added - Sermon page: Added Register button next to Login in notes section - Sermon page: Added Register button in header for non-authenticated users - Home page: Added Register button next to Login for easy discovery - All Register links redirect to login page in register mode ## Navigation Consistency - Home button now visible on sermon pages (desktop and mobile) - Consistent navigation experience across all pages - Mobile menu available on both home and sermon pages - Better mobile UX with less header crowding Benefits: - Users can easily find how to register - Cleaner mobile interface - Consistent navigation patterns - Better discoverability of account features 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
151
components/MobileMenu.vue
Normal file
151
components/MobileMenu.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<!-- Hamburger Button -->
|
||||
<button
|
||||
@click="isOpen = !isOpen"
|
||||
class="p-2 text-gray-700 hover:bg-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
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 route = useRoute()
|
||||
|
||||
const loginRedirectUrl = computed(() => {
|
||||
const redirectPath = props.currentPath || route.fullPath
|
||||
return `/login?redirect=${encodeURIComponent(redirectPath)}`
|
||||
})
|
||||
|
||||
const handleLogout = async () => {
|
||||
isOpen.value = false
|
||||
await $fetch('/api/auth/logout', { method: 'POST' })
|
||||
await navigateTo('/login')
|
||||
}
|
||||
|
||||
// Close menu when clicking outside
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
const target = event.target as HTMLElement
|
||||
if (isOpen.value && !target.closest('.relative')) {
|
||||
isOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
@@ -11,20 +11,12 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<QRCodeButton v-if="sermon" :sermon="sermon" />
|
||||
<ClientOnly fallback-tag="div">
|
||||
<button
|
||||
v-if="isAuthenticated"
|
||||
@click="handleLogout"
|
||||
class="px-4 py-2 text-sm font-medium text-red-700 bg-red-50 rounded-md hover:bg-red-100 whitespace-nowrap"
|
||||
>
|
||||
Log Out
|
||||
</button>
|
||||
<NuxtLink
|
||||
v-else
|
||||
:to="`/login?redirect=${encodeURIComponent(route.fullPath)}`"
|
||||
class="px-4 py-2 text-sm font-medium text-green-700 bg-green-50 rounded-md hover:bg-green-100 whitespace-nowrap"
|
||||
>
|
||||
Log In
|
||||
</NuxtLink>
|
||||
<MobileMenu
|
||||
:is-authenticated="isAuthenticated"
|
||||
:is-admin="isAdmin"
|
||||
:show-home="true"
|
||||
:current-path="route.fullPath"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,6 +41,12 @@
|
||||
<img src="/logos/logo.png" alt="New Life Christian Church" class="h-16 w-auto" />
|
||||
</NuxtLink>
|
||||
<div class="flex items-end gap-4">
|
||||
<NuxtLink
|
||||
to="/"
|
||||
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-md hover:bg-gray-200 whitespace-nowrap"
|
||||
>
|
||||
🏠 Home
|
||||
</NuxtLink>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="font-size" class="text-xs font-medium text-gray-700">Font Size:</label>
|
||||
<select
|
||||
@@ -63,20 +61,28 @@
|
||||
</div>
|
||||
<QRCodeButton v-if="sermon" :sermon="sermon" />
|
||||
<ClientOnly fallback-tag="div">
|
||||
<button
|
||||
v-if="isAuthenticated"
|
||||
@click="handleLogout"
|
||||
class="px-4 py-2 text-sm font-medium text-red-700 bg-red-50 rounded-md hover:bg-red-100 whitespace-nowrap"
|
||||
>
|
||||
Log Out
|
||||
</button>
|
||||
<NuxtLink
|
||||
v-else
|
||||
:to="`/login?redirect=${encodeURIComponent(route.fullPath)}`"
|
||||
class="px-4 py-2 text-sm font-medium text-green-700 bg-green-50 rounded-md hover:bg-green-100 whitespace-nowrap"
|
||||
>
|
||||
Log In
|
||||
</NuxtLink>
|
||||
<template v-if="isAuthenticated">
|
||||
<button
|
||||
@click="handleLogout"
|
||||
class="px-4 py-2 text-sm font-medium text-red-700 bg-red-50 rounded-md hover:bg-red-100 whitespace-nowrap"
|
||||
>
|
||||
Log Out
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NuxtLink
|
||||
:to="`/login?redirect=${encodeURIComponent(route.fullPath)}`"
|
||||
class="px-4 py-2 text-sm font-medium text-green-700 bg-green-50 rounded-md hover:bg-green-100 whitespace-nowrap"
|
||||
>
|
||||
Log In
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/register"
|
||||
class="px-4 py-2 text-sm font-medium text-blue-700 bg-blue-50 rounded-md hover:bg-blue-100 whitespace-nowrap"
|
||||
>
|
||||
Register
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
@@ -208,12 +214,20 @@
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Want to take notes?</h3>
|
||||
<p class="text-gray-600 mb-4">Log in or create an account to save your sermon notes!</p>
|
||||
<NuxtLink
|
||||
:to="`/login?redirect=${encodeURIComponent(route.fullPath)}`"
|
||||
class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-medium"
|
||||
>
|
||||
Log In
|
||||
</NuxtLink>
|
||||
<div class="flex gap-3 justify-center">
|
||||
<NuxtLink
|
||||
:to="`/login?redirect=${encodeURIComponent(route.fullPath)}`"
|
||||
class="inline-flex items-center px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 font-medium"
|
||||
>
|
||||
Log In
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/register"
|
||||
class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-medium"
|
||||
>
|
||||
Register
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</section>
|
||||
|
||||
@@ -8,47 +8,13 @@
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<img src="/logos/logo.png" alt="New Life Christian Church" class="h-16 w-auto" />
|
||||
<ClientOnly fallback-tag="div">
|
||||
<template v-if="isAuthenticated">
|
||||
<div class="flex items-center gap-2">
|
||||
<NuxtLink
|
||||
to="/profile"
|
||||
class="px-3 py-2 text-sm font-medium text-blue-700 bg-blue-50 rounded-md hover:bg-blue-100 whitespace-nowrap"
|
||||
>
|
||||
Edit Profile
|
||||
</NuxtLink>
|
||||
<button
|
||||
@click="handleLogout"
|
||||
class="px-3 py-2 text-sm font-medium text-red-700 bg-red-50 rounded-md hover:bg-red-100 whitespace-nowrap"
|
||||
>
|
||||
Log Out
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<NuxtLink
|
||||
v-else
|
||||
to="/login"
|
||||
class="px-4 py-2 text-sm font-medium text-green-700 bg-green-50 rounded-md hover:bg-green-100"
|
||||
>
|
||||
Log In
|
||||
</NuxtLink>
|
||||
<MobileMenu
|
||||
:is-authenticated="isAuthenticated"
|
||||
:is-admin="isAdmin"
|
||||
:show-home="false"
|
||||
/>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
<ClientOnly fallback-tag="div">
|
||||
<div v-if="isAuthenticated && isAdmin" class="flex items-center justify-center gap-2">
|
||||
<NuxtLink
|
||||
to="/admin"
|
||||
class="px-3 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 font-medium text-xs whitespace-nowrap"
|
||||
>
|
||||
Manage Sermons
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/users"
|
||||
class="px-3 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 font-medium text-xs whitespace-nowrap"
|
||||
>
|
||||
Manage Users
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Layout -->
|
||||
@@ -86,13 +52,20 @@
|
||||
Log Out
|
||||
</button>
|
||||
</template>
|
||||
<NuxtLink
|
||||
v-else
|
||||
to="/login"
|
||||
class="px-4 py-2 text-sm font-medium text-green-700 bg-green-50 rounded-md hover:bg-green-100"
|
||||
>
|
||||
Log In
|
||||
</NuxtLink>
|
||||
<template v-else>
|
||||
<NuxtLink
|
||||
to="/login"
|
||||
class="px-4 py-2 text-sm font-medium text-green-700 bg-green-50 rounded-md hover:bg-green-100"
|
||||
>
|
||||
Log In
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/register"
|
||||
class="px-4 py-2 text-sm font-medium text-blue-700 bg-blue-50 rounded-md hover:bg-blue-100"
|
||||
>
|
||||
Register
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div class="h-10"></div>
|
||||
</template>
|
||||
|
||||
12
pages/register.vue
Normal file
12
pages/register.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Redirect to login page in register mode
|
||||
definePageMeta({
|
||||
middleware: () => {
|
||||
return navigateTo('/login?mode=register')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user