diff --git a/.changeset/friendly-tables-chew.md b/.changeset/friendly-tables-chew.md new file mode 100644 index 0000000000..2e8c7995aa --- /dev/null +++ b/.changeset/friendly-tables-chew.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Use `userMemberships` instead of `organizationList` inside ``. diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/UserMembershipList.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/UserMembershipList.tsx index e8b69a1ef8..c3aa09dc32 100644 --- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/UserMembershipList.tsx +++ b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/UserMembershipList.tsx @@ -1,6 +1,7 @@ import type { OrganizationResource } from '@clerk/types'; import React from 'react'; +import { InfiniteListSpinner } from '../../common'; import { useCoreOrganization, useCoreOrganizationList, @@ -9,25 +10,54 @@ import { } from '../../contexts'; import { Box, descriptors, localizationKeys } from '../../customizables'; import { OrganizationPreview, PersonalWorkspacePreview, PreviewButton } from '../../elements'; +import { useInView } from '../../hooks'; import { SwitchArrows } from '../../icons'; import { common } from '../../styledSystem'; +import { organizationListParams } from './utils'; export type UserMembershipListProps = { onPersonalWorkspaceClick: React.MouseEventHandler; onOrganizationClick: (org: OrganizationResource) => unknown; }; + +const useFetchMemberships = () => { + const { userMemberships } = useCoreOrganizationList({ + userMemberships: organizationListParams.userMemberships, + }); + + const { ref } = useInView({ + threshold: 0, + onChange: inView => { + if (!inView) { + return; + } + if (userMemberships.hasNextPage) { + userMemberships.fetchNext?.(); + } + }, + }); + + return { + userMemberships, + ref, + }; +}; export const UserMembershipList = (props: UserMembershipListProps) => { const { onPersonalWorkspaceClick, onOrganizationClick } = props; const { hidePersonal } = useOrganizationSwitcherContext(); const { organization: currentOrg } = useCoreOrganization(); - const { organizationList } = useCoreOrganizationList(); + const { ref, userMemberships } = useFetchMemberships(); const user = useCoreUser(); - const otherOrgs = (organizationList || []).map(e => e.organization).filter(o => o.id !== currentOrg?.id); + const otherOrgs = ((userMemberships.count || 0) > 0 ? userMemberships.data || [] : []) + .map(e => e.organization) + .filter(o => o.id !== currentOrg?.id); const { username, primaryEmailAddress, primaryPhoneNumber, ...userWithoutIdentifiers } = user; + const { isLoading, hasNextPage } = userMemberships; + return ( ({ @@ -71,6 +101,7 @@ export const UserMembershipList = (props: UserMembershipListProps) => { /> ))} + {(hasNextPage || isLoading) && } ); }; diff --git a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx index 711ee40fcd..251bf8e0f9 100644 --- a/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx +++ b/packages/clerk-js/src/ui.retheme/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx @@ -4,7 +4,11 @@ import { describe } from '@jest/globals'; import { act, render, runFakeTimers, waitFor } from '../../../../testUtils'; import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { OrganizationSwitcher } from '../OrganizationSwitcher'; -import { createFakeUserOrganizationInvitation, createFakeUserOrganizationSuggestion } from './utlis'; +import { + createFakeUserOrganizationInvitation, + createFakeUserOrganizationMembership, + createFakeUserOrganizationSuggestion, +} from './utlis'; const { createFixtures } = bindCreateFixtures('OrganizationSwitcher'); @@ -130,11 +134,43 @@ describe('OrganizationSwitcher', () => { }); it('lists all organizations the user belongs to', async () => { - const { wrapper, props } = await createFixtures(f => { + const { wrapper, props, fixtures } = await createFixtures(f => { f.withOrganizations(); f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: ['Org1', 'Org2'] }); }); + fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce( + Promise.resolve({ + data: [ + createFakeUserOrganizationMembership({ + id: '1', + organization: { + id: '1', + name: 'Org1', + slug: 'org1', + membersCount: 1, + adminDeleteEnabled: false, + maxAllowedMemberships: 1, + pendingInvitationsCount: 1, + }, + }), + createFakeUserOrganizationMembership({ + id: '2', + organization: { + id: '2', + name: 'Org2', + slug: 'org2', + membersCount: 1, + adminDeleteEnabled: false, + maxAllowedMemberships: 1, + pendingInvitationsCount: 1, + }, + }), + ], + total_count: 2, + }), + ); + props.setProps({ hidePersonal: false }); const { getAllByText, getByText, getByRole, userEvent } = render(, { wrapper }); await userEvent.click(getByRole('button')); @@ -313,6 +349,39 @@ describe('OrganizationSwitcher', () => { create_organization_enabled: false, }); }); + + fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce( + Promise.resolve({ + data: [ + createFakeUserOrganizationMembership({ + id: '1', + organization: { + id: '1', + name: 'Org1', + slug: 'org1', + membersCount: 1, + adminDeleteEnabled: false, + maxAllowedMemberships: 1, + pendingInvitationsCount: 1, + }, + }), + createFakeUserOrganizationMembership({ + id: '2', + organization: { + id: '2', + name: 'Org2', + slug: 'org2', + membersCount: 1, + adminDeleteEnabled: false, + maxAllowedMemberships: 1, + pendingInvitationsCount: 1, + }, + }), + ], + total_count: 2, + }), + ); + fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve()); props.setProps({ hidePersonal: true }); diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/UserMembershipList.tsx b/packages/clerk-js/src/ui/components/OrganizationSwitcher/UserMembershipList.tsx index e37d18a6f5..c3aa09dc32 100644 --- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/UserMembershipList.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/UserMembershipList.tsx @@ -1,6 +1,7 @@ import type { OrganizationResource } from '@clerk/types'; import React from 'react'; +import { InfiniteListSpinner } from '../../common'; import { useCoreOrganization, useCoreOrganizationList, @@ -9,26 +10,54 @@ import { } from '../../contexts'; import { Box, descriptors, localizationKeys } from '../../customizables'; import { OrganizationPreview, PersonalWorkspacePreview, PreviewButton } from '../../elements'; +import { useInView } from '../../hooks'; import { SwitchArrows } from '../../icons'; import { common } from '../../styledSystem'; +import { organizationListParams } from './utils'; export type UserMembershipListProps = { onPersonalWorkspaceClick: React.MouseEventHandler; onOrganizationClick: (org: OrganizationResource) => unknown; }; + +const useFetchMemberships = () => { + const { userMemberships } = useCoreOrganizationList({ + userMemberships: organizationListParams.userMemberships, + }); + + const { ref } = useInView({ + threshold: 0, + onChange: inView => { + if (!inView) { + return; + } + if (userMemberships.hasNextPage) { + userMemberships.fetchNext?.(); + } + }, + }); + + return { + userMemberships, + ref, + }; +}; export const UserMembershipList = (props: UserMembershipListProps) => { const { onPersonalWorkspaceClick, onOrganizationClick } = props; const { hidePersonal } = useOrganizationSwitcherContext(); const { organization: currentOrg } = useCoreOrganization(); - const { organizationList } = useCoreOrganizationList(); + const { ref, userMemberships } = useFetchMemberships(); const user = useCoreUser(); - const otherOrgs = (organizationList || []).map(e => e.organization).filter(o => o.id !== currentOrg?.id); + const otherOrgs = ((userMemberships.count || 0) > 0 ? userMemberships.data || [] : []) + .map(e => e.organization) + .filter(o => o.id !== currentOrg?.id); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const { username, primaryEmailAddress, primaryPhoneNumber, ...userWithoutIdentifiers } = user; + const { isLoading, hasNextPage } = userMemberships; + return ( ({ @@ -72,6 +101,7 @@ export const UserMembershipList = (props: UserMembershipListProps) => { /> ))} + {(hasNextPage || isLoading) && } ); }; diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx b/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx index 711ee40fcd..251bf8e0f9 100644 --- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx @@ -4,7 +4,11 @@ import { describe } from '@jest/globals'; import { act, render, runFakeTimers, waitFor } from '../../../../testUtils'; import { bindCreateFixtures } from '../../../utils/test/createFixtures'; import { OrganizationSwitcher } from '../OrganizationSwitcher'; -import { createFakeUserOrganizationInvitation, createFakeUserOrganizationSuggestion } from './utlis'; +import { + createFakeUserOrganizationInvitation, + createFakeUserOrganizationMembership, + createFakeUserOrganizationSuggestion, +} from './utlis'; const { createFixtures } = bindCreateFixtures('OrganizationSwitcher'); @@ -130,11 +134,43 @@ describe('OrganizationSwitcher', () => { }); it('lists all organizations the user belongs to', async () => { - const { wrapper, props } = await createFixtures(f => { + const { wrapper, props, fixtures } = await createFixtures(f => { f.withOrganizations(); f.withUser({ email_addresses: ['test@clerk.com'], organization_memberships: ['Org1', 'Org2'] }); }); + fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce( + Promise.resolve({ + data: [ + createFakeUserOrganizationMembership({ + id: '1', + organization: { + id: '1', + name: 'Org1', + slug: 'org1', + membersCount: 1, + adminDeleteEnabled: false, + maxAllowedMemberships: 1, + pendingInvitationsCount: 1, + }, + }), + createFakeUserOrganizationMembership({ + id: '2', + organization: { + id: '2', + name: 'Org2', + slug: 'org2', + membersCount: 1, + adminDeleteEnabled: false, + maxAllowedMemberships: 1, + pendingInvitationsCount: 1, + }, + }), + ], + total_count: 2, + }), + ); + props.setProps({ hidePersonal: false }); const { getAllByText, getByText, getByRole, userEvent } = render(, { wrapper }); await userEvent.click(getByRole('button')); @@ -313,6 +349,39 @@ describe('OrganizationSwitcher', () => { create_organization_enabled: false, }); }); + + fixtures.clerk.user?.getOrganizationMemberships.mockReturnValueOnce( + Promise.resolve({ + data: [ + createFakeUserOrganizationMembership({ + id: '1', + organization: { + id: '1', + name: 'Org1', + slug: 'org1', + membersCount: 1, + adminDeleteEnabled: false, + maxAllowedMemberships: 1, + pendingInvitationsCount: 1, + }, + }), + createFakeUserOrganizationMembership({ + id: '2', + organization: { + id: '2', + name: 'Org2', + slug: 'org2', + membersCount: 1, + adminDeleteEnabled: false, + maxAllowedMemberships: 1, + pendingInvitationsCount: 1, + }, + }), + ], + total_count: 2, + }), + ); + fixtures.clerk.setActive.mockReturnValueOnce(Promise.resolve()); props.setProps({ hidePersonal: true });