Skip to content

improvement(organization): invite validation experience#5008

Merged
icecrasher321 merged 2 commits into
stagingfrom
improvement/org-invite-ux
Jun 12, 2026
Merged

improvement(organization): invite validation experience#5008
icecrasher321 merged 2 commits into
stagingfrom
improvement/org-invite-ux

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

Summary

  • Invite existing members to more workspaces: the org invite modal no longer rejects emails of current organization members — the batch invite API now sends them a single workspace invitation covering only the selected workspaces they don't already have access to (or a pending invite for), while new emails still get full org invitations. Workspace invitation emails now render multiple workspace names.

  • Clearer invite validation: external collaborators and already-pending emails are flagged on the chip with specific tooltip messages (including a pointer to the per-workspace Teammates tab for externals), duplicate workspace selections are deduped server-side, and inviting someone already covered returns an accurate error instead of a generic one.

  • Modal & input fixes: dropdowns inside modals are scrollable/selectable again (ChipDropdown switches to Radix modal behavior via the new InsideModalContext), and two paste bugs are fixed — rejected emails no longer render twice in the input, and pasting multiple emails no longer drops all but the last one (stale-closure fix in both the invite modal and chat deploy modal email fields).

Type of Change

  • Bug fix
  • Other: UX Improvement

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 12, 2026 9:33pm

Request Review

@cursor

cursor Bot commented Jun 12, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes organization invitation and permission-grant logic (batch vs non-batch, seat counting, audit), which affects who gets access and how invites are created—mitigated by new API tests but still security-adjacent.

Overview
Batch organization invites now treat existing members differently from new emails: newcomers still get full org invitations with the selected workspace grants, while members only receive workspace invitations for workspaces they lack (skipping ones they already access or already have pending). The non-batch org invite path still rejects members outright. Seat checks apply only to net-new org invites; duplicate workspace picks in a batch are deduped server-side, with clearer 400s when everyone is already covered.

The org invite modal stops blocking current member emails and instead flags external collaborators and non-member pending invites with specific chip errors; team settings feed those lists separately from member re-invites.

Workspace invitation emails and sending now support multiple workspace names in copy and subject lines.

UI fixes: ChipDropdown switches to Radix modal menus inside dialogs via InsideModalContext; paste handling in TagInput no longer duplicates rejected values in the text field; invite-modal and chat-deploy email fields use refs so multi-value paste does not drop all but the last address.

Reviewed by Cursor Bugbot for commit d539b53. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d539b53. Configure here.

Comment thread apps/sim/app/api/organizations/[id]/invitations/route.ts
@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves the organization invite flow in three areas: existing org members can now receive workspace-only invitations for workspaces they lack (instead of being rejected outright), the invite modal surfaces more specific validation messages for external collaborators and pending invites, and a handful of input bugs are fixed across the invite modal and chat deploy email field.

  • Batch invite route (route.ts): splits incoming emails into new-email org invitations and member-only workspace invitations, with per-member dedup against both the permissions table and pending invitationWorkspaceGrant rows; seat validation is also correctly skipped when no new-org emails are present.
  • Stale-closure paste fix (chip-modal.tsx, chat.tsx, tag-input.tsx): all three components switch to a useRef-backed state mirror so consecutive onAdd calls during a single paste event compose correctly instead of overwriting each other.
  • InsideModalContext (modal.tsx, chip-dropdown.tsx): ModalContent now provides a context that ChipDropdown reads to flip its Radix dropdown into modal mode, resolving the pointer-events:none body-lock and focus-trap conflict when dropdowns are used inside dialogs.

Confidence Score: 4/5

Safe to merge; the new batch invite path is well-tested and the stale-closure fixes are correct. One design gap in the workspace access check is worth a follow-up.

The accessibleRows query determines whether an existing member "already has access" to a workspace by checking for the presence of any permission row, without considering the permission level. An admin who selects write for a workspace where the member already has read will see no invite sent and no feedback that the upgrade was skipped. This is a silent no-op rather than a data-corrupting failure, but it leaves the advertised behaviour ("invite to workspaces they don't already have access to") slightly misleading for the permission-upgrade case.

apps/sim/app/api/organizations/[id]/invitations/route.ts — the accessibleRows permission check and how it interacts with the requested permission level in validGrants.

Important Files Changed

Filename Overview
apps/sim/app/api/organizations/[id]/invitations/route.ts Core batch invite logic refactored: existing members now receive workspace-only invitations; dedup of workspace grants, per-member access and pending-grant checks, seat validation fix, and split audit trail. One P2: permission level is not considered when checking if a member "already has access".
apps/sim/app/api/organizations/[id]/invitations/route.test.ts Four new tests cover the new batch paths: member gets partial workspace invite, member fully covered returns 400, mixed batch with new+member emails, and non-batch still rejects existing members. Mock selects match the query sequence in the implementation.
apps/sim/components/emcn/components/chip-modal/chip-modal.tsx Stale-closure fix: itemsRef + commitItems pattern ensures consecutive handleAdd calls during a paste see up-to-date state. External-value sync effect also updated to use the ref. Well-implemented.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/chat/chat.tsx Same ref-backed stale-closure fix applied to the chat deploy email field; emailsRef is synced from the prop via useEffect, invalidEmailItemsRef is mutation-only (no sync effect needed). P2: a comment clarifying the intentional absence of a sync effect for invalidEmailItemsRef would help future readers.
apps/sim/components/emcn/components/modal/modal.tsx Adds InsideModalContext and wraps ModalContent children in its provider so nested floaters can switch to modal behaviour. Clean, well-documented.
apps/sim/components/emcn/components/chip-dropdown/chip-dropdown.tsx Consumes InsideModalContext to flip the Radix dropdown between modal and non-modal mode, resolving the pointer-events:none body-lock and focus-trap conflicts when used inside a dialog.
apps/sim/components/emcn/components/tag-input/tag-input.tsx Paste handler simplified: all pasted values are committed via onAdd immediately (no fallback back into the text buffer). Intentional change that prevents double-rendering of rejected emails as both an invalid chip and raw input text.
apps/sim/components/emails/invitations/workspace-invitation-email.tsx Workspace name generalised to an array; preview text and body copy adapt to singular vs plural. Oxford-comma joining logic is correct.
apps/sim/app/workspace/[workspaceId]/settings/components/team-management/team-management.tsx Splits former existingEmails into externalEmails (blocked) and pendingEmails (blocked only for non-members). External collaborators correctly excluded from pending list since they're caught by a prior check in the modal.
apps/sim/lib/invitations/send.ts Workspace invitation email now fetches all granted workspace names in one query and uses a dynamic subject line for multi-workspace invites.

Sequence Diagram

sequenceDiagram
    participant UI as OrganizationInviteModal
    participant API as POST /organizations/[id]/invitations?batch=true
    participant DB as Database
    participant Email as sendInvitationEmail

    UI->>API: emails[], workspaceInvitations[]
    API->>DB: "validate workspace ownership & admin access"
    API->>DB: query existing org members (member table)
    API->>DB: query pending org invitations
    note over API: split emails into newEmails / memberEmails
    alt "isBatch && memberEmails.length > 0"
        API->>DB: query permissions table (user x workspace)
        API->>DB: query invitationWorkspaceGrant (pending workspace invites)
        note over API: compute grantsNeeded per member
    end
    loop each email in emailsToInvite (org invite)
        API->>DB: "createPendingInvitation (kind=organization)"
        API->>Email: send org invitation email
    end
    loop each email in memberWorkspaceInvites (workspace-only invite)
        API->>DB: "createPendingInvitation (kind=workspace)"
        API->>Email: send workspace invitation email (multi-workspace names)
    end
    API-->>UI: "{ invitationsSent, invitedEmails, existingMembers, ... }"
Loading

Reviews (1): Last reviewed commit: "address comments" | Re-trigger Greptile

Comment thread apps/sim/app/api/organizations/[id]/invitations/route.ts
@icecrasher321 icecrasher321 merged commit 020baad into staging Jun 12, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant