diff --git a/.changeset/org-general-panel-cardstate.md b/.changeset/org-general-panel-cardstate.md new file mode 100644 index 00000000000..fe9d61a27cd --- /dev/null +++ b/.changeset/org-general-panel-cardstate.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Fix `OrganizationProfileGeneralPanel` (from `@clerk/ui/experimental`) throwing "CardState not found" when opening the leave-organization or delete-organization confirmation in its default, no-children page mode. The panel now provides the same root `CardStateProvider` that a mounted `` supplies. diff --git a/packages/ui/src/composed/OrganizationProfile/General.tsx b/packages/ui/src/composed/OrganizationProfile/General.tsx index fdf8bbeacdf..59a1df22f16 100644 --- a/packages/ui/src/composed/OrganizationProfile/General.tsx +++ b/packages/ui/src/composed/OrganizationProfile/General.tsx @@ -26,7 +26,15 @@ function GeneralComposed({ children }: PropsWithChildren): ReactNode { export function OrganizationProfileGeneralPanel({ children }: PropsWithChildren): ReactNode { if (!children) { - return ; + // Unlike AccountPage/SecurityPage, OrganizationGeneralPage does not self-wrap in a + // CardStateProvider. Mounted supplies one at its root, so the + // leave/delete confirmation forms (useLeaveWithRevalidations -> useCardState) resolve it. + // The composed panel is a standalone entry point, so it must provide the same root here. + return ( + + + + ); } // The section confirmation forms (leave/delete) call useCardState(), so children must be wrapped diff --git a/packages/ui/src/composed/__tests__/OrganizationProfileSections.test.tsx b/packages/ui/src/composed/__tests__/OrganizationProfileSections.test.tsx index f9ef40b1ba8..a2dd8d4fc17 100644 --- a/packages/ui/src/composed/__tests__/OrganizationProfileSections.test.tsx +++ b/packages/ui/src/composed/__tests__/OrganizationProfileSections.test.tsx @@ -135,6 +135,25 @@ describe('OrganizationProfile composed sections', () => { }); }); + describe('General — default page mode (no children)', () => { + it('provides CardState so the leave-organization confirmation can open', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withOrganizations(); + f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: [{ name: 'TestOrg' }] }); + }); + + fixtures.clerk.organization?.getDomains.mockReturnValue(Promise.resolve({ data: [], total_count: 0 })); + + const { userEvent } = render(, { wrapper }); + + // Before the fix, LeaveOrganizationForm's useLeaveWithRevalidations() calls useCardState() + // with no ancestor CardStateProvider and throws "CardState not found". + await userEvent.click(await screen.findByRole('button', { name: /leave organization/i })); + + await waitFor(() => expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument()); + }); + }); + describe('General — section outside page', () => { it('useRequirePage throws when rendered outside a page component', async () => { const { wrapper, fixtures } = await createFixtures(f => {