feat: add TypeScript types and React Query hooks for account and vendor admin APIs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-27 14:49:44 +02:00
parent 0da1ffea7f
commit bf42f13afc
3 changed files with 167 additions and 0 deletions

100
ui/src/api/account-hooks.ts Normal file
View File

@@ -0,0 +1,100 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from './client';
import type { AccountProfile, MfaStatus, MfaSetupResponse, BackupCodesResponse, PasskeyCredential } from '../types/api';
// --- Profile ---
export function useAccountProfile() {
return useQuery<AccountProfile>({
queryKey: ['account', 'profile'],
queryFn: () => api.get('/account/profile'),
});
}
export function useUpdateDisplayName() {
const qc = useQueryClient();
return useMutation<void, Error, string>({
mutationFn: (name) => api.patch('/account/profile', { name }),
onSuccess: () => qc.invalidateQueries({ queryKey: ['account', 'profile'] }),
});
}
// --- Password ---
export function useChangePassword() {
return useMutation<void, Error, { currentPassword: string; newPassword: string }>({
mutationFn: (body) => api.post('/account/password', body),
});
}
// --- MFA ---
export function useAccountMfaStatus() {
return useQuery<MfaStatus>({
queryKey: ['account', 'mfa', 'status'],
queryFn: () => api.get('/account/mfa/status'),
});
}
export function useAccountMfaSetup() {
return useMutation<MfaSetupResponse, Error, void>({
mutationFn: () => api.post('/account/mfa/totp/setup'),
});
}
export function useAccountMfaVerify() {
const qc = useQueryClient();
return useMutation<{ verified: boolean }, Error, { secret: string; code: string }>({
mutationFn: (body) => api.post('/account/mfa/totp/verify', body),
onSuccess: () => qc.invalidateQueries({ queryKey: ['account', 'mfa'] }),
});
}
export function useAccountBackupCodes() {
const qc = useQueryClient();
return useMutation<BackupCodesResponse, Error, void>({
mutationFn: () => api.post('/account/mfa/backup-codes'),
onSuccess: () => qc.invalidateQueries({ queryKey: ['account', 'mfa'] }),
});
}
export function useAccountMfaRemove() {
const qc = useQueryClient();
return useMutation<void, Error, void>({
mutationFn: () => api.delete('/account/mfa/totp'),
onSuccess: () => qc.invalidateQueries({ queryKey: ['account', 'mfa'] }),
});
}
// --- Passkeys ---
export function useAccountPasskeyList() {
return useQuery<PasskeyCredential[]>({
queryKey: ['account', 'mfa', 'webauthn'],
queryFn: () => api.get('/account/mfa/webauthn'),
});
}
export function useAccountRenamePasskey() {
const qc = useQueryClient();
return useMutation<void, Error, { id: string; name: string }>({
mutationFn: ({ id, name }) => api.patch(`/account/mfa/webauthn/${id}/name`, { name }),
onSuccess: () => qc.invalidateQueries({ queryKey: ['account', 'mfa'] }),
});
}
export function useAccountDeletePasskey() {
const qc = useQueryClient();
return useMutation<void, Error, string>({
mutationFn: (id) => api.delete(`/account/mfa/webauthn/${id}`),
onSuccess: () => qc.invalidateQueries({ queryKey: ['account', 'mfa'] }),
});
}
// --- MFA Preference ---
export function useAccountMfaPreference() {
return useMutation<void, Error, string>({
mutationFn: (preference) => api.post('/account/mfa/method-preference', { preference }),
});
}

View File

@@ -0,0 +1,43 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from './client';
import type { VendorAdmin, CreateAdminRequest, CreateAdminResponse } from '../types/api';
export function useVendorAdminList() {
return useQuery<VendorAdmin[]>({
queryKey: ['vendor', 'admins'],
queryFn: () => api.get('/vendor/admins'),
});
}
export function useCreateVendorAdmin() {
const qc = useQueryClient();
return useMutation<CreateAdminResponse, Error, CreateAdminRequest>({
mutationFn: (req) => api.post('/vendor/admins', req),
onSuccess: () => qc.invalidateQueries({ queryKey: ['vendor', 'admins'] }),
});
}
export function useRemoveVendorAdmin() {
const qc = useQueryClient();
return useMutation<void, Error, string>({
mutationFn: (userId) => api.delete(`/vendor/admins/${userId}`),
onSuccess: () => qc.invalidateQueries({ queryKey: ['vendor', 'admins'] }),
});
}
export function useResetVendorAdminPassword() {
const qc = useQueryClient();
return useMutation<void, Error, { userId: string; password: string }>({
mutationFn: ({ userId, password }) =>
api.post(`/vendor/admins/${userId}/reset-password`, { password }),
onSuccess: () => qc.invalidateQueries({ queryKey: ['vendor', 'admins'] }),
});
}
export function useResetVendorAdminMfa() {
const qc = useQueryClient();
return useMutation<void, Error, string>({
mutationFn: (userId) => api.delete(`/vendor/admins/${userId}/mfa`),
onSuccess: () => qc.invalidateQueries({ queryKey: ['vendor', 'admins'] }),
});
}

View File

@@ -273,3 +273,27 @@ export interface AuthPolicy {
passkeyEnabled: boolean;
passkeyMode: string;
}
// Account profile types
export interface AccountProfile {
userId: string;
name: string;
email: string;
}
// Vendor admin types
export interface VendorAdmin {
userId: string;
name: string;
email: string;
}
export interface CreateAdminRequest {
email: string;
tempPassword?: string;
}
export interface CreateAdminResponse {
invited: boolean;
tempPassword: string | null;
}