fix(invitations): Support both platform and staff invitation types

- 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>
This commit is contained in:
poduck
2025-12-03 15:49:59 -05:00
parent 4f515c3710
commit abf67a36ed
2 changed files with 46 additions and 9 deletions

View File

@@ -28,8 +28,9 @@ export interface InvitationDetails {
business_name: string; business_name: string;
invited_by: string | null; invited_by: string | null;
expires_at: string; expires_at: string;
create_bookable_resource: boolean; create_bookable_resource?: boolean;
resource_name: string; resource_name?: string;
invitation_type?: 'tenant' | 'staff';
} }
export interface StaffPermissions { export interface StaffPermissions {
@@ -113,13 +114,21 @@ export const useResendInvitation = () => {
/** /**
* Hook to get invitation details by token (for acceptance page) * 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) => { export const useInvitationDetails = (token: string | null) => {
return useQuery<InvitationDetails>({ return useQuery<InvitationDetails>({
queryKey: ['invitation', token], queryKey: ['invitation', token],
queryFn: async () => { queryFn: async () => {
const { data } = await apiClient.get(`/staff/invitations/token/${token}/`); // Try platform tenant invitation first
return data; 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, enabled: !!token,
retry: false, retry: false,
@@ -128,6 +137,7 @@ export const useInvitationDetails = (token: string | null) => {
/** /**
* Hook to accept an invitation * Hook to accept an invitation
* Tries platform tenant invitation first, then falls back to staff invitation
*/ */
export const useAcceptInvitation = () => { export const useAcceptInvitation = () => {
return useMutation({ return useMutation({
@@ -136,28 +146,54 @@ export const useAcceptInvitation = () => {
firstName, firstName,
lastName, lastName,
password, password,
invitationType,
}: { }: {
token: string; token: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
password: string; password: string;
invitationType?: 'tenant' | 'staff';
}) => { }) => {
const { data } = await apiClient.post(`/staff/invitations/token/${token}/accept/`, { const payload = {
first_name: firstName, first_name: firstName,
last_name: lastName, last_name: lastName,
password, password,
}); };
return data;
// 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 * Hook to decline an invitation
* Note: Platform tenant invitations may not have a decline endpoint
*/ */
export const useDeclineInvitation = () => { export const useDeclineInvitation = () => {
return useMutation({ return useMutation({
mutationFn: async (token: string) => { 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/`); const { data } = await apiClient.post(`/staff/invitations/token/${token}/decline/`);
return data; return data;
}, },

View File

@@ -68,6 +68,7 @@ const AcceptInvitePage: React.FC = () => {
firstName: firstName.trim(), firstName: firstName.trim(),
lastName: lastName.trim(), lastName: lastName.trim(),
password, password,
invitationType: invitation?.invitation_type,
}); });
// Set auth tokens and redirect to dashboard // Set auth tokens and redirect to dashboard
@@ -89,7 +90,7 @@ const AcceptInvitePage: React.FC = () => {
} }
try { try {
await declineInvitationMutation.mutateAsync(token!); await declineInvitationMutation.mutateAsync({ token: token!, invitationType: invitation?.invitation_type });
setDeclined(true); setDeclined(true);
} catch (err: any) { } catch (err: any) {
setFormError(err.response?.data?.error || t('acceptInvite.declineFailed', 'Failed to decline invitation')); setFormError(err.response?.data?.error || t('acceptInvite.declineFailed', 'Failed to decline invitation'));