feat(ui): Gäste-Verwaltung UI — SettingsGuests (UI-Backlog Modul 14/16)

- api/guests.ts: useGuests, useInviteGuest, useRevokeGuest (/api/v1/guests)
- SettingsGuests.tsx: Admin-Gate (Outbox-Muster), Gästeliste mit Status-Badges
  (invited/active/disabled), Invite-Modal (RHF+zod), Revoke-ConfirmDialog
- Route /settings/guests + Nav-Eintrag in Settings.tsx
- i18n de/en: 14 Keys
- Vitest 8/8, tsc clean, Build OK
This commit is contained in:
Agent Zero
2026-09-16 00:08:59 +02:00
parent f8b07032e5
commit b3eaa0e39b
7 changed files with 473 additions and 2 deletions
+65
View File
@@ -0,0 +1,65 @@
/**
* Guests API client — admin guest management (UI-Backlog module 14/16).
*
* Backend: /api/v1/guests (app/routes/guests.py, require_admin).
* - GET / → list guests (role=guest memberships for tenant)
* - POST /invite → invite guest (name + email; 409 if already active)
* - DELETE /{id} → revoke guest (status=disabled + session invalidation)
*
* Guests are regular users with role='guest' in user_tenants; they log in
* via the normal login flow (password via reset flow).
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiGet, apiPost, apiDelete } from '@/api/client';
export interface Guest {
id: string;
email: string;
name: string;
status: 'invited' | 'active' | 'disabled' | string;
role: string;
created_at: string | null;
}
export interface GuestInviteResponse {
email: string;
name: string;
status: string;
role: string;
message: string;
}
export interface GuestInvitePayload {
email: string;
name: string;
}
export function useGuests(enabled = true) {
return useQuery({
queryKey: ['guests'],
queryFn: () => apiGet<Guest[]>('/guests'),
enabled,
});
}
export function useInviteGuest() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: GuestInvitePayload) =>
apiPost<GuestInviteResponse>('/guests/invite', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['guests'] });
},
});
}
export function useRevokeGuest() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (guestId: string) => apiDelete<{ message: string }>(`/guests/${guestId}`),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['guests'] });
},
});
}