- Update useInvitationDetails to try platform tenant invitation first, then fall back to staff invitation - Update useAcceptInvitation to handle both invitation types - Update useDeclineInvitation to handle both invitation types - Pass invitation type from AcceptInvitePage to mutations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
202 lines
5.5 KiB
TypeScript
202 lines
5.5 KiB
TypeScript
/**
|
|
* 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<string, boolean>;
|
|
}
|
|
|
|
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<StaffInvitation[]>({
|
|
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<InvitationDetails>({
|
|
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;
|
|
},
|
|
});
|
|
};
|