Skip to content

[Core]: Company Switcher #138

Closed
Closed

Description

@nielsdrost7

Users managing multiple companies need to switch contexts quickly, similar to Slack/Discord workspace switching.


Specific

As a user, I want to switch between companies without logging out.

  • Show company name top right corner.
  • Click on Company name.
  • Maybe show modal with all companies user belongs to
  • Maybe edit button next to each company
  • Clicking on Company switches to that company

Also:

  • company slug shows in address bar
  • Typing in other slug switches to that company if the user belongs to it

AAA Scenarios

Scenario 1 — Basic UI Switch (top-right button)

Arrange

  • User is authenticated and belongs to companies A and B.
  • Current company context = A.
  • User has at least one resource visible (e.g., invoices) scoped to company A.

Act

  • User clicks the company name in the top-right corner and selects company B.

Assert

  • The UI reflects company B (top-right label updates).
  • All visible data updates to company B’s scoped data.
  • No data from company A is visible after switch.
  • User remains authenticated and session persists.

Scenario 2 — Modal Company Picker (many companies)

Arrange

  • User belongs to 10 companies.
  • Company switcher shows a modal with the list.
  • User has permissions on a subset of companies.

Act

  • User opens the modal, selects a company the user has access to, and confirms.

Assert

  • The modal closes and the UI shows the chosen company.
  • The list respects user permissions (edit buttons shown only where allowed).
  • Data is fully scoped to the selected company.
  • No slow full-page reload is needed unless necessary; context change is seamless.

Scenario 3 — Edit / Quick-actions from Picker

Arrange

  • User has "manage company" permission for company C.
  • Picker shows an edit button next to company C.

Act

  • User clicks the edit button in the picker for company C.

Assert

  • The edit action opens the company settings for company C (modal or page).
  • User can save changes and see them applied to current context if company C is active.
  • Authorization is enforced; edit button not shown where user lacks permission.

Scenario 4 — URL Slug Switch (direct navigation)

Arrange

  • Route format includes company slug: /companies/{slug}/invoices.
  • User is authenticated and belongs to company D.
  • Browser address bar currently shows /companies/company-x/invoices.

Act

  • User types /companies/company-d/invoices into the address bar and navigates.

Assert

  • If user belongs to company D: the app switches context to company D and shows scoped data.
  • The top-right company label updates to company D.
  • No cross-tenant data leaks; user sees only company D data.
  • If user does not belong to company D: the app shows an access error (403) or redirects to an allowed company.

Scenario 5 — Attempted Unauthorized Slug Switch

Arrange

  • User belongs to companies E and F only.
  • The user attempts to access /companies/company-g/... (company G not in their list).

Act

  • User navigates to the URL with company G’s slug.

Assert

  • The system denies access (403 or redirect to a safe default).
  • No company G data is exposed.
  • The user is shown a clear message and a link to accessible companies.

Scenario 6 — Persistence Across Tabs & Reloads

Arrange

  • User switches to company H in tab A.
  • User opens tab B previously at a different company context.

Act

  • User reloads tab B after switching in tab A.

Assert

  • Tab B either respects a global session-level company context (if designed) or indicates the currently active company and offers to sync.
  • No inconsistent data appears; choice of persistence strategy is consistent and documented.

Scenario 7 — Permissions & Feature-Gating After Switch

Arrange

  • User has role changes between companies (e.g., admin in I, viewer in J).
  • Both companies have resources with different accessible features.

Act

  • User switches from company I to company J.

Assert

  • Feature availability updates to reflect new company role (edit buttons hidden where viewer).
  • Actions failing due to permissions return appropriate errors and are not shown where unauthorized.
  • Logs record the switch event for audit.

Scenario 8 — Edge Case: Pending Background Syncs / Long-running Jobs

Arrange

  • User triggers a long-running sync job in company K (background).
  • User attempts to switch to company L during the job.

Act

  • User switches context while background job for company K is still running.

Assert

  • Background job continues and is scoped to company K (no cross-tenant effect).
  • User sees the correct status for company L and is not blocked by K’s job.
  • Operations remain isolated per tenant.

Notes (non-assertions)

  • Ensure server-side authorization is the source-of-truth for all switches (client UI should not be the only guard).
  • Log company switch events (user, from → to, timestamp) for audits.
  • Consider UX for “default company” and initial onboarding choice.
  • Decide and document session vs per-tab persistence strategy and implement consistent behavior.

Activity

  1. added this to the v2.0.0-alpha milestone on Nov 27, 2025
  2. added a commit that references this issue on Jun 12, 2026
  3. nielsdrost7 commented on Jun 14, 2026

    @nielsdrost7
    CollaboratorAuthor

    Sprint 5 — Implementation Plan

    What already works (no code needed):

    • URL-slug company switching ✅ — ConfigureTenant + SetTenantFromQueryString middleware already handles /ivplv2/dashboard → /other-co/dashboard navigation. Typing a different slug switches tenant.
    • 403 on unauthorised slug ✅ — EnsureUserCanAccessCompany enforces access.

    What needs to be built — the UI switcher:

    Filament v4 supports a custom tenant menu. Configure in CompanyPanelProvider:

    ->tenantMenu(
        fn (\Filament\Navigation\NavigationItem $item) => $item->label(fn () => filament()->getTenant()?->name ?? 'Switch Company')
    )

    For a proper modal-based switcher:

    1. Create a Livewire component: Modules/Core/Filament/Company/Components/CompanySwitcher.php

    public function switchCompany(int $companyId): void
    {
        $company = auth()->user()->companies()->findOrFail($companyId);
        Filament::setTenant($company);
        session(['current_company_id' => $company->id]);
        $this->redirect(route('filament.company.pages.dashboard', ['tenant' => Str::lower($company->search_code)]));
    }

    2. Register it as a navigation widget or render it in the panel header.

    Scope for Sprint 5 (MVP):

    • Show current company name in top-right (Filament tenant label)
    • Clicking shows a modal listing $user->companies() with a Switch button per row
    • Switching sets session + Filament tenant and redirects
    • Edit button next to companies the user has admin rights for

    Defer to later:

    • Scenarios 6–8 (cross-tab persistence, long-running job isolation) — over-engineered for MVP

    Tests: Cover Scenario 1, 4, and 5 from the issue body.
    Effort: ~5 hours for MVP switcher.


    Generated by Claude Code

  4. linked a pull request that will close this issue[IP-138] company switcher #474on Jul 3, 2026
  5. nielsdrost7 commented on Jul 24, 2026

    @nielsdrost7
    CollaboratorAuthor

    SMART Plan — [Core] Company Switcher

    Specific

    Implement a company context switcher in the Filament company panel UI allowing users to switch between companies they belong to without logging out. The switcher should:

    • Display the current company name/identifier in the top-right corner.
    • Open a company-picker modal (or dropdown) when clicked.
    • Show all companies the authenticated user has access to.
    • Update the application context (session, Filament tenant, route) when a company is selected.
    • Enforce authorization at every step (server-side, via Spatie roles/permissions).
    • Log all company switches for audit purposes.
    • Update the URL slug (search_code) to reflect the new company.
    • Persist the choice across page reloads and tabs (session-level).

    Measurable

    • Current company name appears in a top-right UI element (e.g. top navigation bar).
    • Clicking the element opens a modal or dropdown listing all companies the user has access to.
    • Selecting a company updates the Filament tenant context.
    • The URL changes to reflect the new company's search_code (e.g. /ivplv2/dashboard → /other-code/dashboard).
    • All data displayed after the switch is scoped to the new company (invoice list, etc.).
    • Audit log records the company switch event (user, timestamp, from company → to company).
    • User cannot access a company they don't belong to (403 or redirect).
    • PHPUnit tests confirm context switching, authorization, and data isolation.

    Achievable

    1. Add UI element in the company panel's layout/header:
      • Display current company via Filament::getTenant().
      • Render a clickable button or dropdown trigger.
    2. Create Livewire modal Modules/Core/Filament/Company/Components/CompanySwitcher:
      • Fetch all companies for the authenticated user via auth()->user()->companies().
      • Render a list with company names.
      • Add an optional "Edit" button per company (if user has permission).
      • Dispatch a Livewire event on company selection.
    3. Create an action that:
      • Receives the selected company ID/slug.
      • Verifies authorization via Gate::authorize('access', $company) (use existing EnsureUserCanAccessCompany::class middleware pattern).
      • Sets the Filament tenant: Filament::setTenant($company).
      • Updates the session: session(['current_company_id' => $company->id]).
      • Redirects to the new company's route with its slug: route('filament.company.pages.dashboard', ['tenant' => Str::lower($company->search_code)]).
      • Logs the switch event via AuditLog::create([...]).
    4. Handle edge cases:
      • Ensure background jobs remain scoped to their original company (use tenant context in job payloads, not session).
      • If no company is selected yet, default to the user's first company or a "select company" landing page.
      • If a user's company access is revoked, log out and redirect to login.
    5. Add PHPUnit tests:
      • Test switching between two companies and verifying data scope.
      • Test that unauthorized access returns 403.
      • Test URL slug updates correctly.
      • Test audit log records the switch.
      • Test session persistence across requests.

    Relevant

    Multi-tenant users waste significant time logging out/in to switch contexts. A seamless switcher keeps users in the app and dramatically improves workflow for agencies, accountants, and multi-company management scenarios.

    Time-bound

    Sprint 5.


    Acceptance Criteria

    • Current company name displayed in top-right UI.
    • Company picker modal lists all user's companies.
    • Selecting a company updates session, Filament tenant, and URL.
    • New company's data is displayed after switch; no old data leaks.
    • Unauthorized company access returns 403 or safe redirect.
    • Audit log records switch events.
    • No full-page reload required (context change is seamless via Livewire).
    • PHPUnit test: user can switch between two companies and see scoped data.
    • PHPUnit test: unauthorized company access is blocked.
    • PHPUnit test: audit log records the switch.
  6. nielsdrost7 commented on Jul 24, 2026

    @nielsdrost7
    CollaboratorAuthor

    Correction: an unmerged local branch, feature/138-company-switcher (25 commits ahead of develop, no open PR yet), already implements a company switcher — including resources/views/filament/pages/switch-company-modal.blade.php and switch-company.blade.php, matching this plan's proposed design. The SMART Plan comment above was posted in error by an automated grooming pass that didn't check this branch (its numeric-ID check should have caught feature/138-company-switcher but didn't). The earlier "Sprint 5 — Implementation Plan" comment on this issue already reflects that branch's actual design. Please check that branch before implementing from scratch — it likely just needs finishing/rebasing and a PR opened.

  7. nielsdrost7 commented on Jul 24, 2026

    @nielsdrost7
    CollaboratorAuthor

    Already implemented — Modules/Core/Filament/Company/Pages/MyCompanies.php (live on develop) covers company-switching: lists every company the user belongs to, disables the switch action for the currently active one, sets session('current_company_id') and calls Filament::setTenant() on switch. Two prior attempts at this (#583, #474) were closed as superseded by this — neither ever closed this issue since neither merged. feature/138-company-switcher is stale/redundant against this and shouldn't be pursued further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    high-priorefinedRefined with SMART plan / test suggestions

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions