web/src/stores/auth.js
Rama main · réplica de lectura del repositorio original.
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import axios from '@/utils/axios'
import { getProfileImageUrl } from '@/utils/url'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const token = ref(localStorage.getItem('token') || null)
const refreshToken = ref(localStorage.getItem('refreshToken') || null)
const loading = ref(false)
const error = ref(null)
const isCoach = ref(false)
const schedulingEnabled = ref(false)
const logoVariant = ref('logo')
const logoPath = computed(() => logoVariant.value === 'betalogo' ? '/beta/logo.svg' : '/logo.svg')
const isAuthenticated = computed(() => !!token.value && !!user.value)
const isCoachRole = computed(() => user.value?.role === 'coach')
const isAdmin = computed(() => user.value?.role === 'admin')
const isCoachOrAdmin = computed(() => user.value?.role === 'coach' || user.value?.role === 'admin')
// Initialize auth state from localStorage
async function init() {
const savedUser = localStorage.getItem('user')
if (savedUser && token.value) {
try {
const parsedUser = JSON.parse(savedUser)
// Ensure profile_image has full URL for display
if (parsedUser.profile_image) {
parsedUser.profile_image = getProfileImageUrl(parsedUser.profile_image)
}
user.value = parsedUser
// Set default authorization header
axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}`
// Fetch full profile to get is_coach status
await fetchProfile()
} catch {
logout()
}
}
}
async function login(email, password, rememberMe = false) {
loading.value = true
error.value = null
try {
const response = await axios.post('/api/auth/login', {
email,
password,
remember_me: rememberMe
})
token.value = response.data.token
user.value = response.data.user
// Save to localStorage
localStorage.setItem('token', token.value)
localStorage.setItem('user', JSON.stringify(user.value))
// Save refresh token if provided (when remember_me is true)
if (response.data.refresh_token) {
refreshToken.value = response.data.refresh_token
localStorage.setItem('refreshToken', refreshToken.value)
}
// Set default authorization header
axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}`
// Fetch full profile including is_coach status
await fetchProfile()
return true
} catch (e) {
console.error('Login error:', e)
if (e.response) {
// Server responded with error status
error.value = e.response.data?.message || `Server error: ${e.response.status}`
} else if (e.request) {
// Request was made but no response received
error.value = 'Cannot connect to server. Please check if the server is running.'
} else {
// Something else happened
error.value = e.message || 'Login failed'
}
return false
} finally {
loading.value = false
}
}
async function register(userData) {
loading.value = true
error.value = null
try {
const response = await axios.post('/api/auth/register', userData)
token.value = response.data.token
user.value = response.data.user
localStorage.setItem('token', token.value)
localStorage.setItem('user', JSON.stringify(user.value))
axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}`
return true
} catch (e) {
console.error('Registration error:', e)
if (e.response) {
// Server responded with error status
error.value = e.response.data?.message || `Server error: ${e.response.status}`
} else if (e.request) {
// Request was made but no response received
error.value = 'Cannot connect to server. Please check if the server is running.'
} else {
// Something else happened
error.value = e.message || 'Registration failed'
}
return false
} finally {
loading.value = false
}
}
async function logout() {
// Revoke refresh token if it exists
if (refreshToken.value) {
try {
await axios.post('/api/auth/revoke', {
refresh_token: refreshToken.value
})
} catch (e) {
// Ignore errors during revocation
console.error('Failed to revoke refresh token:', e)
}
}
user.value = null
token.value = null
refreshToken.value = null
isCoach.value = false
localStorage.removeItem('token')
localStorage.removeItem('user')
localStorage.removeItem('refreshToken')
delete axios.defaults.headers.common['Authorization']
}
async function refreshAccessToken() {
if (!refreshToken.value) {
return false
}
try {
const response = await axios.post('/api/auth/refresh', {
refresh_token: refreshToken.value
})
token.value = response.data.token
user.value = response.data.user
// Update localStorage
localStorage.setItem('token', token.value)
localStorage.setItem('user', JSON.stringify(user.value))
// Set default authorization header
axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}`
return true
} catch {
// Refresh token is invalid or expired, logout
logout()
return false
}
}
async function updateProfile(updates) {
loading.value = true
error.value = null
try {
const response = await axios.put('/api/users/profile', updates)
user.value = response.data.user
localStorage.setItem('user', JSON.stringify(user.value))
return true
} catch (e) {
error.value = e.response?.data?.message || 'Profile update failed'
return false
} finally {
loading.value = false
}
}
// Fetch user profile and is_coach status
async function fetchProfile() {
if (!token.value) return false
try {
const response = await axios.get('/api/users/profile')
user.value = response.data.user
isCoach.value = response.data.is_coach || false
localStorage.setItem('user', JSON.stringify(user.value))
return true
} catch (e) {
console.error('Failed to fetch profile:', e)
return false
}
}
// Fetch scheduling_enabled and logo_variant from version endpoint
async function fetchSchedulingEnabled() {
try {
const response = await axios.get('/api/version')
schedulingEnabled.value = response.data.scheduling_enabled || false
logoVariant.value = response.data.logo_variant || 'logo'
// Dynamically update favicon and apple-touch-icon if using beta variant
if (logoVariant.value !== 'logo') {
const prefix = '/beta'
const favicon = document.querySelector('link[rel="icon"]')
if (favicon) favicon.href = `${prefix}/favicon.ico`
const appleTouchIcon = document.querySelector('link[rel="apple-touch-icon"]')
if (appleTouchIcon) appleTouchIcon.href = `${prefix}/apple-touch-icon.png`
}
} catch (e) {
console.error('Failed to fetch version info:', e)
schedulingEnabled.value = false
}
}
// Initialize on store creation
init()
// Fetch scheduling status (public endpoint, no auth required)
fetchSchedulingEnabled()
return {
user,
token,
refreshToken,
loading,
error,
isCoach,
schedulingEnabled,
logoVariant,
logoPath,
isAuthenticated,
isCoachRole,
isAdmin,
isCoachOrAdmin,
login,
register,
logout,
refreshAccessToken,
updateProfile,
fetchProfile,
fetchSchedulingEnabled
}
})