feat: add profile/avatar/MFA types and service functions

Extend User and UserResponse interfaces with first_name, last_name,
display_name, avatar_url fields. Add ProfileUpdatePayload,
ChangePasswordPayload, MFASetupResponse, MFAVerifyPayload types.
Add authService functions for profile update, avatar upload/delete,
password change, and MFA setup/verify/disable. Add refreshUser to
AuthContext so components can re-fetch user data after changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
CCS Admin 2026-02-26 09:42:46 +00:00
parent fc609401c3
commit 832f5c0a63
3 changed files with 76 additions and 1 deletions

View file

@ -9,6 +9,7 @@ interface AuthContextType {
login: (data: LoginRequest) => Promise<void>
register: (data: RegisterRequest) => Promise<void>
logout: () => Promise<void>
refreshUser: () => Promise<void>
}
const AuthContext = createContext<AuthContextType | null>(null)
@ -46,6 +47,11 @@ export function AuthProvider({ children }: { children: ReactNode }) {
setUser(null)
}
const refreshUserFn = async () => {
const updatedUser = await authService.getMe()
setUser(updatedUser)
}
return (
<AuthContext.Provider value={{
user,
@ -54,6 +60,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
login: loginFn,
register: registerFn,
logout: logoutFn,
refreshUser: refreshUserFn,
}}>
{children}
</AuthContext.Provider>

View file

@ -1,5 +1,5 @@
import api from './api'
import type { LoginRequest, RegisterRequest, TokenResponse, User } from '@/types'
import type { LoginRequest, RegisterRequest, TokenResponse, User, ProfileUpdatePayload, ChangePasswordPayload, MFASetupResponse, MFAVerifyPayload } from '@/types'
export async function login(data: LoginRequest): Promise<TokenResponse> {
const response = await api.post<TokenResponse>('/auth/login', data)
@ -29,3 +29,39 @@ export async function getMe(): Promise<User> {
const response = await api.get<User>('/auth/me')
return response.data
}
export async function updateProfile(data: ProfileUpdatePayload): Promise<User> {
const response = await api.put<User>('/auth/profile', data)
return response.data
}
export async function uploadAvatar(file: File): Promise<User> {
const formData = new FormData()
formData.append('file', file)
const response = await api.post<User>('/auth/avatar', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
})
return response.data
}
export async function deleteAvatar(): Promise<User> {
const response = await api.delete<User>('/auth/avatar')
return response.data
}
export async function changePassword(data: ChangePasswordPayload): Promise<void> {
await api.post('/auth/change-password', data)
}
export async function setupMFA(): Promise<MFASetupResponse> {
const response = await api.post<MFASetupResponse>('/auth/mfa/setup')
return response.data
}
export async function verifyMFA(data: MFAVerifyPayload): Promise<void> {
await api.post('/auth/mfa/verify', data)
}
export async function disableMFA(password: string): Promise<void> {
await api.delete('/auth/mfa', { data: { password } })
}

View file

@ -2,6 +2,10 @@ export interface User {
id: number
username: string
email: string
first_name: string | null
last_name: string | null
display_name: string | null
avatar_url: string | null
role: 'admin' | 'dak_mitarbeiter'
mfa_enabled: boolean
is_active: boolean
@ -196,6 +200,10 @@ export interface UserResponse {
id: number
username: string
email: string
first_name: string | null
last_name: string | null
display_name: string | null
avatar_url: string | null
role: 'admin' | 'dak_mitarbeiter'
is_active: boolean
mfa_enabled: boolean
@ -217,6 +225,30 @@ export interface UpdateUserPayload {
is_active?: boolean
}
// Account / Profile
export interface ProfileUpdatePayload {
first_name?: string
last_name?: string
display_name?: string
username?: string
email?: string
}
export interface ChangePasswordPayload {
old_password: string
new_password: string
}
export interface MFASetupResponse {
secret: string
qr_uri: string
}
export interface MFAVerifyPayload {
secret: string
code: string
}
// Admin Invitations
export interface InvitationResponse {
id: number