Enrich the logged-in user with directory name + photo (by SSO)
When the employees directory is enabled, look up the current user by their SSO (= username) and show their full name + photo in the sidebar user menu. - auth store: enrichFromDirectory() runs on login + fetchUser; sets directoryname/directorypicture from employeesApi.lookup(sso). Best-effort - falls back to the raw SSO when employees is off, the SSO is not in the directory, or the lookup fails. - displayName / avatarUrl getters; sidebar shows the photo (/static/employees/ <Picture>) + name over the SSO. Broken photos hide gracefully. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { authApi } from '../api'
|
||||
import { authApi, employeesApi } from '../api'
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
@@ -12,7 +12,12 @@ export const useAuthStore = defineStore('auth', {
|
||||
username: (state) => state.user?.username || '',
|
||||
roles: (state) => state.user?.roles || [],
|
||||
hasRole: (state) => (role) => state.user?.roles?.includes(role) || false,
|
||||
isAdmin: (state) => state.user?.roles?.includes('admin') || false
|
||||
isAdmin: (state) => state.user?.roles?.includes('admin') || false,
|
||||
// Full name from the employee directory (falls back to username/SSO).
|
||||
displayName: (state) => state.user?.directoryname || state.user?.username || '',
|
||||
// Employee photo URL if the directory has one for this SSO.
|
||||
avatarUrl: (state) => state.user?.directorypicture
|
||||
? `/static/employees/${state.user.directorypicture}` : null
|
||||
},
|
||||
|
||||
actions: {
|
||||
@@ -24,9 +29,10 @@ export const useAuthStore = defineStore('auth', {
|
||||
this.token = access_token
|
||||
this.user = user
|
||||
|
||||
this.token = access_token
|
||||
localStorage.setItem('token', access_token)
|
||||
localStorage.setItem('refreshToken', refresh_token)
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
await this.enrichFromDirectory()
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
@@ -54,10 +60,28 @@ export const useAuthStore = defineStore('auth', {
|
||||
try {
|
||||
const response = await authApi.me()
|
||||
this.user = response.data.data
|
||||
localStorage.setItem('user', JSON.stringify(this.user))
|
||||
await this.enrichFromDirectory()
|
||||
} catch (error) {
|
||||
this.logout()
|
||||
}
|
||||
},
|
||||
|
||||
// Pull the logged-in user's full name + photo from the employee directory
|
||||
// by their SSO (= username). Best-effort: skipped/ignored when the employees
|
||||
// plugin is off, the SSO is not in the directory, or the lookup fails.
|
||||
async enrichFromDirectory() {
|
||||
const sso = this.user?.username
|
||||
if (sso && /^\d+$/.test(sso)) {
|
||||
try {
|
||||
const response = await employeesApi.lookup(sso)
|
||||
const emp = response.data?.data
|
||||
if (emp) {
|
||||
this.user.directoryname = `${emp.First_Name || ''} ${emp.Last_Name || ''}`.trim() || null
|
||||
this.user.directorypicture = emp.Picture || null
|
||||
}
|
||||
} catch (err) { /* directory unavailable - fall back to username */ }
|
||||
}
|
||||
localStorage.setItem('user', JSON.stringify(this.user))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -39,7 +39,12 @@
|
||||
|
||||
<div class="user-menu">
|
||||
<template v-if="authStore.isAuthenticated">
|
||||
<div class="username">{{ authStore.username }}</div>
|
||||
<img v-if="authStore.avatarUrl" :src="authStore.avatarUrl" class="user-avatar"
|
||||
:alt="authStore.displayName" @error="onAvatarError" />
|
||||
<div class="user-ident">
|
||||
<div class="username">{{ authStore.displayName }}</div>
|
||||
<div v-if="authStore.displayName !== authStore.username" class="user-sso">{{ authStore.username }}</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary" @click="handleLogout">Logout</button>
|
||||
</template>
|
||||
<router-link v-else to="/login" class="btn btn-primary">Login</router-link>
|
||||
@@ -196,4 +201,16 @@ async function handleLogout() {
|
||||
await authStore.logout()
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
// Hide a broken avatar (photo filename set but file missing).
|
||||
function onAvatarError(event) {
|
||||
event.target.style.display = 'none'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-menu { display: flex; align-items: center; gap: 0.6rem; }
|
||||
.user-avatar { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; border: 1px solid var(--border); }
|
||||
.user-ident { display: flex; flex-direction: column; line-height: 1.1; }
|
||||
.user-sso { font-size: 0.72rem; color: var(--text-light); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user