Files
ulitka-app/src/layouts/DefaultLayout.vue
T
vadim f0706432d4
Gitea Actions / Deploy (push) Successful in 29s
avatar fix
sessions fix
password change
sign-in fix
2026-10-23 19:25:11 +03:00

152 lines
4.6 KiB
Vue

<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { authClient } from '@/lib/auth-client.ts'
import { computed } from 'vue'
import AppBreadcrumbs from '@/components/AppBreadcrumbs.vue'
import BackButton from '@/components/BackButton.vue'
import { useThemeMode } from '@/composables/useThemeMode.ts'
import { THEME_ICONS } from '@/lib/theme.ts'
import NavigationMenu from '@/components/NavigationMenu.vue'
import { usePushNotifications } from '@/composables/usePushNotifications.ts'
import ProjectMenu from '@/components/ProjectMenu.vue'
import NotificationsList from '@/components/NotificationsList.vue'
const router = useRouter()
const route = useRoute()
const session = authClient.useSession()
const { mode: themeMode, cycleThemeMode } = useThemeMode()
const push = usePushNotifications()
const title = 'Ulitka'
const isAuth = computed(() => !!session.value.data)
/** Shown in the avatar until the user uploads a picture */
const initial = computed(() => {
const user = session.value.data?.user
return (user?.name.trim() || user?.email || '?').charAt(0).toUpperCase()
})
function goToSignIn() {
router.push({ path: '/sign-in' })
}
function goToSignUp() {
router.push({ path: '/sign-up' })
}
function goToProfile() {
router.push({ path: '/profile' })
}
async function goToSignOut() {
// Пока сессия жива — иначе бэкенд не даст удалить токен и пуши продолжат приходить в этот браузер
await push.disable()
await authClient.signOut({
fetchOptions: {
onSuccess: () => {
router.push('/sign-in')
},
},
})
}
const goToSettings = () => {
router.push({ path: '/settings' })
}
const fluid = computed(() => {
return route.meta.fluid !== undefined ? !!route.meta.fluid : true
})
</script>
<template>
<v-layout>
<v-app-bar density="compact" elevation="0" border rounded="0">
<v-app-bar-title class="d-flex">
<router-link class="text-decoration-none text-link font-weight-bold d-flex align-baseline ga-2" to="/">
<v-icon icon="mdi-snail" />
<div>{{ title }}</div>
</router-link>
</v-app-bar-title>
<div v-if="!session.data">
<v-btn @click="goToSignIn">Sign In</v-btn>
<v-btn @click="goToSignUp">Sign Up</v-btn>
</div>
<div v-if="session.data" class="d-flex align-center">
<project-menu />
<notifications-list />
<v-btn rounded="0" :icon="THEME_ICONS[themeMode]" :title="`Theme: ${themeMode}`" @click="cycleThemeMode" />
<v-menu>
<template v-slot:activator="{ props }">
<v-btn rounded="0" icon v-bind="props" class="mr-4">
<v-icon icon="mdi-account" />
</v-btn>
</template>
<v-list nav density="compact" class="pa-1">
<v-list-item density="compact">
<v-sheet border class="d-flex align-center ga-2 pa-2">
<!-- No slot content: with a default slot v-avatar ignores image; text is used only without image -->
<v-avatar rounded="circle" border :image="session.data.user.image ?? undefined" :text="initial" />
<div>
<div>{{ session.data.user.name }}</div>
<div class="text-label-medium">{{ session.data.user.email }}</div>
</div>
</v-sheet>
</v-list-item>
<v-list-item rounded="0" density="compact" @click="goToProfile">
<v-icon icon="mdi-account" />
Profile
</v-list-item>
<v-list-item rounded="0" density="compact" @click="goToSettings">
<v-icon icon="mdi-cog" />
settings
</v-list-item>
<v-divider class="my-1" />
<v-list-item rounded="0" density="compact" @click="goToSignOut">
<v-icon icon="mdi-logout" />
Sign Out
</v-list-item>
</v-list>
</v-menu>
</div>
</v-app-bar>
<navigation-menu :is-auth="isAuth" />
<v-main>
<v-container :fluid="fluid" class="layout-container">
<div class="nav-row d-flex align-center ga-2 mb-2">
<back-button />
<app-breadcrumbs />
</div>
<div class="layout-content">
<slot />
</div>
</v-container>
</v-main>
</v-layout>
</template>
<style scoped lang="scss">
.nav-row:empty {
display: none;
}
.layout-container {
height: calc(100dvh - var(--v-layout-top) - var(--v-layout-bottom));
display: flex;
flex-direction: column;
}
.layout-content {
flex: 1;
min-height: 0;
}
</style>