# Technical documentation — im_angular_app

Evidence date: 2026-10-12. Source revision: `master` `060172d` (2024-06-25).

This describes the committed tree. It is not a target architecture.

## What it is

An Angular 15 browser app. Package name `im-bank`, version `0.0.0`, `private: true`. There is no server, database, queue, or background worker in the repository. Persistence on the client is `localStorage`.

`README_OLD.md` says the POC deploys to AWS Amplify and uses Lambda for third-party calls. Amplify config and Lambda source are not in this repo. The app's only backend is HTTP to the Gateway base URL below.

## Repository layout

```text
angular.json
bitbucket-pipelines.yml
package.json
package-lock.json
README.md
README_OLD.md
sonar-project.properties
src/
  index.html
  main.ts
  styles.css
  app/
    app.module.ts
    app-routing.module.ts
    app.component.ts
    core/components/     screens, blocks, modals
    core/services/       HTTP and in-memory state
    core/environments/   environment.ts and environment.prod.ts
    interfaces/          Account, Transaction, Member, Yield
    lib/utilities.ts
    lib/constants.ts     STATEMENTS_PER_PAGE and an embedded Lato font for jsPDF
.github/workflows/build.yml
```

The task that created this onboarding listed `sonarqube_scan.bat` at the root. That file is not in `060172d`.

`src/app/lib/constants.ts` is dominated by a base64 Lato font used when a statement PDF is built. It is font data, not configuration.

## Framework and toolchain

| Item | Value | Evidence |
|---|---|---|
| Angular | `^15.2.0` | `package.json` |
| Angular CLI | `~15.2.6` | `package.json`, README |
| TypeScript | `~4.9.4` | `package.json` |
| RxJS | `~7.8.0` | `package.json` |
| Bootstrap (npm) | `^5.2.3` plus bootstrap-icons | `package.json`, `angular.json` scripts |
| Bootstrap (CDN) | 4.5.2 from `maxcdn.bootstrapcdn.com` | `src/index.html` |
| DataTables / jQuery | `datatables.net` 1.13, `angular-datatables` 15, jQuery 3.6 | `package.json` |
| Charts | `apexcharts` 3.39, `ng-apexcharts` 1.7 | Used by the unmounted portfolio block |
| PDF | `jspdf` 2.5 | `statements.component.ts` |
| Dates | `pikaday` 1.8 | Statements screen |
| JWT | `jwt-decode` 4 | `jwt.service.ts` |
| Tests | Jasmine 4.5, Karma 6.4, Chrome launcher | `package.json` |
| Package manager | npm | `angular.json` `cli.packageManager` |

`ng build` default configuration is `production`, which file-replaces `environment.ts` with `environment.prod.ts`. Those two files are identical. Output path `dist/im-bank`. Initial bundle budget warning 5mb, error 15mb.

`npm start` is `ng serve --host 192.168.8.168`. README development section still says `ng serve` and `http://localhost:4200/`. **Documented** and **Implemented** disagree.

## Application structure

`AppModule` declares every component and imports `BrowserModule`, `HttpClientModule`, `FormsModule`, `AppRoutingModule`, `DataTablesModule`, and `NgApexchartsModule`. No HTTP interceptor. No `providedIn` override. Services use `providedIn: 'root'`.

Routes (`app-routing.module.ts`):

| Path | Component |
|---|---|
| `''` | redirect to `/landing` |
| `landing` | `LandingComponent` |
| `dashboard` | `DashboardComponent` |

No wildcard route. No guards. `RouterModule.forRoot` is nested in an extra array inside `imports`. **Implemented.**

State between widgets is Angular `EventEmitter`s on the services (`accountSelected`, `transactionSelected`, `memberSelected*`, `tabName`), not a store.

## Environments

Both `environment.ts` and `environment.prod.ts`:

| Key | Value |
|---|---|
| `production` | `false` in the dev file, `true` in the prod file |
| `apiUrl` | `https://5t9f8yr10b.execute-api.eu-west-1.amazonaws.com/dev/` |
| `IMLink` | `https://test-dxp.imbank.com/inm-retail/select-context#/` |
| `IMAuthLink` | `https://test-dxp.imbank.com/auth/realms/inm-retail/protocol/openid-connect/auth` |
| `IMRefreshLink` | `https://test-dxp.imbank.com/auth/realms/inm-retail/protocol/openid-connect/token` |
| `termsLink` | I&M Capital Information Memorandum PDF on `www.imbankgroup.com` |
| `privacyLink` | `https://www.imbankgroup.com/ke/information-security/privacy-notice/` |

No `.env` file. No Amplify environment mapping. Changing host per deploy is **Unknown / Requires Confirmation**.

## Authentication

Login is a full-page redirect, not an Angular HTTP login.

Query string built in `LandingComponent` and again in `DashboardComponent.redirectToUrl`:

- `client_id=bb-web-client`
- `redirect_uri={window.location.origin}/landing`
- `response_mode=fragment`
- `response_type=code id_token token`
- `scope=openid`
- `nonce=65ab0bdf-4239-48ee-92de-e7035627b267` (fixed)

Stored keys written by landing: `access_token`, `session_state`, `id_token`, `cif`, `kraPin`, `middle_name`, `new_member`, `ip`.

`Authorization` on Gateway calls is the raw access token string, not `Bearer `, except `SanlamGetStatementsService.getSanlamToken`, which builds a `Bearer` header and then posts with a second header set to the raw token. **Implemented.**

`JwtService.checkIfTokenValid` decodes `exp` and returns whether the token is already expired. Dashboard and the Sanlam token helpers use that as the expired flag.

`TokenRefreshService` would POST form data to `IMRefreshLink` with `client_id=im-wealth` and `client_secret` equal to `session_state`. Nothing calls `startTokenRefresh`. `refreshToken` is never written in this repo. **Implemented** as dead code.

`GET https://api.ipify.org/?format=json` supplies `ip` for `createsession`.

## API surface (this app as client)

Base: `environment.apiUrl`. Bodies are `JSON.stringify` unless noted. Methods that were read:

| Method | Path | Caller |
|---|---|---|
| POST | `createuser` | `UserService.addUserFromToken` |
| POST | `profileimage` | `UserService.getProfileImage` |
| POST | `getuser` | `UserService.checkUser` |
| POST | `gettransaction` | `TransactionService.getTransactions` — no component caller found |
| POST | `updatetransaction` | `TransactionService.updateTransactions` — no component caller found |
| POST | `transaction` | `TransactionService.createTransaction` |
| POST | `imstatement` | `StatementsService.generateStatements` |
| POST | `dailyyield` | `SanlamYieldService.getYield` |
| POST | `getsanlammembers` | `SanlamMemberService` |
| POST | `sanlamaccountbalance` | `SanlamMemberService.getMembersAccountBalanse` |
| GET | `sanlamgettoken` | Sanlam member service and statement service |
| POST | `initiatepayment` | `PaymentService.initiatePayment` |
| POST | `sanlamwithdrawalflow` | `PaymentService.sanlamWithdrawal` |
| POST | `sanlampurchaseflow` | `PaymentService.sanlamPurchase` |
| POST | `requestotp` | `OtpService.requestOtp` |
| POST | `validateotp` | `OtpService.validateOtp` |
| POST | `sanlamwitdrawalcharge` | `AccountService.sanlamWithdrawalCharges` (path spelling is `witdrawal`) |
| POST | `imgetcustomer` | `AccountService.getIMUserDetails` |
| POST | `checkwhitelist` | `AccountService.checkWhiteList` |
| GET | `getsettings?setting=` | `AccountService.getSettings` |
| POST | `createsession` | `AccountService.createSession` |
| POST | `updatesession` | `AccountService.updateSession` |
| POST | `imgetaccounts` | `AccountService.getIMAccounts` |
| POST | `statementdata` | `SanlamGetStatementsService.getStatements` |
| POST | `serviceagreement` | `ImbankAuthService.getServiceAgreement` — no component caller found |
| GET | `getToken` | `ImbankAuthService.getToken` — no component caller found |
| POST | `logerror` | Commented calls in `PaymentService` only |

`parent_no` `09576` is hardcoded on the two Sanlam flow bodies.

`gettransaction` and `updatetransaction` have no component caller. The dashboard transaction list uses `statementdata`. **Implemented.**

`POST createuser` is only called from `getServiceAgreement`, which itself has no component caller. That method reads `localStorage` key `newMember`. Landing stores the whitelist member number under `new_member`. **Implemented.**

## Data held in the browser

| Key | Set by | Used for |
|---|---|---|
| `access_token` | Landing URL parse | Gateway `Authorization` and OTP bodies |
| `id_token` | Landing URL parse | Read in `initiatePayment` and not placed in the JSON that was read |
| `session_state` | Landing URL parse | Session create/update; would be the refresh client secret |
| `cif` | `imgetcustomer` `custID` | Accounts, whitelist, transactions, profile |
| `kraPin` | `imgetcustomer` | Sanlam purchase |
| `middle_name` | `imgetcustomer` `lastName` | Sanlam purchase |
| `new_member` | whitelist `member_no` | Landing `canProceed` on a later visit |
| `newMember` | not written | Read by `addUserFromToken` and sent as `new_member` on `POST createuser`. Landing writes `new_member`, not this key. `createuser` runs only from `getServiceAgreement`, which has no component caller |
| `sanlam_token` | `sanlamgettoken` | Sanlam bodies as `auth_token` |
| `member_no` | `SanlamMemberService.setMember` | Selected member |
| `ip` | ipify | `createsession` |
| `refreshToken` | not written | Read by the unused refresh service |
| `lastActiveTime` | not written | Read by the unused idle check |

Account shape (`interfaces/account.ts`): `account_id`, `name`, `account_number`, `balance`. Dashboard fills `account_id` and `account_number` from `accountNo`, `name` from `accountName`, `balance` from `availableBalance`. The interface types `account_id` as `number` while `accountNo` is passed through as returned.

Member interface in source is only `{ member_no: string }`. Call sites also read `actual_balance`, `first_name`, and `pending_withdrawals`. **Implemented** as untyped extra fields.

## UI behaviour worth knowing when changing code

- Greeting tabs `purchase`, `withdraw`, and `statements` drive `TabService` and which block is visible. Purchase and withdraw share `app-transaction-block`.
- After `POST transaction` succeeds, `AccountService` opens Bootstrap modal `#reviewWithdrawalModal`.
- `#transactionModal` (`app-transaction-model`) and `#confirm-modal` are in the dashboard DOM. No `data-bs-target="#transactionModal"` was found.
- `app-portfolio-block` chart data is a fixed series. It is not on the dashboard template.
- Success and failed modals are Bootstrap modals created with `window.bootstrap.Modal` in `ValidateModalComponent`. The script that provides `bootstrap` is `bootstrap.bundle.min.js` from the npm package (Bootstrap 5), while `index.html` also loads Bootstrap 4.5.2 CSS.

## Security notes (as implemented, not a review verdict)

- Access tokens are in `localStorage` and in the redirect URL fragment.
- The auth nonce does not change per visit.
- Gateway authorization is a raw token header.
- `initiatepayment` does not send the amount the person entered.
- Production file points at a host named `test-dxp` and a stage named `dev`.
- No CSRF token, no route guard, no captcha, no rate limit in this frontend.
- Customer care address and phone are public in the templates.

## Build, test, and CI

| Command | What it does |
|---|---|
| `npm install` | README note from Liam, 02 May 2023 |
| `npm start` | `ng serve --host 192.168.8.168` |
| `npm run build` | `ng build` (production configuration by default) |
| `npm test` | `ng test` (Karma). Specs opened are generated "should be created" tests |
| `ng e2e` | README only. No e2e dependency |

Bitbucket Pipelines (`bitbucket-pipelines.yml`): image `maven:3.3.9`, full clone, branch filter `{master}`, one step, pipe `sonarsource/sonarqube-scan:2.0.1` with `SONAR_HOST_URL` and `SONAR_TOKEN` from repository variables. It does not run `npm ci`, `ng build`, or `ng test`.

`sonar-project.properties` sets `sonar.projectKey=overdrive_im_angular_app_AYvUxBc-T-jCdK_QYjQ9` and nothing else.

`.github/workflows/build.yml` checks out the repo and runs `sonarsource/sonarqube-scan-action` on pushes to branch `main`. Default branch on Bitbucket is `master`. The quality-gate step is commented out. Whether this workflow runs anywhere is **Unknown / Requires Confirmation**.

## Deployment and operations

**Unknown / Requires Confirmation.** `README_OLD.md` names AWS Amplify. No Amplify file, Dockerfile, or hosting config is in the tree. Last commit on `master` is 2024-06-25. Monitoring and alerting are not configured here. Support contacts on screen are the I&M customer-care email and the 24-hour number `+254 719 088 000`.

## Related systems

This repository is not `im_flutter_app`. That Silicon Ark directory is a separate Bitbucket repo whose `master` commit, inspected for OVER-153, contains only a `.gitignore`.
