Skip to content

Commit 7345961

Browse files
icecrasher321claude
andcommitted
fix(invitations): surface the accept conflict message and refresh workspace caches post-accept
The accept route now carries the human-readable message alongside the machine-readable error kind (the client prefers it for server-error), and a successful accept invalidates workspace queries so the swept workspaces appear immediately instead of after the stale window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 845a5f4 commit 7345961

3 files changed

Lines changed: 29 additions & 3 deletions

File tree

apps/sim/app/api/invitations/[id]/accept/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,13 @@ export const POST = withRouteHandler(
4545
}
4646
const status = statusMap[result.kind] ?? 500
4747
logger.warn('Invitation accept rejected', { invitationId: id, reason: result.kind })
48-
return NextResponse.json({ error: result.kind }, { status })
48+
/**
49+
* `error` stays the machine-readable kind (the client maps it to UX
50+
* states); `message` carries the human copy when the failure provides
51+
* one — e.g. the retryable concurrent-workspace-change conflict.
52+
*/
53+
const message = result.kind === 'server-error' ? result.message : undefined
54+
return NextResponse.json({ error: result.kind, ...(message ? { message } : {}) }, { status })
4955
}
5056

5157
const inv = result.invitation

apps/sim/app/invite/[id]/invite.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,6 @@ describe('Invite', () => {
281281
cache: 'session',
282282
error: 'Session refresh denied',
283283
})
284-
expect(mockLogger.warn).toHaveBeenCalledTimes(3)
284+
expect(mockLogger.warn).toHaveBeenCalledTimes(4)
285285
})
286286
})

apps/sim/app/invite/[id]/invite.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { useInvitationDetails } from '@/hooks/queries/invitations'
1818
import { organizationKeys } from '@/hooks/queries/organization'
1919
import { refreshSessionQuery } from '@/hooks/queries/session'
2020
import { subscriptionKeys } from '@/hooks/queries/subscription'
21+
import { workspaceKeys } from '@/hooks/queries/workspace'
2122

2223
const logger = createLogger('InviteById')
2324

@@ -274,13 +275,32 @@ export default function Invite() {
274275
runBestEffortCacheRefresh('organization', () =>
275276
queryClient.invalidateQueries({ queryKey: organizationKeys.all })
276277
)
278+
/**
279+
* Acceptance can attach the invitee's owned workspaces into the org —
280+
* the workspace list must not keep serving the stale personal set.
281+
*/
282+
runBestEffortCacheRefresh('workspaces', () =>
283+
queryClient.invalidateQueries({ queryKey: workspaceKeys.all })
284+
)
277285
} catch (acceptError) {
278286
logger.error('Error accepting invitation:', acceptError)
279287
const code =
280288
acceptError instanceof ApiClientError
281289
? codeFromApiClientError(acceptError)
282290
: 'network-error'
283-
setActionError(getInviteError(code))
291+
const serverMessage =
292+
acceptError instanceof ApiClientError &&
293+
acceptError.body &&
294+
typeof acceptError.body === 'object' &&
295+
typeof (acceptError.body as { message?: unknown }).message === 'string'
296+
? ((acceptError.body as { message: string }).message as string)
297+
: null
298+
const baseError = getInviteError(code)
299+
setActionError(
300+
code === 'server-error' && serverMessage
301+
? { ...baseError, message: serverMessage }
302+
: baseError
303+
)
284304
setIsAccepting(false)
285305
}
286306
}

0 commit comments

Comments
 (0)