180 lines
4.8 KiB
TypeScript
180 lines
4.8 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref, computed } from 'vue'
|
|
|
|
export interface UserProfile {
|
|
id: number
|
|
email: string
|
|
first_name: string | null
|
|
last_name: string | null
|
|
is_active: boolean
|
|
role: string
|
|
region_code: string
|
|
subscription_plan: string
|
|
subscription_expires_at?: string | null
|
|
max_vehicles?: number
|
|
max_garages?: number
|
|
scope_level: string
|
|
scope_id: string | null
|
|
ui_mode: string
|
|
active_organization_id: number | null
|
|
preferred_language: string
|
|
person_id: number | null
|
|
person: Record<string, any> | null
|
|
system_capabilities?: Record<string, boolean>
|
|
org_capabilities?: Record<string, Record<string, boolean>>
|
|
}
|
|
|
|
export const useAuthStore = defineStore('auth', () => {
|
|
// ── State ──────────────────────────────────────────────────────────
|
|
const user = ref<UserProfile | null>(null)
|
|
const token = ref<string | null>(null)
|
|
const isLoading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
const isInitialized = ref(false)
|
|
|
|
// ── Getters ────────────────────────────────────────────────────────
|
|
const isAuthenticated = computed(() => !!token.value && !!user.value)
|
|
|
|
const isAdmin = computed(() => {
|
|
const role = user.value?.role
|
|
if (!role) return false
|
|
const staffRoles = ['SUPERADMIN', 'ADMIN', 'MODERATOR', 'SALES_REP', 'SERVICE_MGR']
|
|
return staffRoles.includes(role.toUpperCase())
|
|
})
|
|
|
|
const userName = computed(() => {
|
|
if (!user.value) return ''
|
|
const { first_name, last_name } = user.value
|
|
if (first_name && last_name) return `${first_name} ${last_name}`
|
|
return first_name || last_name || user.value.email
|
|
})
|
|
|
|
const userEmail = computed(() => user.value?.email ?? '')
|
|
const userRole = computed(() => user.value?.role ?? 'guest')
|
|
|
|
// ── Actions ────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Login with email + password.
|
|
* Calls the FastAPI /auth/login endpoint using OAuth2PasswordRequestForm.
|
|
* Stores the JWT token in a cookie (via useCookie) for SSR compatibility.
|
|
*/
|
|
async function login(email: string, password: string) {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
const body = new URLSearchParams()
|
|
body.append('username', email)
|
|
body.append('password', password)
|
|
|
|
const res = await $fetch<{
|
|
access_token: string
|
|
refresh_token?: string
|
|
token_type: string
|
|
is_active: boolean
|
|
}>('/api/v1/auth/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body,
|
|
})
|
|
|
|
// Persist token in a cookie (SSR-safe) and in memory
|
|
const tokenCookie = useCookie('access_token', {
|
|
maxAge: 60 * 60 * 24 * 7, // 7 days
|
|
path: '/',
|
|
sameSite: 'lax',
|
|
secure: true,
|
|
})
|
|
tokenCookie.value = res.access_token
|
|
token.value = res.access_token
|
|
|
|
// Fetch user profile
|
|
await fetchUser()
|
|
} catch (err: any) {
|
|
error.value = err?.data?.detail || err?.message || 'Login failed'
|
|
throw err
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch the current user profile from /auth/me.
|
|
*/
|
|
async function fetchUser() {
|
|
const tokenCookie = useCookie('access_token')
|
|
const currentToken = tokenCookie.value || token.value
|
|
if (!currentToken) return
|
|
|
|
try {
|
|
const res = await $fetch<UserProfile>('/api/v1/auth/me', {
|
|
headers: { Authorization: `Bearer ${currentToken}` },
|
|
})
|
|
user.value = res
|
|
} catch (err: any) {
|
|
if (err?.response?.status === 401) {
|
|
logout()
|
|
}
|
|
throw err
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logout — clear token, user state, and cookie.
|
|
*/
|
|
function logout() {
|
|
token.value = null
|
|
user.value = null
|
|
error.value = null
|
|
const tokenCookie = useCookie('access_token')
|
|
tokenCookie.value = null
|
|
}
|
|
|
|
/**
|
|
* Initialize the store on app load — if a token cookie exists, fetch the user.
|
|
*/
|
|
async function init() {
|
|
const tokenCookie = useCookie('access_token')
|
|
if (tokenCookie.value) {
|
|
token.value = tokenCookie.value
|
|
try {
|
|
await fetchUser()
|
|
} catch {
|
|
logout()
|
|
}
|
|
}
|
|
isInitialized.value = true
|
|
}
|
|
|
|
/**
|
|
* Clear any stored error message.
|
|
*/
|
|
function clearError() {
|
|
error.value = null
|
|
}
|
|
|
|
return {
|
|
// State
|
|
user,
|
|
token,
|
|
isLoading,
|
|
error,
|
|
isInitialized,
|
|
|
|
// Getters
|
|
isAuthenticated,
|
|
isAdmin,
|
|
userName,
|
|
userEmail,
|
|
userRole,
|
|
|
|
// Actions
|
|
login,
|
|
fetchUser,
|
|
logout,
|
|
init,
|
|
clearError,
|
|
}
|
|
})
|