189 lines
4.5 KiB
Vue
189 lines
4.5 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { useSessionStore } from '@/stores/session'
|
|
import { useApi } from '@/composables/useApi'
|
|
import { useSidebarCollapsed } from '@/composables/useSidebarCollapsed'
|
|
import AppBoot from '@/components/AppBoot.vue'
|
|
import AppLogo from '@/components/AppLogo.vue'
|
|
import ErrorView from '@/components/ErrorView.vue'
|
|
import OfflineBanner from '@/components/OfflineBanner.vue'
|
|
import UserMenu from '@/components/UserMenu.vue'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
mobileTitle?: string
|
|
fullBleed?: boolean
|
|
}>(),
|
|
{
|
|
mobileTitle: undefined,
|
|
fullBleed: false,
|
|
},
|
|
)
|
|
|
|
const bodyUi = computed(() =>
|
|
props.fullBleed
|
|
? 'gap-0 p-0 sm:gap-0 sm:p-0 lg:p-0'
|
|
: 'gap-0 px-4 pt-5 pb-22 sm:gap-0 sm:px-4 sm:pt-5 sm:pb-22 lg:p-8',
|
|
)
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const session = useSessionStore()
|
|
const api = useApi()
|
|
|
|
const navItems = computed(() =>
|
|
[
|
|
{ label: 'Home', to: '/home', icon: 'i-lucide-home' },
|
|
{ label: 'Articles', to: '/article', icon: 'i-lucide-file-text' },
|
|
{ label: 'Integration', to: '/integration', icon: 'i-lucide-plug' },
|
|
].map((item) => ({
|
|
...item,
|
|
active: route.path === item.to || route.path.startsWith(`${item.to}/`),
|
|
})),
|
|
)
|
|
|
|
const routeTitle = computed(() => (route.meta.title as string) ?? '')
|
|
const pageTitle = computed(() => props.mobileTitle ?? routeTitle.value)
|
|
const sidebarCollapsed = useSidebarCollapsed()
|
|
|
|
const sidebarUi = computed(() => ({
|
|
root: sidebarCollapsed.value ? 'min-w-14 px-1 py-3' : 'min-w-14 p-3',
|
|
header: 'mb-3 h-10 gap-2.5 px-1.5',
|
|
body: 'gap-0.5 p-0',
|
|
footer: 'p-0',
|
|
}))
|
|
|
|
const navUi = computed(() => ({
|
|
list: 'flex flex-col gap-0.5',
|
|
link: sidebarCollapsed.value
|
|
? 'h-8 justify-center gap-0 px-0 text-sm'
|
|
: 'h-8 gap-2.5 px-2.5 py-0 text-sm',
|
|
linkLeadingIcon: 'size-4',
|
|
}))
|
|
const loggingOut = ref(false)
|
|
|
|
function onRetry() {
|
|
session.retry()
|
|
}
|
|
|
|
async function onLogout() {
|
|
loggingOut.value = true
|
|
try {
|
|
await api.request('/v1/auth/logout', { method: 'POST' })
|
|
} finally {
|
|
loggingOut.value = false
|
|
}
|
|
session.clear()
|
|
await router.push('/login')
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<UDashboardGroup unit="rem">
|
|
<a href="#dashboard-content" class="sr-only focus:not-sr-only"
|
|
>Skip to content</a
|
|
>
|
|
|
|
<UDashboardSidebar
|
|
v-model:collapsed="sidebarCollapsed"
|
|
collapsible
|
|
:resizable="false"
|
|
:default-size="14"
|
|
:collapsed-size="3.5"
|
|
:ui="sidebarUi"
|
|
>
|
|
<template #header>
|
|
<AppLogo :size="26" />
|
|
<span
|
|
v-if="!sidebarCollapsed"
|
|
aria-hidden="true"
|
|
class="text-base font-semibold text-highlighted"
|
|
>RenewCMS</span
|
|
>
|
|
</template>
|
|
|
|
<UNavigationMenu
|
|
orientation="vertical"
|
|
:items="navItems"
|
|
:collapsed="sidebarCollapsed"
|
|
aria-label="Main"
|
|
:ui="navUi"
|
|
/>
|
|
|
|
<template #footer>
|
|
<UserMenu
|
|
:username="session.session?.username ?? ''"
|
|
:collapsed="sidebarCollapsed"
|
|
@logout="onLogout"
|
|
/>
|
|
</template>
|
|
</UDashboardSidebar>
|
|
|
|
<UDashboardPanel :ui="{ body: bodyUi }">
|
|
<template #header>
|
|
<UDashboardNavbar
|
|
:toggle="{ ui: { leadingIcon: 'size-5' } }"
|
|
:title="pageTitle"
|
|
:ui="{
|
|
root: 'h-14 gap-1 bg-default px-1.5 sm:px-1.5 lg:h-12 lg:gap-2 lg:px-6',
|
|
left: 'min-w-0 flex-1 gap-1 lg:gap-2',
|
|
title: 'text-lg lg:sr-only',
|
|
toggle: 'size-11 justify-center',
|
|
}"
|
|
>
|
|
<template #leading>
|
|
<UDashboardSidebarCollapse
|
|
color="neutral"
|
|
variant="ghost"
|
|
size="sm"
|
|
class="-mx-1.5 text-muted"
|
|
:aria-expanded="!sidebarCollapsed"
|
|
data-testid="sidebar-collapse"
|
|
/>
|
|
<span
|
|
aria-hidden="true"
|
|
class="mx-1.5 hidden h-4 w-px bg-(--ui-border) lg:block"
|
|
/>
|
|
<slot name="breadcrumb">
|
|
<UBreadcrumb
|
|
:items="[{ label: routeTitle }]"
|
|
color="neutral"
|
|
class="hidden lg:block"
|
|
:ui="{ link: 'text-sm font-medium' }"
|
|
/>
|
|
</slot>
|
|
</template>
|
|
<template #right>
|
|
<slot name="navbar-right" />
|
|
</template>
|
|
</UDashboardNavbar>
|
|
</template>
|
|
|
|
<template #body>
|
|
<div
|
|
id="dashboard-content"
|
|
:class="
|
|
fullBleed ? 'flex h-full min-h-0 flex-1 flex-col' : ''
|
|
"
|
|
>
|
|
<OfflineBanner />
|
|
|
|
<AppBoot v-if="session.status === 'checking'" />
|
|
<ErrorView
|
|
v-else-if="session.status === 'error'"
|
|
kind="error"
|
|
@retry="onRetry"
|
|
/>
|
|
<div
|
|
v-else
|
|
:class="fullBleed ? 'flex min-h-0 flex-1 flex-col' : ''"
|
|
>
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</UDashboardPanel>
|
|
</UDashboardGroup>
|
|
</template>
|