import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { useFetchJson } from '@/composable/useFetchJson' export interface User { id: string email: string name: string } interface AuthResponse { access_token: string token_type: string expires_in: number user: User } // Read the non-HTTPOnly is_authenticated cookie set by the backend. // The backend sets it to "1" on login and removes it on logout. function readIsAuthenticatedCookie(): boolean { if (typeof document === 'undefined') return false return document.cookie.split('; ').some((c) => c === 'is_authenticated=1') } export const useAuthStore = defineStore('auth', () => { // useRouter must be called at the top level of the setup function, not inside a method // We use window.location as a fallback-safe redirect mechanism instead, to avoid // the "Cannot read properties of undefined" error when the router is not yet available. const user = ref(null) const loading = ref(false) const error = ref(null) // Auth state is derived from the is_authenticated cookie (set/cleared by backend). // We use a ref so Vue reactivity works; it is initialised from the cookie on store creation. const _isAuthenticated = ref(readIsAuthenticatedCookie()) const isAuthenticated = computed(() => _isAuthenticated.value) /** Call after any successful login to sync the reactive flag. */ function _syncAuthState() { _isAuthenticated.value = readIsAuthenticatedCookie() } async function login(email: string, password: string) { loading.value = true error.value = null try { const data = await useFetchJson('/api/v1/public/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }) const response = (data as any).items || data if (!response.access_token) { throw new Error('No access token received') } user.value = response.user _syncAuthState() return true } catch (e: any) { error.value = e?.error ?? 'An error occurred' return false } finally { loading.value = false } } async function register( first_name: string, last_name: string, email: string, password: string, confirm_password: string, lang?: string, company_name?: string, company_email?: string, company_address?: { street: string zipCode: string city: string country: string }, regon?: string, nip?: string, vat?: string, billing_address?: { street: string zipCode: string city: string country: string }, ) { loading.value = true error.value = null try { const body: any = { first_name, last_name, email, password, confirm_password, lang: lang || 'en' } // Add company information if provided if (company_name) body.company_name = company_name if (company_email) body.company_email = company_email if (company_address) body.company_address = company_address if (regon) body.regon = regon if (nip) body.nip = nip if (vat) body.vat = vat if (billing_address) body.billing_address = billing_address await useFetchJson('/api/v1/public/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) return { success: true, requiresVerification: true } } catch (e: any) { error.value = e?.error ?? 'An error occurred' return { success: false, requiresVerification: false } } finally { loading.value = false } } async function requestPasswordReset(email: string) { loading.value = true error.value = null try { await useFetchJson('/api/v1/public/auth/forgot-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }), }) return true } catch (e: any) { error.value = e?.error ?? 'An error occurred' return false } finally { loading.value = false } } async function resetPassword(token: string, password: string) { loading.value = true error.value = null try { await useFetchJson('/api/v1/public/auth/reset-password', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token, password }), }) return true } catch (e: any) { error.value = e?.error ?? 'An error occurred' return false } finally { loading.value = false } } function loginWithGoogle() { window.location.href = '/api/v1/public/auth/google' } /** * Logout: calls the backend to revoke the refresh token and clear HTTPOnly cookies, * clears local reactive state, then redirects to the login page. */ async function logout() { try { await useFetchJson('/api/v1/public/auth/logout', { method: 'POST', }) } catch { // Continue with local cleanup even if the backend call fails } finally { user.value = null _isAuthenticated.value = false // Use dynamic import to get the router instance safely from outside the setup context const { default: router } = await import('@/router') router.push({ name: 'login' }) } } /** * Refresh the access token by calling the backend. * The backend reads the HTTPOnly refresh_token cookie, rotates it, and sets new cookies. * Returns true on success. */ async function refreshAccessToken(): Promise { try { await useFetchJson('/api/v1/public/auth/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, // No body needed — the backend reads the refresh_token from the HTTPOnly cookie }) _syncAuthState() return true } catch { // Refresh failed — clear local state user.value = null _isAuthenticated.value = false return false } } function clearError() { error.value = null } return { user, loading, error, isAuthenticated, login, loginWithGoogle, register, requestPasswordReset, resetPassword, logout, refreshAccessToken, clearError, } })