2026.06.04 frontend építés közben
This commit is contained in:
258
frontend/src/stores/auth.ts
Normal file
258
frontend/src/stores/auth.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import router from '../router'
|
||||
import api from '../api/axios'
|
||||
|
||||
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
|
||||
scope_level: string
|
||||
scope_id: string | null
|
||||
ui_mode: string
|
||||
active_organization_id: number | null
|
||||
// person_id is set when KYC is complete (Person record exists)
|
||||
person_id: number | null
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
// ────────────────────────────── State ──────────────────────────────
|
||||
const user = ref<UserProfile | null>(null)
|
||||
const token = ref<string | null>(localStorage.getItem('access_token'))
|
||||
const isLoading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// ────────────────────────────── Getters ────────────────────────────
|
||||
const isAuthenticated = computed(() => !!token.value && !!user.value)
|
||||
const isKycComplete = computed(() => {
|
||||
// KYC is complete when the user has a linked Person record (person_id is set)
|
||||
return !!user.value?.person_id
|
||||
})
|
||||
const userRole = computed(() => user.value?.role ?? 'guest')
|
||||
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 userId = computed(() => user.value?.id ?? null)
|
||||
|
||||
// ────────────────────────────── Actions ────────────────────────────
|
||||
|
||||
/**
|
||||
* Login with email + password.
|
||||
* The FastAPI /auth/login endpoint uses OAuth2PasswordRequestForm,
|
||||
* which expects application/x-www-form-urlencoded with `username` and `password`.
|
||||
*/
|
||||
async function login(email: string, password: string) {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const body = new URLSearchParams()
|
||||
body.append('username', email) // FastAPI OAuth2 form uses `username` key
|
||||
body.append('password', password)
|
||||
|
||||
const res = await api.post('/auth/login', body, {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
})
|
||||
|
||||
const data = res.data as {
|
||||
access_token: string
|
||||
refresh_token?: string
|
||||
token_type: string
|
||||
is_active: boolean
|
||||
}
|
||||
|
||||
// Persist token
|
||||
localStorage.setItem('access_token', data.access_token)
|
||||
if (data.refresh_token) {
|
||||
localStorage.setItem('refresh_token', data.refresh_token)
|
||||
}
|
||||
token.value = data.access_token
|
||||
|
||||
// Immediately fetch the user profile
|
||||
await fetchUser()
|
||||
} catch (err: any) {
|
||||
const message =
|
||||
err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
'Bejelentkezés sikertelen. Ellenőrizd az adataidat.'
|
||||
error.value = message
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the current user profile from /auth/me.
|
||||
*/
|
||||
async function fetchUser() {
|
||||
if (!token.value) return
|
||||
|
||||
try {
|
||||
const res = await api.get('/auth/me')
|
||||
user.value = res.data as UserProfile
|
||||
} catch (err: any) {
|
||||
// If the token is invalid, clear everything
|
||||
if (err.response?.status === 401) {
|
||||
logout()
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new user.
|
||||
* The /auth/register endpoint expects JSON with UserLiteRegister schema.
|
||||
* Default hidden values (region_code, lang, timezone) are appended here.
|
||||
*/
|
||||
async function register(data: {
|
||||
email: string
|
||||
password: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
}) {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
...data,
|
||||
region_code: 'HU',
|
||||
lang: 'hu',
|
||||
timezone: 'Europe/Budapest',
|
||||
}
|
||||
|
||||
const res = await api.post('/auth/register', payload)
|
||||
|
||||
return res.data as {
|
||||
status: string
|
||||
message: string
|
||||
user_id: number
|
||||
email: string
|
||||
}
|
||||
} catch (err: any) {
|
||||
const message =
|
||||
err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
'Regisztráció sikertelen.'
|
||||
error.value = message
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a password reset email.
|
||||
*/
|
||||
async function forgotPassword(email: string) {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await api.post('/auth/forgot-password', { email })
|
||||
return res.data as { status: string; message: string }
|
||||
} catch (err: any) {
|
||||
const message =
|
||||
err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
'Hiba történt a jelszó-visszaállítási kérelem elküldésekor.'
|
||||
error.value = message
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout — clear token, user state, and localStorage.
|
||||
*/
|
||||
function logout() {
|
||||
token.value = null
|
||||
user.value = null
|
||||
error.value = null
|
||||
localStorage.removeItem('access_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
// Redirect to the landing page
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the store on app load — if a token exists, fetch the user.
|
||||
*/
|
||||
async function init() {
|
||||
if (token.value) {
|
||||
try {
|
||||
await fetchUser()
|
||||
} catch {
|
||||
// Token invalid — already cleared by fetchUser → logout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete KYC (Know Your Customer) — full profile submission.
|
||||
* POSTs the KYC data to /auth/complete-kyc, then refreshes the user profile.
|
||||
* Returns true on success, throws on failure.
|
||||
*/
|
||||
async function completeKyc(kycData: Record<string, any>): Promise<boolean> {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
await api.post('/auth/complete-kyc', kycData)
|
||||
// Refresh the user profile so isKycComplete getter updates
|
||||
await fetchUser()
|
||||
return true
|
||||
} catch (err: any) {
|
||||
const message =
|
||||
err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
'A KYC kitöltése sikertelen. Kérlek ellenőrizd az adatokat.'
|
||||
error.value = message
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any stored error message.
|
||||
*/
|
||||
function clearError() {
|
||||
error.value = null
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
user,
|
||||
token,
|
||||
isLoading,
|
||||
error,
|
||||
|
||||
// Getters
|
||||
isAuthenticated,
|
||||
isKycComplete,
|
||||
userRole,
|
||||
userName,
|
||||
userId,
|
||||
|
||||
// Actions
|
||||
login,
|
||||
fetchUser,
|
||||
register,
|
||||
forgotPassword,
|
||||
completeKyc,
|
||||
logout,
|
||||
init,
|
||||
clearError,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user