From 8fd775afc5e4ec3703d816566eb378ba364be635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Antunes?= Date: Fri, 18 Sep 2026 14:58:32 -0300 Subject: [PATCH 1/7] feat(ui,clerk-js,shared,localizations): Add all members of a role to the SSO allow list --- .changeset/sso-bypass-allowlist-by-role.md | 12 ++ .../src/core/resources/SSOBypassAllowlist.ts | 24 +++ .../resources/__tests__/Organization.test.ts | 43 ++++++ packages/localizations/src/ar-SA.ts | 9 ++ packages/localizations/src/be-BY.ts | 9 ++ packages/localizations/src/bg-BG.ts | 9 ++ packages/localizations/src/bn-IN.ts | 9 ++ packages/localizations/src/ca-ES.ts | 9 ++ packages/localizations/src/cs-CZ.ts | 9 ++ packages/localizations/src/da-DK.ts | 9 ++ packages/localizations/src/de-DE.ts | 9 ++ packages/localizations/src/el-GR.ts | 9 ++ packages/localizations/src/en-GB.ts | 9 ++ packages/localizations/src/en-US.ts | 9 ++ packages/localizations/src/es-CR.ts | 9 ++ packages/localizations/src/es-ES.ts | 9 ++ packages/localizations/src/es-MX.ts | 9 ++ packages/localizations/src/es-UY.ts | 9 ++ packages/localizations/src/fa-IR.ts | 9 ++ packages/localizations/src/fi-FI.ts | 9 ++ packages/localizations/src/fr-FR.ts | 9 ++ packages/localizations/src/he-IL.ts | 9 ++ packages/localizations/src/hi-IN.ts | 9 ++ packages/localizations/src/hr-HR.ts | 9 ++ packages/localizations/src/hu-HU.ts | 9 ++ packages/localizations/src/id-ID.ts | 9 ++ packages/localizations/src/is-IS.ts | 9 ++ packages/localizations/src/it-IT.ts | 9 ++ packages/localizations/src/ja-JP.ts | 9 ++ packages/localizations/src/kk-KZ.ts | 9 ++ packages/localizations/src/ko-KR.ts | 9 ++ packages/localizations/src/mn-MN.ts | 9 ++ packages/localizations/src/ms-MY.ts | 9 ++ packages/localizations/src/nb-NO.ts | 9 ++ packages/localizations/src/nl-BE.ts | 9 ++ packages/localizations/src/nl-NL.ts | 9 ++ packages/localizations/src/pl-PL.ts | 9 ++ packages/localizations/src/pt-BR.ts | 9 ++ packages/localizations/src/pt-PT.ts | 9 ++ packages/localizations/src/ro-RO.ts | 9 ++ packages/localizations/src/ru-RU.ts | 9 ++ packages/localizations/src/sk-SK.ts | 9 ++ packages/localizations/src/sr-RS.ts | 9 ++ packages/localizations/src/sv-SE.ts | 9 ++ packages/localizations/src/ta-IN.ts | 9 ++ packages/localizations/src/te-IN.ts | 9 ++ packages/localizations/src/th-TH.ts | 9 ++ packages/localizations/src/tr-TR.ts | 9 ++ packages/localizations/src/uk-UA.ts | 9 ++ packages/localizations/src/vi-VN.ts | 9 ++ packages/localizations/src/zh-CN.ts | 9 ++ packages/localizations/src/zh-TW.ts | 9 ++ ...useOrganizationSSOBypassAllowlist.spec.tsx | 14 +- .../useOrganizationSSOBypassAllowlist.tsx | 18 ++- packages/shared/src/types/localization.ts | 9 ++ .../shared/src/types/ssoBypassAllowlist.ts | 29 ++++ .../SSOBypassAllowlistPage.tsx | 140 +++++++++++++++++- .../__tests__/SSOBypassAllowlist.test.tsx | 56 +++++++ .../src/customizables/elementDescriptors.ts | 2 + packages/ui/src/internal/appearance.ts | 2 + 60 files changed, 786 insertions(+), 4 deletions(-) create mode 100644 .changeset/sso-bypass-allowlist-by-role.md diff --git a/.changeset/sso-bypass-allowlist-by-role.md b/.changeset/sso-bypass-allowlist-by-role.md new file mode 100644 index 00000000000..8b054fd5123 --- /dev/null +++ b/.changeset/sso-bypass-allowlist-by-role.md @@ -0,0 +1,12 @@ +--- +'@clerk/localizations': minor +'@clerk/clerk-js': minor +'@clerk/shared': minor +'@clerk/ui': minor +--- + +The SSO allow list page of `` can now add every member with a given role at once. The "Add member" card gains a role select and an "Add all" action; members whose email address is not served by one of the organization's enterprise connections are skipped, and the page reports how many were added and skipped. + +For custom flows, `organization.ssoBypassAllowlist` gains `addUsers({ userIds })`, which calls the new bulk endpoint in batches of 100 and returns the added entries together with the users that could not be added and why. + +New customization handles: the `organizationProfileSecuritySsoBypassAddAllButton` and `organizationProfileSecuritySsoBypassBulkResult` appearance elements. diff --git a/packages/clerk-js/src/core/resources/SSOBypassAllowlist.ts b/packages/clerk-js/src/core/resources/SSOBypassAllowlist.ts index 4addb975e7a..5d4ee065340 100644 --- a/packages/clerk-js/src/core/resources/SSOBypassAllowlist.ts +++ b/packages/clerk-js/src/core/resources/SSOBypassAllowlist.ts @@ -1,7 +1,10 @@ import type { AddSSOBypassAllowlistUserParams, + AddSSOBypassAllowlistUsersParams, DeletedObjectJSON, DeletedObjectResource, + SSOBypassAllowlistBulkCreateJSON, + SSOBypassAllowlistBulkCreateResult, SSOBypassAllowlistResource, SSOBypassAllowlistUserJSON, SSOBypassAllowlistUserResource, @@ -11,6 +14,8 @@ import { BaseResource } from './Base'; import { DeletedObject } from './DeletedObject'; import { SSOBypassAllowlistUser } from './SSOBypassAllowlistUser'; +const BULK_SIZE = 100; + export class SSOBypassAllowlist implements SSOBypassAllowlistResource { declare private readonly organization: { id: string }; @@ -45,6 +50,25 @@ export class SSOBypassAllowlist implements SSOBypassAllowlistResource { return new SSOBypassAllowlistUser(json); }; + addUsers = async (params: AddSSOBypassAllowlistUsersParams): Promise => { + const result: SSOBypassAllowlistBulkCreateResult = { data: [], errors: [] }; + + for (let start = 0; start < params.userIds.length; start += BULK_SIZE) { + const json = ( + await BaseResource._fetch({ + path: `${this.path}/bulk`, + method: 'POST', + body: { user_id: params.userIds.slice(start, start + BULK_SIZE) } as any, + }) + )?.response as unknown as SSOBypassAllowlistBulkCreateJSON; + + result.data.push(...(json?.data ?? []).map(entry => new SSOBypassAllowlistUser(entry))); + result.errors.push(...(json?.errors ?? []).map(error => ({ userId: error.user_id, code: error.code }))); + } + + return result; + }; + removeUser = async (userId: string): Promise => { const json = ( await BaseResource._fetch({ diff --git a/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts b/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts index 674fba28e18..a1f253af1a3 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Organization.test.ts @@ -468,6 +468,49 @@ describe('Organization', () => { expect(entry.userId).toBe('user_1'); }); + it('adds users in chunks of 100 and merges the partial results', async () => { + const userIds = Array.from({ length: 150 }, (_, i) => `user_${i}`); + const fetchMock = vi + .fn() + .mockResolvedValueOnce({ + response: { data: [entryJSON], errors: [{ user_id: 'user_5', code: 'sso_bypass_domain_not_served' }] }, + }) + .mockResolvedValueOnce({ + response: { data: [{ ...entryJSON, user_id: 'user_120' }], errors: [] }, + }); + // @ts-ignore + BaseResource._fetch = fetchMock; + + const organization = createOrganization(); + const result = await organization.ssoBypassAllowlist.addUsers({ userIds }); + + expect(fetchMock).toHaveBeenCalledTimes(2); + expect(fetchMock).toHaveBeenNthCalledWith(1, { + method: 'POST', + path: `${ALLOWLIST_PATH}/bulk`, + body: { user_id: userIds.slice(0, 100) }, + }); + expect(fetchMock).toHaveBeenNthCalledWith(2, { + method: 'POST', + path: `${ALLOWLIST_PATH}/bulk`, + body: { user_id: userIds.slice(100) }, + }); + expect(result.data.map(entry => entry.userId)).toEqual(['user_1', 'user_120']); + expect(result.errors).toEqual([{ userId: 'user_5', code: 'sso_bypass_domain_not_served' }]); + }); + + it('sends nothing for an empty batch', async () => { + // @ts-ignore + BaseResource._fetch = vi.fn(); + + const organization = createOrganization(); + const result = await organization.ssoBypassAllowlist.addUsers({ userIds: [] }); + + // @ts-ignore + expect(BaseResource._fetch).not.toHaveBeenCalled(); + expect(result).toEqual({ data: [], errors: [] }); + }); + it('removes a user by id', async () => { // @ts-ignore BaseResource._fetch = vi diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index bae370e4160..9ce72d947f9 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -1377,14 +1377,23 @@ export const arSA: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index fd61dffb722..101f545a33d 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -1380,14 +1380,23 @@ export const beBY: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index 8bbf53b726d..de53fd9e5b1 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -1380,14 +1380,23 @@ export const bgBG: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/bn-IN.ts b/packages/localizations/src/bn-IN.ts index 686f31ed8da..dae353a3a32 100644 --- a/packages/localizations/src/bn-IN.ts +++ b/packages/localizations/src/bn-IN.ts @@ -1387,14 +1387,23 @@ export const bnIN: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index 5f08487cd0c..d9f66e735ec 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -1387,14 +1387,23 @@ export const caES: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index d5df10b95ed..50e3af1d671 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -1385,14 +1385,23 @@ export const csCZ: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 4dec7aca1c2..7fa28ad4aa4 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -1378,14 +1378,23 @@ export const daDK: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index adce063eade..fd2bb5fc37b 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -1393,14 +1393,23 @@ export const deDE: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index 2a2afeabb01..fe4db2c9e0d 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -1383,14 +1383,23 @@ export const elGR: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index e07a96bce7e..6fcfcb1dc14 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -1379,14 +1379,23 @@ export const enGB: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 387aa8b15f7..6eab6844032 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -1416,14 +1416,23 @@ export const enUS: LocalizationResource = { action__add: 'Add', action__search: 'Search users', addForm: { + addAllButton: 'Add all', changeButton: 'Change', memberLabel: 'Member', memberPlaceholder: 'Search members', noResults: 'No members found', + roleLabel: 'Or add everyone with a role', submitButton: 'Add', subtitle: 'Members on this list can sign in with an email code when SSO is unavailable.', title: 'Add member', }, + bulkResult: { + added: 'Added {{count}} members to the allow list.', + added__one: 'Added 1 member to the allow list.', + none: 'Everyone with that role is already on the allow list.', + skipped: '{{count}} members could not be added because their email address is not served by a connection.', + skipped__one: '1 member could not be added because their email address is not served by a connection.', + }, table: { emptyState: 'No members on the allow list', emptyState__search: 'No members match your search', diff --git a/packages/localizations/src/es-CR.ts b/packages/localizations/src/es-CR.ts index 3333de57e3f..91e30914568 100644 --- a/packages/localizations/src/es-CR.ts +++ b/packages/localizations/src/es-CR.ts @@ -1383,14 +1383,23 @@ export const esCR: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index 5318d7322a1..74c754d5098 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -1388,14 +1388,23 @@ export const esES: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index c0895676430..32cd2053e2c 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -1384,14 +1384,23 @@ export const esMX: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/es-UY.ts b/packages/localizations/src/es-UY.ts index 5d85d52d811..bbee98e5fdc 100644 --- a/packages/localizations/src/es-UY.ts +++ b/packages/localizations/src/es-UY.ts @@ -1382,14 +1382,23 @@ export const esUY: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/fa-IR.ts b/packages/localizations/src/fa-IR.ts index 15d607bebcd..a8e639fe50b 100644 --- a/packages/localizations/src/fa-IR.ts +++ b/packages/localizations/src/fa-IR.ts @@ -1387,14 +1387,23 @@ export const faIR: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index 162703f38c1..6c272d77e33 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -1388,14 +1388,23 @@ export const fiFI: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index 461e10efcb6..888afa974ce 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -1393,14 +1393,23 @@ export const frFR: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index 7836eb1b6dd..ad0187d8267 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -1374,14 +1374,23 @@ export const heIL: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/hi-IN.ts b/packages/localizations/src/hi-IN.ts index c71848b381b..6617777b015 100644 --- a/packages/localizations/src/hi-IN.ts +++ b/packages/localizations/src/hi-IN.ts @@ -1387,14 +1387,23 @@ export const hiIN: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index d51d0ad8d93..454503b41b0 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -1388,14 +1388,23 @@ export const hrHR: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index be0c7d46357..88c78eb124e 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -1389,14 +1389,23 @@ export const huHU: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index 4f4434c49c0..165e681ff89 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -1381,14 +1381,23 @@ export const idID: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index 2eeefbeadcd..5cbac52d3b7 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -1388,14 +1388,23 @@ export const isIS: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index d938f226c74..9348b5ec9ac 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -1388,14 +1388,23 @@ export const itIT: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index 60c0fea1061..d99eb106753 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -1387,14 +1387,23 @@ export const jaJP: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/kk-KZ.ts b/packages/localizations/src/kk-KZ.ts index 3d1d8d96497..a51c0d86c6d 100644 --- a/packages/localizations/src/kk-KZ.ts +++ b/packages/localizations/src/kk-KZ.ts @@ -1374,14 +1374,23 @@ export const kkKZ: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index 132442c3d20..85b3c7ac9de 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -1379,14 +1379,23 @@ export const koKR: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index 3f9c7b0695d..7112cbe3401 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -1381,14 +1381,23 @@ export const mnMN: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/ms-MY.ts b/packages/localizations/src/ms-MY.ts index f1f5657ec03..53430afdba7 100644 --- a/packages/localizations/src/ms-MY.ts +++ b/packages/localizations/src/ms-MY.ts @@ -1390,14 +1390,23 @@ export const msMY: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index a4151c4bb37..d03dd9d34d5 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -1389,14 +1389,23 @@ export const nbNO: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index 5e1adc6c082..af947b1df6d 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -1381,14 +1381,23 @@ export const nlBE: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index 52a3b5bcc49..e4e060b1270 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -1381,14 +1381,23 @@ export const nlNL: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index d4684783343..b0fca4d0fa2 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -1379,14 +1379,23 @@ export const plPL: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index 64a2822da6b..8d17b5b7580 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -1389,14 +1389,23 @@ export const ptBR: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index 8a9b1731bd8..f6c44b5315d 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -1389,14 +1389,23 @@ export const ptPT: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index ebd873d5716..ce149c0bd27 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -1389,14 +1389,23 @@ export const roRO: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index 96d03ce5a3f..02b9c25a97f 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -1383,14 +1383,23 @@ export const ruRU: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index 9ba2f4f156a..7c79381f9b2 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -1379,14 +1379,23 @@ export const skSK: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index ee1093c3073..ffab17f338d 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -1379,14 +1379,23 @@ export const srRS: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index 5b652afb4da..0334310cb47 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -1379,14 +1379,23 @@ export const svSE: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/ta-IN.ts b/packages/localizations/src/ta-IN.ts index e4e15915cde..0d9f4b9d9a1 100644 --- a/packages/localizations/src/ta-IN.ts +++ b/packages/localizations/src/ta-IN.ts @@ -1392,14 +1392,23 @@ export const taIN: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/te-IN.ts b/packages/localizations/src/te-IN.ts index d9a957f8b1c..8c4de39f5f3 100644 --- a/packages/localizations/src/te-IN.ts +++ b/packages/localizations/src/te-IN.ts @@ -1389,14 +1389,23 @@ export const teIN: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index fb6515d892c..9afb796c9b2 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -1380,14 +1380,23 @@ export const thTH: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index 0e6efae4e09..cc79c90c279 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -1379,14 +1379,23 @@ export const trTR: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index c4f96eb5c38..7a9ef9dec23 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -1379,14 +1379,23 @@ export const ukUA: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index 8c3c23b9588..4370f833195 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -1387,14 +1387,23 @@ export const viVN: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index 07fd70dd618..7cbde95212c 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -1372,14 +1372,23 @@ export const zhCN: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index 75018e1a8f2..4fb131a2433 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -1375,14 +1375,23 @@ export const zhTW: LocalizationResource = { action__add: undefined, action__search: undefined, addForm: { + addAllButton: undefined, changeButton: undefined, memberLabel: undefined, memberPlaceholder: undefined, noResults: undefined, + roleLabel: undefined, submitButton: undefined, subtitle: undefined, title: undefined, }, + bulkResult: { + added: undefined, + added__one: undefined, + none: undefined, + skipped: undefined, + skipped__one: undefined, + }, table: { emptyState: undefined, emptyState__search: undefined, diff --git a/packages/shared/src/react/hooks/__tests__/useOrganizationSSOBypassAllowlist.spec.tsx b/packages/shared/src/react/hooks/__tests__/useOrganizationSSOBypassAllowlist.spec.tsx index 0b9c5c34d54..3dad722af44 100644 --- a/packages/shared/src/react/hooks/__tests__/useOrganizationSSOBypassAllowlist.spec.tsx +++ b/packages/shared/src/react/hooks/__tests__/useOrganizationSSOBypassAllowlist.spec.tsx @@ -9,6 +9,7 @@ const entry = (userId: string) => ({ id: userId, userId, publicUserData: { ident const getSpy = vi.fn(() => Promise.resolve([entry('user_1')])); const addSpy = vi.fn(() => Promise.resolve(entry('user_2'))); +const addManySpy = vi.fn(() => Promise.resolve({ data: [entry('user_2'), entry('user_3')], errors: [] })); const removeSpy = vi.fn(() => Promise.resolve({ id: 'user_1', deleted: true })); const defaultQueryClient = createMockQueryClient(); @@ -20,7 +21,7 @@ const mockClerk = createMockClerk({ session: null, organization: { id: 'org_1', - ssoBypassAllowlist: { getUsers: getSpy, addUser: addSpy, removeUser: removeSpy }, + ssoBypassAllowlist: { getUsers: getSpy, addUser: addSpy, addUsers: addManySpy, removeUser: removeSpy }, }, client: null, }, @@ -70,6 +71,17 @@ describe('useOrganizationSSOBypassAllowlist', () => { await waitFor(() => expect(getSpy).toHaveBeenCalledTimes(2)); }); + it('adds several users and refetches the list before resolving', async () => { + const { result } = renderAllowlist(); + await waitFor(() => expect(result.current.isLoading).toBe(false)); + + const added = await result.current.addUsers({ userIds: ['user_2', 'user_3'] }); + + expect(addManySpy).toHaveBeenCalledWith({ userIds: ['user_2', 'user_3'] }); + expect(added).toEqual({ data: [entry('user_2'), entry('user_3')], errors: [] }); + await waitFor(() => expect(getSpy).toHaveBeenCalledTimes(2)); + }); + it('removes a user and refetches the list before resolving', async () => { const { result } = renderAllowlist(); await waitFor(() => expect(result.current.isLoading).toBe(false)); diff --git a/packages/shared/src/react/hooks/useOrganizationSSOBypassAllowlist.tsx b/packages/shared/src/react/hooks/useOrganizationSSOBypassAllowlist.tsx index c8114205aee..f269e0e81ec 100644 --- a/packages/shared/src/react/hooks/useOrganizationSSOBypassAllowlist.tsx +++ b/packages/shared/src/react/hooks/useOrganizationSSOBypassAllowlist.tsx @@ -1,7 +1,12 @@ import { useCallback } from 'react'; import type { DeletedObjectResource } from '../../types/deletedObject'; -import type { AddSSOBypassAllowlistUserParams, SSOBypassAllowlistUserResource } from '../../types/ssoBypassAllowlist'; +import type { + AddSSOBypassAllowlistUserParams, + AddSSOBypassAllowlistUsersParams, + SSOBypassAllowlistBulkCreateResult, + SSOBypassAllowlistUserResource, +} from '../../types/ssoBypassAllowlist'; import { useClerkInstanceContext } from '../contexts'; import { defineKeepPreviousDataFn } from '../query/keep-previous-data'; import { useClerkQueryClient } from '../query/use-clerk-query-client'; @@ -21,6 +26,7 @@ export type UseOrganizationSSOBypassAllowlistReturn = { isLoading: boolean; isFetching: boolean; addUser: (params: AddSSOBypassAllowlistUserParams) => Promise; + addUsers: (params: AddSSOBypassAllowlistUsersParams) => Promise; removeUser: (userId: string) => Promise; revalidate: () => Promise; }; @@ -71,6 +77,15 @@ function useOrganizationSSOBypassAllowlist( [organization, revalidate], ); + const addUsers = useCallback( + async (addParams: AddSSOBypassAllowlistUsersParams) => { + const result = await organization?.ssoBypassAllowlist.addUsers(addParams); + await revalidate(); + return result; + }, + [organization, revalidate], + ); + const removeUser = useCallback( async (userId: string) => { const removed = await organization?.ssoBypassAllowlist.removeUser(userId); @@ -86,6 +101,7 @@ function useOrganizationSSOBypassAllowlist( isLoading: query.isLoading, isFetching: query.isFetching, addUser, + addUsers, removeUser, revalidate, }; diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 34834095c01..99aaa233e96 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1218,6 +1218,8 @@ export type __internal_LocalizationResource = { subtitle: LocalizationValue; memberLabel: LocalizationValue; changeButton: LocalizationValue; + roleLabel: LocalizationValue; + addAllButton: LocalizationValue; memberPlaceholder: LocalizationValue; noResults: LocalizationValue; submitButton: LocalizationValue; @@ -1229,6 +1231,13 @@ export type __internal_LocalizationResource = { emptyState__search: LocalizationValue; menuAction__remove: LocalizationValue; }; + bulkResult: { + added: LocalizationValue<'count'>; + added__one: LocalizationValue; + skipped: LocalizationValue<'count'>; + skipped__one: LocalizationValue; + none: LocalizationValue; + }; }; ssoSection: { title: LocalizationValue; diff --git a/packages/shared/src/types/ssoBypassAllowlist.ts b/packages/shared/src/types/ssoBypassAllowlist.ts index ecc177a140d..26e908a6bec 100644 --- a/packages/shared/src/types/ssoBypassAllowlist.ts +++ b/packages/shared/src/types/ssoBypassAllowlist.ts @@ -25,6 +25,30 @@ export type AddSSOBypassAllowlistUserParams = { userId: string; }; +export type AddSSOBypassAllowlistUsersParams = { + userIds: string[]; +}; + +export interface SSOBypassAllowlistBulkCreateErrorJSON { + user_id: string; + code: string; +} + +export interface SSOBypassAllowlistBulkCreateJSON { + data: SSOBypassAllowlistUserJSON[]; + errors: SSOBypassAllowlistBulkCreateErrorJSON[]; +} + +export interface SSOBypassAllowlistBulkCreateError { + userId: string; + code: string; +} + +export interface SSOBypassAllowlistBulkCreateResult { + data: SSOBypassAllowlistUserResource[]; + errors: SSOBypassAllowlistBulkCreateError[]; +} + export interface SSOBypassAllowlistResource { /** * Lists the members who may sign in with an email code when the organization's enterprise SSO is unavailable. @@ -36,6 +60,11 @@ export interface SSOBypassAllowlistResource { * organization's enterprise connections. */ addUser: (params: AddSSOBypassAllowlistUserParams) => Promise; + /** + * Adds several members to the allowlist, one request per 100 ids. Members who cannot be added are reported in + * `errors` with the same code `addUser` returns for them, and do not fail the batch. + */ + addUsers: (params: AddSSOBypassAllowlistUsersParams) => Promise; /** * Removes a member from the allowlist. */ diff --git a/packages/ui/src/components/OrganizationProfile/SSOBypassAllowlistPage.tsx b/packages/ui/src/components/OrganizationProfile/SSOBypassAllowlistPage.tsx index 3f2b9597840..dc99631b5cc 100644 --- a/packages/ui/src/components/OrganizationProfile/SSOBypassAllowlistPage.tsx +++ b/packages/ui/src/components/OrganizationProfile/SSOBypassAllowlistPage.tsx @@ -1,7 +1,11 @@ import { __internal_useOrganizationSSOBypassAllowlist, useOrganization, useUser } from '@clerk/shared/react'; import type { AddSSOBypassAllowlistUserParams, + AddSSOBypassAllowlistUsersParams, + OrganizationCustomRoleKey, OrganizationMembershipResource, + OrganizationResource, + SSOBypassAllowlistBulkCreateResult, SSOBypassAllowlistUserResource, } from '@clerk/shared/types'; import React, { useMemo, useRef, useState } from 'react'; @@ -23,6 +27,7 @@ import { ThreeDotsMenu } from '@/ui/elements/ThreeDotsMenu'; import { UserPreview } from '@/ui/elements/UserPreview'; import { handleError } from '@/ui/utils/errorHandler'; +import type { LocalizationKey } from '../../customizables'; import { Badge, Button, @@ -34,7 +39,9 @@ import { Text, useLocalizations, } from '../../customizables'; +import { useFetchRoles } from '../../hooks/useFetchRoles'; import { mqu } from '../../styledSystem'; +import { RoleSelect } from './MemberListTable'; import { SecurityBackControl } from './SecurityBackControl'; type SSOBypassAllowlistPageProps = { @@ -43,6 +50,61 @@ type SSOBypassAllowlistPageProps = { const MEMBER_SEARCH_DEBOUNCE_MS = 500; const MEMBER_SEARCH_PAGE_SIZE = 10; +const ROLE_MEMBERS_PAGE_SIZE = 100; + +type BulkResult = { added: number; skipped: number }; + +const collectUserIdsByRole = async (organization: OrganizationResource, role: OrganizationCustomRoleKey) => { + const userIds: string[] = []; + let fetched = 0; + for (let page = 1; ; page++) { + const { data, total_count } = await organization.getMemberships({ + role: [role], + pageSize: ROLE_MEMBERS_PAGE_SIZE, + initialPage: page, + }); + fetched += data.length; + data.forEach(membership => { + const userId = membership.publicUserData?.userId; + if (userId) { + userIds.push(userId); + } + }); + if (data.length === 0 || fetched >= total_count) { + return userIds; + } + } +}; + +const bulkResultMessages = ( + result: BulkResult, +): { variant: 'info' | 'warning'; title: LocalizationKey; subtitle?: LocalizationKey } => { + if (result.added === 0 && result.skipped === 0) { + return { + variant: 'info', + title: localizationKeys('organizationProfile.securityPage.ssoBypassPage.bulkResult.none'), + }; + } + const title = + result.added === 1 + ? localizationKeys('organizationProfile.securityPage.ssoBypassPage.bulkResult.added__one') + : localizationKeys('organizationProfile.securityPage.ssoBypassPage.bulkResult.added', { + count: String(result.added), + }); + if (result.skipped === 0) { + return { variant: 'info', title }; + } + return { + variant: 'warning', + title, + subtitle: + result.skipped === 1 + ? localizationKeys('organizationProfile.securityPage.ssoBypassPage.bulkResult.skipped__one') + : localizationKeys('organizationProfile.securityPage.ssoBypassPage.bulkResult.skipped', { + count: String(result.skipped), + }), + }; +}; const matchesSearch = (entry: SSOBypassAllowlistUserResource, term: string): boolean => { const { firstName, lastName, identifier, username } = entry.publicUserData; @@ -54,9 +116,10 @@ export const SSOBypassAllowlistPage = withCardStateProvider(({ onBack }: SSOBypa const card = useCardState(); const { t } = useLocalizations(); const { user } = useUser(); - const { data, isLoading, error, addUser, removeUser } = __internal_useOrganizationSSOBypassAllowlist(); + const { data, isLoading, error, addUser, addUsers, removeUser } = __internal_useOrganizationSSOBypassAllowlist(); const [search, setSearch] = useState(''); + const [bulkResult, setBulkResult] = useState(null); const term = search.trim().toLowerCase(); const entries = useMemo( @@ -117,6 +180,7 @@ export const SSOBypassAllowlistPage = withCardStateProvider(({ onBack }: SSOBypa - ))} - - {options.length === 0 && !memberships?.isLoading && ( - - )} - - - )} - - - {roles && roles.length > 0 && ( + + + + {mode === 'email' ? ( + + + + ) : ( - ({ width: '100%', justifyContent: 'space-between', color: t.colors.$colorForeground })} /> - ({ width: 'auto', color: t.colors.$colorForeground })} + -