Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/oauth-consent-last-active-org.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/ui": patch
---

Default the organization selection in `<OAuthConsent />` to the user's last active organization, falling back to the first membership when it is not set or no longer available.
5 changes: 4 additions & 1 deletion packages/ui/src/components/OAuthConsent/OAuthConsent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ function _OAuthConsent() {
}))
: [];

const lastActiveOrgId = clerk.session?.lastActiveOrganizationId;
const defaultOrg = orgOptions.find(o => o.value === lastActiveOrgId)?.value ?? orgOptions[0]?.value ?? null;

const [selectedOrg, setSelectedOrg] = useState<string | null>(null);
const effectiveOrg = selectedOrg ?? orgOptions[0]?.value ?? null;
const effectiveOrg = selectedOrg ?? defaultOrg;

// onAllow and onDeny are always provided as a pair by the accounts portal.
const hasContextCallbacks = Boolean(ctx.onAllow || ctx.onDeny);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,5 +415,87 @@ describe('OAuthConsent', () => {
expect(form.querySelector('input[name="organization_id"]')).toBeNull();
});
});

it('defaults the selected org to session.lastActiveOrganizationId when it matches a membership', async () => {
const { wrapper, fixtures, props } = await createFixtures(f => {
f.withUser({
email_addresses: ['jane@example.com'],
organization_memberships: [
{ id: 'org_1', name: 'Acme Corp' },
{ id: 'org_2', name: 'Globex' },
{ id: 'org_3', name: 'Initech' },
],
});
f.withOrganizations();
});

fixtures.clerk.session.lastActiveOrganizationId = 'org_3';

props.setProps({ componentName: 'OAuthConsent', __internal_enableOrgSelection: true } as any);
mockOAuthApplication(fixtures.clerk, { getConsentInfo: vi.fn().mockResolvedValue(fakeConsentInfo) });

const { baseElement } = render(<OAuthConsent />, { wrapper });

await waitFor(() => {
const form = baseElement.querySelector('form[action*="/v1/me/oauth/consent/"]')!;
const hiddenInput = form.querySelector('input[name="organization_id"]') as HTMLInputElement | null;
expect(hiddenInput).not.toBeNull();
expect(hiddenInput!.value).toBe('org_3');
});
});

it('falls back to the first membership when lastActiveOrganizationId does not match any membership', async () => {
const { wrapper, fixtures, props } = await createFixtures(f => {
f.withUser({
email_addresses: ['jane@example.com'],
organization_memberships: [
{ id: 'org_1', name: 'Acme Corp' },
{ id: 'org_2', name: 'Globex' },
],
});
f.withOrganizations();
});

fixtures.clerk.session.lastActiveOrganizationId = 'org_deleted';

props.setProps({ componentName: 'OAuthConsent', __internal_enableOrgSelection: true } as any);
mockOAuthApplication(fixtures.clerk, { getConsentInfo: vi.fn().mockResolvedValue(fakeConsentInfo) });

const { baseElement } = render(<OAuthConsent />, { wrapper });

await waitFor(() => {
const form = baseElement.querySelector('form[action*="/v1/me/oauth/consent/"]')!;
const hiddenInput = form.querySelector('input[name="organization_id"]') as HTMLInputElement | null;
expect(hiddenInput).not.toBeNull();
expect(hiddenInput!.value).toBe('org_1');
});
});

it('falls back to the first membership when lastActiveOrganizationId is null', async () => {
const { wrapper, fixtures, props } = await createFixtures(f => {
f.withUser({
email_addresses: ['jane@example.com'],
organization_memberships: [
{ id: 'org_1', name: 'Acme Corp' },
{ id: 'org_2', name: 'Globex' },
],
});
f.withOrganizations();
});

fixtures.clerk.session.lastActiveOrganizationId = null;

props.setProps({ componentName: 'OAuthConsent', __internal_enableOrgSelection: true } as any);
mockOAuthApplication(fixtures.clerk, { getConsentInfo: vi.fn().mockResolvedValue(fakeConsentInfo) });

const { baseElement } = render(<OAuthConsent />, { wrapper });

await waitFor(() => {
const form = baseElement.querySelector('form[action*="/v1/me/oauth/consent/"]')!;
const hiddenInput = form.querySelector('input[name="organization_id"]') as HTMLInputElement | null;
expect(hiddenInput).not.toBeNull();
expect(hiddenInput!.value).toBe('org_1');
});
});
});
});
Loading