From e02a1aff2d4b1478601a2e7b598d600ab3902169 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Mon, 21 Aug 2023 14:57:33 +0300 Subject: [PATCH 1/2] feat(clerk-js): Reject a membership request as an organization admin (#1612) * feat(clerk-js): Add reject method to OrganizationMembershipRequest * feat(clerk-js): Organization admins can reject membership requests * test(clerk-js): Update snapshot of OrganizationMembership class * chore(clerk-js): Add changeset --- .changeset/rotten-rules-argue.md | 10 ++ .../OrganizationMembershipRequest.ts | 6 + ...OrganizationMembershipRequest.test.ts.snap | 1 + .../InvitedMembersList.tsx | 1 - .../OrganizationProfile/RequestToJoinList.tsx | 107 +++++++++++------- .../clerk-js/src/ui/elements/UserPreview.tsx | 67 +++++------ .../ui/elements/contexts/CardStateContext.tsx | 6 +- packages/localizations/src/en-US.ts | 1 + packages/types/src/localization.ts | 1 + .../src/organizationMembershipRequest.ts | 1 + 10 files changed, 121 insertions(+), 80 deletions(-) create mode 100644 .changeset/rotten-rules-argue.md diff --git a/.changeset/rotten-rules-argue.md b/.changeset/rotten-rules-argue.md new file mode 100644 index 0000000000..4b22bdb902 --- /dev/null +++ b/.changeset/rotten-rules-argue.md @@ -0,0 +1,10 @@ +--- +'@clerk/localizations': patch +'@clerk/clerk-js': patch +'@clerk/types': patch +--- + +A OrganizationMembershipRequest can now be rejected + +- New `OrganizationMembershipRequest.reject` method alongside `accept` +- As an organization admin, navigate to `Organization Profile` > `Members` > `Requests`. You can now reject a request from the table. diff --git a/packages/clerk-js/src/core/resources/OrganizationMembershipRequest.ts b/packages/clerk-js/src/core/resources/OrganizationMembershipRequest.ts index 1beb70211e..071315c426 100644 --- a/packages/clerk-js/src/core/resources/OrganizationMembershipRequest.ts +++ b/packages/clerk-js/src/core/resources/OrganizationMembershipRequest.ts @@ -23,6 +23,12 @@ export class OrganizationMembershipRequest extends BaseResource implements Organ }); }; + reject = async (): Promise => { + return await this._basePost({ + path: `/organizations/${this.organizationId}/membership_requests/${this.id}/reject`, + }); + }; + protected fromJSON(data: OrganizationMembershipRequestJSON | null): this { if (data) { this.id = data.id; diff --git a/packages/clerk-js/src/core/resources/__snapshots__/OrganizationMembershipRequest.test.ts.snap b/packages/clerk-js/src/core/resources/__snapshots__/OrganizationMembershipRequest.test.ts.snap index b1d4f1c218..9f0583a730 100644 --- a/packages/clerk-js/src/core/resources/__snapshots__/OrganizationMembershipRequest.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__snapshots__/OrganizationMembershipRequest.test.ts.snap @@ -16,6 +16,7 @@ OrganizationMembershipRequest { "profileImageUrl": "test_url", "userId": undefined, }, + "reject": [Function], "status": "pending", "updatedAt": 1970-01-01T00:00:05.678Z, } diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/InvitedMembersList.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/InvitedMembersList.tsx index 674d5b22e2..17e96f12bf 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/InvitedMembersList.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/InvitedMembersList.tsx @@ -66,7 +66,6 @@ const InvitationRow = (props: { invitation: OrganizationInvitationResource; onRe diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/RequestToJoinList.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/RequestToJoinList.tsx index 432562f86e..d78a6e3d0f 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/RequestToJoinList.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/RequestToJoinList.tsx @@ -2,7 +2,7 @@ import type { OrganizationMembershipRequestResource } from '@clerk/types'; import { useCoreOrganization } from '../../contexts'; import { Button, Flex, localizationKeys, Td } from '../../customizables'; -import { useCardState, UserPreview } from '../../elements'; +import { useCardState, UserPreview, withCardStateProvider } from '../../elements'; import { handleError } from '../../utils'; import { DataTable, RowContainer } from './MemberListTable'; @@ -15,82 +15,101 @@ export const RequestToJoinList = () => { }, }); - const mutateSwrState = () => { - const unstable__mutate = (membershipRequests as any).unstable__mutate; - if (unstable__mutate && typeof unstable__mutate === 'function') { - unstable__mutate(); - } - }; - if (!organization) { return null; } - const approve = (request: OrganizationMembershipRequestResource) => () => { - return card - .runAsync(request.accept) - .then(mutateSwrState) - .catch(err => handleError(err, [], card.setError)); - }; - return ( null)} itemCount={membershipRequests?.count ?? 0} itemsPerPage={ITEMS_PER_PAGE} - isLoading={membershipRequests?.isFetching} + isLoading={membershipRequests?.isLoading} emptyStateLocalizationKey={localizationKeys('organizationProfile.membersPage.requestsTab.table__emptyRow')} headers={[ localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__user'), localizationKeys('organizationProfile.membersPage.requestsTab.tableHeader__requested'), localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__actions'), ]} - rows={(membershipRequests?.data || []).map(i => ( + rows={(membershipRequests?.data || []).map(request => ( ))} /> ); }; -const RequestRow = (props: { request: OrganizationMembershipRequestResource; onAccept: () => unknown }) => { - const { request, onAccept } = props; +const RequestRow = withCardStateProvider( + (props: { request: OrganizationMembershipRequestResource; onError: ReturnType['setError'] }) => { + const { request, onError } = props; + const card = useCardState(); + const { membershipRequests } = useCoreOrganization(); - return ( - - - - - {request.createdAt.toLocaleDateString()} + const mutateSwrState = () => { + const unstable__mutate = (membershipRequests as any).unstable__mutate; + if (unstable__mutate && typeof unstable__mutate === 'function') { + unstable__mutate(); + } + }; - - - - - - - ); -}; + const onAccept = () => { + return card + .runAsync(request.accept, 'accept') + .then(mutateSwrState) + .catch(err => handleError(err, [], onError)); + }; + + const onReject = () => { + return card + .runAsync(request.reject, 'reject') + .then(mutateSwrState) + .catch(err => handleError(err, [], onError)); + }; -const AcceptRejectRequestButtons = (props: { onAccept: () => unknown }) => { + return ( + + + + + {request.createdAt.toLocaleDateString()} + + + + + + ); + }, +); + +const AcceptRejectRequestButtons = (props: { onAccept: () => unknown; onReject: () => unknown }) => { const card = useCardState(); return ( - <> + +