Skip to content

Commit

Permalink
feat(clerk-js): Support Paginated response in OrganizationMembership.…
Browse files Browse the repository at this point in the history
…retrieve & `userMemberships` in useOrganizationList (#1606)

* feat(clerk-js): Support Paginated response in OrganizationMembership.retrieve

* feat(shared): Introduce userMemberships in useOrganizationList

* chore(clerk-js): Mark limit and offset as deprecated

* chore(clerk-js): Add changeset
  • Loading branch information
panteliselef authored Aug 17, 2023
1 parent 0783e62 commit 435d2cf
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 13 deletions.
9 changes: 9 additions & 0 deletions .changeset/gold-jeans-turn.md
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,9 @@ export default class Clerk implements ClerkInterface {
return Organization.create({ name, slug });
};

/**
* @deprecated use User.getOrganizationMemberships
*/
public getOrganizationMemberships = async (): Promise<OrganizationMembership[]> => {
return await OrganizationMembership.retrieve();
};
Expand Down
55 changes: 49 additions & 6 deletions packages/clerk-js/src/core/resources/OrganizationMembership.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import type {
ClerkPaginatedResponse,
ClerkResourceReloadParams,
GetUserOrganizationMembershipParams,
MembershipRole,
OrganizationMembershipJSON,
OrganizationMembershipResource,
PublicUserData,
} 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 {
Expand All @@ -23,18 +26,40 @@ export class OrganizationMembership extends BaseResource implements Organization
this.fromJSON(data);
}

static async retrieve(retrieveMembershipsParams?: RetrieveMembershipsParams): Promise<OrganizationMembership[]> {
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<OrganizationMembershipJSON>;

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<OrganizationMembership> => {
// TODO: Revise the return type of _baseDelete
Expand Down Expand Up @@ -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 = <T extends MembershipParams>(
params?: T,
) => T['paginated'] extends true
? Promise<ClerkPaginatedResponse<OrganizationMembership>>
: Promise<OrganizationMembership[]>;
9 changes: 3 additions & 6 deletions packages/clerk-js/src/core/resources/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
EmailAddressResource,
ExternalAccountJSON,
ExternalAccountResource,
GetOrganizationMemberships,
GetUserOrganizationInvitationsParams,
GetUserOrganizationSuggestionsParams,
ImageResource,
Expand All @@ -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,
Expand Down Expand Up @@ -266,11 +266,8 @@ export class User extends BaseResource implements UserResource {
return OrganizationSuggestion.retrieve(params);
};

getOrganizationMemberships = async (
retrieveMembership: RetrieveMembershipsParams,
): Promise<OrganizationMembership[]> => {
return await OrganizationMembership.retrieve(retrieveMembership);
};
getOrganizationMemberships: GetOrganizationMemberships = retrieveMembership =>
OrganizationMembership.retrieve(retrieveMembership);

get verifiedExternalAccounts() {
return this.externalAccounts.filter(externalAccount => externalAccount.verification?.status == 'verified');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { useCoreOrganizationList } from '../../contexts';

export const organizationListParams = {
userMemberships: {
infinite: true,
},
userInvitations: {
infinite: true,
},
Expand Down
68 changes: 67 additions & 1 deletion packages/shared/src/hooks/useOrganizationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
ClerkPaginatedResponse,
CreateOrganizationParams,
GetUserOrganizationInvitationsParams,
GetUserOrganizationMembershipParams,
GetUserOrganizationSuggestionsParams,
OrganizationMembershipResource,
OrganizationResource,
Expand All @@ -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 & {
Expand All @@ -34,25 +41,40 @@ type OrganizationList = ReturnType<typeof createOrganizationList>;
type UseOrganizationListReturn =
| {
isLoaded: false;
/**
* @deprecated Use userMemberships instead
*/
organizationList: undefined;
createOrganization: undefined;
setActive: undefined;
userMemberships: PaginatedResourcesWithDefault<OrganizationMembershipResource>;
userInvitations: PaginatedResourcesWithDefault<UserOrganizationInvitationResource>;
userSuggestions: PaginatedResourcesWithDefault<OrganizationSuggestionResource>;
}
| {
isLoaded: boolean;
/**
* @deprecated Use userMemberships instead
*/
organizationList: OrganizationList;
createOrganization: (params: CreateOrganizationParams) => Promise<OrganizationResource>;
setActive: SetActive;
userMemberships: PaginatedResources<OrganizationMembershipResource>;
userInvitations: PaginatedResources<UserOrganizationInvitationResource>;
userSuggestions: PaginatedResources<OrganizationSuggestionResource>;
};

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,
Expand All @@ -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
Expand All @@ -93,6 +123,26 @@ export const useOrganizationList: UseOrganizationList = params => {

const isClerkLoaded = !!(clerk.loaded && user);

const memberships = usePagesOrInfinite<
GetUserOrganizationMembershipParams,
ClerkPaginatedResponse<OrganizationMembershipResource>
>(
{
...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<UserOrganizationInvitationResource>
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
};
Expand Down
27 changes: 27 additions & 0 deletions packages/types/src/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export interface UserResource extends ClerkResource {
getSessions: () => Promise<SessionWithActivitiesResource[]>;
setProfileImage: (params: SetProfileImageParams) => Promise<ImageResource>;
createExternalAccount: (params: CreateExternalAccountParams) => Promise<ExternalAccountResource>;
getOrganizationMemberships: GetOrganizationMemberships;
getOrganizationInvitations: (
params?: GetUserOrganizationInvitationsParams,
) => Promise<ClerkPaginatedResponse<UserOrganizationInvitationResource>>;
Expand Down Expand Up @@ -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 = <T extends MembershipParams>(
params?: T,
) => T['paginated'] extends true
? Promise<ClerkPaginatedResponse<OrganizationMembershipResource>>
: Promise<OrganizationMembershipResource[]>;

0 comments on commit 435d2cf

Please sign in to comment.