# PRD — unity-frontend

Evidence date: 2026-09-30. Source: `git@bitbucket.org:overdrive/unity-frontend.git`, `master` `0de9d02` (2019-08-14). Unmerged branches `banele`, `trev`, and `sergei` are called out where their behaviour differs.

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

No formal PRD existed in the repository or in `silicon_ark/unity-frontend/` before this file. Requirements below are recovered from the README, the route tables, and the login and dashboard screens. They are not a new product decision.

The Google Maps key in `frontend/.env` is omitted. The file is named where a screen reads it.

This repository is not `unityFrontend` (Bitbucket `unityfrontend`, Silicon Ark `unityFrontend/`).

## Product purpose

A person can open a web app whose README title is **Unity** and whose Fuse document title is **Unity WebSystem**. The custom login panel says **Welcome to the UNITY!** and describes "all in one business software" for startups and SMEs.

**Documented** title and stack: root `README.md`. **Implemented** document title: `frontend/public/index.html`. **Implemented** splash copy: `frontend/src/app/main/login/Login.js`. The package name remains `fuse-react-app`. Which string is the product name is **Unknown / Requires Confirmation**.

A second app in `public/` has document title **Unity** and shows only a login form and a dashboard table.

The Laravel API the README describes is not in this repository. There is no server here.

## Users and roles

The Fuse user record starts as a guest: `role: []`, display name `John Doe`, email `johndoe@withinpixels.com` (`frontend/src/app/auth/store/reducers/user.reducer.js`). That email is the Fuse template default.

`frontend/src/app/auth/authRoles.js` defines:

| Key | Roles that pass |
|---|---|
| `admin` | `admin` |
| `staff` | `admin`, `staff` |
| `user` | `admin`, `staff`, `user` |
| `onlyGuest` | empty array (guest only) |

`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 must be in the array (a string role, or any element of an array role).

Routes that set `auth` on `master`:

| Path | `auth` | Provenance |
|---|---|---|
| `/logout` | `authRoles.user` | **Implemented** (`LogoutConfig.js`) |
| `/auth/admin-role-example` | `authRoles.admin` | **Implemented** |
| `/auth/staff-role-example` | `authRoles.staff` | **Implemented** |
| `/auth/guest-role-example` | `authRoles.onlyGuest` | **Implemented** |

`/login`, `/pages/auth/login-2`, and the Fuse demo apps (mail, todo, contacts, and the rest registered in `appsConfigs.js`) do not set `auth`. A guest can open them. **Implemented.**

The custom login success path builds a user object with `role: ['admin']` and `displayName` set to the literal `Jaco`. It calls `setUserData` without `dispatch`, and it returns `<Redirect>` from an axios callback, which React does not render. **Inferred:** a successful HTTP response does not change the Redux role and does not navigate. Not executed in a browser for this document.

The `public/` app has no user record and no role check. `/dashboard` is a public route.

No registration of real people is implemented against the Laravel API. The Fuse register page still offers JWT, Firebase, and Auth0 tabs that post to `/api/auth/register` or to placeholder provider configs. **Implemented** as template UI. Firebase and Auth0 config values are commented out.

## Major workflows

### 1. Open the Fuse app

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

Behaviour:

- `serviceWorker.unregister()` runs.
- `Auth` renders its children immediately. `jwtCheck`, `auth0Check`, and `firebaseCheck` are commented out, so a stored token is not read on startup.
- Routes are built from `routesConfig.js`. The path `/` redirects to `/login`.
- There is no catch-all redirect to the 404 page. `FuseAuthorization` treats an unmatched path as allowed (`accessGranted: true` when `matchRoutes` returns nothing).

Output: the Unity login screen for a visit to `/`. **Implemented.**

### 2. Sign in on `/login` (Fuse)

Trigger: LOGIN button on `/login`. The button stays disabled until email and password are both non-empty. There is no email-format check and no minimum length beyond non-empty.

Inputs: email, password, and a Remember Me checkbox (default checked). Remember Me is not sent.

Behaviour when the form is submitted (`handleSubmit`):

- `preventDefault` runs.
- `POST http://local.unity/api/public/api/v1/login` with JSON `{ email, password }`.
- Request headers set `Content-Type` and `Access-Control-Allow-Origin: *`. The CORS header on the request does not configure the server. **Implemented.**
- HTTP status 200: console "Login successfull", then the undispatched `setUserData` and the unrendered `<Redirect to="/pages/profile" />` described above.
- HTTP status 204: `alert("username password do not match")`.
- Any other resolved status: `alert("Username does not exist")`.
- Axios rejects non-2xx responses into `catch`, which only `console.log`s the error. The alerts for 204 and "does not exist" do not run for 4xx/5xx.

Google and Facebook buttons are rendered with no `onClick`. **Implemented.**

`/login` has no `auth` array, so a signed-in user can open it again. Nothing in this handler writes `jwt_access_token`.

### 3. Sign in on `/pages/auth/login-2`

Trigger: LOGIN on the Fuse login-2 page. Same non-empty email and password rule. `handleSubmit` does not call `preventDefault`.

Request: `POST` to `http://localhost/coreUnit/api/public/api/v1/login` plus the suffix `login`, so the URL the code builds is `http://localhost/coreUnit/api/public/api/v1/loginlogin`.

Success check is `response.data.code === 200` (a console log only). `204` alerts a mismatch. Anything else alerts that the username does not exist. No token, no navigation, no Redux update. **Implemented.**

The root README names `http://localhost/coreUnit/api/public/api/v1/login` (no doubled `login`). The two login screens and the README do not use one URL. Which host is correct is **Unknown / Requires Confirmation**.

### 4. Open the `public/` app

Trigger: browser loads `public/src/index.js`. `App.js` is imported and not rendered. The router is:

| Path | Screen |
|---|---|
| `/` exact | `LoginForm` |
| `/dashboard` | `Dashboard` |
| anything else | `Notfound` (`<h1>Not found</h1>`) |

**Implemented.**

### 5. Sign in on the `public/` login form

Trigger: Login button. Formik + Yup.

Validation (**Implemented** on `master`):

- Email is required and must pass Yup `.email()`.
- Password is required, minimum 8 characters, and must match `/(?=.*[0-9])/` ("Password must contain a number.").

`onSubmit` waits 500 ms, logs the values, and calls `this.props.history.push("/dashboard")` inside an arrow function. There is no `this`. **Inferred:** the push throws, and the user stays on the form. Not executed in a browser for this document.

`api_call_url` is `https://localhost/api/public/api` and is never called. A commented block compares email `super` and password `dev`. That comparison does not run.

On branch `sergei` only: the Yup schema is commented out, so this form would submit without those checks. Not on `master`.

### 6. View the client table

Trigger: navigation to `/dashboard` in the `public/` app. No login check.

On mount, `GET http://localhost/coreUnit/api/public/api/client/list`. The response is stored on `this.state.contacts`. The table reads `this.state.clients`, which is initialised to four hard-coded rows (Wasif, Ali, Saad, Asad) and is never replaced. **Implemented.**

A header renderer is commented out and refers to `this.state.students`, which does not exist.

### 7. Fuse register, logout, and demo apps

Register (`/register`) is the Fuse three-tab screen. The splash was edited to the same "all in one business software" sentence. The heading still says **Welcome to the FUSE!**. JWT registration posts to `/api/auth/register` through `jwtService`. There is no handler for that path in this repo.

Logout (`/logout`) dispatches `logoutUser` and renders the text `Logging out..`. The route requires `authRoles.user`. A guest (`role: []`) fails `hasPermission` and `FuseAuthorization` sends them to `/login`. **Implemented.**

Demo apps remain routed and request relative `/api/...` paths (todo, mail, contacts, calendar, chat, notes, scrumboard, e-commerce, academy, both dashboards, file manager, quick panel). `@fake-db` is absent, and `axios-mock-adapter` is not constructed anywhere under `frontend/src`. Those screens have no data source in this repository. **Implemented.**

## Integrations

| System | Direction | Trigger | Payload | Auth | Failure in this repo |
|---|---|---|---|---|---|
| `http://local.unity/api/public/api/v1/login` | Browser → API | `/login` submit | `{ email, password }` | None on the request | `catch` logs only. Server not in this repo |
| `http://localhost/coreUnit/api/public/api/v1/loginlogin` | Browser → API | login-2 submit | `{ email, password }` | None | Alerts only when the promise resolves. README URL does not match |
| `http://localhost/coreUnit/api/public/api/client/list` | Browser → API | `public/` dashboard mount | none | None | Table keeps hard-coded rows. Errors are unhandled (no `.catch`) |
| `https://localhost/api/public/api` | unused constant | never | — | — | Dead code in `LoginForm.js` |
| Relative `/api/*` | Browser → same origin | Fuse demo screens and `jwtService` | template payloads | Bearer token only if `jwtService.setSession` ran. The custom login never calls it | No mock and no server in this repo |
| Google Maps | Browser → Google | e-commerce order, analytics widget, a docs example | `REACT_APP_MAP_KEY` | API key in `frontend/.env` | Key value omitted. Whether the key is still valid is **Unknown** |
| Firebase, Auth0 | not started | auto-checks commented out | placeholder config | — | Buttons and tabs render; provider configs are comments |
| Laravel Passport | **Documented** only | README | login URL above | Passport | No Passport code in this repo |

## Constraints

- Two CRA apps both default to port 3000 (`frontend/README.md` documents `http://localhost:3000` for the Fuse app). The `public/` README is the stock CRA text and documents the same port. **Inferred:** they cannot both listen on 3000 at once. Not executed for this document.
- Internet Explorer 11 is in the Fuse `browserslist` development set. The IE polyfill import in `frontend/src/index.js` is commented out.
- Root and `frontend/.htaccess` allow any origin. There is no other security control (no rate limit, no CSRF token) in these frontends.
- `frontend/.env` is committed. `.gitignore` ignores `.env.local` and the other CRA local env files, not `.env` itself.

## Requirements discovered from the implementation

### FR-001 — Fuse entry shows the Unity login

Requirement: A visit to `/` on the Fuse app redirects to `/login`, which shows the Unity splash and the email/password form.

Who: any browser user.

Trigger: load `/`.

Inputs: none.

Expected behaviour: redirect to `/login`; navbar, toolbar, and footer hidden (`LoginConfig.js`).

Output: login card.

Validation: LOGIN disabled until both fields are non-empty.

Error conditions: none on load. Auth auto-checks do not run.

Evidence: `routesConfig.js`, `LoginConfig.js`, `Login.js`, `Auth.js`.

Implementation: **Implemented**. Confidence: High.

### FR-002 — Fuse login posts credentials to `local.unity`

Requirement: Submitting `/login` posts email and password to `http://local.unity/api/public/api/v1/login`.

Who: any browser user.

Trigger: valid form submit.

Inputs: email, password.

Expected behaviour: see workflow 2. Remember Me is not included. HTTP 200 does not navigate, because the redirect is returned from the axios callback and `setUserData` is not dispatched.

Outputs: console log, and alerts only for resolved non-200 statuses.

Validation: both fields non-empty.

Error conditions: network and non-2xx errors are logged and not shown in the form.

Evidence: `frontend/src/app/main/login/Login.js`.

Implementation: **Implemented**. Confidence: High for the request; High that navigation is not wired, from reading the callback.

### FR-003 — Login-2 posts to a different URL

Requirement: `/pages/auth/login-2` posts to the `coreUnit` URL with `login` appended twice in the path.

Who: any browser user.

Trigger: form submit. Default submit is not cancelled.

Inputs: email, password.

Expected behaviour: workflow 3. No session is stored.

Evidence: `Login2Page.js`. README URL differs. **Requires Confirmation** which URL was intended.

Implementation: **Implemented**. Confidence: High.

### FR-004 — `public/` login validates email and password shape

Requirement: The `public/` form rejects a missing email, a non-email, a missing password, a password shorter than 8 characters, or a password with no digit.

Who: any browser user of that app.

Trigger: submit.

Inputs: email, password.

Expected behaviour: Yup messages under the fields. The success path calls `this.props.history` on a function component.

Evidence: `public/src/components/LoginForm.js`.

Implementation: **Implemented** on `master`. On `sergei` the schema is commented out (**not on `master`**). Confidence: High.

### FR-005 — Dashboard lists clients from the API

Requirement: The dashboard requests `GET http://localhost/coreUnit/api/public/api/client/list` and renders a table of id, name, and email.

Who: anyone who opens `/dashboard`. No session is required.

Trigger: `componentDidMount`.

Inputs: none.

Expected behaviour: the request result is stored on `contacts`. The rendered rows stay the four hard-coded `clients`.

Evidence: `public/src/components/Dashboard.js`.

Implementation: **Implemented** as a request plus a hard-coded table. The list is not what the table shows. Confidence: High.

### FR-006 — Guest can open Fuse apps; role examples are gated

Requirement: Routes without `auth` are open to the guest. Admin, staff, and guest example routes, and `/logout`, use `authRoles`.

Who: guest versus a user whose `role` array contains the required string.

Trigger: navigation.

Expected behaviour: `FuseAuthorization` sends a guest who fails a check to `/login`, and sends a signed-in user who fails a check to `/` (then `/login`, because `/` redirects there). The custom login never dispatches a role, so the session stays the guest unless some other action calls `user.actions`.

Evidence: `FuseAuthorization.js`, `FuseUtils.hasPermission`, `authRoles.js`, the role-example configs, `LogoutConfig.js`.

Implementation: **Implemented**. Confidence: High.

### FR-007 — Laravel Passport API

Requirement: The README says Laravel APIs are set up, auth uses Passport, controllers are made with Artisan under `Api`, and login is `POST` (the README says "url to post") `http://localhost/coreUnit/api/public/api/v1/login`.

Who: a developer, from the README.

Evidence: root `README.md` only.

Implementation: **Not in this repository**. **Documented**. Confidence: High that the code is absent. Whether another repo fulfilled this is **Unknown / Requires Confirmation**.

## Planned behaviour

Nothing in the repository is marked as a future release. Unmerged branch work is unfinished parallel experiments, not a written plan:

- `trev` moves the `public/` app to the repo root.
- `sergei` adds `react-frontend/`, where a Login button sets `userAuth.isAuthenticated` without reading the username or password, and the dashboard shows a heading and a Log Out button.
- `sergei` also adds a direct `/pages/profile` route using `ProfilePage` beside the routes already generated from page configs.

## Unknown / Requires Confirmation

- The product name: Unity, Unity WebSystem, or fuse-react-app.
- Which of `frontend/`, `public/`, and `sergei`'s `react-frontend/` was meant to be used.
- The real API base URL and whether Passport login returns HTTP status, `response.data.code`, or a token.
- Whether a successful login was supposed to land on `/pages/profile`, `/dashboard`, or somewhere else.
- Whether Google and Facebook buttons were supposed to do anything.
- Whether the hard-coded dashboard people are placeholders or were meant to be replaced by the API.
- Whether `unityFrontend` or another Unity repository replaced this one.
- Whether SonarQube project files existed only on the scan host. They are not in git.
