Orgs & Org Switcher
This area covers 30 features. Watch the walkthrough, then use the reference below — each feature links to the exact moment it appears (▶).
Who this is for: Org owner / org admin. Each feature below lists the role/permission it requires.
Features
View organizations list ▶ 00:00
As a user with multiple organizations, I want to see all my organizations, so that I can understand my workspace portfolio.
How it works. When a user has 2+ orgs, OrgContext computes isAllOrgs=true (orgs.length > 1 && activeOrgId == null). The dashboard renders 'All organizations' as the workspace label. A grid card shows the count of organizations. The OrgSwitcher dropdown shows 'All organizations' as an option (line 100-105, OrgSwitcher.tsx). Users can click 'All organizations' in the switcher to navigate to this view via ?org=all query param.
Key files
src/context/OrgContext.tsx:92src/components/org/OrgSwitcher.tsx:30src/components/org/OrgHome.tsx:313,453
Switch between organizations ▶ 00:00
As a user with multiple organizations, I want to switch between them quickly, so that I can work on different org workspaces.
How it works. OrgSwitcher renders a dropdown button (ChevronsUpDown icon) showing current org name + role, or 'All organizations' if in all-orgs view. Clicking the button toggles open state. Dropdown lists each org as a clickable row with role subtitle and a checkmark on the active one. Selecting an org calls setActiveOrg(orgId), stores it in localStorage as org:active, and navigates to ?org={orgId}. The context emits the new activeOrgId.
Key files
src/components/org/OrgSwitcher.tsx:76-90src/context/OrgContext.tsx:82-85src/context/OrgContext.tsx:67-80
Create new organization ▶ 00:00
As a user, I want to create a new organization, so that I can manage a separate workspace.
How it works. In OrgSwitcher dropdown, a '+ Create org' button appears at the bottom (line 178-184, OrgSwitcher.tsx). Clicking it reveals an input[aria-label='New org name'] + Create button. User types an org name and clicks Create. The createOrg(jwt, name) API call fires (lib/frontier/orgs.ts:115). On success, org list refreshes and the new org becomes active. The switcher trigger updates to show the new org name. If create fails, error bubbles (currently caught but not displayed in createName form state).
Key files
src/components/org/OrgSwitcher.tsx:49-62src/lib/frontier/orgs.ts:115-119
Rename organization (inline in switcher) ▶ 00:23
As an org owner/admin, I want to rename my organization inline, so that I can update the workspace name without navigating away.
How it works. In OrgSwitcher dropdown, a 'Rename' button appears (visible only when activeOrg?.role.level >= 600, line 31, OrgSwitcher.tsx). Clicking 'Rename' shows input[aria-label='Rename org'] prefilled with current name + Save button. User edits and clicks Save. renameOrg(jwt, activeOrg.id, newName) API fires. On success, context refreshes orgs and the switcher trigger updates to show the new name. Form clears and closes.
Key files
src/components/org/OrgSwitcher.tsx:124-154src/lib/frontier/orgs.ts:121-124
View org home / projects dashboard ▶ 00:00
As an org user, I want to see my organization's projects on a dashboard, so that I can browse and filter translation work.
How it works. OrgHome renders at / when jwt is set and an org is selected (activeOrgId != null && !isAllOrgs). The page displays: org name as h1 heading (line 420, OrgHome.tsx), a rollup strip with stat cards (Projects, Avg translated %, Avg validated %, Avg audio %, Stalled count, Overdue count), a project list filtered by status and search query, and org-specific panels (WorkloadRollup, UsageRollup, CreditsPanel). When jwt is null, shows unauthenticated message with Sign in link.
Key files
src/components/org/OrgHome.tsx:161-417src/components/org/OrgHome.tsx:812-877
View all organizations overview (cross-org dashboard) ▶ 00:00
As a user with multiple orgs, I want to see a dashboard across all my organizations, so that I can monitor progress holistically.
How it works. When isAllOrgs=true (activeOrgId == null && orgs.length > 1), OrgHome renders a distinct cross-org layout. Top section shows 6 stat cards: org count, project count, avg translated %, avg validated %, stalled count, overdue count. Below: left grid shows Organizations section (filterable list of org cards with per-org stats), right grid shows Projects section (all projects across all orgs, filterable by name/status, sortable by lens). Each org card is clickable and opens that org's dashboard.
Key files
src/components/org/OrgHome.tsx:453-704src/components/org/OrgHome.tsx:332-350
Filter organizations by name ▶ 00:00
As a user with many organizations, I want to search/filter orgs by name, so that I can find a specific org quickly.
How it works. When viewing 'All organizations' (isAllOrgs=true), an input[placeholder='Filter organizations…'] appears above the org list (line 492-500, OrgHome.tsx). User types; orgs are filtered client-side by name (case-insensitive includes check, line 347-350). Empty result shows 'No matching organizations.' message. Clearing the input restores the full list.
Key files
src/components/org/OrgHome.tsx:492-510
Filter projects by name ▶ 00:00
As an org user, I want to filter projects by name, so that I can quickly find a project to work on.
How it works. On org dashboard, input[placeholder='Filter projects…', aria-label='Filter projects by name'] is present. User types a query; projects are filtered client-side (case-insensitive includes, line 382, OrgHome.tsx). When a filter is active and no projects match, shows 'No matching projects.' Empty result message changes based on the active project lens (e.g., 'No recently updated projects yet.'). Clear button (X icon) appears when input has text (line 563-573).
Key files
src/components/org/OrgHome.tsx:738-764src/components/org/OrgHome.tsx:381-391
Filter projects by status (All/Stalled/Overdue) ▶ 00:00
As an org user, I want to filter projects by status, so that I can focus on problematic or active projects.
How it works. Status filter pills appear on org dashboard when projects exist (line 576-595, OrgHome.tsx): All (default), Stalled, Overdue. Each pill has aria-pressed attribute. Clicking a pill sets statusFilter state and updates aria-pressed. 'Stalled' shows projects with no recent activity (14+ days, activityStatus=stalled). 'Overdue' shows projects past their deadline. 'All' shows all projects. Filter integrates with name search (both must match).
Key files
src/components/org/OrgHome.tsx:383-391src/components/org/OrgHome.tsx:576-595
Sort projects by lens (Recently updated/Needs attention/Least translated/Most progress/Name) ▶ 00:00
As an org user, I want to sort projects by different criteria, so that I can view work in order of priority or progress.
How it works. A 'Sort by' dropdown appears in the filter bar (line 786-807, OrgHome.tsx, using Select component). Options: Recently updated, Needs attention, Least translated, Most progress, Name. Default is 'Recently updated'. Selection is persisted to localStorage (PROJECT_LENS_STORAGE_KEY, line 38). Each lens applies a specific sort algorithm: 'recent' by lastEditAt desc, 'attention' by attentionRank desc, 'least-translated' by translatedPct asc, 'most-progress' by translatedPct desc, 'name' by alphabetical. Empty state message varies by lens.
Key files
src/components/org/OrgHome.tsx:62-93src/components/org/OrgHome.tsx:144-159src/components/org/OrgHome.tsx:787-806
View pending project invitations ▶ 00:00
As a user, I want to see incoming project invitations addressed to my email, so that I can discover invites that may not have arrived via email.
How it works. On org dashboard, if pendingInvites.length > 0 (fetched by listMyPendingInvites, line 280, OrgHome.tsx), a 'Pending invitations' section appears (line 426-451). Each invite row shows project name(s), inviter, role, expiry date (if set), and a 'Review & accept' link to /join/:token. Section is org-independent (not scoped to activeOrgId). Unauthenticated users do not see this section.
Key files
src/components/org/OrgHome.tsx:276-284src/components/org/OrgHome.tsx:426-451
View shared projects (cross-org access via invite link) ▶ 00:00
As a user who has accepted a project invite from another org, I want to see that project on my dashboard, so that I can access projects shared with me.
How it works. Below the org's own project list, if sharedProjects.length > 0 (from partitionSharedProjects, line 330, OrgHome.tsx), a 'Shared with you' section appears (line 882-898). Each shared project shows name, role badge, and links to /projects/:id. Shared projects come from cross-org grant paths (invite link, bulk add) and are not part of the current org's portfolio. If no shared projects exist, section is hidden.
Key files
src/components/org/OrgHome.tsx:330src/components/org/OrgHome.tsx:882-898
Project status indicators (Overdue/Due soon) ▶ 00:00
As an org user, I want to see which projects are overdue or due soon, so that I can prioritize urgent work.
How it works. On each project row, if project has a deadline, deadlineStatus(project, now) returns 'overdue', 'soon', or null. Matching projects show status chips: red 'Overdue' badge or amber 'Due soon' badge (line 667-676, OrgHome.tsx). Overdue projects are included in overdueCount stat card. The 'Overdue' status filter shows only projects with 'overdue' status.
Key files
src/components/org/OrgHome.tsx:667-676src/components/org/OrgHome.tsx:326
Project stalled indicator ▶ 00:00
As an org user, I want to know which projects have stalled (no activity for 14+ days), so that I can re-engage with inactive work.
How it works. For each project, activityStatus(project, now) returns 'stalled' if lastEditAt is null or older than 14 days (STALE_THRESHOLD_MS = 14 * 24 * 60 * 60 * 1000, line 30, OrgHome.tsx). Projects with 'stalled' status show warning text in the right-hand status column (line 691, OrgHome.tsx: 'Stalled' in red destructive color). Stalled projects are counted in stalledCount stat card. The 'Stalled' status filter option (now labeled 'Active' in pill group) may appear in future (currently 'All/Stalled/Overdue', line 56-60).
Key files
src/components/org/OrgHome.tsx:121-125src/components/org/OrgHome.tsx:30src/components/org/OrgHome.tsx:325
Project progress bars (translated % and validated %) ▶ 00:00
As an org user, I want to see translation and validation progress for each project at a glance, so that I can assess completion status.
How it works. On each project row, two horizontal progress bars render (line 682-687, OrgHome.tsx): amber bar for translated %, emerald bar for validated %. Bar width reflects percentage of translated or validated cells. Aria-label summarizes both percentages. Right-hand status column shows numerical percentages (e.g., '75% validated'). Progress data comes from portfolio project metrics (translatedPct, validatedPct helpers).
Key files
src/components/org/OrgHome.tsx:682-687src/components/org/OrgHome.tsx:635-637
View rollup statistics (org-level) ▶ 00:00
As an org admin, I want to see aggregated statistics across all org projects, so that I can monitor overall progress and health.
How it works. On org dashboard, a strip of 6 stat cards appears (line 708-733, OrgHome.tsx, single-org view; or line 456-480, multi-org view): Projects (count), Avg translated %, Avg validated %, Avg audio %, Stalled (count), Overdue (count). Stats are computed from the loaded portfolio (averagePct helper, or direct counts for stalled/overdue). All stats have centered text layout with bold number + small label. Overdue count is red if > 0. Multi-org view shows these plus org count.
Key files
src/components/org/OrgHome.tsx:708-733src/components/org/OrgHome.tsx:316-345
Create project from org dashboard ▶ 00:00
As an org owner, I want to create a new project from the dashboard, so that I can set up translation work quickly.
How it works. On org dashboard header (line 400-406, OrgHome.tsx), a ProjectCreateDialog button appears when activeOrgId != null. Clicking opens a dialog to create a project. On success, handleCreated navigates to /projects/:id. If no org is selected (activeOrgId == null), a disabled hint appears: 'Select an organization to create a project'.
Key files
src/components/org/OrgHome.tsx:400-406
Access org-specific panels (WorkloadRollup, UsageRollup, CreditsPanel) ▶ 00:00
As an org admin, I want to see org-specific workload, usage, and credit information, so that I can manage org resources.
How it works. Below the project list in single-org view, three panels render conditionally (line 902-910, OrgHome.tsx) when jwt is set, not in isAllOrgs mode, and activeOrgId != null: WorkloadRollup (project team assignment info), UsageRollup (usage metrics), CreditsPanel (credit balance and purchasing, role-gated to >= level 600 via orgRoleLevel prop). These panels are hidden in multi-org 'All organizations' view.
Key files
src/components/org/OrgHome.tsx:902-910
Org sidebar navigation ▶ 00:00
As an org user, I want a sidebar with org-scoped navigation, so that I can access org pages (projects, teams, members, settings) easily.
How it works. OrgSidebar renders (AppShell sidebar prop). Top shows OrgSwitcher (org/all-org selection). Below: nav links to /projects (Projects), and org-scoped routes when not isAllOrgs: /teams (Teams), /assigned (Assigned to me). Admin-only routes (when role.level >= 600): /members (Members), /projects/archived (Archived), /settings (Settings). Platform admin route: /admin (Admin). Bottom: 'Take the tour' button + AccountSwitcher.
Key files
src/components/org/OrgSidebar.tsx:26-74
Breadcrumb navigation (org context) ▶ 00:00
As an org user, I want breadcrumbs showing my org context, so that I understand where I am in the navigation hierarchy.
How it works. OrgBreadcrumb renders (AppShell header). Displays crumb sequence: 'All organizations' › org-name › section-name (e.g., 'Projects', 'Teams'). 'All organizations' breadcrumb is clickable and navigates to ?org=all when orgs.length > 1 and not isAllOrgs. Org name is clickable (navigates /) only when not on the Projects page (canNavigateToOrg logic, line 15, OrgBreadcrumb.tsx). Section only shows if section !== 'Projects' or parent exists.
Key files
src/components/org/OrgBreadcrumb.tsx:31-69
Auto-select single org ▶ 00:00
As a user with only one org, I want that org to be automatically selected, so that I see the org dashboard without extra navigation.
How it works. On OrgProvider mount or org list refresh, if orgs.length === 1, activeOrgId is set to orgs[0].id automatically (line 49, OrgContext.tsx). If already at / (org home), the dashboard shows that org's projects. User does not see 'All organizations' view (isAllOrgs=false, line 92).
Key files
src/context/OrgContext.tsx:41-53src/context/OrgContext.tsx:47-52
Persist org selection in localStorage ▶ 00:00
As an org user, I want my selected org to be remembered across sessions, so that I return to my preferred workspace.
How it works. OrgProvider stores activeOrgId in localStorage (key 'org:active', line 8, OrgContext.tsx) on every setActiveOrg/setAllOrgs call. On mount, the stored value is read and set as initial activeOrgId (line 32-37). If the stored value is 'all', activeOrgId remains null (isAllOrgs logic). Invalid or missing values default to null.
Key files
src/context/OrgContext.tsx:8src/context/OrgContext.tsx:32-37src/context/OrgContext.tsx:82-90
Sync org selection with URL query param ▶ 00:00
As an org user, I want URL-based org selection (shareable/bookmarkable), so that org context persists in links.
How it works. OrgContext watches location.search (line 67-80, OrgContext.tsx). If pathname === '/' and ?org={value}, the org ID is extracted and set as activeOrgId, stored in localStorage. Value 'all' sets activeOrgId to null (isAllOrgs mode). Invalid values are ignored. OrgSwitcher and OrgBreadcrumb navigate with ?org= when switching orgs (line 38, 45, 21, OrgBreadcrumb.tsx).
Key files
src/context/OrgContext.tsx:67-80
Handle session expiration in org context ▶ 00:00
As a user whose session expires, I want the app to notify me and halt org operations, so that I re-authenticate.
How it works. When OrgContext or OrgHome fetch calls encounter a UserError with category='session-expired' (e.g., 401 from listMyOrgs or getPortfolio), notifySessionExpired() is called (line 56, OrgContext.tsx; line 216, 246, OrgHome.tsx). This emits a SessionExpiredSignal that triggers a toast/redirect to login. Error state is set but rendering continues (gracefully handling stale data).
Key files
src/context/OrgContext.tsx:54-57src/components/org/OrgHome.tsx:215-249
Handle org load errors ▶ 00:00
As a user with network issues, I want error messages about org loading, so that I understand why the dashboard is unavailable.
How it works. If listMyOrgs or getPortfolio fails (network, 5xx, timeout), error state is captured (line 58, OrgContext.tsx; line 218, OrgHome.tsx). OrgHome displays error text in red (line 415, OrgHome.tsx): 'Something went wrong: [error message]'. User can retry via refresh (explicit or via automatic navigation). Error does not render fake empty state.
Key files
src/context/OrgContext.tsx:54-62src/components/org/OrgHome.tsx:414-415
Organization search (org switcher hint text) ▶ 00:00
As a user with many orgs, I want to search within the org switcher, so that I can find the right org quickly.
How it works. OrgSwitcher dropdown renders a list of org items (line 107-121, OrgSwitcher.tsx). While no native search box is implemented in the switcher, the dropdown is scrollable (max-h-60 overflow-y-auto, line 91) and labeled with org role subtitle for quick visual scanning.
Key files
src/components/org/OrgSwitcher.tsx:91-122
Unauthenticated user org dashboard view ▶ 00:00
As a visitor without an account, I want to know I'm signed out, so that I can sign in.
How it works. When sessionLoading completes and jwt is null (line 288, OrgHome.tsx), instead of rendering project cards, a centered message appears: 'Sign in to see your workspace' + 'Your session has ended or you are not signed in...' + a blue 'Sign in' button linking to /login?next=/. No rollup stats, no project list, no spinner.
Key files
src/components/org/OrgHome.tsx:288-309
Org role display (in switcher and sidebar) ▶ 00:00
As an org user, I want to see my role within each org, so that I understand my permissions.
How it works. In OrgSwitcher dropdown, each org item shows role.name (with underscores replaced by spaces, line 116, OrgSwitcher.tsx) as a small subtitle below the org name. In OrgHome rollup, org items in the 'All organizations' grid show a role badge (line 523-525, OrgHome.tsx) with the role name. Role affects available features: 'owner' / 'maintainer' (level >= 600) unlock rename + admin pages.
Key files
src/components/org/OrgSwitcher.tsx:116src/components/org/OrgHome.tsx:523-525src/components/org/OrgHome.tsx:135-137
Org member access control display ▶ 00:00
As an org user, I want to know if I can perform org actions (invite, settings), so that I don't attempt prohibited operations.
How it works. Rename button in OrgSwitcher is visible only when role.level >= 600 (line 31, OrgSwitcher.tsx). OrgSidebar shows Members/Archived/Settings links only when role.level >= 600 (line 45-50, OrgSidebar.tsx). CreditsPanel visibility is gated the same way (line 908, OrgHome.tsx). Non-admins see stripped-down sidebar (Projects, Teams, Assigned to me only).
Key files
src/components/org/OrgSwitcher.tsx:31src/components/org/OrgSidebar.tsx:16,45-50
Refresh org list ▶ 00:00
As an org admin, I want to manually refresh the org list, so that I see newly created orgs or membership changes immediately.
How it works. OrgContext exposes a refresh() function (line 41-63, OrgContext.tsx) that re-fetches listMyOrgs and updates state. Callers can invoke refresh() directly (e.g., OrgSwitcher does after createOrg, line 54). If called during an active render, the new org list replaces the old one. If no orgs exist after refresh, activeOrgId is set to null.
Key files
src/context/OrgContext.tsx:41-63src/components/org/OrgSwitcher.tsx:54