feat(nav): client area tabs in main navigation
Some checks failed
Build Docker Image / build (push) Has been cancelled

- Add Cabinet button to header (dashboard icon)
- When in /clientarea/* show tabs instead of search input
- Tabs: Заказы | Предложения (SELLER only) | Адреса | Профиль | Команда
- Hide Explore/Quote toggle in client area
- Remove SubNavigation for clientarea (tabs moved to MainNavigation)
This commit is contained in:
Ruslan Bakiev
2026-01-28 05:28:16 +07:00
parent 45acef9b20
commit 8b0e1900d1
4 changed files with 80 additions and 8 deletions

View File

@@ -12,8 +12,8 @@
<span class="font-bold text-xl" :class="useWhiteText ? 'text-white' : 'text-base-content'">Optovia</span> <span class="font-bold text-xl" :class="useWhiteText ? 'text-white' : 'text-base-content'">Optovia</span>
</NuxtLink> </NuxtLink>
<!-- Service nav links --> <!-- Service nav links (hide in client area) -->
<nav v-if="showModeToggle" class="flex items-center gap-1"> <nav v-if="showModeToggle && !isClientArea" class="flex items-center gap-1">
<button <button
class="px-3 py-1 text-sm font-medium rounded-lg transition-colors" class="px-3 py-1 text-sm font-medium rounded-lg transition-colors"
:class="showActiveMode && catalogMode === 'explore' :class="showActiveMode && catalogMode === 'explore'
@@ -35,13 +35,55 @@
</nav> </nav>
</div> </div>
<!-- Center: Search input (vertically centered) --> <!-- Center: Search input OR Client Area tabs (vertically centered) -->
<div class="flex-1 flex flex-col items-center max-w-2xl mx-auto gap-2 justify-center"> <div class="flex-1 flex flex-col items-center max-w-2xl mx-auto gap-2 justify-center">
<!-- Hero slot for home page title --> <!-- Hero slot for home page title -->
<slot name="hero" /> <slot name="hero" />
<!-- Client Area tabs -->
<template v-if="isClientArea">
<div class="flex items-center gap-1 rounded-full border border-white/20 bg-white/80 backdrop-blur-md shadow-lg p-1">
<NuxtLink
:to="localePath('/clientarea/orders')"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors whitespace-nowrap"
:class="isClientAreaTabActive('/clientarea/orders') ? 'bg-primary text-primary-content' : 'text-base-content/70 hover:text-base-content hover:bg-base-200/50'"
>
{{ $t('cabinetNav.orders') }}
</NuxtLink>
<NuxtLink
v-if="isSeller"
:to="localePath('/clientarea/offers')"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors whitespace-nowrap"
:class="isClientAreaTabActive('/clientarea/offers') ? 'bg-primary text-primary-content' : 'text-base-content/70 hover:text-base-content hover:bg-base-200/50'"
>
{{ $t('cabinetNav.myOffers') }}
</NuxtLink>
<NuxtLink
:to="localePath('/clientarea/addresses')"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors whitespace-nowrap"
:class="isClientAreaTabActive('/clientarea/addresses') ? 'bg-primary text-primary-content' : 'text-base-content/70 hover:text-base-content hover:bg-base-200/50'"
>
{{ $t('cabinetNav.addresses') }}
</NuxtLink>
<NuxtLink
:to="localePath('/clientarea/profile')"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors whitespace-nowrap"
:class="isClientAreaTabActive('/clientarea/profile') ? 'bg-primary text-primary-content' : 'text-base-content/70 hover:text-base-content hover:bg-base-200/50'"
>
{{ $t('cabinetNav.profile') }}
</NuxtLink>
<NuxtLink
:to="localePath('/clientarea/team')"
class="px-4 py-2 rounded-full text-sm font-medium transition-colors whitespace-nowrap"
:class="isClientAreaTabActive('/clientarea/team') ? 'bg-primary text-primary-content' : 'text-base-content/70 hover:text-base-content hover:bg-base-200/50'"
>
{{ $t('cabinetNav.team') }}
</NuxtLink>
</div>
</template>
<!-- Quote mode: Simple segmented input with search inside (white glass) --> <!-- Quote mode: Simple segmented input with search inside (white glass) -->
<template v-if="catalogMode === 'quote'"> <template v-else-if="catalogMode === 'quote'">
<div class="flex items-center w-full rounded-full border border-white/40 bg-white/80 backdrop-blur-md shadow-lg divide-x divide-base-300/30"> <div class="flex items-center w-full rounded-full border border-white/40 bg-white/80 backdrop-blur-md shadow-lg divide-x divide-base-300/30">
<!-- Product segment --> <!-- Product segment -->
<button <button
@@ -135,8 +177,19 @@
</template> </template>
</div> </div>
<!-- Right: AI + Globe + Team + User (top aligned like logo) --> <!-- Right: Cabinet + AI + Globe + Team + User (top aligned like logo) -->
<div class="flex items-start gap-1 flex-shrink-0 pt-4"> <div class="flex items-start gap-1 flex-shrink-0 pt-4">
<!-- Cabinet button (when logged in and not in client area) -->
<NuxtLink
v-if="loggedIn && !isClientArea"
:to="localePath('/clientarea/orders')"
class="w-8 h-8 rounded-full flex items-center justify-center transition-colors"
:class="useWhiteText ? 'text-white/70 hover:text-white hover:bg-white/10' : 'text-base-content/70 hover:text-base-content hover:bg-base-200'"
:title="$t('cabinetNav.cabinet')"
>
<Icon name="lucide:layout-dashboard" size="18" />
</NuxtLink>
<!-- AI Assistant button --> <!-- AI Assistant button -->
<NuxtLink <NuxtLink
:to="localePath('/clientarea/ai')" :to="localePath('/clientarea/ai')"
@@ -310,6 +363,8 @@ const props = withDefaults(defineProps<{
isCollapsed?: boolean isCollapsed?: boolean
// Home page flag for transparent background // Home page flag for transparent background
isHomePage?: boolean isHomePage?: boolean
// Client area flag - shows cabinet tabs instead of search
isClientArea?: boolean
// Dynamic height for hero effect // Dynamic height for hero effect
height?: number height?: number
}>(), { }>(), {
@@ -334,10 +389,18 @@ defineEmits([
]) ])
const localePath = useLocalePath() const localePath = useLocalePath()
const route = useRoute()
const { locale, locales } = useI18n() const { locale, locales } = useI18n()
const switchLocalePath = useSwitchLocalePath() const switchLocalePath = useSwitchLocalePath()
const { t } = useI18n() const { t } = useI18n()
// Check if client area tab is active
const isClientAreaTabActive = (path: string) => {
const currentPath = route.path
const localizedPath = localePath(path)
return currentPath === localizedPath || currentPath.startsWith(localizedPath + '/')
}
const inputRef = ref<HTMLInputElement>() const inputRef = ref<HTMLInputElement>()
const localSearchQuery = ref(props.searchQuery || '') const localSearchQuery = ref(props.searchQuery || '')
const localQuantity = ref(props.quantity || '') const localQuantity = ref(props.quantity || '')

View File

@@ -30,6 +30,7 @@
:show-active-mode="isCatalogSection" :show-active-mode="isCatalogSection"
:is-collapsed="isHomePage ? heroIsCollapsed : isCatalogSection" :is-collapsed="isHomePage ? heroIsCollapsed : isCatalogSection"
:is-home-page="isHomePage" :is-home-page="isHomePage"
:is-client-area="isClientArea"
@toggle-theme="toggleTheme" @toggle-theme="toggleTheme"
@set-catalog-mode="setCatalogMode" @set-catalog-mode="setCatalogMode"
@sign-out="onClickSignOut" @sign-out="onClickSignOut"
@@ -54,9 +55,9 @@
</template> </template>
</MainNavigation> </MainNavigation>
<!-- Sub Navigation (section-specific tabs) - only for non-catalog/non-home sections --> <!-- Sub Navigation (section-specific tabs) - only for non-catalog/non-home/non-clientarea sections -->
<SubNavigation <SubNavigation
v-if="!isHomePage && !isCatalogSection" v-if="!isHomePage && !isCatalogSection && !isClientArea"
:section="currentSection" :section="currentSection"
/> />
</div> </div>
@@ -174,8 +175,13 @@ const isCatalogSection = computed(() => {
route.path.startsWith('/ru/catalog') route.path.startsWith('/ru/catalog')
}) })
// Client area detection (cabinet tabs in MainNavigation, no SubNav needed)
const isClientArea = computed(() => {
return route.path.includes('/clientarea')
})
// Collapsible header logic - only for pages with SubNav // Collapsible header logic - only for pages with SubNav
const hasSubNav = computed(() => !isHomePage.value && !isCatalogSection.value) const hasSubNav = computed(() => !isHomePage.value && !isCatalogSection.value && !isClientArea.value)
const canCollapse = computed(() => hasSubNav.value) const canCollapse = computed(() => hasSubNav.value)
const isHeaderCollapsed = computed(() => canCollapse.value && isCollapsed.value) const isHeaderCollapsed = computed(() => canCollapse.value && isCollapsed.value)
@@ -193,6 +199,7 @@ const headerContainerStyle = computed(() => {
const mainStyle = computed(() => { const mainStyle = computed(() => {
if (isCatalogSection.value) return { paddingTop: '0' } if (isCatalogSection.value) return { paddingTop: '0' }
if (isHomePage.value) return { paddingTop: `${heroBaseHeight.value}px` } if (isHomePage.value) return { paddingTop: `${heroBaseHeight.value}px` }
if (isClientArea.value) return { paddingTop: '116px' } // Header only, no SubNav
return { paddingTop: '154px' } return { paddingTop: '154px' }
}) })

View File

@@ -1,5 +1,6 @@
{ {
"cabinetNav": { "cabinetNav": {
"cabinet": "My Cabinet",
"search": "Search", "search": "Search",
"catalog": "Catalog", "catalog": "Catalog",
"orders": "My orders", "orders": "My orders",

View File

@@ -1,5 +1,6 @@
{ {
"cabinetNav": { "cabinetNav": {
"cabinet": "Мой кабинет",
"search": "Поиск", "search": "Поиск",
"catalog": "Каталог", "catalog": "Каталог",
"orders": "Мои заказы", "orders": "Мои заказы",