diff --git a/.changeset/social-items-strive.md b/.changeset/social-items-strive.md new file mode 100644 index 00000000000..202bbd3f2e0 --- /dev/null +++ b/.changeset/social-items-strive.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Fix role select being disabled on `OrganizationProfile` invite members page when default role is not in roles list diff --git a/packages/ui/src/components/OrganizationProfile/InviteMembersForm.tsx b/packages/ui/src/components/OrganizationProfile/InviteMembersForm.tsx index 7f9e848850b..3a8ecc68b8d 100644 --- a/packages/ui/src/components/OrganizationProfile/InviteMembersForm.tsx +++ b/packages/ui/src/components/OrganizationProfile/InviteMembersForm.tsx @@ -48,19 +48,10 @@ export const InviteMembersForm = (props: InviteMembersFormProps) => { label: localizationKeys('formFieldLabel__emailAddresses'), }); - const defaultRole = useDefaultRole(); const roleField = useFormControl('role', '', { label: localizationKeys('formFieldLabel__role'), }); - useEffect(() => { - if (roleField.value || !defaultRole) { - return; - } - - roleField.setValue(defaultRole); - }, [defaultRole, roleField]); - if (!organization) { return null; } @@ -200,8 +191,23 @@ export const InviteMembersForm = (props: InviteMembersFormProps) => { const AsyncRoleSelect = (field: ReturnType>) => { const { options, isLoading, hasRoleSetMigration } = useFetchRoles(); - const { t } = useLocalizations(); + const defaultRole = useDefaultRole(); + + useEffect(() => { + if (field.value || !defaultRole) { + return; + } + + // Skip if the default role from org settings is not in the current role set + // This will eventually be returned by the roles endpoint, and `organizationSettings.domains.defaultRole` will be deprecated + const defaultRoleExists = options?.some(option => option.value === defaultRole); + if (!defaultRoleExists) { + return; + } + + field.setValue(defaultRole); + }, [defaultRole, options, field]); return ( diff --git a/packages/ui/src/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx b/packages/ui/src/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx index c6dede1606d..2eaf39d7aa3 100644 --- a/packages/ui/src/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx +++ b/packages/ui/src/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx @@ -1,7 +1,6 @@ import { ClerkAPIResponseError } from '@clerk/shared/error'; import type { OrganizationInvitationResource } from '@clerk/shared/types'; import { waitFor } from '@testing-library/react'; -import React from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { bindCreateFixtures } from '@/test/create-fixtures'; @@ -246,6 +245,59 @@ describe('InviteMembersPage', () => { await waitFor(() => expect(getByRole('button', { name: /select role/i })).toBeInTheDocument()); }); + it('enables selecting other options if default role is not available', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withOrganizations(); + f.withOrganizationDomains(undefined, 'mydefaultrole'); + f.withUser({ + email_addresses: ['test@clerk.com'], + organization_memberships: [{ name: 'Org1', role: 'admin' }], + }); + }); + + fixtures.clerk.organization?.getInvitations.mockRejectedValue(null); + fixtures.clerk.organization?.getRoles.mockResolvedValue({ + total_count: 1, + data: [ + { + pathRoot: '', + reload: vi.fn(), + id: 'member', + key: 'member', + name: 'member', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, + { + pathRoot: '', + reload: vi.fn(), + id: 'admin', + key: 'admin', + name: 'admin', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, + ], + }); + + fixtures.clerk.organization?.inviteMembers.mockResolvedValueOnce([{}] as OrganizationInvitationResource[]); + const { getByRole, userEvent, getByTestId } = render( + + + , + { wrapper }, + ); + await userEvent.type(getByTestId('tag-input'), 'test+1@clerk.com,'); + await waitFor(() => expect(getByRole('button', { name: /select role/i })).toBeInTheDocument()); + await userEvent.click(getByRole('button', { name: /select role/i })); + await userEvent.click(getByRole('button', { name: /admin/i })); + await waitFor(() => expect(getByRole('button', { name: 'Send invitations' })).not.toBeDisabled()); + }); + it('enables send button with default role once email address has been entered', async () => { const defaultRole = 'mydefaultrole';