Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
nikosdouvlis authored Aug 22, 2023
2 parents 514b91e + 70bd35f commit a1fdcfa
Show file tree
Hide file tree
Showing 15 changed files with 134 additions and 90 deletions.
2 changes: 2 additions & 0 deletions .changeset/light-pumas-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
10 changes: 10 additions & 0 deletions .changeset/rotten-rules-argue.md
Original file line number Diff line number Diff line change
@@ -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.
9 changes: 4 additions & 5 deletions packages/clerk-js/src/core/resources/OrganizationDomain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
OrganizationDomainVerification,
OrganizationEnrollmentMode,
PrepareAffiliationVerificationParams,
UpdateOrganizationDomainParams,
UpdateEnrollmentModeParams,
} from '@clerk/types';

import { unixEpochToDate } from '../../utils/date';
Expand Down Expand Up @@ -57,10 +57,9 @@ export class OrganizationDomain extends BaseResource implements OrganizationDoma
});
};

update = (params: UpdateOrganizationDomainParams): Promise<OrganizationDomainResource> => {
return this._basePatch({
method: 'PATCH',
path: `/organizations/${this.organizationId}/domains/${this.id}`,
updateEnrollmentMode = (params: UpdateEnrollmentModeParams): Promise<OrganizationDomainResource> => {
return this._basePost({
path: `/organizations/${this.organizationId}/domains/${this.id}/update_enrollment_mode`,
body: params,
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export class OrganizationMembershipRequest extends BaseResource implements Organ
});
};

reject = async (): Promise<OrganizationMembershipRequestResource> => {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ OrganizationDomain {
"organizationId": "test_org_id",
"pathRoot": "",
"prepareAffiliationVerification": [Function],
"update": [Function],
"updateEnrollmentMode": [Function],
"verification": null,
}
`;
Expand All @@ -27,7 +27,7 @@ OrganizationDomain {
"organizationId": "test_org_id",
"pathRoot": "",
"prepareAffiliationVerification": [Function],
"update": [Function],
"updateEnrollmentMode": [Function],
"verification": {
"attempts": 1,
"expiresAt": 1970-01-01T00:00:12.345Z,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ OrganizationMembershipRequest {
"profileImageUrl": "test_url",
"userId": undefined,
},
"reject": [Function],
"status": "pending",
"updatedAt": 1970-01-01T00:00:05.678Z,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ const InvitationRow = (props: { invitation: OrganizationInvitationResource; onRe
<Td>
<UserPreview
sx={{ maxWidth: '30ch' }}
showAvatar={false}
user={{ primaryEmailAddress: { emailAddress: invitation.emailAddress } } as any}
/>
</Td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 (
<DataTable
page={membershipRequests?.page || 1}
onPageChange={membershipRequests?.fetchPage ?? (() => 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 => (
<RequestRow
key={i.id}
request={i}
onAccept={approve(i)}
key={request.id}
request={request}
onError={card.setError}
/>
))}
/>
);
};

const RequestRow = (props: { request: OrganizationMembershipRequestResource; onAccept: () => unknown }) => {
const { request, onAccept } = props;
const RequestRow = withCardStateProvider(
(props: { request: OrganizationMembershipRequestResource; onError: ReturnType<typeof useCardState>['setError'] }) => {
const { request, onError } = props;
const card = useCardState();
const { membershipRequests } = useCoreOrganization();

return (
<RowContainer>
<Td>
<UserPreview
sx={{ maxWidth: '30ch' }}
showAvatar={false}
user={{ primaryEmailAddress: { emailAddress: request.publicUserData.identifier } } as any}
/>
</Td>
<Td>{request.createdAt.toLocaleDateString()}</Td>
const mutateSwrState = () => {
const unstable__mutate = (membershipRequests as any).unstable__mutate;
if (unstable__mutate && typeof unstable__mutate === 'function') {
unstable__mutate();
}
};

<Td>
<Flex>
<AcceptRejectRequestButtons onAccept={onAccept} />
</Flex>
</Td>
</RowContainer>
);
};
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 (
<RowContainer>
<Td>
<UserPreview
sx={{ maxWidth: '30ch' }}
showAvatar={false}
user={{ primaryEmailAddress: { emailAddress: request.publicUserData.identifier } } as any}
/>
</Td>
<Td>{request.createdAt.toLocaleDateString()}</Td>

<Td>
<AcceptRejectRequestButtons {...{ onAccept, onReject }} />
</Td>
</RowContainer>
);
},
);

const AcceptRejectRequestButtons = (props: { onAccept: () => unknown; onReject: () => unknown }) => {
const card = useCardState();
return (
<>
<Flex gap={2}>
<Button
textVariant='buttonExtraSmallBold'
variant='ghost'
isLoading={card.isLoading && card.loadingMetadata === 'reject'}
isDisabled={card.isLoading && card.loadingMetadata !== 'reject'}
onClick={props.onReject}
localizationKey={localizationKeys('organizationProfile.membersPage.requestsTab.menuAction__reject')}
/>

<Button
textVariant='buttonExtraSmallBold'
variant='solid'
isLoading={card.isLoading}
isLoading={card.isLoading && card.loadingMetadata === 'accept'}
isDisabled={card.isLoading && card.loadingMetadata !== 'accept'}
onClick={props.onAccept}
localizationKey={localizationKeys('organizationProfile.membersPage.requestsTab.menuAction__approve')}
/>
</>
</Flex>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export const VerifiedDomainPage = withCardStateProvider(() => {
}

try {
await domain.update({
await domain.updateEnrollmentMode({
enrollmentMode: enrollmentMode.value as OrganizationEnrollmentMode,
});

Expand Down
67 changes: 35 additions & 32 deletions packages/clerk-js/src/ui/elements/UserPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,39 +77,42 @@ export const UserPreview = (props: UserPreviewProps) => {
sx={[{ minWidth: '0px', width: '100%' }, sx]}
{...rest}
>
{showAvatar ? (
<Flex
elementDescriptor={descriptors.userPreviewAvatarContainer}
elementId={descriptors.userPreviewAvatarContainer.setId(elementId)}
justify='center'
sx={{ position: 'relative' }}
>
<UserAvatar
boxElementDescriptor={descriptors.userPreviewAvatarBox}
imageElementDescriptor={descriptors.userPreviewAvatarImage}
{...user}
{...externalAccount}
{...samlAccount}
name={name}
avatarUrl={imageUrl}
size={getAvatarSizes}
sx={avatarSx}
rounded={rounded}
/>
{/*Do not attempt to render or reserve space based on height if image url is not defined*/}
{imageUrl ? (
showAvatar ? (
<Flex
elementDescriptor={descriptors.userPreviewAvatarContainer}
elementId={descriptors.userPreviewAvatarContainer.setId(elementId)}
justify='center'
sx={{ position: 'relative' }}
>
<UserAvatar
boxElementDescriptor={descriptors.userPreviewAvatarBox}
imageElementDescriptor={descriptors.userPreviewAvatarImage}
{...user}
{...externalAccount}
{...samlAccount}
name={name}
avatarUrl={imageUrl}
size={getAvatarSizes}
sx={avatarSx}
rounded={rounded}
/>

{icon && <Flex sx={{ position: 'absolute', left: 0, bottom: 0 }}>{icon}</Flex>}
</Flex>
) : (
// Reserve layout space when avatar is not visible
<Flex
elementDescriptor={descriptors.userPreviewAvatarContainer}
elementId={descriptors.userPreviewAvatarContainer.setId(elementId)}
justify='center'
sx={t => ({
height: getAvatarSizes(t),
})}
/>
)}
{icon && <Flex sx={{ position: 'absolute', left: 0, bottom: 0 }}>{icon}</Flex>}
</Flex>
) : (
// Reserve layout space when avatar is not visible
<Flex
elementDescriptor={descriptors.userPreviewAvatarContainer}
elementId={descriptors.userPreviewAvatarContainer.setId(elementId)}
justify='center'
sx={t => ({
height: getAvatarSizes(t),
})}
/>
)
) : null}

<Flex
elementDescriptor={descriptors.userPreviewTextContainer}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ const useCardState = () => {
const setIdle = (metadata?: Metadata) => setState(s => ({ ...s, status: 'idle', metadata }));
const setError = (metadata: ClerkAPIError | Metadata) => setState(s => ({ ...s, error: translateError(metadata) }));
const setLoading = (metadata?: Metadata) => setState(s => ({ ...s, status: 'loading', metadata }));
const runAsync = async <T = unknown,>(cb: Promise<T> | (() => Promise<T>)) => {
setLoading();
const runAsync = async <T = unknown,>(cb: Promise<T> | (() => Promise<T>), metadata?: Metadata) => {
setLoading(metadata);
return (typeof cb === 'function' ? cb() : cb)
.then(res => {
return res;
})
.finally(() => setIdle());
.finally(() => setIdle(metadata));
};

return {
Expand Down
1 change: 1 addition & 0 deletions packages/localizations/src/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,7 @@ export const enUS: LocalizationResource = {
requestsTab: {
tableHeader__requested: 'Requested access',
menuAction__approve: 'Approve',
menuAction__reject: 'Reject',
table__emptyRow: 'No requests to display',
requests: {
headerTitle: 'Requests',
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ type _LocalizationResource = {
requestsTab: {
tableHeader__requested: LocalizationValue;
menuAction__approve: LocalizationValue;
menuAction__reject: LocalizationValue;
table__emptyRow: LocalizationValue;
requests: {
headerTitle: LocalizationValue;
Expand Down
6 changes: 4 additions & 2 deletions packages/types/src/organizationDomain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface OrganizationDomainResource extends ClerkResource {

attemptAffiliationVerification: (params: AttemptAffiliationVerificationParams) => Promise<OrganizationDomainResource>;
delete: () => Promise<void>;
update: (params: UpdateOrganizationDomainParams) => Promise<OrganizationDomainResource>;
updateEnrollmentMode: (params: UpdateEnrollmentModeParams) => Promise<OrganizationDomainResource>;
}

export type PrepareAffiliationVerificationParams = {
Expand All @@ -35,4 +35,6 @@ export type AttemptAffiliationVerificationParams = {
code: string;
};

export type UpdateOrganizationDomainParams = Partial<Pick<OrganizationDomainResource, 'enrollmentMode'>>;
export type UpdateEnrollmentModeParams = Pick<OrganizationDomainResource, 'enrollmentMode'> & {
deleteExisting?: boolean;
};
1 change: 1 addition & 0 deletions packages/types/src/organizationMembershipRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export interface OrganizationMembershipRequestResource extends ClerkResource {
updatedAt: Date;

accept: () => Promise<OrganizationMembershipRequestResource>;
reject: () => Promise<OrganizationMembershipRequestResource>;
}

0 comments on commit a1fdcfa

Please sign in to comment.