feat(js): add InviteMembersButton and Clerk.openInviteMembers#9124
feat(js): add InviteMembersButton and Clerk.openInviteMembers#9124alexcarpenter wants to merge 5 commits into
Conversation
Adds a control component that opens the organization invite-members form in a modal, plus openInviteMembers/closeInviteMembers on the Clerk class and an @clerk/ui InviteMembers modal.
🦋 Changeset detectedLatest commit: 79c2172 The changes in this PR will be included in the next version bump. This PR includes changesets to release 23 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds an organization invite-members modal with permission-gated UI, public Clerk open/close APIs, React and Next.js launchers, lazy component wiring, form customization, tests, sandbox access, and package changeset documentation. ChangesInvite members experience
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant ReactClient
participant IsomorphicClerk
participant Clerk
participant Components
participant InviteMembersModal
ReactClient->>IsomorphicClerk: click InviteMembersButton
IsomorphicClerk->>Clerk: openInviteMembers(props)
Clerk->>Components: openModal('inviteMembers', props)
Components->>InviteMembersModal: mount modal at /inviteMembers
InviteMembersModal-->>ReactClient: render invite form
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/electron
@clerk/electron-passkeys
@clerk/eslint-plugin
@clerk/expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
API Changes Report
Summary
@clerk/clerk-jsCurrent version: 6.25.2 Subpath
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
packages/react/src/isomorphicClerk.ts (1)
1073-1087: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd explicit return types to the new public methods.
openInviteMembersandcloseInviteMembersare public APIs and should explicitly returnvoid.Proposed fix
- openInviteMembers = (props?: InviteMembersModalProps) => { + openInviteMembers = (props?: InviteMembersModalProps): void => { ... - closeInviteMembers = () => { + closeInviteMembers = (): void => {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/react/src/isomorphicClerk.ts` around lines 1073 - 1087, Add explicit void return types to the public openInviteMembers and closeInviteMembers methods, preserving their existing behavior and parameter typing.Sources: Coding guidelines, Learnings
packages/react/src/components/index.ts (1)
38-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winVerify this feature-level barrel export.
This
components/index.tsre-export conflicts with the repository guidance to avoid barrel files because of circular-dependency risk. Prefer a direct package-entrypoint export, or confirm that this file is intentionally the public entrypoint.As per coding guidelines, avoid
index.tsre-export barrels where possible.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/react/src/components/index.ts` at line 38, Replace the InviteMembersButton re-export in the components/index.ts barrel with a direct package-entrypoint export, or remove it if the barrel is not intended as a public API; verify consumers import InviteMembersButton through the package entrypoint and avoid introducing circular dependencies.Source: Coding guidelines
packages/ui/src/components/OrganizationProfile/InviteMembersForm.tsx (1)
36-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd or confirm regression coverage for the hidden-reset variant.
This changes both the rendered controls and their spacing. Ensure modal tests cover
hideResetButton={true}and the defaultfalsepath.As per coding guidelines, unit tests are required for new functionality.
Also applies to: 308-331
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/components/OrganizationProfile/InviteMembersForm.tsx` around lines 36 - 40, The InviteMembersForm tests lack regression coverage for the hideResetButton variants. Add or update modal tests covering both hideResetButton={true} and the default false behavior, asserting the reset button visibility and controls-row spacing for each path, using the relevant InviteMembersForm render and controls selectors.Source: Coding guidelines
packages/react/src/components/__tests__/InviteMembersButton.test.tsx (1)
99-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPrefer a semantic query for the rendered button.
Use
screen.getByRole('button', { name: 'Invite' })instead ofcontainer.querySelector('button')so the test verifies the accessible element and remains resilient to DOM structure changes.As per coding guidelines, React Testing Library tests should use proper test queries.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/react/src/components/__tests__/InviteMembersButton.test.tsx` around lines 99 - 107, The test “does not pass appearance prop to child element” uses a DOM selector instead of a semantic query. Replace container.querySelector('button') with screen.getByRole('button', { name: 'Invite' }) and ensure the screen utility is imported from React Testing Library, while keeping the appearance attribute assertion unchanged.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/react/src/components/__tests__/InviteMembersButton.test.tsx`:
- Around line 11-17: Replace the untyped any usages in the mockClerk object and
the withClerk mock’s Component and props parameters with appropriate
Clerk/component prop types, or use unknown and narrow it before access. Preserve
compile-time validation of the public component contract while keeping the test
mock behavior unchanged.
In `@packages/react/src/isomorphicClerk.ts`:
- Around line 1073-1078: Update openInviteMembers in the unloaded branch to
preserve an omitted props argument as undefined rather than converting it to the
null cancellation sentinel; assign props directly to preopenInviteMembers so
preload calls without props are replayed correctly.
In `@packages/ui/src/components/InviteMembers/InviteMembers.tsx`:
- Around line 55-62: Prevent unauthorized users from seeing a blank Invite
Members modal: update the `Protect` usage around `InviteMembersModalInner` to
render an access-denied fallback or close the modal when
`org:sys_memberships:manage` is missing, rather than leaving the outer modal
mounted empty. Extend the negative authorization test to assert the chosen
denied or closed outcome.
---
Nitpick comments:
In `@packages/react/src/components/__tests__/InviteMembersButton.test.tsx`:
- Around line 99-107: The test “does not pass appearance prop to child element”
uses a DOM selector instead of a semantic query. Replace
container.querySelector('button') with screen.getByRole('button', { name:
'Invite' }) and ensure the screen utility is imported from React Testing
Library, while keeping the appearance attribute assertion unchanged.
In `@packages/react/src/components/index.ts`:
- Line 38: Replace the InviteMembersButton re-export in the components/index.ts
barrel with a direct package-entrypoint export, or remove it if the barrel is
not intended as a public API; verify consumers import InviteMembersButton
through the package entrypoint and avoid introducing circular dependencies.
In `@packages/react/src/isomorphicClerk.ts`:
- Around line 1073-1087: Add explicit void return types to the public
openInviteMembers and closeInviteMembers methods, preserving their existing
behavior and parameter typing.
In `@packages/ui/src/components/OrganizationProfile/InviteMembersForm.tsx`:
- Around line 36-40: The InviteMembersForm tests lack regression coverage for
the hideResetButton variants. Add or update modal tests covering both
hideResetButton={true} and the default false behavior, asserting the reset
button visibility and controls-row spacing for each path, using the relevant
InviteMembersForm render and controls selectors.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Repository UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: d6df0d3b-58da-450b-8b55-76a78ac44e66
⛔ Files ignored due to path filters (2)
packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snapis excluded by!**/*.snappackages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (19)
.changeset/invite-members-button.mdpackages/clerk-js/sandbox/app.tspackages/clerk-js/sandbox/template.htmlpackages/clerk-js/src/core/clerk.tspackages/nextjs/src/client-boundary/uiComponents.tsxpackages/nextjs/src/index.tspackages/react/src/components/InviteMembersButton.tsxpackages/react/src/components/__tests__/InviteMembersButton.test.tsxpackages/react/src/components/index.tspackages/react/src/isomorphicClerk.tspackages/react/src/utils/childrenUtils.tsxpackages/shared/src/internal/clerk-js/warnings.tspackages/shared/src/types/clerk.tspackages/ui/src/Components.tsxpackages/ui/src/components/InviteMembers/InviteMembers.tsxpackages/ui/src/components/InviteMembers/__tests__/InviteMembersModal.test.tsxpackages/ui/src/components/InviteMembers/index.tsxpackages/ui/src/components/OrganizationProfile/InviteMembersForm.tsxpackages/ui/src/lazyModules/components.ts
| const mockClerk = { | ||
| openInviteMembers: mockOpenInviteMembers, | ||
| } as any; | ||
|
|
||
| vi.mock('../withClerk', () => { | ||
| return { | ||
| withClerk: (Component: any) => (props: any) => { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Remove untyped any from the test mocks.
The mocked Clerk object, component, and props use any, disabling compile-time validation of the new public component contract. Use typed mocks or unknown with narrowing.
As per coding guidelines, any is disallowed without justification.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/react/src/components/__tests__/InviteMembersButton.test.tsx` around
lines 11 - 17, Replace the untyped any usages in the mockClerk object and the
withClerk mock’s Component and props parameters with appropriate Clerk/component
prop types, or use unknown and narrow it before access. Preserve compile-time
validation of the public component contract while keeping the test mock behavior
unchanged.
Source: Coding guidelines
| <Route path='inviteMembers'> | ||
| <SubscriberTypeContext.Provider value='organization'> | ||
| <Protect permission='org:sys_memberships:manage'> | ||
| {/*TODO: Used by InvisibleRootBox, can we simplify? */} | ||
| <div> | ||
| <InviteMembersModalInner /> | ||
| </div> | ||
| </Protect> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Avoid opening an empty modal for unauthorized users.
Protect returns null here, but the outer modal is already mounted. A user without org:sys_memberships:manage gets a blank dialog/overlay. Render an access-denied fallback or close the modal when authorization fails, and extend the negative test to assert that outcome.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/components/InviteMembers/InviteMembers.tsx` around lines 55 -
62, Prevent unauthorized users from seeing a blank Invite Members modal: update
the `Protect` usage around `InviteMembersModalInner` to render an access-denied
fallback or close the modal when `org:sys_memberships:manage` is missing, rather
than leaving the outer modal mounted empty. Extend the negative authorization
test to assert the chosen denied or closed outcome.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/shared/src/internal/clerk-js/warnings.ts (1)
30-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd an explicit return type to the warning factory.
This helper is exposed through the exported
warningsobject. Add: stringto satisfy the repository’s TypeScript return-type guideline.Suggested change
-const createCannotRenderComponentWhenPermissionIsMissing = (componentName: 'InviteMembers', permission: string) => { +const createCannotRenderComponentWhenPermissionIsMissing = ( + componentName: 'InviteMembers', + permission: string, +): string => {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/shared/src/internal/clerk-js/warnings.ts` around lines 30 - 34, Add the explicit return type annotation `: string` to the `createCannotRenderComponentWhenPermissionIsMissing` warning factory, which returns the result of `formatWarning` and is exposed through the exported `warnings` object.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/shared/src/internal/clerk-js/warnings.ts`:
- Around line 30-34: Update createCannotRenderComponentWhenPermissionIsMissing
to say “this is a no-op” instead of “this is no-op,” and update the
corresponding expected warning text in the warnings test assertion.
---
Nitpick comments:
In `@packages/shared/src/internal/clerk-js/warnings.ts`:
- Around line 30-34: Add the explicit return type annotation `: string` to the
`createCannotRenderComponentWhenPermissionIsMissing` warning factory, which
returns the result of `formatWarning` and is exposed through the exported
`warnings` object.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Repository UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: f0cbadb3-a7d3-46fa-a74c-b6758693d6d2
📒 Files selected for processing (6)
.changeset/invite-members-button.mdpackages/clerk-js/src/core/clerk.tspackages/react/src/components/InviteMembersButton.tsxpackages/react/src/isomorphicClerk.tspackages/shared/src/internal/clerk-js/__tests__/warnings.test.tspackages/shared/src/internal/clerk-js/warnings.ts
✅ Files skipped from review due to trivial changes (1)
- .changeset/invite-members-button.md
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/clerk-js/src/core/clerk.ts
- packages/react/src/components/InviteMembersButton.tsx
- packages/react/src/isomorphicClerk.ts
| const createCannotRenderComponentWhenPermissionIsMissing = (componentName: 'InviteMembers', permission: string) => { | ||
| return formatWarning( | ||
| `<${componentName}/> cannot render unless the current user has the \`${permission}\` permission. Since the current user is missing this permission, this is no-op. Render it only for members who can manage memberships, for example by wrapping it in <Show when={{ permission: '${permission}' }}>.`, | ||
| ); | ||
| }; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the no-op wording in the development warning.
Line 32 says this is no-op; change it to this is a no-op. Update the corresponding test assertion in packages/shared/src/internal/clerk-js/__tests__/warnings.test.ts as part of the same change.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/shared/src/internal/clerk-js/warnings.ts` around lines 30 - 34,
Update createCannotRenderComponentWhenPermissionIsMissing to say “this is a
no-op” instead of “this is no-op,” and update the corresponding expected warning
text in the warnings test assertion.
Description
Adds
<InviteMembersButton />to@clerk/react(re-exported through@clerk/nextjs), a control component that works like<SignInButton mode="modal">: wrap your own button and clicking it opens a modal containing only the organization invite-members form. It's backed by new stableClerk.openInviteMembers()/closeInviteMembers()methods on the core Clerk class and a new@clerk/uiInviteMembersmodal (its own code-split chunk) that reuses the existing invite form, renders it inside a card matching the sign-in modal, and gates on theorg:sys_memberships:managepermission. To test: runpnpm dev:sandbox, sign in with an active organization as an admin, and open "Open invite members" under the Orgs section.Checklist
pnpm testruns as expected.pnpm buildruns as expected.Docs PR: clerk/clerk#2921
Type of change
Summary by CodeRabbit
InviteMembersButton(default or custom children) and added it to the React and Next.js public exports.hideResetButton.InviteMembersButton,InviteMembersModal, and permission-missing warnings.