/** * Staff Invitations Management Hooks */ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import apiClient from '../api/client'; export interface StaffInvitation { id: number; email: string; role: 'TENANT_MANAGER' | 'TENANT_STAFF'; role_display: string; status: 'PENDING' | 'ACCEPTED' | 'DECLINED' | 'EXPIRED' | 'CANCELLED'; invited_by: number | null; invited_by_name: string | null; created_at: string; expires_at: string; accepted_at: string | null; create_bookable_resource: boolean; resource_name: string; permissions: Record; } export interface InvitationDetails { email: string; role: string; role_display: string; business_name: string; invited_by: string | null; expires_at: string; create_bookable_resource?: boolean; resource_name?: string; invitation_type?: 'tenant' | 'staff'; } export interface StaffPermissions { // Manager permissions can_invite_staff?: boolean; can_manage_resources?: boolean; can_manage_services?: boolean; can_view_reports?: boolean; can_access_settings?: boolean; can_refund_payments?: boolean; // Staff permissions can_view_all_schedules?: boolean; can_manage_own_appointments?: boolean; } export interface CreateInvitationData { email: string; role: 'TENANT_MANAGER' | 'TENANT_STAFF'; create_bookable_resource?: boolean; resource_name?: string; permissions?: StaffPermissions; } /** * Hook to fetch pending invitations for the current business */ export const useInvitations = () => { return useQuery({ queryKey: ['invitations'], queryFn: async () => { const { data } = await apiClient.get('/staff/invitations/'); return data; }, }); }; /** * Hook to create a new staff invitation */ export const useCreateInvitation = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (invitationData: CreateInvitationData) => { const { data } = await apiClient.post('/staff/invitations/', invitationData); return data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['invitations'] }); }, }); }; /** * Hook to cancel a pending invitation */ export const useCancelInvitation = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (invitationId: number) => { await apiClient.delete(`/staff/invitations/${invitationId}/`); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['invitations'] }); }, }); }; /** * Hook to resend an invitation email */ export const useResendInvitation = () => { return useMutation({ mutationFn: async (invitationId: number) => { const { data } = await apiClient.post(`/staff/invitations/${invitationId}/resend/`); return data; }, }); }; /** * Hook to get invitation details by token (for acceptance page) * Tries platform tenant invitations first, then falls back to staff invitations */ export const useInvitationDetails = (token: string | null) => { return useQuery({ queryKey: ['invitation', token], queryFn: async () => { // Try platform tenant invitation first try { const { data } = await apiClient.get(`/platform/tenant-invitations/token/${token}/`); return { ...data, invitation_type: 'tenant' }; } catch { // Fall back to staff invitation const { data } = await apiClient.get(`/staff/invitations/token/${token}/`); return { ...data, invitation_type: 'staff' }; } }, enabled: !!token, retry: false, }); }; /** * Hook to accept an invitation * Tries platform tenant invitation first, then falls back to staff invitation */ export const useAcceptInvitation = () => { return useMutation({ mutationFn: async ({ token, firstName, lastName, password, invitationType, }: { token: string; firstName: string; lastName: string; password: string; invitationType?: 'tenant' | 'staff'; }) => { const payload = { first_name: firstName, last_name: lastName, password, }; // Use known invitation type if provided, otherwise try tenant first if (invitationType === 'staff') { const { data } = await apiClient.post(`/staff/invitations/token/${token}/accept/`, payload); return data; } try { const { data } = await apiClient.post(`/platform/tenant-invitations/token/${token}/accept/`, payload); return data; } catch { const { data } = await apiClient.post(`/staff/invitations/token/${token}/accept/`, payload); return data; } }, }); }; /** * Hook to decline an invitation * Note: Platform tenant invitations may not have a decline endpoint */ export const useDeclineInvitation = () => { return useMutation({ mutationFn: async ({ token, invitationType }: { token: string; invitationType?: 'tenant' | 'staff' }) => { if (invitationType === 'tenant') { // Platform tenant invitations - check if decline endpoint exists try { const { data } = await apiClient.post(`/platform/tenant-invitations/token/${token}/decline/`); return data; } catch { // May not have decline endpoint, just return success return { status: 'declined' }; } } const { data } = await apiClient.post(`/staff/invitations/token/${token}/decline/`); return data; }, }); };