158 lines
5.5 KiB
Vue
158 lines
5.5 KiB
Vue
<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>
|