# Frontend (`public/app/`)

A faithful rebuild of the supplied reference UI (Tailwind CDN + Inter
font + Hugeicons/Iconify + Alpine.js + a fetch-based SPA shell with a
sliding full-page panel), re-pointed at this project's own Core PHP
backend instead of the reference's WordPress/Pulsepoint API.

## What was preserved exactly

- **Theme engine**: the same light/dark/system `<html data-theme>`
  script from the reference, run pre-paint to avoid a flash of the
  wrong theme, plus the full dark-mode CSS variable/override block.
- **Shell architecture**: `dashboard.php` is a thin shell; every
  section (`header`, `wallet`, `quick-actions`, `services`, `kyc`,
  `recent-transactions`, `bottom-nav`, `fab`) is a separate HTML
  partial under `pages/`, fetched and injected with the same
  stale-while-revalidate `localStorage` cache pattern as the
  reference, and scripts inside the injected HTML are re-executed the
  same way (`runScripts()`).
- **Full-page panel**: `openFullPage()` / `closeFullPage()` slide a
  panel in from the right exactly like the reference (`#fullpage-panel`
  + the `.open` transform transition), used for every money-moving
  flow (airtime, data, electricity, cable, transfer, cards, crypto,
  KYC, PIN).
- **Bottom-sheet pattern**: provider pickers and the purchase
  confirmation step are bottom sheets with the same
  `translate-y-full` → `translate-y-0` transition and backdrop.
- **PIN entry**: the 4-dot keypad bottom sheet is identical in markup
  and behavior (auto-submits at 4 digits, shake/error state on a
  wrong PIN) — centralized in `assets/js/pin-sheet.js` as
  `requestPin(callback)` so every component reuses one implementation
  instead of each page carrying its own copy.
- **Bottom nav**: the same floating pill nav with the active-tab
  highlight logic (`setActiveNav()`).
- Fonts, icon library, card radii (`rounded-2xl`/`rounded-3xl`),
  spacing scale, button shapes (`rounded-full` primary actions),
  skeleton-loading placeholders, and toast notifications all match
  the reference's Tailwind utility patterns.

## What changed (business content only, per the brief)

The reference was a VTU/bill-payment platform with modules (betting,
exam pins, gift cards, a leaderboard) that have no equivalent in this
fintech backend. Per the instruction to "replace only the business
content where necessary," those were swapped for the modules this
backend actually implements:

| Reference module         | This app's module                          |
|---------------------------|---------------------------------------------|
| Airtime / Data / Cable / Electricity | Same — wired to `/bills/*`          |
| Withdraw / Convert         | **Transfer** — `/transfers/*` (bank payout) |
| Sell Gift Card             | **Virtual Cards** — `/cards/*`               |
| (none)                     | **Crypto Funding** — `/crypto/*` (USDT TRC20)|
| (implicit wallet funding)  | **Fund Wallet** panel — reserve/dynamic accounts (`/accounts/*`) |
| Betting, Exam Pins, Leaderboard | Dropped — no backend module maps to these |

The wallet card, quick-actions grid, services row, KYC banner,
recent-transactions list, bottom nav, and FAB all keep the reference's
exact visual structure with fintech content substituted in.

## Auth model difference (necessary, not cosmetic)

The reference used PHP session cookies (WordPress). This backend is a
stateless JWT API, so `assets/js/api.js` keeps the access/refresh
token pair in `localStorage` instead, with silent refresh-and-retry
on a `401` (`FintechAPI` wraps every request). Everything else —
loading overlay, toasts, caching — is unchanged.

## New backend endpoints added to support the UI

Two small additions were needed that Prompts 1–3 didn't require on
their own, both following the existing architecture (repository →
service → controller, same validation/response conventions):

- `GET/POST /api/v1/profile/me` (`ProfileController`) — the frontend
  needs "who is logged in" on nearly every page; this returns the
  user record (minus password/PIN hashes) plus their KYC tier.
- `GET /api/v1/transactions` (`TransactionController::history`) — a
  unified, wallet-owner-scoped transaction feed for the History tab
  and the dashboard's recent-transactions list, backed by two small
  additions to `TransactionRepository`/`WalletRepository`
  (`forWallets()`, `findAllForUserViaSql()`).

## Page-by-page module map

| File | Backend endpoints used |
|---|---|
| `login.php` | `POST /auth/login`, `POST /auth/2fa/verify` |
| `register.php` | `POST /auth/register` |
| `dashboard.php` + `pages/*` | `GET /profile/me`, `GET /wallets`, `GET /wallets/{id}/balance`, `GET /accounts/reserve`, `POST /accounts/dynamic`, `GET /transactions`, `GET /notifications`, `GET /kyc/status` |
| `pages/wallet-menu.html` | `GET /wallets`, `GET /wallets/{id}/balance`, `GET /transfers/beneficiaries` |
| `pages/history.html` | `GET /transactions` |
| `pages/profile.html` | `GET /profile/me`, `POST /auth/2fa/enable`, `POST /auth/logout` |
| `component/airtime.html` | `POST /bills/airtime` |
| `component/data.html` | `GET /bills/data/plans`, `POST /bills/data` |
| `component/electricity.html` | `GET /bills/electricity/providers`, `POST /bills/electricity/verify`, `POST /bills/electricity` |
| `component/cable.html` | `GET /bills/cabletv/plans`, `POST /bills/cabletv/verify`, `POST /bills/cabletv` |
| `component/transfer.html` | `GET /transfers/banks`, `POST /transfers/name-enquiry`, `GET/POST /transfers/beneficiaries`, `POST /transfers/bank` |
| `component/cards.html` | `GET/POST /cards`, `POST /cards/{code}/fund`, `/freeze`, `/unfreeze`, `/close`, `GET /cards/{code}/statement` |
| `component/crypto.html` | `GET /crypto/address`, `GET /crypto/history` |
| `component/kyc.html` | `POST /accounts/reserve`, `POST /kyc/submit` (multipart), `GET /kyc/status` |
| `component/password-pin.html` | `POST /auth/pin` |
| `component/notifications.html` | `GET /notifications`, `POST /notifications/{id}/read` |

## Setup

The frontend is served from the same `public/` document root as the
API — no separate build step, no CORS config needed (same origin).

```bash
php -S localhost:8000 -t public public/router.php
```

Then visit `http://localhost:8000/app/login.php`. `public/router.php`
is a dev-only convenience so the built-in server serves `/app/*`
files directly while still routing `/api/v1/*` to the front
controller; in production, point Apache/Nginx at `public/` and this
split happens naturally (serve static/PHP files directly, proxy
unmatched paths to `index.php`).

## Known gaps (flagged honestly, not silently skipped)

- **Card creation/statement UI is functional but plain**: card art,
  provider pickers for cable/electricity, and the bank list use
  simple lists/selects rather than the reference's icon-rich
  bottom-sheets (those used static brand image assets — e.g.
  `MTN.png` — that weren't part of the supplied UI package. Swap in
  real brand assets and the bottom-sheet markup from `airtime.html`'s
  pattern for full parity).
- **Recovery/reset-password pages** (`recovery.php`, forgot-password
  flow) weren't rebuilt in this pass — `AuthController` already
  exposes `POST /auth/password/forgot` and `POST /auth/password/reset`;
  wiring a page to them is the same pattern as `login.php`.
- **FeeService** (built in the security/admin pass) isn't yet reflected
  in the UI's confirmation sheets — they show the raw amount, not
  amount + platform fee. Once fee deduction is wired into
  `TransferService`/`BillPaymentService` (see the backend README's
  own note on this), the preview sheets need a "Fee" row added.
- Every component assumes a single NGN wallet per user (true today,
  since `AuthService::register()` only provisions one) — multi-currency
  wallet switching isn't built.
