Better UX for profile view logout buttons.

This commit is contained in:
Leo Vasanko
2025-10-04 16:22:16 -06:00
parent bfb11cc20f
commit 29be642dbe
3 changed files with 25 additions and 7 deletions

View File

@@ -14,15 +14,14 @@
<script setup>
import { onMounted, ref } from 'vue'
import { useAuthStore } from '@/stores/auth'
import { getSettings } from '@/utils/settings'
import StatusMessage from '@/components/StatusMessage.vue'
import ProfileView from '@/components/ProfileView.vue'
const store = useAuthStore()
const initialized = ref(false)
onMounted(async () => {
const settings = await getSettings()
if (settings?.rp_name) document.title = settings.rp_name
await store.loadSettings()
if (store.settings?.rp_name) document.title = store.settings.rp_name
try { await store.loadUserInfo() } catch (_) { /* user info load errors ignored */ }
initialized.value = true
})

View File

@@ -3,7 +3,7 @@
<div class="view-content">
<header class="view-header">
<h1>👋 Welcome!</h1>
<Breadcrumbs :entries="breadcrumbEntries" />
<Breadcrumbs :entries="breadcrumbEntries" />
<p class="view-lede">Manage your account details and passkeys.</p>
</header>
@@ -61,10 +61,15 @@
</Modal>
<section class="section-block">
<div class="button-row logout-row single">
<button @click="logoutEverywhere" class="btn-danger logout-button">Logout all sessions</button>
<div class="button-row logout-row" :class="{ single: !hasMultipleSessions }">
<button v-if="!hasMultipleSessions" @click="logoutEverywhere" class="btn-danger logout-button" :disabled="authStore.isLoading">Logout</button>
<template v-else>
<button @click="logout" class="btn-danger logout-button" :disabled="authStore.isLoading">Logout</button>
<button @click="logoutEverywhere" class="btn-danger logout-button" :disabled="authStore.isLoading">All</button>
</template>
</div>
<p class="logout-note">Immediately revokes access for every device and browser signed in to your account.</p>
<p class="logout-note" v-if="!hasMultipleSessions">End your current session on {{ currentSessionHost }}.</p>
<p class="logout-note" v-else><strong>Logout</strong> this session on {{ currentSessionHost }}, or <strong>All</strong> sessions across all sites and devices for {{ rpName }}. You'll need to log in again with your passkey afterwards.</p>
</section>
<RegistrationLinkModal
v-if="showRegLink"
@@ -129,7 +134,12 @@ const handleDelete = async (credential) => {
} catch (error) { authStore.showMessage(`Failed to delete passkey: ${error.message}`, 'error') }
}
const rpName = computed(() => authStore.settings?.rp_name || 'this service')
const sessions = computed(() => authStore.userInfo?.sessions || [])
const currentSessionHost = computed(() => {
const currentSession = sessions.value.find(session => session.is_current)
return currentSession?.host || 'this host'
})
const terminatingSessions = ref({})
const terminateSession = async (session) => {
@@ -146,8 +156,10 @@ const terminateSession = async (session) => {
}
const logoutEverywhere = async () => { await authStore.logoutEverywhere() }
const logout = async () => { await authStore.logout() }
const openNameDialog = () => { newName.value = authStore.userInfo?.user?.user_name || ''; showNameDialog.value = true }
const isAdmin = computed(() => !!(authStore.userInfo?.is_global_admin || authStore.userInfo?.is_org_admin))
const hasMultipleSessions = computed(() => sessions.value.length > 1)
const breadcrumbEntries = computed(() => { const entries = [{ label: 'Auth', href: makeUiHref() }]; if (isAdmin.value) entries.push({ label: 'Admin', href: adminUiPath() }); return entries })
const saveName = async () => {

View File

@@ -1,5 +1,6 @@
import { defineStore } from 'pinia'
import { register, authenticate } from '@/utils/passkey'
import { getSettings } from '@/utils/settings'
export const useAuthStore = defineStore('auth', {
state: () => ({
@@ -7,6 +8,9 @@ export const useAuthStore = defineStore('auth', {
userInfo: null, // Contains the full user info response: {user, credentials, aaguid_info}
isLoading: false,
// Settings
settings: null,
// UI State
currentView: 'login',
status: {
@@ -74,6 +78,9 @@ export const useAuthStore = defineStore('auth', {
if (!this.userInfo) this.currentView = 'login'
else this.currentView = 'profile'
},
async loadSettings() {
this.settings = await getSettings()
},
async loadUserInfo() {
const response = await fetch('/auth/api/user-info', { method: 'POST' })
let result = null