Files
b2b/bo/src/layouts/default.vue

295 lines
7.5 KiB
Vue

<template>
<div class="flex flex-1 overflow-x-hidden h-svh">
<USidebar v-model:open="open" collapsible="icon" rail :ui="{
container: 'h-full z-80',
inner: 'bg-elevated/25 divide-transparent',
body: 'py-0'
}">
<template #header>
<UDropdownMenu :items="teamsItems" :content="{ align: 'start', collisionPadding: 12 }"
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }">
<UButton v-bind="selectedTeam" trailing-icon="i-lucide-chevrons-up-down" color="neutral" variant="ghost"
square class="w-full data-[state=open]:bg-elevated overflow-hidden" :ui="{
trailingIcon: 'text-dimmed ms-auto'
}" />
</UDropdownMenu>
</template>
<template #default="{ state }">
<UNavigationMenu :key="state" :items="menuItems" orientation="vertical"
:ui="{ link: 'p-1.5 overflow-hidden' }" />
</template>
<template #footer>
<UDropdownMenu :items="userItems" :content="{ align: 'center', collisionPadding: 12 }"
:ui="{ content: 'w-(--reka-dropdown-menu-trigger-width) min-w-48' }">
<UButton v-bind="user" :label="user?.name" trailing-icon="i-lucide-chevrons-up-down" color="neutral"
variant="ghost" square class="w-full data-[state=open]:bg-elevated overflow-hidden" :ui="{
trailingIcon: 'text-dimmed ms-auto'
}" />
</UDropdownMenu>
</template>
</USidebar>
<div class="flex-1 flex flex-col">
<div class="flex h-(--ui-header-height) shrink-0 items-center justify-between px-4 border-b border-default">
<UButton icon="i-lucide-panel-left" color="neutral" variant="ghost" aria-label="Toggle sidebar"
@click="open = !open" />
<div class="hidden md:flex items-center gap-12">
<div class="flex items-center gap-2">
<CountryCurrencySwitch />
<LangSwitch />
</div>
<div class="flex items-center gap-2">
<ThemeSwitch />
<button v-if="authStore.isAuthenticated" @click="authStore.logout()"
class="px-3 py-1.5 text-sm font-medium text-black dark:text-white hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg transition-colors border border-(--border-light) dark:border-(--border-dark) whitespace-nowrap">
{{ $t('general.logout') }}
</button>
</div>
</div>
</div>
<div class="flex-1 p-4 bg-slate-50">
<slot />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useColorMode } from '@vueuse/core'
import type { DropdownMenuItem, NavigationMenuItem } from '@nuxt/ui'
import { defineShortcuts, extractShortcuts } from '@nuxt/ui/runtime/composables/defineShortcuts.js'
import { LabelTrans, TopMenuItem } from '@/types'
const open = ref(true)
const authStore = useAuthStore()
const colorMode = useColorMode()
const teams = ref([
{
label: 'Nuxt',
avatar: {
src: 'https://github.com/nuxt.png',
alt: 'Nuxt'
}
},
{
label: 'Vue',
avatar: {
src: 'https://github.com/vuejs.png',
alt: 'Vue'
}
},
{
label: 'UnJS',
avatar: {
src: 'https://github.com/unjs.png',
alt: 'UnJS'
}
}
])
const selectedTeam = ref(teams.value[0])
const teamsItems = computed<DropdownMenuItem[][]>(() => {
return [
teams.value.map((team, index) => ({
...team,
kbds: ['meta', String(index + 1)],
onSelect() {
selectedTeam.value = team
}
})),
[
{
label: 'Create team',
icon: 'i-lucide-circle-plus'
}
]
]
})
function getItems(state: 'collapsed' | 'expanded') {
return [
{
label: 'Inbox',
icon: 'i-lucide-inbox',
badge: '4'
},
{
label: 'Issues',
icon: 'i-lucide-square-dot'
},
{
label: 'Activity',
icon: 'i-lucide-square-activity'
},
{
label: 'Settings',
icon: 'i-lucide-settings',
defaultOpen: true,
children:
state === 'expanded'
? [
{
label: 'General',
icon: 'i-lucide-house'
},
{
label: 'Team',
icon: 'i-lucide-users'
},
{
label: 'Billing',
icon: 'i-lucide-credit-card'
}
]
: []
}
] satisfies NavigationMenuItem[]
}
//
import { useRouter } from 'vue-router'
import { currentLang } from '@/router/langs'
import { useFetchJson } from '@/composable/useFetchJson'
import { useAuthStore } from '@/stores/auth'
import CountryCurrencySwitch from '@/components/inner/CountryCurrencySwitch.vue'
import LangSwitch from '@/components/inner/LangSwitch.vue'
import ThemeSwitch from '@/components/inner/ThemeSwitch.vue'
const router = useRouter()
const menu = ref<TopMenuItem[] | null>(null)
async function getTopMenu() {
try {
const { items } = await useFetchJson<TopMenuItem[]>('/api/v1/restricted/menu/get-top-menu')
menu.value = items
} catch (err) {
console.log(err)
}
}
onMounted(() => {
getTopMenu()
})
const menuItems = computed(() => {
if (!menu.value?.length) return []
return transformMenu(
menu.value || [],
currentLang.value?.iso_code
)
})
function transformMenu(
items: TopMenuItem[],
locale: string | undefined
): NavigationMenuItem[] {
return items.map((item) => {
const route: NavigationMenuItem = {
icon: item.label.icon || 'i-lucide-house',
label:
item.label.trans?.[locale as keyof LabelTrans]?.label ||
item.label.trans?.en?.label ||
'—',
children: item.children
? transformMenu(item.children, locale)
: undefined,
onSelect: () => {
router.push({
name: item.params.route.name,
params: {
...(item.params.route.params || {}),
locale: currentLang.value?.iso_code
}
})
}
}
return route
})
}
const user = ref({
name: 'Benjamin Canac',
avatar: {
src: 'https://github.com/benjamincanac.png',
alt: 'Benjamin Canac'
}
})
const userItems = computed<DropdownMenuItem[][]>(() => [
[
{
label: 'Profile',
icon: 'i-lucide-user'
},
{
label: 'Billing',
icon: 'i-lucide-credit-card'
},
{
label: 'Settings',
icon: 'i-lucide-settings',
to: '/settings'
}
],
[
{
label: 'Appearance',
icon: 'i-lucide-sun-moon',
children: [
{
label: 'Light',
icon: 'i-lucide-sun',
type: 'checkbox',
checked: colorMode.value === 'light',
onUpdateChecked(checked: boolean) {
if (checked) {
colorMode.preference = 'light'
}
},
onSelect(e: Event) {
e.preventDefault()
}
},
{
label: 'Dark',
icon: 'i-lucide-moon',
type: 'checkbox',
checked: colorMode.value === 'dark',
onUpdateChecked(checked: boolean) {
if (checked) {
colorMode.preference = 'dark'
}
},
onSelect(e: Event) {
e.preventDefault()
}
}
]
}
],
[
{
label: 'GitHub',
icon: 'i-simple-icons-github',
to: 'https://github.com/nuxt/ui',
target: '_blank'
},
{
label: 'Log out',
icon: 'i-lucide-log-out'
}
]
])
defineShortcuts(extractShortcuts(teamsItems.value))
</script>