Skip to content

Commit

Permalink
feat(types,clerk-js): Introduce OrganizationSuggestion resource and t…
Browse files Browse the repository at this point in the history
…ypes

We introduce a new OrganizationSuggestion resource and the corresponding
types, in order to preview user with suggestions of organizations they
can join. The available methods for the time being are retrieve() which
return a list of organization suggestions of the user and accept() which
allows a user to request to join the organization.
Also make available the user's suggestions from the useOrganizationList hook
  • Loading branch information
chanioxaris committed Aug 10, 2023
1 parent 96cc192 commit 940e28e
Show file tree
Hide file tree
Showing 11 changed files with 293 additions and 27 deletions.
7 changes: 7 additions & 0 deletions .changeset/orange-taxis-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': minor
'@clerk/types': minor
---

Introduce a new resource called OrganizationSuggestion along with retrieve() & accept() methods
Also make available the user's suggestions from the useOrganizationList hook
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { OrganizationSuggestion } from './internal';

describe('OrganizationSuggestion', () => {
it('has the same initial properties', () => {
const organizationSuggestion = new OrganizationSuggestion({
object: 'organization_suggestion',
id: 'test_id',
public_organization_data: {
id: 'test_org_id',
name: 'Test org',
slug: 'test-org',
image_url: 'test_image_url',
has_image: true,
},
status: 'pending',
created_at: 12345,
updated_at: 5678,
});

expect(organizationSuggestion).toMatchSnapshot();
});
});
71 changes: 71 additions & 0 deletions packages/clerk-js/src/core/resources/OrganizationSuggestion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type {
ClerkPaginatedResponse,
GetUserOrganizationSuggestionsParams,
OrganizationSuggestionJSON,
OrganizationSuggestionResource,
OrganizationSuggestionStatus,
UserOrganizationInvitationResource,
} from '@clerk/types';

import { unixEpochToDate } from '../../utils/date';
import { convertPageToOffset } from '../../utils/pagesToOffset';
import { BaseResource } from './Base';

export class OrganizationSuggestion extends BaseResource implements OrganizationSuggestionResource {
id!: string;
publicOrganizationData!: UserOrganizationInvitationResource['publicOrganizationData'];
status!: OrganizationSuggestionStatus;
createdAt!: Date;
updatedAt!: Date;

constructor(data: OrganizationSuggestionJSON) {
super();
this.fromJSON(data);
}

static async retrieve(
params?: GetUserOrganizationSuggestionsParams,
): Promise<ClerkPaginatedResponse<OrganizationSuggestion>> {
return await BaseResource._fetch({
path: '/me/organization_suggestions',
method: 'GET',
search: convertPageToOffset(params) as any,
})
.then(res => {
const { data: suggestions, total_count } =
res?.response as unknown as ClerkPaginatedResponse<OrganizationSuggestionJSON>;

return {
total_count,
data: suggestions.map(suggestion => new OrganizationSuggestion(suggestion)),
};
})
.catch(() => ({
total_count: 0,
data: [],
}));
}

accept = async (): Promise<OrganizationSuggestionResource> => {
return await this._basePost({
path: `/me/organization_suggestions/${this.id}/accept`,
});
};

protected fromJSON(data: OrganizationSuggestionJSON | null): this {
if (data) {
this.id = data.id;
this.status = data.status;
this.publicOrganizationData = {
hasImage: data.public_organization_data.has_image,
imageUrl: data.public_organization_data.image_url,
name: data.public_organization_data.name,
id: data.public_organization_data.id,
slug: data.public_organization_data.slug,
};
this.createdAt = unixEpochToDate(data.created_at);
this.updatedAt = unixEpochToDate(data.updated_at);
}
return this;
}
}
6 changes: 6 additions & 0 deletions packages/clerk-js/src/core/resources/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
ExternalAccountJSON,
ExternalAccountResource,
GetUserOrganizationInvitationsParams,
GetUserOrganizationSuggestionsParams,
ImageResource,
OrganizationMembershipResource,
PhoneNumberResource,
Expand Down Expand Up @@ -39,6 +40,7 @@ import {
ExternalAccount,
Image,
OrganizationMembership,
OrganizationSuggestion,
PhoneNumber,
SamlAccount,
SessionWithActivities,
Expand Down Expand Up @@ -260,6 +262,10 @@ export class User extends BaseResource implements UserResource {
return UserOrganizationInvitation.retrieve(params);
};

getOrganizationSuggestions = (params?: GetUserOrganizationSuggestionsParams) => {
return OrganizationSuggestion.retrieve(params);
};

getOrganizationMemberships = async (
retrieveMembership: RetrieveMembershipsParams,
): Promise<OrganizationMembership[]> => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`OrganizationSuggestion has the same initial properties 1`] = `
OrganizationSuggestion {
"accept": [Function],
"createdAt": 1970-01-01T00:00:12.345Z,
"id": "test_id",
"pathRoot": "",
"publicOrganizationData": {
"hasImage": true,
"id": "test_org_id",
"imageUrl": "test_image_url",
"name": "Test org",
"slug": "test-org",
},
"status": "pending",
"updatedAt": 1970-01-01T00:00:05.678Z,
}
`;
1 change: 1 addition & 0 deletions packages/clerk-js/src/core/resources/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from './OrganizationDomain';
export * from './OrganizationInvitation';
export * from './OrganizationMembership';
export * from './OrganizationMembershipRequest';
export * from './OrganizationSuggestion';
export * from './SamlAccount';
export * from './Session';
export * from './SessionWithActivities';
Expand Down
141 changes: 114 additions & 27 deletions packages/shared/src/hooks/useOrganizationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import type {
ClerkPaginatedResponse,
CreateOrganizationParams,
GetUserOrganizationInvitationsParams,
GetUserOrganizationSuggestionsParams,
OrganizationMembershipResource,
OrganizationResource,
OrganizationSuggestionResource,
SetActive,
UserOrganizationInvitationResource,
} from '@clerk/types';
Expand All @@ -19,6 +21,12 @@ type UseOrganizationListParams = {
infinite?: boolean;
keepPreviousData?: boolean;
});
userSuggestions?:
| true
| (GetUserOrganizationSuggestionsParams & {
infinite?: boolean;
keepPreviousData?: boolean;
});
};

type OrganizationList = ReturnType<typeof createOrganizationList>;
Expand All @@ -30,19 +38,21 @@ type UseOrganizationListReturn =
createOrganization: undefined;
setActive: undefined;
userInvitations: PaginatedResourcesWithDefault<UserOrganizationInvitationResource>;
userSuggestions: PaginatedResourcesWithDefault<OrganizationSuggestionResource>;
}
| {
isLoaded: boolean;
organizationList: OrganizationList;
createOrganization: (params: CreateOrganizationParams) => Promise<OrganizationResource>;
setActive: SetActive;
userInvitations: PaginatedResources<UserOrganizationInvitationResource>;
userSuggestions: PaginatedResources<OrganizationSuggestionResource>;
};

type UseOrganizationList = (params?: UseOrganizationListParams) => UseOrganizationListReturn;

export const useOrganizationList: UseOrganizationList = params => {
const { userInvitations } = params || {};
const { userInvitations, userSuggestions } = params || {};

const userInvitationsSafeValues = useWithSafeValues(userInvitations, {
initialPage: 1,
Expand All @@ -52,6 +62,14 @@ export const useOrganizationList: UseOrganizationList = params => {
infinite: false,
});

const userSuggestionsSafeValues = useWithSafeValues(userSuggestions, {
initialPage: 1,
pageSize: 10,
status: 'pending',
keepPreviousData: false,
infinite: false,
});

const clerk = useClerkInstanceContext();
const user = useUserContext();

Expand All @@ -64,22 +82,31 @@ export const useOrganizationList: UseOrganizationList = params => {
status: userInvitationsSafeValues.status,
};

const userSuggestionsParams =
typeof userSuggestions === 'undefined'
? undefined
: {
initialPage: userSuggestionsSafeValues.initialPage,
pageSize: userSuggestionsSafeValues.pageSize,
status: userSuggestionsSafeValues.status,
};

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

const {
data: isomorphicData,
count: isomorphicCount,
isLoading: isomorphicIsLoading,
isFetching: isomorphicIsFetching,
isError: isomorphicIsError,
page: isomorphicPage,
pageCount,
fetchPage: isomorphicSetPage,
fetchNext,
fetchPrevious,
hasNextPage,
hasPreviousPage,
unstable__mutate,
data: isomorphicDataInvitations,
count: isomorphicCountInvitations,
isLoading: isomorphicIsLoadingInvitations,
isFetching: isomorphicIsFetchingInvitations,
isError: isomorphicIsErrorInvitations,
page: isomorphicPageInvitations,
pageCount: pageCountInvitations,
fetchPage: isomorphicSetPageInvitations,
fetchNext: fetchNextInvitations,
fetchPrevious: fetchPreviousInvitations,
hasNextPage: hasNextPageInvitations,
hasPreviousPage: hasPreviousPageInvitations,
unstable__mutate: unstableMutateInvitations,
} = usePagesOrInfinite<
GetUserOrganizationInvitationsParams,
ClerkPaginatedResponse<UserOrganizationInvitationResource>
Expand All @@ -99,6 +126,36 @@ export const useOrganizationList: UseOrganizationList = params => {
},
);

const {
data: isomorphicDataSuggestions,
count: isomorphicCountSuggestions,
isLoading: isomorphicIsLoadingSuggestions,
isFetching: isomorphicIsFetchingSuggestions,
isError: isomorphicIsErrorSuggestions,
page: isomorphicPageSuggestions,
pageCount: pageCountSuggestions,
fetchPage: isomorphicSetPageSuggestions,
fetchNext: fetchNextSuggestions,
fetchPrevious: fetchPreviousSuggestions,
hasNextPage: hasNextPageSuggestions,
hasPreviousPage: hasPreviousPageSuggestions,
unstable__mutate: unstableMutateSuggestions,
} = usePagesOrInfinite<GetUserOrganizationSuggestionsParams, ClerkPaginatedResponse<OrganizationSuggestionResource>>(
{
...userSuggestionsParams,
},
user?.getOrganizationSuggestions,
{
keepPreviousData: userSuggestionsSafeValues.keepPreviousData,
infinite: userSuggestionsSafeValues.infinite,
enabled: !!userSuggestionsParams,
},
{
type: 'userSuggestions',
userId: user?.id,
},
);

// TODO: Properly check for SSR user values
if (!isClerkLoaded) {
return {
Expand All @@ -121,6 +178,21 @@ export const useOrganizationList: UseOrganizationList = params => {
hasPreviousPage: false,
unstable__mutate: undefined,
},
userSuggestions: {
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,
},
};
}

Expand All @@ -130,19 +202,34 @@ export const useOrganizationList: UseOrganizationList = params => {
setActive: clerk.setActive,
createOrganization: clerk.createOrganization,
userInvitations: {
data: isomorphicData,
count: isomorphicCount,
isLoading: isomorphicIsLoading,
isFetching: isomorphicIsFetching,
isError: isomorphicIsError,
page: isomorphicPage,
pageCount,
fetchPage: isomorphicSetPage,
fetchNext,
fetchPrevious,
hasNextPage,
hasPreviousPage,
unstable__mutate,
data: isomorphicDataInvitations,
count: isomorphicCountInvitations,
isLoading: isomorphicIsLoadingInvitations,
isFetching: isomorphicIsFetchingInvitations,
isError: isomorphicIsErrorInvitations,
page: isomorphicPageInvitations,
pageCount: pageCountInvitations,
fetchPage: isomorphicSetPageInvitations,
fetchNext: fetchNextInvitations,
fetchPrevious: fetchPreviousInvitations,
hasNextPage: hasNextPageInvitations,
hasPreviousPage: hasPreviousPageInvitations,
unstable__mutate: unstableMutateInvitations,
},
userSuggestions: {
data: isomorphicDataSuggestions,
count: isomorphicCountSuggestions,
isLoading: isomorphicIsLoadingSuggestions,
isFetching: isomorphicIsFetchingSuggestions,
isError: isomorphicIsErrorSuggestions,
page: isomorphicPageSuggestions,
pageCount: pageCountSuggestions,
fetchPage: isomorphicSetPageSuggestions,
fetchNext: fetchNextSuggestions,
fetchPrevious: fetchPreviousSuggestions,
hasNextPage: hasNextPageSuggestions,
hasPreviousPage: hasPreviousPageSuggestions,
unstable__mutate: unstableMutateSuggestions,
},
};
};
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export * from './organizationInvitation';
export * from './organizationMembership';
export * from './organizationMembershipRequest';
export * from './organizationSettings';
export * from './organizationSuggestion';
export * from './passwords';
export * from './phoneNumber';
export * from './redirects';
Expand Down
Loading

0 comments on commit 940e28e

Please sign in to comment.