2026-02-26 01:32:04 +00:00
|
|
|
import { defineStore } from 'pinia'
|
|
|
|
|
import { ref, computed } from 'vue'
|
|
|
|
|
import { apiClient, isAxiosError, API } from '../router/api'
|
|
|
|
|
import type { User, SessionResponse } from '../types/user'
|
|
|
|
|
import type { Organization, Role } from 'src/types/organization'
|
|
|
|
|
|
|
|
|
|
const orgUuidKey = 'userSelectedOrganizationUuid'
|
|
|
|
|
|
|
|
|
|
export const useUserStore = defineStore('user', () => {
|
|
|
|
|
const isAuthenticated = ref(false)
|
|
|
|
|
const isAdmin = ref(false)
|
|
|
|
|
const isGeneralManager = computed(() => {
|
|
|
|
|
if (!isAuthenticated.value || !user.value) return false
|
|
|
|
|
return user.value.is_manager
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
const error = ref<string | null>(null)
|
|
|
|
|
|
|
|
|
|
const user = ref<User | null>(null)
|
|
|
|
|
const userJoinedOrganizations = ref<Organization[]>([])
|
|
|
|
|
const userSelectedOrganization = ref<Organization | null>(null)
|
|
|
|
|
const userJoinedRoles = ref<Role[]>([])
|
|
|
|
|
|
|
|
|
|
const displayName = computed(() => {
|
|
|
|
|
if (!user.value) return ''
|
|
|
|
|
return `${user.value.first_name} ${user.value.last_name}`
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const setUser = (userData: User | null) => {
|
|
|
|
|
user.value = userData
|
|
|
|
|
isAuthenticated.value = !!userData
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const setJoinedOrganizations = (organizations: Organization[]) => {
|
|
|
|
|
userJoinedOrganizations.value = organizations
|
|
|
|
|
if (organizations.length > 0) {
|
|
|
|
|
const stored = localStorage.getItem(orgUuidKey)
|
|
|
|
|
const matched = organizations.find((org) => org.uuid === stored)
|
|
|
|
|
if (matched) {
|
|
|
|
|
userSelectedOrganization.value = matched
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
userSelectedOrganization.value = organizations[0]
|
|
|
|
|
localStorage.setItem(orgUuidKey, organizations[0].uuid)
|
|
|
|
|
} else {
|
|
|
|
|
userSelectedOrganization.value = null
|
|
|
|
|
localStorage.removeItem(orgUuidKey)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const setSelectedOrganization = (organization: Organization | null) => {
|
|
|
|
|
userSelectedOrganization.value = organization
|
|
|
|
|
if (organization) {
|
|
|
|
|
localStorage.setItem(orgUuidKey, organization.uuid)
|
|
|
|
|
} else {
|
|
|
|
|
localStorage.removeItem(orgUuidKey)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchSession = async (force = false) => {
|
|
|
|
|
if (isAuthenticated.value && !force) return user.value
|
|
|
|
|
loading.value = true
|
|
|
|
|
error.value = null
|
|
|
|
|
try {
|
2026-02-27 12:26:51 +00:00
|
|
|
const response = await apiClient.get<SessionResponse>(API.auth.session())
|
2026-02-26 01:32:04 +00:00
|
|
|
if (response.data?.isAuthenticated) {
|
2026-02-27 12:26:51 +00:00
|
|
|
const userData = await apiClient.get<User>(API.auth.me())
|
2026-02-26 01:32:04 +00:00
|
|
|
setUser(userData.data)
|
|
|
|
|
await fetchJoinedOrganizations()
|
|
|
|
|
} else {
|
|
|
|
|
setUser(null)
|
|
|
|
|
isAuthenticated.value = false
|
|
|
|
|
}
|
|
|
|
|
return user.value
|
|
|
|
|
} catch (err: unknown) {
|
|
|
|
|
if (isAxiosError(err)) {
|
|
|
|
|
error.value = err.response?.data?.detail || err.response?.data?.error || err.message
|
|
|
|
|
} else if (err instanceof Error) {
|
|
|
|
|
error.value = err.message
|
|
|
|
|
} else {
|
|
|
|
|
error.value = String(err)
|
|
|
|
|
}
|
|
|
|
|
setUser(null)
|
|
|
|
|
throw err
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchJoinedOrganizations = async () => {
|
|
|
|
|
if (!user.value) return
|
|
|
|
|
try {
|
2026-02-27 12:26:51 +00:00
|
|
|
const response = await apiClient.get<Organization[]>(API.organization.list())
|
2026-02-26 01:32:04 +00:00
|
|
|
setJoinedOrganizations(response.data)
|
|
|
|
|
return response.data
|
|
|
|
|
} catch (err: unknown) {
|
|
|
|
|
console.error('Failed to fetch organizations', err)
|
|
|
|
|
setJoinedOrganizations([])
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchJoinedRoles = async () => {
|
|
|
|
|
if (!user.value || !userSelectedOrganization.value) return
|
|
|
|
|
try {
|
|
|
|
|
const response = await apiClient.get<Role[]>(
|
2026-02-27 12:26:51 +00:00
|
|
|
API.organization.roles.list(userSelectedOrganization.value.uuid),
|
2026-02-26 01:32:04 +00:00
|
|
|
)
|
|
|
|
|
setJoinedRoles(response.data)
|
|
|
|
|
return response.data
|
|
|
|
|
} catch (err: unknown) {
|
|
|
|
|
console.error('Failed to fetch role memberships', err)
|
|
|
|
|
setJoinedRoles([])
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const setJoinedRoles = (roles: Role[]) => {
|
|
|
|
|
userJoinedRoles.value = roles
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const login = async (emailAddress: string, password: string) => {
|
|
|
|
|
loading.value = true
|
|
|
|
|
error.value = null
|
|
|
|
|
try {
|
|
|
|
|
const res = await apiClient.post<{
|
|
|
|
|
user: User
|
|
|
|
|
message?: string
|
2026-02-27 12:26:51 +00:00
|
|
|
}>(API.auth.login(), { email_address: emailAddress, password })
|
2026-02-26 01:32:04 +00:00
|
|
|
setUser(res.data?.user ?? null)
|
|
|
|
|
return res.data
|
|
|
|
|
} catch (err: unknown) {
|
|
|
|
|
if (isAxiosError(err)) {
|
|
|
|
|
error.value = err.response?.data?.error || err.response?.data?.detail || err.message
|
|
|
|
|
} else if (err instanceof Error) {
|
|
|
|
|
error.value = err.message
|
|
|
|
|
} else {
|
|
|
|
|
error.value = String(err)
|
|
|
|
|
}
|
|
|
|
|
throw err
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const register = async (payload: {
|
|
|
|
|
email_address: string
|
|
|
|
|
password: string
|
|
|
|
|
confirm_password?: string
|
|
|
|
|
first_name: string
|
|
|
|
|
last_name: string
|
|
|
|
|
date_of_birth?: string
|
|
|
|
|
manager: boolean
|
|
|
|
|
}) => {
|
|
|
|
|
loading.value = true
|
|
|
|
|
error.value = null
|
|
|
|
|
try {
|
2026-02-27 12:26:51 +00:00
|
|
|
await apiClient.post(API.auth.signup(), payload)
|
2026-02-26 01:32:04 +00:00
|
|
|
await login(payload.email_address, payload.password)
|
|
|
|
|
} catch (err: unknown) {
|
|
|
|
|
if (isAxiosError(err)) {
|
|
|
|
|
error.value = err.response?.data?.detail || err.response?.data?.error || err.message
|
|
|
|
|
} else if (err instanceof Error) {
|
|
|
|
|
error.value = err.message
|
|
|
|
|
} else {
|
|
|
|
|
error.value = String(err)
|
|
|
|
|
}
|
|
|
|
|
throw err
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const logout = async () => {
|
|
|
|
|
loading.value = true
|
|
|
|
|
error.value = null
|
|
|
|
|
try {
|
2026-02-27 12:26:51 +00:00
|
|
|
await apiClient.post(API.auth.logout())
|
2026-02-26 01:32:04 +00:00
|
|
|
} catch (err: unknown) {
|
|
|
|
|
if (isAxiosError(err)) {
|
|
|
|
|
error.value = err.response?.data?.detail || err.response?.data?.error || err.message
|
|
|
|
|
} else if (err instanceof Error) {
|
|
|
|
|
error.value = err.message
|
|
|
|
|
} else {
|
|
|
|
|
error.value = String(err)
|
|
|
|
|
}
|
|
|
|
|
throw err
|
|
|
|
|
} finally {
|
|
|
|
|
setUser(null)
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
user,
|
|
|
|
|
displayName,
|
|
|
|
|
isAuthenticated,
|
|
|
|
|
isAdmin,
|
|
|
|
|
isGeneralManager,
|
|
|
|
|
loading,
|
|
|
|
|
error,
|
|
|
|
|
userJoinedOrganizations,
|
|
|
|
|
userSelectedOrganization,
|
|
|
|
|
userJoinedRoles,
|
|
|
|
|
|
|
|
|
|
setUser,
|
|
|
|
|
fetchSession,
|
|
|
|
|
setJoinedOrganizations,
|
|
|
|
|
setSelectedOrganization,
|
|
|
|
|
setJoinedRoles,
|
|
|
|
|
fetchJoinedOrganizations,
|
|
|
|
|
fetchJoinedRoles,
|
|
|
|
|
login,
|
|
|
|
|
register,
|
|
|
|
|
logout,
|
|
|
|
|
}
|
|
|
|
|
})
|