Skip to content

Commit 1195481

Browse files
authored
Implement organization creation permission controls (#1373)
* feat(clerk-js): Hide 'create organization' button in org switcher if no permissions
1 parent 3214849 commit 1195481

File tree

8 files changed

+33
-4
lines changed

8 files changed

+33
-4
lines changed

.changeset/fifty-eels-work.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@clerk/clerk-js': minor
3+
'@clerk/types': minor
4+
---
5+
6+
If user does not have permission to create an org, create org button will not display in the OrganizationSwitcher UI

packages/clerk-js/src/core/resources/User.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export class User extends BaseResource implements UserResource {
8080
publicMetadata: UserPublicMetadata = {};
8181
unsafeMetadata: UserUnsafeMetadata = {};
8282
lastSignInAt: Date | null = null;
83+
createOrganizationEnabled = false;
8384
deleteSelfEnabled = false;
8485
updatedAt: Date | null = null;
8586
createdAt: Date | null = null;
@@ -320,6 +321,7 @@ export class User extends BaseResource implements UserResource {
320321
this.backupCodeEnabled = data.backup_code_enabled;
321322
this.twoFactorEnabled = data.two_factor_enabled;
322323

324+
this.createOrganizationEnabled = data.create_organization_enabled;
323325
this.deleteSelfEnabled = data.delete_self_enabled;
324326

325327
if (data.last_sign_in_at) {

packages/clerk-js/src/core/resources/UserSettings.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type {
22
Attributes,
3-
Actions,
43
OAuthProviders,
54
OAuthStrategy,
65
PasswordSettingsData,
@@ -17,6 +16,11 @@ import { BaseResource } from './internal';
1716
const defaultMaxPasswordLength = 72;
1817
const defaultMinPasswordLength = 8;
1918

19+
export type Actions = {
20+
create_organization: boolean;
21+
delete_self: boolean;
22+
};
23+
2024
/**
2125
* @internal
2226
*/

packages/clerk-js/src/ui/components/OrganizationSwitcher/OtherOrganizationActions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const OrganizationActionList = (props: OrganizationActionListProps) => {
8484
</PreviewButton>
8585
))}
8686
</Box>
87-
{createOrganizationButton}
87+
{user.createOrganizationEnabled && createOrganizationButton}
8888
</SecondaryActions>
8989
);
9090
};

packages/clerk-js/src/ui/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe('OrganizationSwitcher', () => {
4646
it('opens the organization switcher popover when clicked', async () => {
4747
const { wrapper, props } = await createFixtures(f => {
4848
f.withOrganizations();
49-
f.withUser({ email_addresses: ['test@clerk.dev'] });
49+
f.withUser({ email_addresses: ['test@clerk.dev'], create_organization_enabled: true });
5050
});
5151
props.setProps({ hidePersonal: true });
5252
const { getByText, getByRole, userEvent } = render(<OrganizationSwitcher />, { wrapper });
@@ -107,6 +107,7 @@ describe('OrganizationSwitcher', () => {
107107
f.withUser({
108108
email_addresses: ['test@clerk.dev'],
109109
organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
110+
create_organization_enabled: true,
110111
});
111112
});
112113
props.setProps({ hidePersonal: true });
@@ -116,6 +117,20 @@ describe('OrganizationSwitcher', () => {
116117
expect(fixtures.clerk.openCreateOrganization).toHaveBeenCalled();
117118
});
118119

120+
it('does not display create organization button if permissions not present', async () => {
121+
const { wrapper, props } = await createFixtures(f => {
122+
f.withOrganizations();
123+
f.withUser({
124+
email_addresses: ['test@clerk.dev'],
125+
organization_memberships: [{ name: 'Org1', role: 'basic_member' }],
126+
create_organization_enabled: false,
127+
});
128+
});
129+
props.setProps({ hidePersonal: true });
130+
const { queryByRole } = render(<OrganizationSwitcher />, { wrapper });
131+
expect(queryByRole('button', { name: 'Create Organization' })).not.toBeInTheDocument();
132+
});
133+
119134
it.todo('switches between active organizations when one is clicked');
120135
});
121136
});

packages/types/src/json.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ export interface UserJSON extends ClerkResourceJSON {
214214
public_metadata: UserPublicMetadata;
215215
unsafe_metadata: UserUnsafeMetadata;
216216
last_sign_in_at: number | null;
217+
create_organization_enabled: boolean;
217218
delete_self_enabled: boolean;
218219
updated_at: number;
219220
created_at: number;

packages/types/src/user.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export interface UserResource extends ClerkResource {
7777
publicMetadata: UserPublicMetadata;
7878
unsafeMetadata: UserUnsafeMetadata;
7979
lastSignInAt: Date | null;
80+
createOrganizationEnabled: boolean;
8081
deleteSelfEnabled: boolean;
8182
updatedAt: Date | null;
8283
createdAt: Date | null;

packages/types/src/userSettings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export interface UserSettingsJSON extends ClerkResourceJSON {
9191
id: never;
9292
object: never;
9393
attributes: AttributesJSON;
94+
actions: Actions;
9495
social: OAuthProviders;
9596

9697
/**
@@ -101,7 +102,6 @@ export interface UserSettingsJSON extends ClerkResourceJSON {
101102
sign_in: SignInData;
102103
sign_up: SignUpData;
103104
password_settings: PasswordSettingsData;
104-
actions: Actions;
105105
}
106106

107107
export interface UserSettingsResource extends ClerkResource {

0 commit comments

Comments
 (0)