# PRD — unityFrontend (Unity - Work Management)

Evidence date: 2026-09-30. Source: `git@bitbucket.org:overdrive/unityfrontend.git`, `master` `9742198` (2020-04-14).

Provenance labels: **Documented**, **Implemented**, **Inferred**, **Planned**, **Unknown**.

No formal PRD existed in the repository or in `silicon_ark/unityFrontend/` before this file. Requirements below are recovered from the React screens, the route table, and the mock API. They are not a new product decision.

Seeded passwords in `src/@fake-db/db/auth-db.js` are omitted. The Google Maps key in `.env` is omitted. The files that hold them are named where the behaviour depends on their presence.

## Product purpose

A person opens a web app titled **Unity - Work Management**. The login card says **LOGIN TO UNITY**. After a JWT login against the in-browser mock, the shell can show a ticket list, a ticket detail page, an admin dashboard, a technician dashboard, a user list, and the Fuse e-commerce demo.

**Documented** title: `public/index.html`. **Implemented** login copy: `src/app/main/apps/user-management/login/LoginPage.js`. The package name remains `fuse-react-app`. Which string is the product name is **Unknown / Requires Confirmation**.

There is no server in this repository. Every `/api` call the custom screens make is handled by `src/@fake-db`, which then allows any other request to pass through (`mock.onAny().passThrough()`).

## Users and roles

The guest record starts with `role: []` and display name Guest (`src/app/auth/store/reducers/user.reducer.js`).

The mock auth database seeds four users. Passwords are plaintext in that file and are omitted here.

| Seed email | `role` string stored | `redirectUrl` after login | Provenance |
|---|---|---|---|
| `admin@unity.com` | `admin` | `/apps/unity/landing` | **Implemented** |
| `tman@unity.com` | `team manager` | `/apps/ticketing/tickets` | **Implemented** |
| `man@unity.com` | `manager` | `/apps/ticketing/tickets` | **Implemented** |
| `tech@unity.com` | `technician` | `/apps/unity/technicianHome` | **Implemented** |

`src/app/auth/authRoles.js` only exports `admin: ['admin']` and `onlyGuest: []`. The team-manager, manager, and technician entries are commented out.

Registration (`POST /api/auth/register`) creates `role: "unassigned"` and `redirectUrl: "/apps/unity/landing"`.

`FuseUtils.hasPermission` allows a route when `route.auth` is null or undefined. An empty `auth` array allows only a guest. Otherwise the user's role string must be included in the array, or, if the role is an array, one element must match.

The ticketing route config sets `auth: ['admin', 'teamManager', 'manager', 'technician']`. The seeded team-manager role is the string `team manager` (with a space), which is not in that array. **Inferred:** that seed user is sent to `/apps/ticketing/tickets` and the authorization check does not grant it. Not executed in a browser for this document.

Navigation items for Tickets, Admin, and Technician have their `auth` lines commented out, so the nav does not hide them by role. **Implemented.**

## Major workflows

### 1. Open the app

Trigger: browser loads the CRA bundle (`src/index.js`).

Behaviour:

- `serviceWorker.unregister()` runs.
- `App` imports `@fake-db` before rendering, so the mock adapter is installed.
- `Auth` shows `FuseSplashScreen` until the JWT check settles. Only `jwtCheck()` runs. Firebase and Auth0 checks are commented out.
- If `localStorage` has no `jwt_access_token`, the splash ends and the guest state remains.
- If a token exists and `jwtDecode` says `exp` is still in the future, the app calls `GET /api/auth/access-token` and stores the returned user.
- Routes are generated with default auth `null`. Unknown paths redirect to `/pages/errors/error-404`. The exact path `/` redirects to `/login`.

Output: login page for a fresh visit. **Implemented.**

### 2. Sign in

Trigger: Login button on `/login`, `JWTLoginTab` `handleSubmit`.

Inputs: email, password. Both required. Each must be at least 4 characters (Formsy). No email-format check on this form.

Behaviour when the form is valid:

- `jwtService.signInWithEmailAndPassword` sends `GET /api/auth` with the email and password in the axios `data` body (not a POST, not query params).
- The mock parses that body, finds a user by email, and compares the password.
- Unknown email returns HTTP 200 with `error.email = "Check your username/email"`.
- Wrong password returns HTTP 200 with `error.password = "Check your password"`.
- Match deletes the password from the user object, signs a JWT (`expiresIn: "2 days"`, secret in `auth-db.js`), and returns `{ user, access_token }`.
- The client stores `jwt_access_token`, sets `Authorization: Bearer <token>`, and `history.push(user.redirectUrl)`.

Errors:

- The login reducer stores `response.data.error` and the form shows those field errors.
- The JWT response interceptor treats HTTP 401 as logout with message `Invalid access_token`. It also references `this.props.history`, and `jwtService` is not a React component. **Inferred:** that `push` throws if a 401 is handled. Not executed for this document.
- `signInWithEmailAndPassword` writes the email and password to `console.log`. **Implemented.**

`/login` has no `auth: onlyGuest`, so a signed-in user can open it again. **Implemented.**

### 3. Register

Trigger: Create an account on `/login`, route `/register`. `auth` is `onlyGuest` (empty array), so a user who already has a role is not granted this route.

The page still shows three tabs: JWT, Firebase, and Auth0. The JWT tab is selected first. Copy on the left still says "Welcome to the FUSE!" and lorem ipsum. **Implemented** template text.

JWT tab inputs: display name (required), email (`isEmail`), password, password confirm (must match). The mock rejects a duplicate email with "The email is already in use" and an empty name with "Enter display name". It does not check password strength (`password: null` in the error object). Success stores a JWT and redirects to `/apps/unity/landing`. The new role is `unassigned`, which is not in the ticketing `auth` array.

Firebase and Auth0 tabs are still rendered. `Auth.js` does not start those services. Whether those tabs can complete registration is **Unknown** without running them. Their config files contain commented placeholder URLs.

### 4. Ticket list

Trigger: nav item Tickets, URL `/apps/ticketing/tickets`. `/apps/ticketing` redirects there.

Who may open it: role string must be one of `admin`, `teamManager`, `manager`, `technician`. See the team-manager mismatch above.

Behaviour:

- `GET /api/ticketing/tickets` returns the in-memory array.
- The table columns are number, name, status, type, active, priority, department, company, assignee, resolved, age in days, created at.
- A header search keeps rows whose `name` contains the text, case-insensitive. Other columns are `filterable` on the table component.
- Add opens `/apps/ticketing/ticket/new`. A row click opens `/apps/ticketing/ticket/<id>`.

Empty mock array would render an empty table. The committed mock is not empty.

### 5. Ticket detail

Trigger: `/apps/ticketing/ticket/:ticketId`.

`ticketId === "new"` dispatches `newTicket()` (client-side id via `FuseUtils.generateGUID()`, `active: true`, empty strings, `createdAt` from moment). Any other id calls `GET /api/ticketing/ticket?ticketId=`.

Tabs: General, Tasks, Resources, Activity, Misc, Notes.

General fields:

| Field | Control | Allowed values in the form | Rule |
|---|---|---|---|
| Name | text, required | free text | Save stays disabled while name length is 0. Also disabled when the form equals the loaded ticket |
| Description | text | free text | Editable only when `form.active` is true |
| Type | select | Operational, Service Request, Maintenance | Same active gate |
| Status | select | New, In Progress, Pending Client Response, Senior Assigned, Document Update Required, Scheduled, Pending Repairs | Same. The list does not include Open or Closed |
| Priority | select | all, Low, Medium, High | Same |
| Department | select | empty ("All"), Technical, Infrastructure, Software, AWS, Human Resources | Same |
| Company | select | GLASSTEP, FURNAFIX, PARLEYNET, ISOLOGIX, NEBULEAN, ACLIMA, KLUGGER, SOFTMICRO, ROCKYARD | Same. Labels are title case of those codes |
| Assigned to | select | Alex Kedian, Kagiso Malepe, Jaco Brits | Same |

Save posts the form to `POST /api/ticketing/ticket/save`. The mock replaces the ticket with the same `id`, or appends it. The UI shows "Ticket Saved". The save is only in the mock array for this page load.

Close sets `data.active = false` and posts `POST /api/ticketing/ticket/close`. It does not set `status` to Closed. The UI shows "Ticket Closed". After `active` is false, the General inputs are disabled. Close itself is not disabled by `canBeSubmitted`.

Tasks renders `TaskListComponent`. Resources, Activity, Misc, and Notes render the matching components. Activity is also rendered under the General card. Whether those child components persist into the ticket payload was not fully traced; the save action posts the ticket `form` from `Ticket.js`, and the new-ticket template sets `tasks: []`. **Requires Confirmation** if task rows are part of `form`.

Mock tickets also have `status` values Open, Closed, and In Progress, `resolved` as the strings `"True"` and `"False"`, and `creator` values including "Email Connector". Those values are **Implemented** as seed data. The detail form cannot select Open or Closed. **Inferred** conflict between seed data and the form, not a written rule.

### 6. Admin dashboard

Trigger: nav Admin → Dashboard, URL `/apps/unity/adminhome`.

The route that lazy-loads `AdminHome` is `/apps/unity/AdminHome` (capital A and H) in `UnityAppConfig.js`. React Router v5 matching is case-sensitive. **Inferred:** the nav URL does not match the route, so Dashboard falls through to the 404 redirect. Not executed in a browser for this document.

If `AdminHome` is mounted:

- It dispatches `GET /api/ticketing/widgets/month-metrics`.
- The reducer is mounted as `test` and stores the payload on `data`.
- The component reads `state.test.monthMetrics.data` and assigns it to a `const` array inside `try/catch`. Assignment to that `const` throws. The catch logs the error. The widget therefore receives the original empty array.
- `TicketsThisMonthWidget` returns `<div>TicketsThisMonth</div>` before the table that would render `props.data.rows`. The table is unreachable.
- The payload itself is five rows titled Holiday Travel, Get Away Deals, Airfare, Vacation, and Hotels, with `clicks` and `conversion`. It is not a ticket count. **Implemented** mock data, copied in shape from a Fuse analytics widget.
- A doughnut labelled Ticket Status uses the fixed series `[12, 20, 32]` for Open, Closed, In Progress. It does not call `getTicketMetrics`. That action exists and has no callers.

Landing (`/apps/unity/landing`, and `/apps/unity` redirects there) shows two cards with the text "This is the content of this card". The admin seed user is sent here after login.

### 7. Technician, manager, and team-manager homes

`TechnicianHome` is routed at `/apps/unity/TechnicianHome`. The nav points at `/apps/unity/technicianhome`. The technician seed `redirectUrl` is `/apps/unity/technicianHome`. Three casings. **Inferred:** only the route's exact casing renders the page. The page content is four papers labelled Component 1 through Component 4.

`ManagerHome` returns the text "This is the home for manager". `TeamManagerHome` exists beside it. Neither path is in `UnityAppConfig`. Their nav entries are commented out. **Implemented** files, not reachable routes.

### 8. Users and groups

Trigger: Admin → Users, URL `/apps/user-management` which redirects to `/apps/user-management/users`. This route config has no `auth` array.

Behaviour:

- The list loads `GET /api/auth/users` (the auth mock, not `user-management-db`).
- A row opens `/apps/user-management/user/:uuid`.
- New user opens `.../user/new`, which builds `{ id, name: '', active: true, role: 'unassigned' }`. The save posts to `POST /api/user-management/user/save`, a different mock (`user-management-db.js`) from the list. **Inferred:** a saved user does not appear in the auth user list. Not executed for this document.
- User groups: `/apps/user-groups` redirects to `/apps/user-groups/groups`. `GET /api/auth/user-groups` returns groups sorted by name. The Admin group has modules Ticketing, User-Management, and Knowledge-base, each with `canView`, `canAdd`, `canEdit`, `canDelete`. Team Manager, Manager, and Technician groups have `modules: []`.
- Toggling a capability posts `POST /api/auth/user-group/module/capabilities/save`. The handler logs the body and returns it. The update loop after that `return` never runs.

Nothing in navigation or route guards reads `canView` / `canAdd` / `canEdit` / `canDelete`. **Implemented** as stored flags and checkboxes only.

The user-groups nav item is commented out. The routes still exist.

## Integrations

| System | Direction | What is exchanged | Status |
|---|---|---|---|
| In-browser mock (`axios-mock-adapter`) | App → mock | Auth, tickets, users, groups, plus Fuse demo `/api/*` routes | **Implemented.** Data dies on refresh except the JWT string in `localStorage` |
| Network pass-through | App → whatever answers the page origin | Any axios call the mock does not handle | **Implemented** as `passThrough`. No custom call in the Unity screens was found that misses the mock |
| Firebase | Would be app ↔ Firebase | Auth and user profile | Code and config exist. `Auth.js` does not call `firebaseCheck`. Config URLs are comments |
| Auth0 | Would be app ↔ Auth0 | Auth | Same. Not started |
| Google Maps | Browser → Google | `REACT_APP_MAP_KEY` from `.env` | Used by Fuse e-commerce order, analytics widget, and a docs example. Not used by the ticket screens. The key value is omitted here |
| Email connector | — | Mock ticket `creator` can be the string "Email Connector" | **Implemented** as seed text only. No mail client in the custom ticket flow |

No authentication to an external Unity API is configured. `axios` has no `baseURL`.

## Constraints

- React 16.8.6 and react-scripts 3.0.1, as locked in `package.json`.
- Dev server URL in the readme is `http://localhost:3000`.
- JWT mock lifetime is the string `"2 days"`.
- Client token check uses `jwtDecode` and `exp` only. It does not verify the signature. The mock verifies the signature on `/api/auth/access-token`.
- Production build script sets `GENERATE_SOURCEMAP=false` and runs Tailwind purge (`purge-tailwindcss.js`).
- IE 11 is in the development browserslist. The IE polyfill import in `src/index.js` is commented out.

## Requirements recovered from the implementation

### FR-001 — Application shell

The app loads a Fuse layout, checks for a stored JWT, and sends `/` to `/login`.

Evidence: `src/index.js`, `src/app/App.js`, `src/app/auth/Auth.js`, `src/app/fuse-configs/routesConfig.js`.

Implementation: Implemented.

Confidence: High.

### FR-002 — JWT login against the mock

A person signs in with email and password of at least 4 characters. A matching mock user receives a bearer token and is sent to that user's `redirectUrl`.

Evidence: `JWTLoginTab.js`, `jwtService.js`, `auth-db.js`, `user.actions.js` `setUserData`.

Implementation: Implemented.

Confidence: High.

### FR-003 — Ticket list and detail

A permitted role can list mock tickets, open one, create one, save one, and mark one inactive.

Evidence: `TicketingAppConfig.js`, `TicketsTable.js`, `Ticket.js`, `ticket.actions.js`, `ticketing-db.js`.

Implementation: Implemented against the mock.

Confidence: High for the mock path. Whether a real API was intended is Unknown.

### FR-004 — Role homes

Admin, technician, manager, and team-manager home components exist. Only Admin, Technician, and Landing are registered as routes, and the nav URLs do not match the route casing.

Evidence: `UnityAppConfig.js`, `navigationConfig.js`, `AdminHome.js`, `TechnicianHome.js`, `ManagerHome.js`.

Implementation: Partial.

Confidence: High.

### FR-005 — User and group maintenance

The UI lists auth-mock users and groups and can post saves. Group capability flags are not enforced on other screens. User save and user list use different mocks.

Evidence: `UserManagementAppConfig.js`, `user.actions.js`, `auth-db.js`, `user-management-db.js`, `ModuleAccessTable.js`.

Implementation: Partial.

Confidence: High.

## Unknown / Requires Confirmation

- Whether unityFrontend is the UI for `unityNodejsApi`, `unity-lambda-api`, or another service.
- Whether the April 2020 prototype was replaced by unity-lite or unity-soft-ui.
- Whether the route-casing and role-string mismatches are defects or unfinished work.
- Production URL, environment, and who the real users are.
- Whether "Email Connector" as a ticket creator was a planned inbound mail feature.
