Skip to content

Commit

Permalink
feat(clerk-js): Allow skipping invitation screen after CreateOrganiza…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
panteliselef committed Jul 20, 2023
1 parent 6715616 commit 6fa4768
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 2 deletions.
6 changes: 6 additions & 0 deletions .changeset/nervous-flies-sleep.md
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': patch
'@clerk/types': patch
---

Introduce the `skipInvitationScreen` prop on `<CreateOrganization />` component
Expand Up @@ -25,7 +25,7 @@ export const CreateOrganizationPage = withCardStateProvider(() => {
const [file, setFile] = React.useState<File | null>();
const { createOrganization, isLoaded } = useCoreOrganizations();
const { setActive, closeCreateOrganization } = useCoreClerk();
const { mode, navigateAfterCreateOrganization } = useCreateOrganizationContext();
const { mode, navigateAfterCreateOrganization, skipInvitationScreen } = useCreateOrganizationContext();
const { organization } = useCoreOrganization();

const wizard = useWizard({ onNextStep: () => card.setError(undefined) });
Expand Down Expand Up @@ -63,7 +63,7 @@ export const CreateOrganizationPage = withCardStateProvider(() => {

await setActive({ organization });

if (organization.maxAllowedMemberships === 1) {
if (skipInvitationScreen ?? organization.maxAllowedMemberships === 1) {
return completeFlow();
}

Expand Down
@@ -0,0 +1,160 @@
import type { OrganizationResource } from '@clerk/types';
import { describe, jest } from '@jest/globals';
import { waitFor } from '@testing-library/dom';
import React from 'react';

import { render } from '../../../../testUtils';
import { bindCreateFixtures } from '../../../utils/test/createFixtures';
import { CreateOrganization } from '../CreateOrganization';

const { createFixtures } = bindCreateFixtures('CreateOrganization');

type FakeOrganizationParams = {
id: string;
createdAt?: Date;
imageUrl?: string;
logoUrl?: string;
slug: string;
name: string;
membersCount: number;
pendingInvitationsCount: number;
adminDeleteEnabled: boolean;
maxAllowedMemberships: number;
};

const createFakeOrganization = (params: FakeOrganizationParams): OrganizationResource => {
return {
logoUrl: null,
pathRoot: '',
id: params.id,
name: params.name,
slug: params.slug,
imageUrl: params.imageUrl || '',
membersCount: params.membersCount,
pendingInvitationsCount: params.pendingInvitationsCount,
publicMetadata: {},
adminDeleteEnabled: params.adminDeleteEnabled,
maxAllowedMemberships: params?.maxAllowedMemberships,
createdAt: params?.createdAt || new Date(),
updatedAt: new Date(),
update: jest.fn() as any,
getMemberships: jest.fn() as any,
getPendingInvitations: jest.fn() as any,
addMember: jest.fn() as any,
inviteMember: jest.fn() as any,
inviteMembers: jest.fn() as any,
updateMember: jest.fn() as any,
removeMember: jest.fn() as any,
destroy: jest.fn() as any,
setLogo: jest.fn() as any,
reload: jest.fn() as any,
};
};

const getCreatedOrg = (params: Partial<FakeOrganizationParams>) =>
createFakeOrganization({
id: '1',
adminDeleteEnabled: false,
maxAllowedMemberships: 1,
membersCount: 1,
name: 'new org',
pendingInvitationsCount: 0,
slug: 'new-org',
...params,
});

describe('CreateOrganization', () => {
it('renders component', async () => {
const { wrapper } = await createFixtures(f => {
f.withOrganizations();
f.withUser({
email_addresses: ['test@clerk.dev'],
});
});
const { getByText } = render(<CreateOrganization />, { wrapper });
expect(getByText('Create Organization')).toBeInTheDocument();
});

it('skips invitation screen', async () => {
const { wrapper, fixtures, props } = await createFixtures(f => {
f.withOrganizations();
f.withUser({
email_addresses: ['test@clerk.dev'],
});
});

fixtures.clerk.createOrganization.mockReturnValue(
Promise.resolve(
getCreatedOrg({
maxAllowedMemberships: 3,
}),
),
);

props.setProps({ skipInvitationScreen: true });
const { getByRole, userEvent, getByLabelText, queryByText } = render(<CreateOrganization />, {
wrapper,
});
await userEvent.type(getByLabelText(/Organization name/i), 'new org');
await userEvent.click(getByRole('button', { name: /create organization/i }));

await waitFor(() => {
expect(queryByText(/Invite members/i)).not.toBeInTheDocument();
});
});

it('always visit invitation screen', async () => {
const { wrapper, fixtures, props } = await createFixtures(f => {
f.withOrganizations();
f.withUser({
email_addresses: ['test@clerk.dev'],
});
});

fixtures.clerk.createOrganization.mockReturnValue(
Promise.resolve(
getCreatedOrg({
maxAllowedMemberships: 1,
}),
),
);

props.setProps({ skipInvitationScreen: false });
const { getByRole, userEvent, getByLabelText, queryByText } = render(<CreateOrganization />, {
wrapper,
});
await userEvent.type(getByLabelText(/Organization name/i), 'new org');
await userEvent.click(getByRole('button', { name: /create organization/i }));

await waitFor(() => {
expect(queryByText(/Invite members/i)).toBeInTheDocument();
});
});

it('auto skip invitation screen', async () => {
const { wrapper, fixtures } = await createFixtures(f => {
f.withOrganizations();
f.withUser({
email_addresses: ['test@clerk.dev'],
});
});

fixtures.clerk.createOrganization.mockReturnValue(
Promise.resolve(
getCreatedOrg({
maxAllowedMemberships: 1,
}),
),
);

const { getByRole, userEvent, getByLabelText, queryByText } = render(<CreateOrganization />, {
wrapper,
});
await userEvent.type(getByLabelText(/Organization name/i), 'new org');
await userEvent.click(getByRole('button', { name: /create organization/i }));

await waitFor(() => {
expect(queryByText(/Invite members/i)).not.toBeInTheDocument();
});
});
});
6 changes: 6 additions & 0 deletions packages/types/src/clerk.ts
Expand Up @@ -662,6 +662,12 @@ export type CreateOrganizationProps = {
* @default undefined
*/
afterCreateOrganizationUrl?: string;
/**
* Hides the screen for sending invitations after an organization is created.
* @default undefined When left undefined Clerk will automatically hide the screen if
* the number of max allowed members is equal to 1
*/
skipInvitationScreen?: boolean;
/**
* Customisation options to fully match the Clerk components to your own brand.
* These options serve as overrides and will be merged with the global `appearance`
Expand Down

0 comments on commit 6fa4768

Please sign in to comment.