Skip to content

Commit

Permalink
feat(clerk-js,types,localizations): Introduces Membership Requests in…
Browse files Browse the repository at this point in the history
… <OrganizationProfile /> (#1576)

* feat(clerk-js): Fetch domains by enrollmentMode

* feat(clerk-js): Add Requests tabs in OrganizationMembers

* test(clerk-js): Update snapshot of OrganizationMembershipRequest

* chore(clerk-js): Rename MemberListTable to DataTable and make it more generic

* chore(clerk-js): Add changeset

* chore(clerk-js): Increase OrganizationProfile chunk by 0.5KB
  • Loading branch information
panteliselef authored Aug 10, 2023
1 parent 96cc192 commit 8d1e7d7
Show file tree
Hide file tree
Showing 17 changed files with 378 additions and 126 deletions.
10 changes: 10 additions & 0 deletions .changeset/giant-timers-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@clerk/localizations': patch
'@clerk/clerk-js': patch
'@clerk/shared': patch
'@clerk/types': patch
---

Introduces Membership Requests in <OrganizationProfile />

- This is a list of users that have requested to join the active organization
2 changes: 1 addition & 1 deletion packages/clerk-js/bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{ "path": "./dist/vendors*.js", "maxSize": "70KB" },
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },
{ "path": "./dist/impersonationfab*.js", "maxSize": "5KB" },
{ "path": "./dist/organizationprofile*.js", "maxSize": "8KB" },
{ "path": "./dist/organizationprofile*.js", "maxSize": "8.5KB" },
{ "path": "./dist/organizationswitcher*.js", "maxSize": "5KB" },
{ "path": "./dist/signin*.js", "maxSize": "10KB" },
{ "path": "./dist/signup*.js", "maxSize": "10KB" },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { OrganizationInvitationStatus, OrganizationMembershipRequestResource, PublicUserData } from '@clerk/types';
import type { OrganizationMembershipRequestJSON } from '@clerk/types';

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

export class OrganizationMembershipRequest extends BaseResource implements OrganizationMembershipRequestResource {
Expand All @@ -27,6 +28,8 @@ export class OrganizationMembershipRequest extends BaseResource implements Organ
this.id = data.id;
this.organizationId = data.organization_id;
this.status = data.status;
this.createdAt = unixEpochToDate(data.created_at);
this.updatedAt = unixEpochToDate(data.updated_at);
if (data.public_user_data) {
this.publicUserData = {
firstName: data.public_user_data.first_name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
exports[`OrganizationMembership has the same initial properties 1`] = `
OrganizationMembershipRequest {
"accept": [Function],
"createdAt": 1970-01-01T00:00:12.345Z,
"id": "test_id",
"organizationId": "test_org_id",
"pathRoot": "",
Expand All @@ -16,5 +17,6 @@ OrganizationMembershipRequest {
"userId": undefined,
},
"status": "pending",
"updatedAt": 1970-01-01T00:00:05.678Z,
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useCoreOrganization, useCoreUser } from '../../contexts';
import { Badge, localizationKeys, Td, Text } from '../../customizables';
import { ThreeDotsMenu, useCardState, usePagination, UserPreview } from '../../elements';
import { handleError, roleLocalizationKey } from '../../utils';
import { MembersListTable, RoleSelect, RowContainer } from './MemberListTable';
import { DataTable, RoleSelect, RowContainer } from './MemberListTable';

const ITEMS_PER_PAGE = 10;

Expand Down Expand Up @@ -53,12 +53,13 @@ export const ActiveMembersList = () => {
};

return (
<MembersListTable
<DataTable
page={page}
onPageChange={changePage}
itemCount={organization.membersCount}
itemsPerPage={ITEMS_PER_PAGE}
isLoading={!membershipList}
emptyStateLocalizationKey={localizationKeys('organizationProfile.membersPage.detailsTitle__emptyRow')}
headers={[
localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__user'),
localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__joined'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useCoreOrganization } from '../../contexts';
import { localizationKeys, Td, Text } from '../../customizables';
import { ThreeDotsMenu, useCardState, usePagination, UserPreview } from '../../elements';
import { handleError, roleLocalizationKey } from '../../utils';
import { MembersListTable, RowContainer } from './MemberListTable';
import { DataTable, RowContainer } from './MemberListTable';

const ITEMS_PER_PAGE = 10;

Expand Down Expand Up @@ -35,12 +35,13 @@ export const InvitedMembersList = () => {
};

return (
<MembersListTable
<DataTable
page={page}
onPageChange={changePage}
itemCount={organization.pendingInvitationsCount}
itemsPerPage={ITEMS_PER_PAGE}
isLoading={!invitationList}
emptyStateLocalizationKey={localizationKeys('organizationProfile.membersPage.invitationsTab.table__emptyRow')}
headers={[
localizationKeys('organizationProfile.membersPage.activeMembersTab.tableHeader__user'),
localizationKeys('organizationProfile.membersPage.invitedMembersTab.tableHeader__invited'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,7 @@ import type { MembershipRole } from '@clerk/types';
import React from 'react';

import type { LocalizationKey } from '../../customizables';
import {
Col,
Flex,
localizationKeys,
Spinner,
Table,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
useLocalizations,
} from '../../customizables';
import { Col, Flex, Spinner, Table, Tbody, Td, Text, Th, Thead, Tr, useLocalizations } from '../../customizables';
import { Pagination, Select, SelectButton, SelectOptionList } from '../../elements';
import type { PropsOfComponent } from '../../styledSystem';
import { roleLocalizationKey } from '../../utils';
Expand All @@ -28,10 +15,11 @@ type MembersListTableProps = {
onPageChange: (page: number) => void;
itemCount: number;
itemsPerPage: number;
emptyStateLocalizationKey: LocalizationKey;
};

export const MembersListTable = (props: MembersListTableProps) => {
const { headers, page, onPageChange, rows, isLoading, itemCount, itemsPerPage } = props;
export const DataTable = (props: MembersListTableProps) => {
const { headers, page, onPageChange, rows, isLoading, itemCount, itemsPerPage, emptyStateLocalizationKey } = props;

const pageCount = rows.length !== 0 ? Math.ceil(itemCount / itemsPerPage) : 1;
const startRowIndex = (page - 1) * rows.length;
Expand Down Expand Up @@ -62,7 +50,10 @@ export const MembersListTable = (props: MembersListTableProps) => {
</Td>
</Tr>
) : !rows.length ? (
<EmptyRow key='empty' />
<EmptyRow
key='empty'
localizationKey={emptyStateLocalizationKey}
/>
) : (
rows
)}
Expand All @@ -86,12 +77,12 @@ export const MembersListTable = (props: MembersListTableProps) => {
);
};

const EmptyRow = () => {
const EmptyRow = (props: { localizationKey: LocalizationKey }) => {
return (
<Tr>
<Td colSpan={4}>
<Text
localizationKey={localizationKeys('organizationProfile.membersPage.detailsTitle__emptyRow')}
localizationKey={props.localizationKey}
sx={t => ({
margin: 'auto',
display: 'block',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { CalloutWithAction } from '../../common';
import { useCoreOrganization, useOrganizationProfileContext } from '../../contexts';
import { Col, descriptors, Flex, Icon, localizationKeys } from '../../customizables';
import { Col, descriptors, Flex, localizationKeys } from '../../customizables';
import {
CardAlert,
Header,
IconButton,
NavbarMenuButtonRow,
Tab,
TabPanel,
Expand All @@ -14,15 +12,12 @@ import {
useCardState,
withCardStateProvider,
} from '../../elements';
import { UserAdd } from '../../icons';
import { useRouter } from '../../router';
import { ActiveMembersList } from './ActiveMembersList';
import { DomainList } from './DomainList';
import { InvitedMembersList } from './InvitedMembersList';
import { MembershipWidget } from './MembershipWidget';
import { OrganizationMembersTabInvitations } from './OrganizationMembersTabInvitations';
import { OrganizationMembersTabRequests } from './OrganizationMembersTabRequests';

export const OrganizationMembers = withCardStateProvider(() => {
const { navigate } = useRouter();
const card = useCardState();
const { membership } = useCoreOrganization();
//@ts-expect-error
Expand Down Expand Up @@ -56,6 +51,9 @@ export const OrganizationMembers = withCardStateProvider(() => {
localizationKey={localizationKeys('organizationProfile.membersPage.start.headerTitle__invitations')}
/>
)}
{isAdmin && (
<Tab localizationKey={localizationKeys('organizationProfile.membersPage.start.headerTitle__requests')} />
)}
</TabsList>
<TabPanels>
<TabPanel sx={{ width: '100%' }}>
Expand All @@ -72,96 +70,12 @@ export const OrganizationMembers = withCardStateProvider(() => {
</TabPanel>
{isAdmin && (
<TabPanel sx={{ width: '100%' }}>
<Col
gap={8}
sx={{
width: '100%',
}}
>
{isAdmin && __unstable_manageBillingUrl && <MembershipWidget />}
<Col
gap={2}
sx={{
width: '100%',
}}
>
<Header.Root>
<Header.Title
localizationKey={localizationKeys(
'organizationProfile.membersPage.invitationsTab.autoInvitations.headerTitle',
)}
textVariant='largeMedium'
/>
<Header.Subtitle
localizationKey={localizationKeys(
'organizationProfile.membersPage.invitationsTab.autoInvitations.headerSubtitle',
)}
variant='regularRegular'
/>
</Header.Root>
<DomainList
fallback={
<CalloutWithAction
text={localizationKeys(
'organizationProfile.membersPage.invitationsTab.autoInvitations.calloutTextLabel',
)}
actionLabel={localizationKeys(
'organizationProfile.membersPage.invitationsTab.autoInvitations.calloutActionLabel',
)}
onClick={() => navigate('organization-settings/domain')}
/>
}
redirectSubPath={'organization-settings/domain/'}
verificationStatus={'verified'}
enrollmentMode={'automatic_invitation'}
/>
</Col>

<Flex
direction='col'
gap={4}
sx={{
width: '100%',
}}
>
<Flex
justify={'between'}
align={'center'}
>
<Header.Root>
<Header.Title
localizationKey={localizationKeys(
'organizationProfile.membersPage.invitationsTab.manualInvitations.headerTitle',
)}
textVariant='largeMedium'
/>
<Header.Subtitle
localizationKey={localizationKeys(
'organizationProfile.membersPage.invitationsTab.manualInvitations.headerSubtitle',
)}
variant='regularRegular'
/>
</Header.Root>
{isAdmin && (
<IconButton
elementDescriptor={descriptors.membersPageInviteButton}
aria-label='Invite'
onClick={() => navigate('invite-members')}
icon={
<Icon
icon={UserAdd}
size={'sm'}
sx={t => ({ marginRight: t.space.$2 })}
/>
}
textVariant='buttonExtraSmallBold'
localizationKey={localizationKeys('organizationProfile.membersPage.action__invite')}
/>
)}
</Flex>
<InvitedMembersList />
</Flex>
</Col>
<OrganizationMembersTabInvitations />
</TabPanel>
)}
{isAdmin && (
<TabPanel sx={{ width: '100%' }}>
<OrganizationMembersTabRequests />
</TabPanel>
)}
</TabPanels>
Expand Down
Loading

0 comments on commit 8d1e7d7

Please sign in to comment.