diff --git a/.changeset/gold-jeans-turn.md b/.changeset/gold-jeans-turn.md new file mode 100644 index 0000000000..a2a50a2bbe --- /dev/null +++ b/.changeset/gold-jeans-turn.md @@ -0,0 +1,9 @@ +--- +'@clerk/clerk-js': patch +'@clerk/shared': patch +'@clerk/types': patch +--- + +Updates signature of OrganizationMembership.retrieve to support backwards compatibility while allowing using the new paginated responses. + ++ userMemberships is now also part of the returned values of useOrganizationList diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 0e84b311b9..fd3aecfcf6 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -1009,6 +1009,9 @@ export default class Clerk implements ClerkInterface { return Organization.create({ name, slug }); }; + /** + * @deprecated use User.getOrganizationMemberships + */ public getOrganizationMemberships = async (): Promise => { return await OrganizationMembership.retrieve(); }; diff --git a/packages/clerk-js/src/core/resources/OrganizationMembership.ts b/packages/clerk-js/src/core/resources/OrganizationMembership.ts index fe3092cc4f..991c7f95af 100644 --- a/packages/clerk-js/src/core/resources/OrganizationMembership.ts +++ b/packages/clerk-js/src/core/resources/OrganizationMembership.ts @@ -1,5 +1,7 @@ import type { + ClerkPaginatedResponse, ClerkResourceReloadParams, + GetUserOrganizationMembershipParams, MembershipRole, OrganizationMembershipJSON, OrganizationMembershipResource, @@ -7,6 +9,7 @@ import type { } from '@clerk/types'; import { unixEpochToDate } from '../../utils/date'; +import { convertPageToOffset } from '../../utils/pagesToOffset'; import { BaseResource, Organization } from './internal'; export class OrganizationMembership extends BaseResource implements OrganizationMembershipResource { @@ -23,18 +26,40 @@ export class OrganizationMembership extends BaseResource implements Organization this.fromJSON(data); } - static async retrieve(retrieveMembershipsParams?: RetrieveMembershipsParams): Promise { + static retrieve: GetOrganizationMembershipsClass = async retrieveMembershipsParams => { + const isDeprecatedParams = + typeof retrieveMembershipsParams === 'undefined' || !retrieveMembershipsParams?.paginated; return await BaseResource._fetch({ path: '/me/organization_memberships', method: 'GET', - search: retrieveMembershipsParams as any, + search: isDeprecatedParams + ? retrieveMembershipsParams + : (convertPageToOffset(retrieveMembershipsParams as unknown as any) as any), }) .then(res => { - const organizationMembershipsJSON = res?.response as unknown as OrganizationMembershipJSON[]; - return organizationMembershipsJSON.map(orgMem => new OrganizationMembership(orgMem)); + if (isDeprecatedParams) { + const organizationMembershipsJSON = res?.response as unknown as OrganizationMembershipJSON[]; + return organizationMembershipsJSON.map(orgMem => new OrganizationMembership(orgMem)) as any; + } + + const { data: suggestions, total_count } = + res?.response as unknown as ClerkPaginatedResponse; + + return { + total_count, + data: suggestions.map(suggestion => new OrganizationMembership(suggestion)), + } as any; }) - .catch(() => []); - } + .catch(() => { + if (isDeprecatedParams) { + return []; + } + return { + total_count: 0, + data: [], + }; + }); + }; destroy = async (): Promise => { // TODO: Revise the return type of _baseDelete @@ -100,7 +125,25 @@ export type UpdateOrganizationMembershipParams = { role: MembershipRole; }; +/** + * @deprecated + */ export type RetrieveMembershipsParams = { + /** + * @deprecated Use pageSize instead + */ limit?: number; + /** + * @deprecated Use initialPage instead + */ offset?: number; }; + +type MembershipParams = (RetrieveMembershipsParams | GetUserOrganizationMembershipParams) & { + paginated?: boolean; +}; +export type GetOrganizationMembershipsClass = ( + params?: T, +) => T['paginated'] extends true + ? Promise> + : Promise; diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index 2bc188fdd8..fd73b78d55 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -10,6 +10,7 @@ import type { EmailAddressResource, ExternalAccountJSON, ExternalAccountResource, + GetOrganizationMemberships, GetUserOrganizationInvitationsParams, GetUserOrganizationSuggestionsParams, ImageResource, @@ -32,7 +33,6 @@ import { unixEpochToDate } from '../../utils/date'; import { normalizeUnsafeMetadata } from '../../utils/resourceParams'; import { getFullName } from '../../utils/user'; import { BackupCode } from './BackupCode'; -import type { RetrieveMembershipsParams } from './internal'; import { BaseResource, DeletedObject, @@ -266,11 +266,8 @@ export class User extends BaseResource implements UserResource { return OrganizationSuggestion.retrieve(params); }; - getOrganizationMemberships = async ( - retrieveMembership: RetrieveMembershipsParams, - ): Promise => { - return await OrganizationMembership.retrieve(retrieveMembership); - }; + getOrganizationMemberships: GetOrganizationMemberships = retrieveMembership => + OrganizationMembership.retrieve(retrieveMembership); get verifiedExternalAccounts() { return this.externalAccounts.filter(externalAccount => externalAccount.verification?.status == 'verified'); diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/utils.ts b/packages/clerk-js/src/ui/components/OrganizationSwitcher/utils.ts index 3770114995..6382e00c60 100644 --- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/utils.ts +++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/utils.ts @@ -1,6 +1,9 @@ import type { useCoreOrganizationList } from '../../contexts'; export const organizationListParams = { + userMemberships: { + infinite: true, + }, userInvitations: { infinite: true, }, diff --git a/packages/shared/src/hooks/useOrganizationList.tsx b/packages/shared/src/hooks/useOrganizationList.tsx index 130a2f2abb..8ec2eb6bf7 100644 --- a/packages/shared/src/hooks/useOrganizationList.tsx +++ b/packages/shared/src/hooks/useOrganizationList.tsx @@ -2,6 +2,7 @@ import type { ClerkPaginatedResponse, CreateOrganizationParams, GetUserOrganizationInvitationsParams, + GetUserOrganizationMembershipParams, GetUserOrganizationSuggestionsParams, OrganizationMembershipResource, OrganizationResource, @@ -15,6 +16,12 @@ import type { PaginatedResources, PaginatedResourcesWithDefault } from './types' import { usePagesOrInfinite, useWithSafeValues } from './usePagesOrInfinite'; type UseOrganizationListParams = { + userMemberships?: + | true + | (GetUserOrganizationMembershipParams & { + infinite?: boolean; + keepPreviousData?: boolean; + }); userInvitations?: | true | (GetUserOrganizationInvitationsParams & { @@ -34,17 +41,25 @@ type OrganizationList = ReturnType; type UseOrganizationListReturn = | { isLoaded: false; + /** + * @deprecated Use userMemberships instead + */ organizationList: undefined; createOrganization: undefined; setActive: undefined; + userMemberships: PaginatedResourcesWithDefault; userInvitations: PaginatedResourcesWithDefault; userSuggestions: PaginatedResourcesWithDefault; } | { isLoaded: boolean; + /** + * @deprecated Use userMemberships instead + */ organizationList: OrganizationList; createOrganization: (params: CreateOrganizationParams) => Promise; setActive: SetActive; + userMemberships: PaginatedResources; userInvitations: PaginatedResources; userSuggestions: PaginatedResources; }; @@ -52,7 +67,14 @@ type UseOrganizationListReturn = type UseOrganizationList = (params?: UseOrganizationListParams) => UseOrganizationListReturn; export const useOrganizationList: UseOrganizationList = params => { - const { userInvitations, userSuggestions } = params || {}; + const { userMemberships, userInvitations, userSuggestions } = params || {}; + + const userMembershipsSafeValues = useWithSafeValues(userMemberships, { + initialPage: 1, + pageSize: 10, + keepPreviousData: false, + infinite: false, + }); const userInvitationsSafeValues = useWithSafeValues(userInvitations, { initialPage: 1, @@ -73,6 +95,14 @@ export const useOrganizationList: UseOrganizationList = params => { const clerk = useClerkInstanceContext(); const user = useUserContext(); + const userMembershipsParams = + typeof userMemberships === 'undefined' + ? undefined + : { + initialPage: userMembershipsSafeValues.initialPage, + pageSize: userMembershipsSafeValues.pageSize, + }; + const userInvitationsParams = typeof userInvitations === 'undefined' ? undefined @@ -93,6 +123,26 @@ export const useOrganizationList: UseOrganizationList = params => { const isClerkLoaded = !!(clerk.loaded && user); + const memberships = usePagesOrInfinite< + GetUserOrganizationMembershipParams, + ClerkPaginatedResponse + >( + { + ...userMembershipsParams, + paginated: true, + } as any, + user?.getOrganizationMemberships as unknown as any, + { + keepPreviousData: userMembershipsSafeValues.keepPreviousData, + infinite: userMembershipsSafeValues.infinite, + enabled: !!userMembershipsParams, + }, + { + type: 'userMemberships', + userId: user?.id, + }, + ); + const invitations = usePagesOrInfinite< GetUserOrganizationInvitationsParams, ClerkPaginatedResponse @@ -138,6 +188,21 @@ export const useOrganizationList: UseOrganizationList = params => { organizationList: undefined, createOrganization: undefined, setActive: undefined, + userMemberships: { + data: undefined, + count: undefined, + isLoading: false, + isFetching: false, + isError: false, + page: undefined, + pageCount: undefined, + fetchPage: undefined, + fetchNext: undefined, + fetchPrevious: undefined, + hasNextPage: false, + hasPreviousPage: false, + unstable__mutate: undefined, + }, userInvitations: { data: undefined, count: undefined, @@ -176,6 +241,7 @@ export const useOrganizationList: UseOrganizationList = params => { organizationList: createOrganizationList(user.organizationMemberships), setActive: clerk.setActive, createOrganization: clerk.createOrganization, + userMemberships: memberships, userInvitations: invitations, userSuggestions: suggestions, }; diff --git a/packages/types/src/user.ts b/packages/types/src/user.ts index 3d10b5f6c2..bea8d4aa09 100644 --- a/packages/types/src/user.ts +++ b/packages/types/src/user.ts @@ -101,6 +101,7 @@ export interface UserResource extends ClerkResource { getSessions: () => Promise; setProfileImage: (params: SetProfileImageParams) => Promise; createExternalAccount: (params: CreateExternalAccountParams) => Promise; + getOrganizationMemberships: GetOrganizationMemberships; getOrganizationInvitations: ( params?: GetUserOrganizationInvitationsParams, ) => Promise>; @@ -190,3 +191,29 @@ export type GetUserOrganizationSuggestionsParams = { status?: OrganizationSuggestionStatus | OrganizationSuggestionStatus[]; }; + +type GetUserOrganizationMembershipOldParams = { + limit?: number; + offset?: number; +}; + +export type GetUserOrganizationMembershipParams = { + /** + * This the starting point for your fetched results. The initial value persists between re-renders + */ + initialPage?: number; + /** + * Maximum number of items returned per request. The initial value persists between re-renders + */ + pageSize?: number; +}; + +type MembershipParams = (GetUserOrganizationMembershipOldParams | GetUserOrganizationMembershipParams) & { + paginated?: boolean; +}; + +export type GetOrganizationMemberships = ( + params?: T, +) => T['paginated'] extends true + ? Promise> + : Promise;