feat(desktop): invite members from every people picker
Summary
- Every people picker in the desktop app now ends with a pinned Invite member row. On a free plan it opens the Pro upsell on Team Collaboration; on a paid plan it opens the invite dialog.
- The "This will affect your billing" confirm step is gone. People know by now that we charge per seat.
- Fixes a shared-dialog bug that this depends on: a dialog opened on top of another dialog closed the one underneath it.
Screenshots of every scenario: https://app.superset.sh/page/invite-from-people-pickers-dqjhc7
Why / Context
Assigning work to someone who isn't in the org yet meant leaving what you were doing to go to Settings → Organization. This adds a quick path from the place where you notice they're missing.
How It Works
useInviteMember()returnsundefinedwhen your role can't invite anyone, so the row doesn't render. Otherwise it returns a handler that callsgateFeature(INVITE_MEMBERS, …): free → paywall, paid → open the dialog.InviteMemberDialogmoved from the members settings folder toroutes/_authenticated/components/. It's now mounted once in the authenticated layout and driven bystores/invite-member-dialog.ts. It reads the org, org name and role itself, so callers pass nothing. The Settings → Organization button goes through the same hook.- Surfaces:
- Project lead: list row, detail sidebar, and the New project chip.
ProjectLeadPickertakesonInvite, threaded from the screens alongsidepeople, which keeps it presentational for Storybook. - Task assignee:
AssigneeMenuItemscalls the hook itself. It only mounts while a menu is open, so the per-row table cell and context menu stay lazy.AssigneePropertyandAssigneeCellwere line-for-line copies ofAssigneeMenuItemsand now use it.CreateTaskAssigneePickergets the row as well.
- Settings: Teams "Add member" (its "Invite people..." link to settings now opens the dialog) and Hosts "Add member".
- Project lead: list row, detail sidebar, and the New project chip.
- The invite row sits outside each menu's scroll area. Otherwise the
max-h-64lists clipped it at four members. useOrganizationRole()is new;useIsOrganizationOwneris now built on it.
Dialog stacking (packages/ui)
Dialog defaults to modal={false}, so focus or a click inside a dialog opened on top counted as "outside" for the dialog below and closed it. That wiped the Create task and New project drafts. DialogContent now ignores outside interactions that land inside another [data-slot="dialog-content"]. The same fix covers the paywall and alert() opened over any dialog.
Focus after a menu closes
A ContextMenu hands focus back to its trigger as it closes, which dismissed the freshly opened invite dialog. The dialog now catches focus-outside and moves focus back to the email field.
Manual QA Checklist
Driven over CDP in the dev app. Pro = Superset org; free = Satya Test Org.
- Projects list lead cell → invite dialog opens with the email field focused
- Project detail lead → dialog
- New project → lead chip → dialog stacks over New project; closing it leaves the draft
- Tasks table assignee cell → dialog
- Right-click a task → Assignee submenu (keyboard) → dialog stays open with the email field focused
- Task detail assignee → dialog
- Create task → assignee → dialog stacks over it; the typed title survives Cancel; Escape closes only the top dialog
- Teams "Add member" → dialog
- Hosts "Add member" → dialog
- Settings → Organization button → dialog, no billing confirm
- Free org → Create task → assignee → Invite member → paywall on Team Collaboration, stacked; Cancel returns to the draft
- No
pointer-events: noneleft on<body>after closing; no new console errors - Actually sending an invitation from a picker (the send path itself is unchanged)
Testing
bun run lint,bun run typecheck(whole repo),bun run check:i18n,bunx sherif: all passbun run test: everything passes except 7deriveSandboxCredentialstests in@superset/trpc. Those fail locally because this worktree's.envhas an emptySANDBOX_GATE_SECRET, and they pass with a placeholder secret. The PR doesn't touchtrpcorshared.
Design Decisions
- @mentions and filters left out: you can't filter by or mention someone who isn't in the org yet.
- Fix stacking in the shared
DialogContentrather than in each host dialog: every stacked dialog, including the paywall andalert(), had the same bug.
Known Limitations
- Inviting someone doesn't assign them; they become pickable after they accept.
settings/members/components/MembersSettings/MembersSettings.tsxisn't routed anywhere (members live under Organization settings). It's edited here only so it still typechecks.
Summary by cubic
Every people picker in the desktop app now ends with a pinned Invite member row, so you can invite someone from the place you notice they're missing instead of leaving for Settings → Organization. Free plans get the Pro upsell; paid plans open the invite dialog directly, and the per-seat billing confirmation step is removed.
Bug Fixes
- A dialog opened on top of another no longer closes the one underneath it, which previously wiped Create task and New project drafts.
- The invite dialog keeps focus on the email field when a closing menu hands focus back to its trigger.
Refactors
- The invite dialog is now mounted once in the authenticated layout and driven by a shared store; callers use the new
useInviteMember()hook, which returnsundefinedwhen the user's role can't invite. - Task sidebar and table cell assignee pickers now reuse a shared
AssigneeMenuItemscomponent instead of maintaining duplicate copies.
Summary by CodeRabbit
-
New Features
- Added “Invite member” actions to assignee, project lead, team, and host member pickers when invitations are available.
- Invitation access now reflects your organization role, and the invitation dialog can be opened from these menus.
- Pending invitations are shown in organization and member settings.
-
Bug Fixes
- Improved dialog focus behavior when opening invitations from a menu.