Documentation
How the product works
This page describes the multi-tenant field-service experience: URLs, roles, main UX features, and how to run the frontend locally for development.
Problem we solve
Field service companies run on dispatched work, customer expectations, and proof of work — but generic tools scatter tickets, portals, and technician updates across inboxes and spreadsheets. Service Ticket SaaS gives each organization an isolated workspace with admin, customer, and technician surfaces under one path-based product, plus a separate platform plane for operators who run the multi-tenant service itself.
On this page
Jump to a section or use search below to filter feature cards and the route table.
System model
Each company is a tenant with its own data (isolated by company-id on the server). Users belong to one tenant per session. The browser URL uses a public slug (not a UUID) so links are shareable: /o/your-slug/….
- Admin creates the org and manages tickets, customers, and settings.
- Customer opens and follows service requests (My Tickets).
- Technician sees assigned work (My Jobs).
- Platform operators use a separate route (not under
/o/) for SaaS-level tasks.
Core architecture (logical)
Browser and mobile clients talk to a single API namespace. Tenant context is enforced from verified JWT claims and tenant-id on every data row — never from client-supplied slugs alone.
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ Next.js (App) │────▶│ Express /api/v1 │────▶│ PostgreSQL + RLS │
│ /o/{slug}/… │ │ + tenant ctx │ │ tenant-id scoped │
└────────┬────────┘ └────────┬─────────┘ └─────────────────────┘
│ │
│ ┌──────────┴──────────┐
│ │ Redis (sessions / │
└──────────────│ queues / rate cap) │
└─────────────────────┘
Platform plane: /platform/* UI → Bearer platform JWT → /api/v1/platform/* (separate from org tokens).Representative user flows
- Org admin registers at
/register→ lands in/o/slug/a/dashboard→ invites staff and creates tickets. - Customer uses
/o/slug/login→/o/slug/c/my-ticketsto open and track requests. - Technician signs in at the same org URL →
/o/slug/t/my-jobsfor field overview, filters, and assignments. - Platform operator uses
/platform/login→ overview metrics → tenant directory → per-tenant management; super admins add delegated operators.
Ticket workflow (overview)
Support tickets close without a billing step; billable tickets may pass through Billing before Closed. Staff can submit work for review; reviewers approve, resolve, or send back. The live product enforces transitions per template in the API.
Loading diagram…
UX features (what you get in the browser)
These are product-facing behaviors implemented or staged in the current frontend. APIs and tenancy rules live in the backend; this list is what users and testers interact with.
Path-based tenancy
Every organization has a public slug. All app URLs live under /o/{slug}/ with role segments: /a (admin), /c (customer), /t (technician). No subdomain setup required for v1.
Session & URL alignment
After sign-in, the client keeps your JWT org slug in sync with the address bar. Wrong slug or wrong role segment (/a vs /c vs /t) is corrected automatically when possible.
Multi-organization accounts
If your email belongs to more than one company, you can pick the active organization (and set a default in Settings). Switching updates the path under the new slug.
Role-specific shells
Admin, customer, and technician areas use dedicated layouts: sidebar or top nav, skip links for accessibility, and consistent semantic colors (design tokens).
Guided tours
First-time flows can launch in-page tours on key screens (admin queue, customer tickets, technician jobs). Completion is remembered in the browser.
Realtime updates
When tickets change (create, status, assignment), the UI can refresh lists and dashboards via a tenant-scoped socket connection.
Technician install shell (PWA)
Technician routes support a minimal offline-friendly install manifest and service worker for shell caching; full offline sync is a future iteration.
Invite & onboarding copy
Org admins get copy-paste registration and login URLs per role. Public org pages explain customer vs technician paths without exposing internal IDs.
RBAC & custom staff roles
A global permission template applies to Owner, Admin, Manager, Reviewer, Technician, and Customer—including tickets.review on staff roles for future workflow surfaces. The Owner can define named custom profiles (FR-040 Phase B) with their own permission sets; platform policy org_max_custom_roles caps how many exist (0 disables). Assign on Users; JWTs carry customRoleId—re-login after assignment.
Ticket workflow templates (admin)
Tickets have a type (support vs billable) that selects the workflow template: support skips billing states; billable may include Billing before close. Admin ticket detail uses Allowed next statuses with POST /tickets/{id}/workflow-transition (optional reason + reasonCode). Review queue nav appears when you have tickets.review (FR-041).
Platform operator shell
After /platform/login, Overview metrics, tenant directory, per-tenant management, delegated operators (super admin), and Legal sit in a sidebar shell: skip link, theme, sign-out, tenant log-in, and footer links to platform policies and product legal (FR-012 v1.2 / FR-045).
Ticket attachments (field capture)
Customer and technician ticket pages support uploads when the API is configured for S3 or local filesystem storage; otherwise the UI shows a stub. Downloads use a short-lived token on the file URL.
Governance & Auditability
Administrative identity changes (email, profile, role) and password resets require an approved Governance Ticket. The Governance Wizard facilitates ticket creation, authorization, and automatic resolution with a full audit summary.
How to navigate
Replace {slug}with your organization's slug. Use the global entry points when you do not yet have a slug bookmarked.
Authenticated workspaces are only under /o/{slug}/…. Legacy flat paths such as /dashboard, /my-tickets, or /my-jobs are not served — update bookmarks and integrations to the slugged URLs below.
| Path or pattern | What it is | Typical user |
|---|---|---|
| / | Product home | Everyone |
| /how-it-works | This guide (routes + dev setup) | Everyone |
| /register | Create organization (first admin) | New SaaS customer |
| /register/customer | Customer signup with company ID (when no slug link) | End customers |
| /register/agent | Technician signup with company ID (when no slug link) | Field staff |
| /login | Sign in (any role, org picker if needed) | Returning users |
| /forgot-password | Password help stub (FR-037) | Anyone locked out |
| /o/{slug} | Organization welcome | Visitors with a slug |
| /o/{slug}/c/register · /o/{slug}/login | Customer join & return (preferred) | End customers |
| /o/{slug}/t/register · /o/{slug}/login | Technician join & return (preferred) | Field staff |
| /o/{slug}/login | Sign-in for that org (all roles → right dashboard) | Admins, techs, customers |
| /o/{slug}/a/dashboard | Admin dashboard & metrics | Admins |
| /o/{slug}/a/tickets | Company ticket queue (list) | Admins |
| /o/{slug}/a/tickets/{id} | Field ticket detail + Allowed next statuses (FSM) | Admins (tickets.read) |
| /o/{slug}/a/settings/requests/{id} | Governance ticket detail (Gov workflow) | Admins (tickets.read) |
| /o/{slug}/a/tickets/new | Create ticket (lazy form) | Admins (tickets.create) |
| /o/{slug}/a/customers | Customer directory | Admins |
| /o/{slug}/a/customers/new | Add customer (lazy form) | Admins |
| /o/{slug}/a/settings | Admin settings, invites, default org | Admins |
| /o/{slug}/a/settings/permissions | Permission matrix (global + custom rows) | Admins (settings.read) |
| /o/{slug}/a/settings/custom-roles | Custom staff roles (Owner edits) | Admins |
| /o/{slug}/a/users | Users directory + Owner custom-role assign | Admins |
| /o/{slug}/c/my-tickets | My Tickets list | Customers |
| /o/{slug}/c/my-tickets/new | New service request | Customers |
| /o/{slug}/c/my-tickets/{id} | Ticket detail, comments, attachments | Customers |
| /o/{slug}/c/settings | Customer account & default org | Customers |
| /o/{slug}/t/my-jobs | My Jobs (assignments) | Technicians |
| /o/{slug}/t/my-jobs/{id} | Job detail & attachments | Technicians |
| /o/{slug}/t/settings | Technician account & default org | Technicians |
| /platform/login | Platform operator sign-in | SaaS staff only |
| /platform | Platform overview (tenant counts) | Platform JWT |
| /platform/tenants | Tenant directory (search, link to manage) | Platform JWT |
| /platform/tenants/{companyId} | Tenant management (lifecycle, flags) | Platform JWT |
| /platform/operators | Delegated platform users (super admin) | Platform JWT |
| /platform/legal | Operator policy placeholders | Platform JWT |
| /o/{slug}/t/my-jobs | Technician jobs (overview + assigned list) | Technicians |
Quick start (frontend development)
From the repository root, install once, then run the API and the Next.js app. The UI expects the backend for sign-in, tickets, and public org lookup.
Commands
Node.js 20+ and npm 10+ recommended.
1. Install dependencies
npm install
2. Infrastructure (PostgreSQL + Redis)
docker compose up -d
3. Database schema + demo data (once per machine)
npm run prisma:migrate:deploy -w backend npm run seed
See README.md for sandbox orgs sandbox-hvac / sandbox-it and the shared demo password.
4. Backend API (or run both apps together — step 5)
npm run dev:backend
Requires DATABASE_URL, JWT_SECRET, and Redis if enabled — see backend/README.md.
5. Frontend (or API + UI concurrently)
npm run dev:frontend
npm run dev
App URL is usually http://localhost:3000. Prefer NEXT_PUBLIC_API_BASE_URL=/api/v1 so Next proxies to the API (same-origin cookies). Set NEXT_PUBLIC_SOCKET_URL to the raw API origin for websockets — see frontend/.env.example and frontend/src/lib/api.ts.