Skip to content

Commit

Permalink
feat(clerk-js,types): Add hideSlug prop to Organization components (#…
Browse files Browse the repository at this point in the history
…3882)

Co-authored-by: panteliselef <[email protected]>
  • Loading branch information
wobsoriano and panteliselef authored Aug 5, 2024
1 parent 020c889 commit 9b2aeac
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 14 deletions.
6 changes: 6 additions & 0 deletions .changeset/polite-tigers-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@clerk/clerk-js": patch
"@clerk/types": patch
---

Add option to hide the slug field in the `<CreateOrganization />`, `<OrganizationSwitcher />`, and `<OrganizationList />` components
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useOrganization, useOrganizationList } from '@clerk/shared/react';
import type { OrganizationResource } from '@clerk/types';
import type { CreateOrganizationParams, OrganizationResource } from '@clerk/types';
import React from 'react';

import { useWizard, Wizard } from '../../common';
Expand Down Expand Up @@ -33,6 +33,7 @@ type CreateOrganizationFormProps = {
headerTitle?: LocalizationKey;
headerSubtitle?: LocalizationKey;
};
hideSlug?: boolean;
};

export const CreateOrganizationForm = withCardStateProvider((props: CreateOrganizationFormProps) => {
Expand Down Expand Up @@ -72,7 +73,13 @@ export const CreateOrganizationForm = withCardStateProvider((props: CreateOrgani
}

try {
const organization = await createOrganization({ name: nameField.value, slug: slugField.value });
const createOrgParams: CreateOrganizationParams = { name: nameField.value };

if (!props.hideSlug) {
createOrgParams.slug = slugField.value;
}

const organization = await createOrganization(createOrgParams);
if (file) {
await organization.setLogo({ file });
}
Expand Down Expand Up @@ -181,15 +188,17 @@ export const CreateOrganizationForm = withCardStateProvider((props: CreateOrgani
ignorePasswordManager
/>
</Form.ControlRow>
<Form.ControlRow elementId={slugField.id}>
<Form.PlainInput
{...slugField.props}
onChange={onChangeSlug}
isRequired
pattern='^[a-z0-9\-]+$'
ignorePasswordManager
/>
</Form.ControlRow>
{!props.hideSlug && (
<Form.ControlRow elementId={slugField.id}>
<Form.PlainInput
{...slugField.props}
onChange={onChangeSlug}
isRequired
pattern='^[a-z0-9\-]+$'
ignorePasswordManager
/>
</Form.ControlRow>
)}
<FormButtonContainer sx={t => ({ marginTop: t.space.$none })}>
<Form.SubmitButton
block={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { CreateOrganizationForm } from './CreateOrganizationForm';
export const CreateOrganizationPage = withCardStateProvider(() => {
const { closeCreateOrganization } = useClerk();

const { mode, navigateAfterCreateOrganization, skipInvitationScreen } = useCreateOrganizationContext();
const { mode, navigateAfterCreateOrganization, skipInvitationScreen, hideSlug } = useCreateOrganizationContext();
const card = useCardState();
const { showDevModeNotice } = useDevMode();

Expand All @@ -31,6 +31,7 @@ export const CreateOrganizationPage = withCardStateProvider(() => {
closeCreateOrganization();
}
}}
hideSlug={hideSlug}
/>
</Card.Content>
<Card.Footer />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,38 @@ describe('CreateOrganization', () => {
expect(getByRole('heading', { name: 'Create organization', level: 1 })).toBeInTheDocument();
});

it('renders component without slug field', async () => {
const { wrapper, fixtures, props } = await createFixtures(f => {
f.withOrganizations();
f.withUser({
email_addresses: ['[email protected]'],
});
});

fixtures.clerk.createOrganization.mockReturnValue(
Promise.resolve(
getCreatedOrg({
maxAllowedMemberships: 1,
slug: 'new-org-1722578361',
}),
),
);

props.setProps({ hideSlug: true });
const { userEvent, getByRole, queryByText, queryByLabelText, getByLabelText } = render(<CreateOrganization />, {
wrapper,
});

expect(queryByLabelText(/Slug/i)).not.toBeInTheDocument();

await userEvent.type(getByLabelText(/Name/i), 'new org');
await userEvent.click(getByRole('button', { name: /create organization/i }));

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

it('skips invitation screen', async () => {
const { wrapper, fixtures, props } = await createFixtures(f => {
f.withOrganizations();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const OrganizationListPage = withCardStateProvider(() => {
});

const OrganizationListFlows = ({ showListInitially }: { showListInitially: boolean }) => {
const { navigateAfterCreateOrganization, skipInvitationScreen } = useOrganizationListContext();
const { navigateAfterCreateOrganization, skipInvitationScreen, hideSlug } = useOrganizationListContext();
const [isCreateOrganizationFlow, setCreateOrganizationFlow] = useState(!showListInitially);
return (
<>
Expand All @@ -133,6 +133,7 @@ const OrganizationListFlows = ({ showListInitially }: { showListInitially: boole
onCancel={
showListInitially && isCreateOrganizationFlow ? () => setCreateOrganizationFlow(false) : undefined
}
hideSlug={hideSlug}
/>
</Box>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,26 @@ describe('OrganizationList', () => {
});
});

it('displays CreateOrganization without slug field', async () => {
const { wrapper, props } = await createFixtures(f => {
f.withOrganizations();
f.withUser({
email_addresses: ['[email protected]'],
create_organization_enabled: true,
});
});

props.setProps({ hideSlug: true });
const { findByRole, getByRole, userEvent, queryByLabelText } = render(<OrganizationList />, { wrapper });

await waitFor(async () =>
expect(await findByRole('menuitem', { name: 'Create organization' })).toBeInTheDocument(),
);
await userEvent.click(getByRole('menuitem', { name: 'Create organization' }));
expect(queryByLabelText(/Name/i)).toBeInTheDocument();
expect(queryByLabelText(/Slug/i)).not.toBeInTheDocument();
});

it('does not display CreateOrganization within OrganizationList when disabled', async () => {
const { wrapper } = await createFixtures(f => {
f.withOrganizations();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const OrganizationSwitcherPopover = React.forwardRef<HTMLDivElement, Orga
navigateAfterSelectOrganization,
organizationProfileProps,
skipInvitationScreen,
hideSlug,
} = useOrganizationSwitcherContext();

const { user } = useUser();
Expand Down Expand Up @@ -85,7 +86,7 @@ export const OrganizationSwitcherPopover = React.forwardRef<HTMLDivElement, Orga
if (createOrganizationMode === 'navigation') {
return navigateCreateOrganization();
}
return openCreateOrganization({ afterCreateOrganizationUrl, skipInvitationScreen });
return openCreateOrganization({ afterCreateOrganizationUrl, skipInvitationScreen, hideSlug });
};

const handleItemClick = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,23 @@ describe('OrganizationSwitcher', () => {
expect(fixtures.clerk.openCreateOrganization).toHaveBeenCalled();
});

it('opens create organization without slug field', async () => {
const { wrapper, fixtures, props } = await createFixtures(f => {
f.withOrganizations();
f.withUser({
email_addresses: ['[email protected]'],
create_organization_enabled: true,
});
});

props.setProps({ hideSlug: true });
const { getByRole, queryByLabelText, userEvent } = render(<OrganizationSwitcher />, { wrapper });
await userEvent.click(getByRole('button', { name: 'Open organization switcher' }));
await userEvent.click(getByRole('menuitem', { name: 'Create organization' }));
expect(fixtures.clerk.openCreateOrganization).toHaveBeenCalled();
expect(queryByLabelText(/Slug/i)).not.toBeInTheDocument();
});

it('does not display create organization button if permissions not present', async () => {
const { wrapper, props } = await createFixtures(f => {
f.withOrganizations();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ export const useOrganizationSwitcherContext = () => {
organizationProfileMode: organizationProfileMode || 'modal',
createOrganizationMode: createOrganizationMode || 'modal',
skipInvitationScreen: ctx.skipInvitationScreen || false,
hideSlug: ctx.hideSlug || false,
afterCreateOrganizationUrl,
afterLeaveOrganizationUrl,
navigateOrganizationProfile,
Expand Down Expand Up @@ -440,6 +441,7 @@ export const useOrganizationListContext = () => {
...ctx,
afterCreateOrganizationUrl,
skipInvitationScreen: ctx.skipInvitationScreen || false,
hideSlug: ctx.hideSlug || false,
hidePersonal: ctx.hidePersonal || false,
navigateAfterCreateOrganization,
navigateAfterSelectOrganization,
Expand Down Expand Up @@ -515,6 +517,7 @@ export const useCreateOrganizationContext = () => {
return {
...ctx,
skipInvitationScreen: ctx.skipInvitationScreen || false,
hideSlug: ctx.hideSlug || false,
navigateAfterCreateOrganization,
componentName,
};
Expand Down
15 changes: 15 additions & 0 deletions packages/types/src/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,11 @@ export type CreateOrganizationProps = RoutingOptions & {
* prop of ClerkProvided (if one is provided)
*/
appearance?: CreateOrganizationTheme;
/**
* Hides the optional "slug" field in the organization creation screen.
* @default false
*/
hideSlug?: boolean;
};

export type CreateOrganizationModalProps = WithoutRouting<CreateOrganizationProps>;
Expand Down Expand Up @@ -996,6 +1001,11 @@ export type OrganizationSwitcherProps = CreateOrganizationMode &
* the number of max allowed members is equal to 1
*/
skipInvitationScreen?: boolean;
/**
* Hides the optional "slug" field in the organization creation screen.
* @default false
*/
hideSlug?: 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 Expand Up @@ -1051,6 +1061,11 @@ export type OrganizationListProps = {
* @default undefined`
*/
afterSelectPersonalUrl?: ((user: UserResource) => string) | LooseExtractedParams<PrimitiveKeys<UserResource>>;
/**
* Hides the optional "slug" field in the organization creation screen.
* @default false
*/
hideSlug?: boolean;
};

export interface HandleEmailLinkVerificationParams {
Expand Down

0 comments on commit 9b2aeac

Please sign in to comment.