This commit is contained in:
157
app/components/AppNavbar.vue
Normal file
157
app/components/AppNavbar.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<script setup lang="ts">
|
||||
const { user, backendUser, signOut, initAuth, isLoading } = useAuth()
|
||||
const loginModal = useLoginModal()
|
||||
const route = useRoute()
|
||||
|
||||
const dropdownRef = ref<HTMLElement | null>(null)
|
||||
const showMenu = ref(false)
|
||||
|
||||
const displayName = computed(() => backendUser.value?.name || user.value?.displayName || backendUser.value?.email || user.value?.email || "Account")
|
||||
|
||||
const avatarInitials = computed(() => {
|
||||
const source = displayName.value
|
||||
if (!source) {
|
||||
return "S"
|
||||
}
|
||||
const parts = source.trim().split(/\s+/)
|
||||
if (parts.length === 1) {
|
||||
return parts[0].charAt(0).toUpperCase()
|
||||
}
|
||||
return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase()
|
||||
})
|
||||
|
||||
const openLoginModal = () => {
|
||||
loginModal.value = true
|
||||
}
|
||||
|
||||
const handleSignOut = async () => {
|
||||
try {
|
||||
await signOut()
|
||||
showMenu.value = false
|
||||
} catch (error) {
|
||||
console.error('Sign out failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleMenu = () => {
|
||||
showMenu.value = !showMenu.value
|
||||
}
|
||||
|
||||
const closeMenu = () => {
|
||||
if (showMenu.value) {
|
||||
showMenu.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (!dropdownRef.value) {
|
||||
return
|
||||
}
|
||||
if (!dropdownRef.value.contains(event.target as Node)) {
|
||||
closeMenu()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initAuth()
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => {
|
||||
closeMenu()
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="sticky top-0 z-30 border-b border-slate-200 bg-white/90 backdrop-blur shadow-sm">
|
||||
<div class="mx-auto max-w-6xl px-4 py-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between gap-6">
|
||||
<NuxtLink to="/" class="text-lg font-semibold text-slate-900 transition hover:text-slate-600 sm:text-xl">
|
||||
TableJerseys
|
||||
</NuxtLink>
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Show user info and logout when authenticated -->
|
||||
<div v-if="user && !isLoading" ref="dropdownRef" class="relative flex items-center gap-3">
|
||||
<span class="hidden text-sm text-slate-700 sm:inline">{{ backendUser?.email || user.email }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-full bg-slate-900 text-sm font-semibold uppercase text-white transition hover:bg-slate-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-slate-900 focus-visible:ring-offset-2 focus-visible:ring-offset-white"
|
||||
@click.stop="toggleMenu"
|
||||
aria-haspopup="menu"
|
||||
:aria-expanded="showMenu"
|
||||
>
|
||||
{{ avatarInitials }}
|
||||
</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-if="showMenu"
|
||||
class="absolute right-0 top-12 w-56 rounded-2xl border border-slate-200 bg-white p-3 shadow-xl backdrop-blur"
|
||||
role="menu"
|
||||
>
|
||||
<p class="px-3 text-xs uppercase tracking-[0.25em] text-slate-500">Signed in as</p>
|
||||
<p class="px-3 text-sm font-medium text-slate-900">{{ displayName }}</p>
|
||||
<p class="px-3 text-xs text-slate-600">{{ backendUser?.email || user.email }}</p>
|
||||
<div class="my-3 h-px bg-slate-200"></div>
|
||||
<NuxtLink
|
||||
to="/profile"
|
||||
class="flex items-center gap-2 rounded-xl px-3 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-100"
|
||||
role="menuitem"
|
||||
>
|
||||
Profile
|
||||
</NuxtLink>
|
||||
<NuxtLink
|
||||
to="/orders"
|
||||
class="flex items-center gap-2 rounded-xl px-3 py-2 text-sm font-medium text-slate-700 transition hover:bg-slate-100"
|
||||
role="menuitem"
|
||||
>
|
||||
Orders
|
||||
</NuxtLink>
|
||||
<button
|
||||
type="button"
|
||||
class="mt-2 flex w-full items-center gap-2 rounded-xl px-3 py-2 text-sm font-semibold text-rose-600 transition hover:bg-rose-50"
|
||||
@click="handleSignOut"
|
||||
role="menuitem"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<!-- Show login button when not authenticated -->
|
||||
<div v-else-if="!isLoading" class="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="openLoginModal"
|
||||
class="rounded-full border-2 border-slate-900 bg-slate-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-white hover:text-slate-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-slate-900 focus-visible:ring-offset-2 focus-visible:ring-offset-white"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
<div v-else class="flex items-center gap-3">
|
||||
<div class="h-8 w-16 animate-pulse rounded bg-slate-200"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LoginModal />
|
||||
</nav>
|
||||
</template>
|
||||
Reference in New Issue
Block a user