import { defineStore } from 'pinia' import { authApi, employeesApi } from '../api' export const useAuthStore = defineStore('auth', { state: () => ({ user: JSON.parse(localStorage.getItem('user') || 'null'), token: localStorage.getItem('token') || null }), getters: { isAuthenticated: (state) => !!state.token, 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, // True if the user holds a named permission. Admins hold every permission // (mirrors the backend require_permission admin bypass), so they pass // regardless of the permissions list. Non-admins check their granted list. hasPermission: (state) => (name) => state.user?.roles?.includes('admin') || state.user?.permissions?.includes(name) || false, // True when an admin-set temporary password must be changed before use. mustChangePassword: (state) => !!state.user?.mustchangepassword, // 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. The directory // resolver already returns a usable URL (self-hosted upload or external HR // path), so it is used as-is. avatarUrl: (state) => state.user?.directoryphotourl || null }, actions: { async login(username, password) { try { const response = await authApi.login(username, password) const { access_token, refresh_token, user } = response.data.data this.token = access_token this.user = user this.token = access_token localStorage.setItem('token', access_token) localStorage.setItem('refreshToken', refresh_token) await this.enrichFromDirectory() return { success: true } } catch (error) { const message = error.response?.data?.message || 'Login failed' return { success: false, message } } }, async logout() { try { await authApi.logout() } catch (e) { // Ignore logout errors } this.token = null this.user = null localStorage.removeItem('token') localStorage.removeItem('refreshToken') localStorage.removeItem('user') }, // Clear the forced-password-change flag after a successful change so the // router guard stops steering the user to the change-password view. clearMustChangePassword() { if (this.user) { this.user.mustchangepassword = false localStorage.setItem('user', JSON.stringify(this.user)) } }, async fetchUser() { try { const response = await authApi.me() this.user = response.data.data 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.directoryphotourl = emp.photourl || null } } catch (err) { /* directory unavailable - fall back to username */ } } localStorage.setItem('user', JSON.stringify(this.user)) } } })