Files
Ehsan/hammam-dev.md
T
2026-06-06 17:01:12 +03:00

157 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Hammam Dev — EHSAN "Closed Donation Loop" (POC)
> Onboarding brief for any developer or AI coding agent (e.g. OpenClaw) taking over
> this project. Read it fully before editing. The repo is hosted on a self-hosted
> **Gitea** server and runs on a **Mac Mini** via Docker.
>
> **تنويه:** مشروع **تجريبي** مستوحى من فكرة منصة «إحسان»، وليس منصة إحسان الرسمية
> ولا تابعاً لها.
## 1. What this product is
A bilingual (Arabic / English, full RTL + LTR) **proof-of-concept charity donation web
app** inspired by the Saudi "EHSAN" (إحسان) platform. Its core idea is a **Closed
Donation Loop**:
- Beneficiaries submit support requests (housing, food, electricity, water, health,
court-ordered debt, appliances like A/C & refrigerator, etc.).
- Each request/opportunity has a **funding target**. Donations from multiple donors
**accumulate and are clamped to the target** — a case can never be over-funded.
- A case only **enters the "closed loop" fulfillment pipeline once it is fully funded**.
This is the central business rule. Do not break it.
- After funding, the flow continues to confirmation / tracking, and a simulated
WhatsApp notification log records donor/beneficiary messaging.
This is a POC/demo: data is **in-memory mock data** (no real database, no real payments).
## 2. Tech stack
- Monorepo: **pnpm workspaces**, Node.js 24, TypeScript 5.9.
- Web app (`artifacts/ehsan-poc`): React + Vite + **wouter** (routing) +
**TanStack Query** (data) + **Tailwind CSS** + shadcn/ui components.
- API (`artifacts/api-server`): **Express 5**, all routes mounted under `/api`,
health at `/api/healthz`. Data lives in `src/lib/mockDb.ts` (in-memory; resets on restart).
- Shared libs under `lib/`: `api-spec` (OpenAPI source of truth), `api-client-react`
(generated React Query hooks), `api-zod` (Zod schemas), `db` (Drizzle schema, not
active at runtime since data is in-memory).
- All workspace libs export TypeScript source directly (`./src/index.ts`) — no lib
pre-build step; Vite/esbuild consume the source.
## 3. Repo map
- `artifacts/ehsan-poc/src/pages/` — screens: home, about, waqf, baraem, request,
opportunities, donate/:id, cart, login, admin, track/:id, thank-you/:id,
whatsapp-log, not-found.
- `artifacts/ehsan-poc/src/contexts/``LanguageContext` (ar/en + RTL), `CartContext`
(multi-case donation cart), `AuthContext` (mock admin login).
- `artifacts/ehsan-poc/src/components/``Riyal.tsx` (renders the NEW Saudi Riyal
symbol via an image mask), `layout/` (Header, AppLayout), `ui/` (shadcn components).
- `artifacts/ehsan-poc/src/App.tsx` — UI root: routing (wouter) + all context providers.
- `artifacts/api-server/src/routes/``health`, `requests`, `donors`, `stats`,
`whatsappLog`. Mounted in `routes/index.ts`.
- `artifacts/api-server/src/routes/requests.ts` — **the closed-loop logic and every
status transition** live here.
- `artifacts/api-server/src/lib/mockDb.ts` — types, in-memory seed data, the
`STATUS_STEP` map, and `checkEligibility`.
- Root deploy files: `Dockerfile.web`, `Dockerfile.api`, `docker/nginx.conf`,
`docker-compose.yml`, `deploy.sh`, `scripts/push-to-gitea.sh`, `DEPLOYMENT.md`.
## 4. How to run locally (development)
```bash
pnpm install
pnpm --filter @workspace/api-server run dev # API (port 5000 in dev)
pnpm --filter @workspace/ehsan-poc run dev # Web (needs PORT and BASE_PATH env)
pnpm run typecheck # full typecheck
pnpm run build # typecheck + build everything
```
Note: `vite.config.ts` REQUIRES `PORT` and `BASE_PATH` env vars even for `build`
(it throws otherwise), e.g.
`PORT=8080 BASE_PATH=/ pnpm --filter @workspace/ehsan-poc run build`.
> **Apple Silicon caveat:** a full `pnpm run build` typechecks all packages but the
> `vite build` step fails locally on darwin-arm64 with
> `Cannot find module @rollup/rollup-darwin-arm64`. That is **by design** — the
> workspace `overrides` in `pnpm-workspace.yaml` strip every non-`linux-x64-gnu`
> native binary so the Docker (`linux/amd64`) build stays clean. The real build runs
> inside Docker; locally the `typecheck` result is the meaningful gate.
## 5. How it runs in production (Mac Mini, Docker)
Two services in `docker-compose.yml`:
- `api` — Express, internal only, listens on `PORT=8080`, healthcheck `/api/healthz`.
- `web` — nginx that serves the built Vite SPA **and reverse-proxies `/api/` to
`api:8080`**. The browser always calls **same-origin `/api/...`**, so there is NO
frontend API URL to configure.
Deploy / redeploy on the Mac Mini:
```bash
./deploy.sh # git pull gitea main → docker compose down → build → up -d
```
App is then served on the Mac Mini at `http://localhost:8080` (override with `WEB_PORT`).
## 6. Deployment flow (how code travels)
```
Edit code → commit → push to Gitea (branch: main)
→ on Mac Mini run ./deploy.sh → Docker rebuilds & restarts
```
- Central repo is **Gitea** (no GitHub), branch `main`. The documented remote name is
`gitea`; on this Mac Mini checkout the remote may be named `origin` but still points
at the Gitea host — verify with `git remote -v`.
- `scripts/push-to-gitea.sh` pushes from a dev machine; `deploy.sh` redeploys on the Mac Mini.
- An AI agent working directly on the Mac Mini clone should use this loop:
edit → test → `pnpm run build``git commit``git push <gitea-remote> main``./deploy.sh`.
## 7. Status lifecycle (create → close)
The case `status` (`RequestStatus`) and its step number (`STATUS_STEP` in `mockDb.ts`):
```
new (1)
→ pending_review (2)
→ verified (3)
→ published (4)
→ donated (5) ← reached ONLY when fully funded
→ delivered (6)
→ receipt_confirmed (7)
→ thank_you_submitted (8)
→ whatsapp_sent (9)
→ closed (10)
rejected (side path, step 2)
```
- **Eligibility on creation:** `POST /requests` checks `nationalId` via
`checkEligibility` → eligible ⇒ `verified`, not eligible ⇒ `rejected`, unknown ⇒
`pending_review`.
- **API transition endpoints:** `verify`, `publish`, `donate`, `deliver`,
`confirm-receipt`, `thank-you`, `send-whatsapp`, `close`, `reject`
(`POST /requests/:id/<action>`).
- **`donate` is the heart of the closed loop:** it only accepts donations while the
case is `published`, clamps each donation to the remaining amount
(`applied = min(amount, requestedAmount collectedAmount)`), and advances the case
to `donated` only once `collectedAmount >= requestedAmount`.
## 8. Hard rules / gotchas — do NOT break these
- **amd64 / glibc ONLY.** The pnpm workspace strips every native binary that is not
`linux-x64-gnu`. Docker build stages MUST use `node:24-bookworm-slim` (glibc, not
alpine) and `platform: linux/amd64` (runs under Rosetta on Apple Silicon). Do not
switch the build base to alpine or arm64 — rollup / tailwind-oxide / lightningcss
will fail to find native binaries.
- **Funding rule (Closed Donation Loop).** Donations accumulate and clamp to the
target; a case can never be over-funded and enters the fulfillment pipeline
(`donated` and beyond) only when fully funded. Preserve this.
- **Bilingual + RTL.** Every user-facing string must exist in both Arabic and English
via `LanguageContext`. Don't hardcode single-language text. Keep RTL layout working.
- **Same-origin API.** The browser calls `/api/...` on the same domain (nginx proxies
it to the `api` service). Never add a separate frontend API URL.
- **Currency.** Saudi Riyal uses the new official symbol rendered by the `<Riyal/>`
component (image mask), not the old "ر.س"/"SAR" text. Reuse `<Riyal/>`.
- **Routing base.** The app is mounted under a base path via `import.meta.env.BASE_URL`.
Use it for routes/links; never hardcode root-relative `/api` in a way that escapes
the base — call same-origin `/api/...` through the proxy.
- **Data is ephemeral.** mockDb is in-memory; restarting the `api` container resets it.
If you add persistence, add a database service to `docker-compose.yml` accordingly.
- **HMR quirk (dev):** if you see "useLanguage must be used within a LanguageProvider"
while the code is correct, it's stale Fast Refresh state — restart the web dev server.
- Always run `pnpm run build` (typecheck + build) before pushing to catch type errors.
## 9. Suggested first task for a new agent
Read `DEPLOYMENT.md`, `artifacts/ehsan-poc/src/App.tsx`, and
`artifacts/api-server/src/routes/index.ts` to confirm the routes and data model, then
summarize your understanding of the funding / closed-loop flow before making any change.