diff --git a/ui/src/api/account-hooks.ts b/ui/src/api/account-hooks.ts new file mode 100644 index 0000000..748ec01 --- /dev/null +++ b/ui/src/api/account-hooks.ts @@ -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({ + queryKey: ['account', 'profile'], + queryFn: () => api.get('/account/profile'), + }); +} + +export function useUpdateDisplayName() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (name) => api.patch('/account/profile', { name }), + onSuccess: () => qc.invalidateQueries({ queryKey: ['account', 'profile'] }), + }); +} + +// --- Password --- + +export function useChangePassword() { + return useMutation({ + mutationFn: (body) => api.post('/account/password', body), + }); +} + +// --- MFA --- + +export function useAccountMfaStatus() { + return useQuery({ + queryKey: ['account', 'mfa', 'status'], + queryFn: () => api.get('/account/mfa/status'), + }); +} + +export function useAccountMfaSetup() { + return useMutation({ + 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({ + mutationFn: () => api.post('/account/mfa/backup-codes'), + onSuccess: () => qc.invalidateQueries({ queryKey: ['account', 'mfa'] }), + }); +} + +export function useAccountMfaRemove() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: () => api.delete('/account/mfa/totp'), + onSuccess: () => qc.invalidateQueries({ queryKey: ['account', 'mfa'] }), + }); +} + +// --- Passkeys --- + +export function useAccountPasskeyList() { + return useQuery({ + queryKey: ['account', 'mfa', 'webauthn'], + queryFn: () => api.get('/account/mfa/webauthn'), + }); +} + +export function useAccountRenamePasskey() { + const qc = useQueryClient(); + return useMutation({ + 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({ + mutationFn: (id) => api.delete(`/account/mfa/webauthn/${id}`), + onSuccess: () => qc.invalidateQueries({ queryKey: ['account', 'mfa'] }), + }); +} + +// --- MFA Preference --- + +export function useAccountMfaPreference() { + return useMutation({ + mutationFn: (preference) => api.post('/account/mfa/method-preference', { preference }), + }); +} diff --git a/ui/src/api/vendor-admin-hooks.ts b/ui/src/api/vendor-admin-hooks.ts new file mode 100644 index 0000000..780e576 --- /dev/null +++ b/ui/src/api/vendor-admin-hooks.ts @@ -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({ + queryKey: ['vendor', 'admins'], + queryFn: () => api.get('/vendor/admins'), + }); +} + +export function useCreateVendorAdmin() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (req) => api.post('/vendor/admins', req), + onSuccess: () => qc.invalidateQueries({ queryKey: ['vendor', 'admins'] }), + }); +} + +export function useRemoveVendorAdmin() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: (userId) => api.delete(`/vendor/admins/${userId}`), + onSuccess: () => qc.invalidateQueries({ queryKey: ['vendor', 'admins'] }), + }); +} + +export function useResetVendorAdminPassword() { + const qc = useQueryClient(); + return useMutation({ + 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({ + mutationFn: (userId) => api.delete(`/vendor/admins/${userId}/mfa`), + onSuccess: () => qc.invalidateQueries({ queryKey: ['vendor', 'admins'] }), + }); +} diff --git a/ui/src/types/api.ts b/ui/src/types/api.ts index aeab2d2..f2488ef 100644 --- a/ui/src/types/api.ts +++ b/ui/src/types/api.ts @@ -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; +}