fix: let users without an organization open public projects#3816
Conversation
A signed-in user with no organization — organization creation disabled on the server, or an SSO-provisioned account — was stuck on the "No permissions" page and could not open a public project even via a direct link. RequirePreferredOrganization wrapped the whole authenticated route tree, so ProjectsRouter never mounted. ProjectContext, the only thing that adopts the project's owning organization as the preferred one, therefore never ran: the user needed an organization to reach the page that would have given them one. Move the /projects routes above the terminal gate and re-apply the gate to the /projects/add leaf inside ProjectsRouter, so viewing a project is reachable without a preferred organization while creating one still requires it.
📝 WalkthroughWalkthroughChangesThe PR expands preferred-organization behavior for organization-less users, restructures dashboard routing and access guards, adds community Cypress coverage, and centralizes organization support entitlements used by Chatwoot and Intercom. Preferred organization access
Support entitlement integrations
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant DashboardRouter
participant RequirePreferredOrganization
participant OrganizationSwitch
User->>DashboardRouter: open community project
DashboardRouter->>RequirePreferredOrganization: check preferred organization
RequirePreferredOrganization->>OrganizationSwitch: render community organization state
OrganizationSwitch-->>User: show project or no-permissions state
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
webapp/src/fixtures/__tests__/organizationEntitlement.test.ts (1)
2-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the Tolgee fixture alias.
Replace the relative import with
tg.fixtures/organizationEntitlement.Proposed fix
import { organizationCompanyInfo, organizationHasSupportChat, -} from '../organizationEntitlement'; +} from 'tg.fixtures/organizationEntitlement';🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@webapp/src/fixtures/__tests__/organizationEntitlement.test.ts` around lines 2 - 5, Update the imports in organizationEntitlement.test.ts to use the tg.fixtures/organizationEntitlement alias instead of the relative ../organizationEntitlement path, preserving the existing imported symbols.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@webapp/src/fixtures/__tests__/organizationEntitlement.test.ts`:
- Around line 2-5: Update the imports in organizationEntitlement.test.ts to use
the tg.fixtures/organizationEntitlement alias instead of the relative
../organizationEntitlement path, preserving the existing imported symbols.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: fb349e77-8d1a-4964-a9fb-772dcd6e6ca7
📒 Files selected for processing (17)
backend/app/src/test/kotlin/io/tolgee/api/v2/controllers/PreferredOrganizationCommunityTest.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/PublicProjectsControllerTestData.ktbackend/data/src/main/kotlin/io/tolgee/development/testDataBuilder/data/PublicProjectsE2eData.kte2e/cypress/e2e/projects/communityPreferredOrganization.cy.tse2e/cypress/support/dataCyType.d.tswebapp/src/component/DashboardRouter.tsxwebapp/src/component/HelpMenu.tsxwebapp/src/component/RootRouter.tsxwebapp/src/component/common/RequirePreferredOrganization.tsxwebapp/src/component/organizationSwitch/OrganizationSwitch.tsxwebapp/src/fixtures/__tests__/organizationEntitlement.test.tswebapp/src/fixtures/organizationEntitlement.tswebapp/src/globalContext/helpers.tsxwebapp/src/hooks/useChatwoot.tswebapp/src/hooks/useIntercom.tswebapp/src/views/projects/BaseProjectView.tsxwebapp/src/views/projects/ProjectsRouter.tsx
Follow-up to #3808 (merged). That PR fixed organization permissions for public projects on the backend; this one fixes the frontend bootstrap that still left the target user stuck.
Problem
A signed-in user with no organization — organization creation disabled on the server, or an SSO-provisioned account — was stuck on the "No permissions" page and could not open a public project, even from a direct link.
Root cause
RequirePreferredOrganizationwrapped the whole authenticated route tree, soProjectsRouternever mounted.ProjectContext— the only thing that adopts the project's owning organization as the preferred one — therefore never ran.Fix
Move the
/projectsroutes above the terminal gate and re-apply the gate to the/projects/addleaf insideProjectsRouter: viewing a project is reachable without a preferred organization, creating one still requires it.Supporting changes that fall out of the route move:
DashboardRouterextracted fromRootRouterso the gated and ungated arms are declared in one place, andHelpMenuhas a single structural mount.HelpMenuguard changed from!preferredOrganizationto!user— it must render for a signed-in user who has no organization, and must not render for anonymous visitors.OrganizationSwitch/BaseProjectViewhandle the no-preferred-organization state instead of rendering an empty switch or a dangling breadcrumb chevron. On/community-projectsthe surface label still renders; the dropdown (which would open onto an empty list) does not.useHasSupportChat, shared byuseChatwootanduseIntercom, and made viewer-scoped: a community contributor parked on someone else's organization no longer inherits that organization's paid support widget. PreviouslysetChatwootAttributeswould have pushed the host organization's plan, subscription status and enabled features to Chatwoot on behalf of a non-member.useIntercomnow shuts the messenger down when a booted session becomes de-entitled mid-navigation — reachable now that the preferred organization changes as a side effect of ordinary navigation.Preferred organization stays synchronized with the selected project; that is deliberate, since organization settings and glossaries need a selected organization.
Tests
PreferredOrganizationCommunityTestcovering an organization-less user adopting an organization via a public project, and failing to adopt one without public projects (7/7 pass).organizationEntitlementpins the support-tier and limited-view rules (mutation-checked — dropping either support tier or the limited-view conjunct turns the suite red).communityPreferredOrganization.cy.tsextended with an organization-less persona (server configured withuserCanCreateOrganizations=false) covering cold deep-link adoption, reload persistence, switch-failure resilience, community project listing, and the create-project/create-organization walls.Notes for review
companyInfo, matching what the boot effect already required before this PR. This keeps self-hosted behaviour identical to today (noactiveCloudSubscriptionthere, so Intercom never booted and still doesn't) while fixing a pre-existing mismatch where the menu entry rendered but the messenger was never initialised. Chatwoot is unaffected and degrades gracefully. Flagging it because it is a deliberate privacy-conservative choice rather than an obvious one — booting Intercom for self-hosted EE licence holders would be a separate, intentional change./projects/addafter adopting a foreign organization currently renders the wall. Whether it should instead be reachable is an open product question, deliberately not decided here.limitedViewis applied to support chat only. Whether QA checks / tasks / TM should also respect it for community viewers is a separate question — arguably they should keep following the content owner's entitlement.