fix(invitations): handle API responses without explicit success field

The sendTeamInvitations function was treating 200 OK responses as errors
because it expected a 'success' field that the API doesn't always return.

- Check for invitations array in response body as success indicator
- Handle various response structures (invitations, data.invitations, data)
- Only treat as error when response is not OK or has explicit error field
This commit is contained in:
Eyal Toledano
2025-12-02 10:53:11 -05:00
parent b61a2a5f90
commit 1c889cccaa

View File

@@ -1534,14 +1534,46 @@ export class ExportService {
};
}
const jsonData = (await response.json()) as SendTeamInvitationsResponse;
const jsonData = (await response.json()) as Record<string, unknown>;
if (!response.ok || !jsonData.success) {
// Check if all users are already members - this is not an error
const invitations = jsonData.invitations || (jsonData as any)?.data;
const jsonError = (jsonData as any)?.error;
// Extract invitations from various possible response structures
const dataField = jsonData.data as Record<string, unknown> | undefined;
const invitations =
jsonData.invitations ||
dataField?.invitations ||
dataField ||
(Array.isArray(jsonData) ? jsonData : null);
const jsonError = jsonData.error as
| { code?: string; message?: string }
| string
| undefined;
// Handle "already member" as success - check both invitations array and error message
// Success case: 200 OK with invitations array
if (response.ok && invitations && Array.isArray(invitations)) {
return {
success: true,
invitations: invitations.map(
(inv: { email?: string; status?: string }) => ({
email: inv.email || '',
status: (inv.status || 'sent') as
| 'sent'
| 'already_member'
| 'already_invited'
| 'failed'
})
)
};
}
// Error case: check for specific error conditions
if (!response.ok || jsonError) {
// Parse error object safely
const errorObj =
typeof jsonError === 'object' && jsonError !== null
? (jsonError as { code?: string; message?: string })
: null;
// Handle "already member" as success
const isAlreadyMember =
(invitations &&
Array.isArray(invitations) &&
@@ -1549,17 +1581,20 @@ export class ExportService {
invitations.every(
(inv: { status: string }) => inv.status === 'already_member'
)) ||
(jsonError?.code === 'invitation_failed' &&
jsonError?.message?.toLowerCase().includes('already member'));
(errorObj?.code === 'invitation_failed' &&
errorObj?.message?.toLowerCase().includes('already member'));
if (isAlreadyMember) {
// Return success with synthetic invitations if we only got an error
const resultInvitations =
invitations ||
emails.map((email) => ({
email,
status: 'already_member' as const
}));
const resultInvitations = Array.isArray(invitations)
? (invitations as Array<{
email: string;
status: 'sent' | 'already_member' | 'already_invited' | 'failed';
}>)
: emails.map((email) => ({
email,
status: 'already_member' as const
}));
return {
success: true,
invitations: resultInvitations
@@ -1567,10 +1602,10 @@ export class ExportService {
}
const errorMessage =
(jsonData as any)?.message ||
(jsonData.message as string) ||
(typeof jsonError === 'string'
? jsonError
: jsonError?.message || JSON.stringify(jsonError)) ||
: errorObj?.message || JSON.stringify(jsonError)) ||
`Failed to send invitations: ${response.status}`;
return {
@@ -1582,9 +1617,13 @@ export class ExportService {
};
}
// Fallback: no invitations in response
return {
success: true,
invitations: jsonData.invitations
success: false,
error: {
code: 'INVALID_RESPONSE',
message: 'No invitations in response'
}
};
} catch (error) {
const errorMessage =