Skip to content

feat(ui): add UserButton controller - #9185

Open
alexcarpenter wants to merge 6 commits into
carp/account-button-switcherfrom
carp/account-button-controller
Open

feat(ui): add UserButton controller#9185
alexcarpenter wants to merge 6 commits into
carp/account-button-switcherfrom
carp/account-button-controller

Conversation

@alexcarpenter

@alexcarpenter alexcarpenter commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

Stacked on #9184 (the presentational view). Adds the controller layer that connects UserButton to live Clerk data, plus its tests (TDD).

  • useUserButtonController() (user-button.controller.tsx) — a plain hook (no machine) returning a 'loading' | 'hidden' | 'ready' discriminated union. When ready it carries UserButtonData & UserButtonCallbacks 1:1 with the contract in user-button.view.tsx.
  • UserButton (user-button.tsx) — the connected container. Owns popover open state and close-on-success (Option A: wraps each one-shot callback with .finally(close); manage/create navigations do not auto-close).
  • Renamed the view's all-in-one export UserButtonUserButtonView, reserving the bare UserButton for the connected container. Updated the swingset story + mdx.

Data mapping (Clerk → contract)

  • activeSession from useUser() + useSession(), name fallback first+last > username > email.
  • activeOrganizationId from useOrganization() (null = personal).
  • memberships/suggestions/invitations from useOrganizationList(organizationListParams) (reused from the legacy switcher).
  • hasOrganizations from userMemberships.count (never array length).
  • additionalSessions from clerk.client.signedInSessions, excluding the active user's session.
  • membershipRequestCount only on the active-org row and only with org:sys_memberships:manage.
  • planLabel / upgradeable deferred (undefined).

Actions (navigation mode)

setActive for select/switch, signOut for sign-out, router.navigate(clerk.build*Url()) for manage/create, .accept() + revalidate() for invitations/suggestions.

Tests

12 controller tests (__tests__/user-button.controller.test.tsx) using the mosaic vi.mock('@clerk/shared/react') harness (copied from organization-profile-delete-section.controller.test.tsx). Written failing first, then implemented to green.

Notes / deviations from the handoff

Two points where the handoff wasn't directly implementable; resolved as follows:

  1. onSelectOrganization navigation. The handoff referenced displayConfig.afterSelectOrganizationUrl, but that field only exists as a component prop, not on the environment. Per decision, navigation goes through setActive({ organization, redirectUrl }) (matching the legacy switcher), using displayConfig.afterCreateOrganizationUrl as the redirect. Fully type-safe, no cast.
  2. Container loading render. The handoff described loading → <UserButtonView status="loading" />, but the view requires activeSession (which is only present when ready) and the view was out of scope to restructure. The container renders null while loading/hidden and the view only when ready. The controller still exposes status: 'loading' for future use.

Checklist

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Aug 3, 2026 8:11pm
swingset Ready Ready Preview Aug 3, 2026 8:11pm

Request Review

@changeset-bot

changeset-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 6ff2457

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a04aed4-0d7c-4b2c-a512-143674d4ca8d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the ui label Jul 16, 2026
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-controller branch from 8786841 to ba22f92 Compare July 16, 2026 21:54
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-controller branch from ba22f92 to c9bdf23 Compare July 30, 2026 18:38
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-switcher branch from dfae67d to ef35f2d Compare July 30, 2026 18:38
@alexcarpenter alexcarpenter changed the title feat(ui): add AccountButton controller feat(ui): add UserButton controller Jul 30, 2026
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-switcher branch from ef35f2d to 8f7ffa2 Compare July 31, 2026 13:32
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-controller branch from c9bdf23 to 93aa6f2 Compare July 31, 2026 13:32
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-switcher branch from 075c15d to 4e052b8 Compare July 31, 2026 19:00
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-controller branch from 93aa6f2 to 4643795 Compare August 3, 2026 15:08
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-controller branch from 4643795 to 4467f3d Compare August 3, 2026 15:38
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-switcher branch from 2bff351 to 0127447 Compare August 3, 2026 17:09
@alexcarpenter
alexcarpenter force-pushed the carp/account-button-controller branch from 4467f3d to fbd5c2f Compare August 3, 2026 17:09
Every other action — switching account, signing out of one, joining a
suggested or invited workspace — now resolves back into an open popover so
the result is visible where it happened. The swingset prototypes fake the
round trip they make against Clerk, so the spinner and stood-down rows are
demonstrable without a running app.
The trigger carried the avatar alone. It now names what is active beside it — the
organization and its plan wherever one heads the trigger, the account otherwise —
behind `showLabel`, which defaults on.

Badge's `neutral` color was unreadable in both schemes: its fill is a 900 and its
text token is a text color, not an on-fill one. It now rides the same black/white
scrim the button's neutral fill does.
`showLabel` becomes `renderTriggerLabel`, and the plan badge gets its own
`renderPlanBadge`. The badge is part of the label, so it needs both.
…ccount

The trigger and the popup's header now always name the same workspace. `combined`
carries both switchers, so `modePriority` picks which one it leads with: the active
organization by default, the account with `modePriority="user"`. Both are still listed
either way.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant