feat(web): hide personal surface for users with disabled personal account - #4453
Conversation
When personal_account_disabled is true (accounts created solely to accept an org invite), redirect these users off personal routes into their organization and trim the personal-account UI: - getUserFromAuthOrRedirect redirects invited-only users off personal routes (allowlist: /connected-accounts) using x-pathname - getProfileRedirectPath sends invited-only users to their oldest org (stable ordering), falling back to /connected-accounts when they have no orgs - OrganizationSwitcher hides the Personal entry for these users - SidebarUserFooter becomes a user menu (Connected Accounts + Sign out), the home for user-global identity surfaces - PersonalAppSidebar drops Connected Accounts (now in the footer)
c350267 to
eaa574a
Compare
Connected Accounts (and any allowlisted personal route) has no org id in the URL, so AppSidebar fell through to the personal sidebar for invited-only users. Render their default organization's sidebar instead, using the same oldest-org default as getProfileRedirectPath, and show an empty sidebar frame while resolving it to avoid flashing the personal menu. Orphan users with no orgs still fall back to the personal sidebar.
…only Keep terminology tied to the personal_account_disabled field rather than introducing an invited-only synonym, so the concept stays greppable.
Instead of blocking direct navigation to personal routes for users with a disabled personal account, only avoid linking to those pages ourselves (sidebar, switcher, footer) and keep the default landing redirect. Direct navigation to a personal route is now allowed rather than redirected.
Only /connected-accounts (the route we link from the footer) keeps a personal-account-disabled user in their org sidebar. Other personal routes reached directly now render the personal sidebar so the page loads instead of erroring; those routes are simply not linked for these users.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of the latest commits: Files Reviewed (3 files)
Previous Review Summary (commit effea99)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit effea99)Status: No Issues Found | Recommendation: Merge Executive SummaryReviewed the invited-only (personal-account-disabled) UI/redirect changes across the sidebar, org switcher, footer menu, and profile-redirect logic; the new gating and sorting logic is internally consistent with existing types and no bugs were found with high confidence in the changed code. Files Reviewed (6 files)
Reviewed by claude-sonnet-5 · Input: 26 · Output: 3.8K · Cached: 525.8K Review guidance: REVIEW.md from base branch |
| // other personal routes are not linked but remain accessible with the personal | ||
| // sidebar if reached directly. | ||
| const isLinkedAccountRoute = | ||
| pathname === '/connected-accounts' || pathname.startsWith('/connected-accounts/'); |
There was a problem hiding this comment.
I think we should also include /install and /learn and move those to the new dropdown as well
There was a problem hiding this comment.
Done in 4d788a2 — moved Install and Learn into the footer user menu alongside Connected Accounts, and removed the Start group from the personal sidebar. They're now available in both the personal and org sidebars.
Per PR review, relocate the Start group (Install, Learn) from the personal sidebar into the footer user menu alongside Connected Accounts, so they are available in both the personal and org sidebars.
Clicking Install or Learn from the footer user menu previously dropped a personal-account-disabled user back to the personal sidebar. Treat all footer menu routes (Connected Accounts, Install, Learn) the same, keeping them in their org sidebar like Connected Accounts already did.
What
Builds on #4447 (
personal_account_disabled). When a user's personal account is disabled — i.e. the login exists only because they were invited to an org — we stop surfacing the personal-account UI and default them into their organization instead.Approach: we don't link these users to personal pages, but we don't hard-block them either. Direct navigation to a personal route still works and renders the page (with the personal sidebar), it's just no longer reachable through our own navigation.
Changes
getProfileRedirectPath): users with a disabled personal account are always sent into an organization (their oldest, deterministic ordering) regardless of org count; falls back to/connected-accountsif they somehow belong to no orgs. This is a default-destination choice, not a block.SidebarUserFooter): the avatar/name row becomes a dropdown containing Connected Accounts + Sign out — the home for user-global identity surfaces, shown in both the personal and org sidebars.AppSidebar): on/connected-accounts(the one personal route we still link to, from the footer), these users get their default org's sidebar instead of the personal one. Every other personal route falls through to the normal personal sidebar so the page renders.What this intentionally does NOT do
/profile,/subscriptions,/cost-insights, etc. is allowed and renders normally; those routes are simply not linked for these users.Design notes
personal_account_disabledfield (no "invited-only" synonym) so the concept stays greppable.Testing
getProfileRedirectPathtests: disabled personal account with multiple orgs (→ an org) and with zero orgs (→/connected-accounts).pnpm --filter web typecheck,server.test.ts(56/56),pnpm --filter web lint(clean),pnpm format.