# Technical documentation — crimemapping

Evidence date: 2026-10-18. Source: `git@bitbucket.org:overdrive/crimemapping.git`, `master` at `5927ab4` (2023-12-06).

Secrets, tokens, and the Google API key literal in a comment are not copied here.

## What this repository is

A single Create React App client. Package name `client`, version `0.3.2`, `"private": true`. There is no server, database, or queue in the tree.

The HTTP API, MySQL schema, and admin UI are the CodeIgniter application in `git@bitbucket.org:overdrive/crimemapping.git`'s sibling `crimemapping_website` (`admin.crimemapping.co.za/`). This document does not restate that PHP. It records how this client calls it.

## Application structure

```text
public/                 index.html, manifest, robots.txt, logos, icons
src/index.js            root, AuthProvider, BrowserRouter, service worker
src/components/App.js   public routes if no currentUser, else private routes
src/components/Routes/  PublicRoutes, PrivateRoutes, PrivateRoute
src/pages/public/       Login, Signup, ForgotPassword, UnAuth
src/pages/private/      Home, CreateEvent, AllEvents, ViewEvent, Debug
src/Context/            AuthContext, DatastoreContext, FirestoreContext
src/components/MapView/ Google map
src/lib/                theme, validation, appConfig, EventConfig, firebase-config
src/lib/services/       geolocation, unused second hook, unused URL signer
```

`src/index.js` render order: `AuthProvider` → `Router` → `StrictMode` → `App`. Bootstrap CSS is imported globally. `FirestoreProvider` and `DatastoreProvider` are not at the root. `DatastoreProvider` wraps only `PrivateRoutes`.

Path aliasing is Create React App default. No `src/setupProxy`, no custom webpack config in the repo (`webpack` is a devDependency and is not configured here).

## Frameworks and languages

| Item | Evidence |
|---|---|
| JavaScript, React 18.1 | `package.json`, JSX under `src/` |
| `react-scripts` 5.0.1 | `package.json` |
| React Router DOM 6.3 | `BrowserRouter`, `Routes`, `Route` |
| React Bootstrap 5 / Bootstrap 5.1 | UI components and `bootstrap.min.css` |
| `react-rainbow-components` 1.31 | `PhoneInput`, `DateTimePicker`, `Spinner` |
| `react-select` 5 | incident multi-select |
| `axios` 0.27 | HTTP |
| `@react-google-maps/api` 2.12 and `react-geocode` 0.2 | map and reverse geocode |
| `react-geolocated` 4 | dependency only. The call site is commented out |
| `react-device-detect` 2 | imported only by unrouted `Debug.js` |
| `firebase` 9.8 | dependency. Config values are empty strings |
| `uuid` | imported (`v4`) by `CreateEvent.js` and `AllEvents.js`. Not listed in `package.json` or as a direct dependency that this file set declares |
| Jest / Testing Library | `src/setupTests.js`, `App.test.js`, `npm test` |

No TypeScript, no PHP, no SQL.

## Runtime configuration

`.env`, `.env.local`, `.env.development.local`, `.env.test.local`, and `.env.production.local` are gitignored. No `.env.example` is committed.

Variables this code reads:

| Variable | Readers |
|---|---|
| `REACT_APP_PUBLIC_API_URL` | `AuthContext.js`, `DatastoreContext.js` |
| `REACT_APP_GOOGLE_API_KEY` | `PrivateRoutes.js` (`useJsApiLoader`), `CreateEvent.js` (`Geocode.setApiKey`) |
| `PUBLIC_URL` | production `build` script sets `https://app.crimemapping.co.za/`. CRA service-worker helper also reads it |
| `CI` | `build` script sets `CI=false` |
| `NODE_ENV` | CRA service-worker registration runs only when it is `production` |

The README also lists `REACT_APP_FB_API_KEY`, `REACT_APP_FB_AUTH_DOMAIN`, `REACT_APP_FB_PROJECT_ID`, `REACT_APP_FB_STORAGE_BUCKET`, `REACT_APP_FB_MESSAGE_SENDER_ID`, `REACT_APP_FB_APP_ID`, `REACT_APP_FB_MEASUREMENT_ID`. `firebase-config.js` does not read them. After commit `bc0c4df` the config object is empty strings. Provenance: **Documented** in the README, **Implemented** differently in the config file.

`CreateEvent.js` contains a commented `fetch` to the Google Geocoding HTTP API with a key literal. The key is not repeated in Silicon Ark.

## Scripts

| Script | Body |
|---|---|
| `start` | `react-scripts start` (README: http://localhost:3000) |
| `start:pwa` | `npm run build && serve -s build` (`serve` is not a dependency) |
| `build` | `set PUBLIC_URL=https://app.crimemapping.co.za/&&set CI=false&&react-scripts build` |
| `build:version:bump` | `npm version patch` then `react-scripts build` without the `PUBLIC_URL` prefix |
| `test` | `react-scripts test` |
| `deploy:dev` | version bump, then `firebase deploy --only hosting` |
| `fb:install` | `npm install -g firebase-tools` |
| `fb:login` | `firebase login` |
| `fb:logout` | `firebase login` |
| `version` | `npm version patch` |

The `set VAR=` syntax is Windows `cmd`. A bash pipeline image will not apply those assignments the same way. The Bitbucket image is `node:lts` and runs `yarn build`, which invokes this script. Whether `PUBLIC_URL` is actually set in that container was not executed here. **Requires confirmation.**

The README's `npm run deploy` is not a script name.

## Routes

Router: `react-router-dom` `BrowserRouter`. Firebase hosting rewrites `**` to `/index.html` (`firebase.json`), which matches a browser-history SPA. The FTP pipeline uploads `build/*` and does not add a rewrite rule of its own. Whether the FTP host rewrites unknown paths to `index.html` is **Unknown / Requires Confirmation**.

Signed out (`PublicRoutes.js`):

| Path | Screen |
|---|---|
| `/signup` | `Signup` |
| `/login` | `Login` |
| `/forgot-password` | `ForgotPassword` |
| `/unauth` | `UnAuth` |
| `*` | navigate to `/` |

An effect then sends `/`, `/report-crime`, `/all-crimes`, and `/view-crime` to `/unauth`.

Signed in (`PrivateRoutes.js`):

| Path | Screen |
|---|---|
| `/` | `Home` inside `PrivateRoute` |
| `/report-crime` | `CreateEvent` inside `PrivateRoute` |

Commented and not mounted: `/debug`, `/all-crimes`, `/view-crime`.

## HTTP API

Base: `process.env.REACT_APP_PUBLIC_API_URL`. No trailing-path helper. Each call concatenates the path.

| Method | Path | Auth header | Body | Client use of the response |
|---|---|---|---|---|
| POST | `/apiusers/login` | none | JSON `cellphone`, `password` | `data.id`, `data.token` |
| POST | `/apiusers/create` | none | JSON `organisation_id`, `name`, `surname`, `email`, `cellphone`, `password` | `data.status` 201 or 403, `data.messages.success` |
| GET | `/apiorganisations` | none | — | array of `{ id, name }` |
| POST | `/apiusers/logout` | Bearer | `{}` | callback clears storage. Body unused beyond `console.log` |
| POST | `/apiusers/getUser` | Bearer | `{}` | if `statusText === "OK"`, `data[0].id` |
| GET | `/apiusers/{id}` | Bearer | — | `data[0]` fields `id`, `role_id`, `organisation_id`, `name`, `surname`, `email`, `active` |
| GET | `/apiincidents` | Bearer | — | `data[]` of `id`, `name_group`, `name` |
| POST | `/apicrimes/create` | Bearer | `multipart/form-data` | `data.error` |

Multipart fields: `files[]`, `user_id`, `organisation_id`, `report_date`, `coordinates_long`, `coordinates_lat`, `note`, `incidents` (JSON string), `coordinates_address`.

There is no retry, no timeout config on the axios calls, and no global 401 handler. A failed `getUser` leaves `currentUser` unset.

Sibling behaviour of these endpoints (JWT lifetime, file rules, empty forgot-password methods) is documented in `silicon_ark/crimemapping_website/` and is not copied in full here.

## Auth

Not Firebase Auth on the live path. `getAuth` is constructed in `firebase-config.js` and no routed file imports `auth`.

Session is `localStorage.loginToken`. `AuthProvider` does not refresh the token. It does not read an expiry claim.

`useAuthStatus` mirrors `currentUser` into `loggedIn`. It is not a server check.

Login navigation does not wait for the POST. See the PRD.

## Maps and geolocation

`useJsApiLoader` id `google-map-script`, key from `REACT_APP_GOOGLE_API_KEY`. Libraries array is not set.

`MapMain` renders `GoogleMap` and `MarkerF`. With `allEntries` (review screen only) it plots `entry.location` and `entry._id`. Otherwise one marker at `mapDrag`. `onDrag` writes the map centre back through `setMapDrag`. `clickableIcons` is false.

Geolocation options in the live helper: high accuracy, timeout 50000 ms, maximum age 100 ms. The unused hook uses timeout 5000 ms, maximum age 24 hours, and the option name `enableHigAccuracy` (misspelled, so the browser default applies).

## Firebase leftover

| File | State |
|---|---|
| `.firebaserc` | default project `crime-mapping-org` |
| `firebase.json` | hosting `public` directory `build`, ignore dotfiles and `node_modules`, SPA rewrite to `/index.html` |
| `src/lib/firebase-config.js` | empty config strings, exports `auth`, `db`, `storage` |
| `FirestoreContext.js` | `setDoc` / `getDocs` / `getDoc` / `updateDoc` / `deleteDoc`. Upload path `ReactEventImages/{eventId}/{file.name}` |

`index.js` does not mount `FirestoreProvider`. Calling `useFirestore` throws. `AllEvents.js` comments out the import and still calls the hook. `ViewEvent.js` calls the hook with no import.

## PWA

`public/manifest.json`: standalone, theme `#000000`, background `#ffffff`, icons `favicon.ico`, `images/logo_black192.png` (declared 192), `images/logo_black285.png` (declared 512). The 285 pixel file is labelled as 512. Apple touch icon in `index.html` is `images/logo192.png`.

`src/index.js` registers the CRA helper, which in production registers `{PUBLIC_URL}/service-worker.js`.

`public/index.html` registers `serviceWorker.js` (no hyphen, relative) on `window.load`. The tree has `src/service-worker.js` (hyphen, CRA output name) and no `public/serviceWorker.js`. The same mismatch is recorded for the committed build in `crimemapping_website`.

## Build, CI, and deploy

`bitbucket-pipelines.yml`:

- Image `node:lts`.
- `clone.depth: full`.
- `definitions.steps` contains one SonarQube step using pipe `sonarsource/sonarqube-scan:1.1.0` and a `sonar` cache. `pipelines.default` does not reference `&build-step`.
- Default pipeline, two steps: `yarn` then `yarn build`, artifact `build/**`; then `apt-get install ncftp` and `ncftpput` of `build/*` to `$FTP_HOST` `$FTP_SITE_ROOT_DEV` with `$FTP_USERNAME` and `$FTP_PASSWORD`.
- The file comment says the default pipeline is "triggered manually via the Bitbucket GUI". Bitbucket's `default` pipeline runs on branches that have no branch-specific pipeline. **Requires confirmation** which reading matches how this repository is configured in Bitbucket. The YAML key is `default`.

`sonar-project.properties`: `sonar.projectKey=sqp_b4d5bc15b93e593ab850c02d5de6c127752750b7`, `sonar.sources=.`. The key is an identifier, not a token. It is still a project id and is left as it appears in the file.

No Dockerfile, no Kubernetes, no monitoring agent, no queue, no background worker.

`.firebase/hosting.YnVpbGQ.cache` is tracked. It is a Firebase hosting cache file.

`.vscode/settings.json` is tracked. It was not needed to describe product behaviour.

## Testing

`npm test` runs react-scripts (Jest). The only test, `App.test.js`, renders `App` and expects `/learn react/i`. `App` requires `useAuth`, which requires `AuthProvider`. The test does not wrap `AuthProvider` or `Router`. The expected text is not in the UI.

No Cypress, no PHPUnit, no end-to-end suite. `src/setupTests.js` imports `@testing-library/jest-dom`.

## Security notes visible in the tree

- Bearer token in `localStorage`. Any script on the origin can read it. Provenance: **Implemented**.
- Organisation list and signup are unauthenticated from this client.
- Login errors are not shown. A failed password and a network failure look like a trip through "Unautherised" back to the form.
- `robots.txt` does not disallow any path.
- Commented Google API key in `CreateEvent.js`. Not copied here.
- `create-signed-urls.js` can sign a URL with HMAC-SHA1. Nothing imports it, and no secret is stored in that file.
- Firebase config is empty, so a call that reached Firestore would fail initialisation or hit an invalid project. Live routes do not make that call.
- Pipeline secrets are Bitbucket variables, not committed values.

## Development process

README: `npm start`, `npm test`, `npm run build`, then Firebase CLI login and deploy. The pipeline instead uses Yarn and FTP. No contribution guide, no lockstep Node version, no editor config requirement beyond the tracked `.vscode/settings.json`.

`package-lock.json` is committed. The pipeline runs `yarn`, which will use or create a Yarn lock. No `yarn.lock` is in the tree. **Inferred** that Yarn and npm metadata can diverge. Not executed.

## Related Silicon Ark project

`silicon_ark/crimemapping_website/` documents the marketing site, the CodeIgniter admin, the API, and a built copy of this client. Its open question "whether the React source lives in another repository" is answered by this repository: `overdrive/crimemapping`, tip `5927ab4`.
