Gitea Actions / Deploy (push) Successful in 29s
sessions fix password change sign-in fix
152 lines
4.6 KiB
Vue
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>
|