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

8.5 KiB
Raw Blame History

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.tsthe 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)

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:

./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 buildgit commitgit 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.