Initial commit from monorepo
This commit is contained in:
182
app/components/TopBar.vue
Normal file
182
app/components/TopBar.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<header class="sticky top-0 z-40 bg-base-100 border-b border-base-300">
|
||||
<div class="flex items-center justify-between h-16 px-4 lg:px-6">
|
||||
<!-- Left: Mobile menu button + Breadcrumbs -->
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Mobile menu button -->
|
||||
<button
|
||||
@click="$emit('toggle-sidebar')"
|
||||
class="btn btn-ghost btn-square lg:hidden"
|
||||
>
|
||||
<Icon name="lucide:menu" size="20" />
|
||||
</button>
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<CabinetBreadcrumbs />
|
||||
</div>
|
||||
|
||||
<!-- Right: Location + Search + Actions + User -->
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Location selector -->
|
||||
<NuxtLink
|
||||
:to="localePath('/select-location')"
|
||||
class="btn btn-ghost gap-1 max-w-48 truncate"
|
||||
>
|
||||
<Icon name="heroicons:map-pin" size="18" />
|
||||
<span class="hidden sm:inline truncate">
|
||||
{{ locationStore.locationName || $t('common.selectLocation') }}
|
||||
</span>
|
||||
<Icon name="heroicons:chevron-down" size="14" class="hidden sm:inline" />
|
||||
</NuxtLink>
|
||||
|
||||
<!-- Search (desktop) -->
|
||||
<div class="hidden sm:block dropdown dropdown-end">
|
||||
<input
|
||||
ref="searchInput"
|
||||
type="text"
|
||||
:placeholder="$t('search.placeholder') || 'Search...'"
|
||||
v-model="searchQuery"
|
||||
class="input input-bordered w-72"
|
||||
@focus="showCommandPalette = true"
|
||||
@blur="hideCommandPalette"
|
||||
/>
|
||||
<ul
|
||||
v-if="showCommandPalette"
|
||||
tabindex="0"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-72 p-2 shadow-lg border border-base-300 mt-1"
|
||||
>
|
||||
<li class="menu-title"><span>Quick actions</span></li>
|
||||
<li><a @click="navigateToAction('/catalog')">Find materials</a></li>
|
||||
<li><a @click="navigateToAction('/clientarea/orders')">My orders</a></li>
|
||||
<li><a @click="navigateToAction('/clientarea/profile')">Profile settings</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Theme toggle -->
|
||||
<button
|
||||
class="btn btn-ghost btn-square"
|
||||
:title="theme === 'night' ? $t('common.theme_light') : $t('common.theme_dark')"
|
||||
@click="$emit('toggle-theme')"
|
||||
>
|
||||
<Icon :name="theme === 'night' ? 'lucide:sun' : 'lucide:moon'" size="18" />
|
||||
</button>
|
||||
|
||||
<!-- AI button -->
|
||||
<NuxtLink
|
||||
:to="localePath('/clientarea/ai')"
|
||||
class="btn btn-ghost btn-square"
|
||||
:title="$t('cabinetNav.ai')"
|
||||
>
|
||||
<Icon name="lucide:sparkles" size="18" />
|
||||
</NuxtLink>
|
||||
|
||||
<!-- Notifications -->
|
||||
<ClientOnly>
|
||||
<NovuNotificationBell
|
||||
v-if="novuSubscriberId"
|
||||
:subscriber-id="novuSubscriberId"
|
||||
/>
|
||||
</ClientOnly>
|
||||
|
||||
<!-- User menu -->
|
||||
<template v-if="sessionChecked">
|
||||
<template v-if="loggedIn">
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar">
|
||||
<div class="w-10 rounded-full">
|
||||
<div v-if="userAvatarSvg" v-html="userAvatarSvg" class="w-full h-full" />
|
||||
<div
|
||||
v-else
|
||||
class="w-full h-full bg-primary flex items-center justify-center text-primary-content font-bold text-sm"
|
||||
>
|
||||
{{ userInitials }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-lg border border-base-300"
|
||||
>
|
||||
<li class="menu-title">
|
||||
<span>{{ userName }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink :to="localePath('/clientarea/profile')">
|
||||
<Icon name="lucide:user" size="16" />
|
||||
{{ $t('dashboard.profile') }}
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<details>
|
||||
<summary>
|
||||
<Icon name="lucide:globe" size="16" />
|
||||
Language
|
||||
</summary>
|
||||
<ul>
|
||||
<li v-for="loc in locales" :key="loc.code">
|
||||
<NuxtLink
|
||||
:to="switchLocalePath(loc.code)"
|
||||
:class="{ active: locale === loc.code }"
|
||||
>
|
||||
{{ loc.code.toUpperCase() }}
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
<div class="divider my-1"></div>
|
||||
<li>
|
||||
<a @click="$emit('sign-out')" class="text-error">
|
||||
<Icon name="lucide:log-out" size="16" />
|
||||
{{ $t('auth.logout') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button @click="$emit('sign-in')" class="btn btn-ghost">
|
||||
{{ $t('auth.login') }}
|
||||
</button>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useLocationStore } from '~/stores/location'
|
||||
|
||||
defineEmits(['toggle-sidebar', 'sign-out', 'sign-in', 'toggle-theme'])
|
||||
|
||||
defineProps<{
|
||||
sessionChecked?: boolean
|
||||
loggedIn?: boolean
|
||||
novuSubscriberId?: string
|
||||
userAvatarSvg?: string
|
||||
userName?: string
|
||||
userInitials?: string
|
||||
theme?: 'default' | 'night'
|
||||
}>()
|
||||
|
||||
const localePath = useLocalePath()
|
||||
const { locale, locales } = useI18n()
|
||||
const switchLocalePath = useSwitchLocalePath()
|
||||
const locationStore = useLocationStore()
|
||||
|
||||
const searchQuery = ref('')
|
||||
const searchInput = ref(null)
|
||||
const showCommandPalette = ref(false)
|
||||
|
||||
const hideCommandPalette = () => {
|
||||
setTimeout(() => {
|
||||
showCommandPalette.value = false
|
||||
}, 150)
|
||||
}
|
||||
|
||||
const navigateToAction = (path: string) => {
|
||||
showCommandPalette.value = false
|
||||
navigateTo(localePath(path))
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user