Skip to content

People

Internal directory that rolls up customers + partners + staff, plus the staff-only user management surface at /people/employees. This is the module ops uses to see "who's who" across all user types in one place; Admin → Permissions is where roles are assigned once a user exists.

Scope

Two pages:

  • /peoplesrc/pages/people/People.tsx. Cross-party directory with KPIs (customers, partners, staff counts + 30-day new-arrivals) and a recent-activity feed drawn from the audit log.
  • /people/employeessrc/pages/people/Employees.tsx. Thin wrapper around the shared <UserManagement> component with excludeRoles={['agent', 'customer']}.

Shared component: src/components/admin/UserManagement.tsx — the actual CRUD surface for staff users, reused here and on Admin.

Pages

People.tsx/people

Route gated by customers.view (src/App.tsx:181).

Route permission is inherited, not semantic

/people reuses customers.view because most viewers of this directory are customer-facing staff. There's no dedicated people.view permission. See docs/PERMISSIONS.md §6.15.

Pulls three parallel fetches on mount (People.tsx:40-75):

  1. fetchCustomers() → customer count + new-last-30d.
  2. fetchAgentsWithStats() → partner count + new-last-30d.
  3. apiFetch('/users') → staff count + new-last-30d. If the call 403s the staff KPI shows null instead of a number.

Then loads recent audit-log activity (People.tsx:77-99) filtered to customer / agent / lead entities, top 20. If the audit call is forbidden, the activity panel shows the "restricted" empty state instead of an error.

Employees.tsx/people/employees

Full file (Employees.tsx:1-10):

import { MainLayout } from '@/components/layout/MainLayout';
import { UserManagement } from '@/components/admin/UserManagement';

export default function Employees() {
  return (
    <MainLayout title="Employees & Staff" subtitle="Manage internal users and roles">
      <UserManagement excludeRoles={['agent', 'customer']} />
    </MainLayout>
  );
}

Route gated by admin.view (src/App.tsx:182). All behavior lives in <UserManagement>.

Role assignment — <UserManagement>

Source: src/components/admin/UserManagement.tsx. Reused in both /people/employees and /admin.

Operations

Operation Call
List users GET /users (UserManagement.tsx:171)
Create user POST /users
Change role POST /users/:id/roles with { role: 'UPPERCASE_NAME' } (UserManagement.tsx:226-229)
Reset password POST /users/:id/password (or similar — see component)
Delete user DELETE /users/:id

Role catalog

UserManagement.tsx is the authoritative UI list of canonical roles (UserManagement.tsx:48-73, :110-120). 18 active roles + 3 legacy buckets (operations, finance, sales) preserved so existing assignments still render. See PERMISSIONS.md §3 for the definitive catalog.

Visual tiering (UserManagement.tsx:77-101):

  • Destructive (red)ceo, gm, director, it_admin, admin_hr (super-admin / admin tier).
  • Primary — manager-tier roles.
  • Info — operational / support roles.
  • Mutedauditor, agent, customer.

Wire format

UI uses lowercase (sales_manager), API uses uppercase (SALES_MANAGER). Conversion helpers at UserManagement.tsx:158-166 (mapApiRole / mapAppRoleToApiRole). Unknown values fall back to null so nothing mis-renders.

Staff email domain gate

isAllowedStaffEmail (UserManagement.tsx:123-126) checks the user's email ends with @alhuda.co.in or @alhudatravels.in.

Relationship to permissions

Role membership does not directly grant permissions in code. The chain is:

  1. <UserManagement> assigns a role_name (e.g. SALES_MANAGER) to a user via POST /users/:id/roles.
  2. The DB has a RolePermission table populated by seed migrations (see supabase/migrations/*permissions*.sql) that maps each role to its explicit list of permission rows.
  3. auth_user_has_permission(perm_name) (Postgres function) resolves a user's effective permissions by UNION-ing (a) role grants with (b) per-user overrides from UserPermission.
  4. Code checks specific permissions via <ProtectedRoute>, <PermissionGate>, or requirePermission()never by role name.

The three super-admin roles (CEO, GM, IT_ADMIN) are super-admins because migration 20260416020000_remove_permission_bypass.sql seeds them every permission row explicitly — not by a hardcoded role short-circuit. Revoking a specific permission in Admin → Permissions will restrict even a CEO.

Per-user overrides (UserPermission.allowed=true/false) beat role grants — see PERMISSIONS.md §2 for the precedence order.

Changing a user's role takes effect on next token refresh

The role+permission set is included in the user's auth context. A user in an active session continues to use their old role until they re-authenticate or their session is refreshed. If you need an immediate revocation (e.g. offboarding), follow up with a session invalidation via the admin tool.

Permissions

Canonical reference: docs/PERMISSIONS.md §6.15.

Action Permission
View /people customers.view
View /people/employees admin.view
Create staff user admin.view (should tighten to admin.users.create)
Reset staff password admin.view (should tighten to admin.users.reset_password)
Change staff role admin.view (should tighten to admin.users.edit)
Delete staff user admin.view (should tighten to admin.users.delete)
View recent activity feed customers.view + (audit log read on server)

Granular admin permissions are seeded but not yet wired

admin.users.create, admin.users.edit, admin.users.delete, admin.users.reset_password, admin.roles.*, admin.permissions.edit exist in the catalog (PERMISSIONS.md §4.4) and are seeded to super-admin-tier roles, but <UserManagement> today gates all its actions behind the coarse admin.view. See docs/PERMISSIONS.md §8 drift item 7.

  • Customers — one of the three populations counted on /people.
  • Partners — another counted population.
  • Admin module — /admin hosts the Permissions matrix UI where role grants and user overrides are edited.
  • PERMISSIONS.md §3, §6.15, §6.16 — role catalog, page matrix, admin surface.