# Technical documentation — paymyfines-frontend-new

Evidence date: 2026-10-07.

Source: `git@bitbucket.org:overdrive/paymyfines-frontend-new.git`, `main` at `a335e20` (`a335e20139975fffafe50478d1b884fe49b30668`).

## Stack

| Item | Evidence |
|---|---|
| UI | React `17.0.2`, React DOM `17.0.2`, Create React App `react-scripts` `4.0.3` |
| Routing | `react-router-dom` `5.2.0`, `history` package via `src/utils/history.js` |
| Components | `react-bootstrap` `1.6.1`, Bootstrap `5.0.1` in dependencies. `public/index.html` also loads Bootstrap `4.3.1` from a CDN, plus W3.CSS |
| Auth and API client | `aws-amplify` `3.3.21` |
| Charts | `recharts` `2.1.0`, `chartist` / `react-chartist`, `plotly.js` / `react-plotly.js` (plotly is used by the pivot renderers) |
| Tables | `react-data-table-component`, `react-table` `7` |
| Other libraries in `package.json` | FullCalendar 5, TinyMCE, `react-trello`, `react-pivottable`, `react-google-recaptcha`, `moment`, `lodash`, `styled-components` |
| Package name | `role-based-access-control` version `0.1.0`, `"private": true` |
| Language | JavaScript. `jsconfig.json` sets `baseUrl` to `./src` so imports such as `config` and `components` resolve there |
| Formatting | `npm run pretty` runs Prettier 1 with tabs, single quotes, and always arrow parens. `.prettierrc.json` is present |

`yarn.lock` and `package-lock.json` are both committed. `README.md` says to use Yarn.

## Application structure

```text
public/                 index.html, iveri.html, manifest, favicons
documentation/iveri.php redirect script
src/index.js            Amplify.configure and ReactDOM.render
src/App.js              renders routes
src/aws-config.js       dev and prod endpoints
src/config/             Roles, RoutingConfig, reCAPTCHA site key
src/routes/             Router, allowed-route filter, switch
src/components/         screens
src/components/common/  nav, footer, tables, graphs, modal
src/utils/index.js      roles, ID check, UUID
src/contexts/           popup modal
src/hooks/              form fields, popup, window size
build/                  committed webpack output
```

`src/serviceWorker.js` is the CRA worker. `index.js` calls `unregister()`.

Path alias: imports use the `baseUrl`, not relative paths, for `components`, `config`, `routes`, `utils`, `App`.

## Runtime configuration

`src/aws-config.js` exports one of two objects.

| | `dev` (default) | `prod` |
|---|---|---|
| Selected when | Any origin except the production host | `window.location.origin === "https://www.paymyfines.co.za"` |
| API Gateway | `https://h8vzvoqq9c.execute-api.eu-west-1.amazonaws.com/development` | `https://puru95wxc8.execute-api.eu-west-1.amazonaws.com/production` |
| Region | `eu-west-1` | `eu-west-1` |
| S3 bucket | `pmf-development-invoices` | `tmt-pmf-prod-invoices` |
| Cognito user pool | `eu-west-1_8MkXacpCX` | `eu-west-1_gzZa8Iitq` |
| App client id | in the `dev` block | in the `prod` block |
| iVeri application id | `iVeriAppID` in the `dev` block | `iVeriAppID` in the `prod` block |
| Max attachment size | `5000000` | `5000000` |

No screen reads `MAX_ATTACHMENT_SIZE`. No `.env` file is in the repository. Hosts, pool ids, and the reCAPTCHA site key are hardcoded.

`src/index.js` registers Amplify:

- Auth: `mandatorySignIn: true`, region, user pool, identity pool id, app client.
- Storage: region, bucket, identity pool id.
- API endpoint name `unity`.

`src/config/google.js` exports a reCAPTCHA v2 site key. Commented lines in that file refer to other keys, including a v3 key. Only the uncommented v2 key is exported.

Localhost, Amplify preview hosts, and any host that is not exactly `https://www.paymyfines.co.za` use the **development** user pool and API. That includes a production-like hostname on another domain.

## Authentication

Cognito is the identity provider (`Auth.signIn`, `signUp`, `confirmSignUp`, `resendSignUp`, `forgotPassword`, `forgotPasswordSubmit`, `changePassword`, `signOut`, `currentAuthenticatedUser`, `currentSession`).

The route guard does not call Cognito. It reads `localStorage.roles`. See the PRD for the `JSON.parse` assumption.

`ForgotPassword.js` writes the submitted password to `localStorage` key `asdf`. `Register.js` reads it. That is browser storage of a password. **Implemented.**

## API

All application calls go through `src/components/unityRequests.js` (`API.get` / `API.post` on endpoint `unity`) or, in `Login.js` and one register branch, `API.get` / `API.post` directly.

On failure the helper returns `{ status: error.response.status }` and logs the status. It does not throw. Callers that only check `result === true` therefore treat HTTP errors as a failed business result when `result` is missing.

| Method | Path | Caller |
|---|---|---|
| GET | `/user/details` | Login, register, forgot password, profile. Query keys vary: `strCognitoUuid`, `email`, `strEmail`, `userId` |
| POST | `/register` | Register, login completion modal |
| POST | `/updateregistration` | Login, register |
| POST | `/update` | Profile |
| POST | `/recaptcha` | Register, contact |
| GET | `/getfinesearch` | Landing search function, quick fine search |
| GET | `/getpaginatedfines` | Landing ID branch, outstanding fines |
| GET | `/queryinfringements` | AARTO detail |
| GET | `/getoutstandingfines` | CPA detail |
| GET | `/getcharges` | CPA detail |
| GET | `/evidence` | CPA detail |
| GET | `/getsettings` | Cart, admin settings |
| POST | `/postsettings` | Admin settings |
| GET | `/getgraphsettings` | Admin settings |
| POST | `/postgraphsettings` | Admin settings |
| GET | `/getgraphs` | Admin dashboard |
| GET | `/reports` | Admin dashboard |
| GET | `/transactions` | Transactions |
| POST | `/contactUs` | Contact |
| POST | `/postiveritransaction` | Cart |
| GET | `{apiGateway}/iveripayment` | Not called with Amplify. Set as the iVeri return URL |

`RequestsAPI.js` is a separate `fetch` helper with `mode: 'cors'`. Nothing in `src` imports it.

There is no database client, ORM, or SQL file in this repository. Admin graph settings display a shortened SQL string from `GraphQuery` (`substr(0, 50)`). The query runs on the server. **Unknown** which database.

## Payments

1. Cart builds a flat object of iVeri Lite field names.
2. `POST /postiveritransaction` records the attempt.
3. A dynamically created HTML form POSTs to `https://portal.nedsecure.co.za/Lite/Authorise.aspx`.
4. Success, fail, error, and try-later URLs are `{API}/iveripayment?transactionID={id}&veriLocation={encodeURI(origin + "/paymentNotification")}&status=...`.

`generateUUID` in `src/utils/index.js` returns a hex id. If the first character is a digit it is prefixed with `a`.

Two other return helpers are in the repo and are not referenced by `Cart.js`:

- `documentation/iveri.php` reads `veriLocation` and sends `Location` to `{veriLocation}/PaymentNotification` plus the original query string.
- `public/iveri.html` sets `window.location` to `http://pay-my-fines.s3-website-eu-west-1.amazonaws.com/PaymentNotification` plus the query string. The scheme is `http`.

`PaymentNotification.js` only interprets the query string. It imports `unityRequests` and `getUserDetails` and does not call them.

## Roles and navigation

`MapAllowedRoutes` renders a `Switch`. Only path `/` is `exact`. Other paths are prefix matches. A child path such as `/transactions` is also a top-level route, so both entries exist.

`TopNav` lists routes with `canNavigate`. Children become a dropdown. The cart icon is separate and always pushes `/Cart` (the route itself still has to be allowed).

`Footer` lists routes with `showInFooter` and is omitted when `currentPage === "/"`.

## Local storage keys

| Key | Contents |
|---|---|
| `roles` | API `strRole`, later `JSON.parse`d |
| `userDetails` | `{ data, additionalIds }` via `useLocalStorage` |
| `userProfileDetails` | Profile form cache |
| `IDNumbers` | Array of `id:type` strings |
| `cartItems` | Map of notice → fine object |
| `cartCheckout` | Declared in `Cart.js`. The checkout function shown does not write it |
| `backFilters` | ID filter, issuing authority, table search; admin finance filter uses the same key name |
| `issuingAuthList` | Authorities seen on the fine list |
| `financeIssuingAuthList` | Authorities seen on the finance report |
| `outstandingFines` | Last list payload |
| `asdf` | Password written by the forgot-password flow |

## Admin UI notes

`AdminDashboard.js` exports `AdminDashoard` (spelling in the function name). The default export is that function.

Graph `Table` rows try `JSON.parse` on `GraphX`. A parse error logs "Invalid table config" and uses `{}`.

Email setting values are `decodeURI` on load and `encodeURI` on save. The convenience key is compared with `trim()` when reading, and with the string `"convenience "` (trailing space) in one render branch of `AdminSettings.js`.

## Analytics and monitoring

`public/index.html`:

- Matomo tracker `//analytics.siliconsoftware.net/matomo.php`, site id `2`, `setDomains` `["*.master.d39rr3ejryd4rk.amplifyapp.com"]`.
- Google tag `G-6M1WRZYG8D`.
- Script `https://apis.google.com/js/client:platform.js`.

SonarQube:

- `sonar-project.properties` sets `sonar.projectKey=overdrive_paymyfines-frontend-new_81b44d4e-c042-41ac-956c-f1644611ea62`.
- `bitbucket-pipelines.yml` on branch pattern `{main}` runs `sonarsource/sonarqube-scan:3.0.2` with `SONAR_HOST_URL` and `SONAR_TOKEN` from repository variables. Docker memory is 3072. Clone depth is full. The pipeline does not build or deploy.
- `sonar-scanner.bat` is a local scanner script.
- `.scannerwork/` from a scan is committed (including `report-task.txt`).

No application log shipper, APM agent, or health endpoint is in the frontend.

## Deployment

### Bitbucket

`origin` is Bitbucket. The only remote branch fetched is `main`. The pipeline on `main` is the SonarQube step above.

### GitHub Actions files (in the tree)

These workflows are committed. This Bitbucket remote does not have `development`, `qa`, or `master` branches. Whether a GitHub remote still runs them is **Unknown / Requires Confirmation**.

| File | Trigger | Node | Deploy |
|---|---|---|---|
| `.github/workflows/developer.yml` | Push to `development`, or manual | `12.x` | `yarn` and `yarn build`, sync `build/` to `secrets.AWS_DEVELOPER_BUCKET_NAME`, invalidate CloudFront paths `/` and `/index.html` |
| `.github/workflows/qa.yml` | Push to `qa`, or manual | `12.x` | Sync to `secrets.AWS_QA_BUCKET_NAME` with `--acl public-read --follow-symlinks --delete`. No CloudFront step |
| `.github/workflows/master.yml` | `workflow_dispatch` only | `12.x` | Sync to `secrets.AWS_PRODUCTION_BUCKET_NAME`, invalidate `secrets.AWS_DISTRIBUTION_ID` |

`CI` is set to an empty string during `yarn build`, which disables the Create React App warning-as-error behaviour.

Bucket names and distribution ids are GitHub secrets, not in the repo.

### Other hosts named in the tree

- `https://www.paymyfines.co.za` — production config switch.
- `https://all.paymyfines.co.za` — terms copy.
- `*.master.d39rr3ejryd4rk.amplifyapp.com` — Matomo domain list.
- `http://pay-my-fines.s3-website-eu-west-1.amazonaws.com` — `public/iveri.html`.

`README.md` says the benefit of the app is "Using Amplify / lambda". There is no Amplify build spec in the repository.

## Development and test

| Command | `package.json` |
|---|---|
| `yarn start` | `react-scripts --max-http-header-size=77777 start` |
| `yarn build` | `react-scripts build` |
| `yarn test` | `react-scripts test` |
| `yarn storybook` | Storybook 5 on port 6006 |

`src/App.test.js` renders `App` and unmounts it. That test loads the router and therefore `localStorage`. It does not call the API. There is no other test file.

Storybook has one story: `src/components/common/JumbotronWrapper.stories.js`.

`debugger` statements remain in `Login.js`, `Register.js`, `LandingPage.js`, `OutstandingFines.js`, `ContactUs.js`, and `Profile.js`.

## Security notes visible in the tree

These are observations, not a review verdict.

- Cognito app client ids, user pool ids, API URLs, iVeri application ids, and the reCAPTCHA site key are in source.
- Password material can be written to `localStorage` (`asdf`).
- Role checks are client-side. A person can edit `localStorage.roles`. Enforcement has to be on the API. This repo does not show that enforcement.
- `public/iveri.html` redirects to an `http://` S3 website.
- QA workflow args include `--acl public-read`.
- Terms and the about page describe card payment and encryption. The card form is hosted by iVeri, not by this React app.
- `identityPoolId` values in `aws-config.js` are shaped like `eu-west-1:{account}:userpool/{poolId}` rather than a classic identity-pool UUID. Amplify is still configured with that string. Whether Cognito accepts it is **Unknown / Requires Confirmation**.

## Background work

No queue consumer, cron, or worker runs in this repository. The four-hour external update mentioned in the fines modal is not implemented here.

## Files that are generated or IDE state

Committed and not application source: `build/`, `.scannerwork/`, `.vs/`, `yarn.lock`, `package-lock.json`. `.vscode/settings.json` is editor configuration.

`public/manifest.json` still says `"name": "Create React App Sample"` and `"short_name": "React App"`. The document title in `index.html` is `PayMyFines`.
