Files
Replit Agent 12111a9562 Build EHSAN Closed Donation Loop POC — full bilingual Arabic/English app
- Backend (api-server): Complete in-memory mock DB with 11 seed cases, 5 eligible
  beneficiaries, 3 donors, and WhatsApp log. All 14 API routes implemented across
  requests, donors, stats, and whatsapp-log. OpenClaw integration with OPENCLAW_SIMULATE
  toggle. UUID-based IDs. Full status machine (new → closed, 10 steps).

- Frontend (ehsan-poc): 8 pages fully implemented using all generated API hooks:
  Home (stats counters, 10-step workflow diagram), Request (form with eligibility
  result), Opportunities (card grid with progress bars), Donate (case summary +
  donor form), Admin (full data table with contextual action buttons), Track
  (10-step visual timeline in green), ThankYou (message form), WhatsApp Log
  (WhatsApp bubble preview + OpenClaw send button).

- Bilingual LanguageContext (AR/EN) with RTL/LTR toggle, localStorage persistence.
  EHSAN green palette (HSL 143), Tajawal font, fully responsive.
  TypeScript clean — zero errors.
2026-06-05 17:05:27 +00:00

570 lines
14 KiB
YAML

openapi: 3.1.0
info:
# Do not change the title, if the title changes, the import paths will be broken
title: Api
version: 0.1.0
description: EHSAN Closed Donation Loop API
servers:
- url: /api
description: Base API path
tags:
- name: health
description: Health operations
- name: requests
description: Donation requests (beneficiary cases)
- name: donors
description: Donor records
- name: stats
description: Dashboard statistics
- name: whatsapp
description: WhatsApp message log
paths:
/healthz:
get:
operationId: healthCheck
tags: [health]
summary: Health check
responses:
"200":
description: Healthy
content:
application/json:
schema:
$ref: "#/components/schemas/HealthStatus"
/requests:
get:
operationId: listRequests
tags: [requests]
summary: List all donation requests
parameters:
- in: query
name: status
schema:
type: string
- in: query
name: needType
schema:
type: string
responses:
"200":
description: List of requests
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/DonationRequest"
post:
operationId: createRequest
tags: [requests]
summary: Submit a new beneficiary request
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/DonationRequestInput"
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/DonationRequest"
/requests/new:
get:
operationId: listNewRequests
tags: [requests]
summary: List new (unreviewed) requests
responses:
"200":
description: List of new requests
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/DonationRequest"
/requests/published:
get:
operationId: listPublishedRequests
tags: [requests]
summary: List published donation opportunities (public)
responses:
"200":
description: Published opportunities
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/DonationRequest"
/requests/{id}:
get:
operationId: getRequest
tags: [requests]
summary: Get a single request by ID
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
description: Request detail
content:
application/json:
schema:
$ref: "#/components/schemas/DonationRequest"
"404":
description: Not found
/requests/{id}/verify:
post:
operationId: verifyRequest
tags: [requests]
summary: Verify a request (admin)
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
description: Updated request
content:
application/json:
schema:
$ref: "#/components/schemas/DonationRequest"
/requests/{id}/publish:
post:
operationId: publishRequest
tags: [requests]
summary: Publish a verified request as a donation opportunity
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
description: Updated request
content:
application/json:
schema:
$ref: "#/components/schemas/DonationRequest"
/requests/{id}/donate:
post:
operationId: donateToRequest
tags: [requests]
summary: Simulate a donation to a published request
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/DonationInput"
responses:
"200":
description: Updated request
content:
application/json:
schema:
$ref: "#/components/schemas/DonationRequest"
/requests/{id}/deliver:
post:
operationId: deliverSupport
tags: [requests]
summary: Mark support as delivered
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
description: Updated request
content:
application/json:
schema:
$ref: "#/components/schemas/DonationRequest"
/requests/{id}/confirm-receipt:
post:
operationId: confirmReceipt
tags: [requests]
summary: Confirm beneficiary received support
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
description: Updated request
content:
application/json:
schema:
$ref: "#/components/schemas/DonationRequest"
/requests/{id}/thank-you:
post:
operationId: submitThankYou
tags: [requests]
summary: Submit beneficiary thank-you message
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ThankYouInput"
responses:
"200":
description: Updated request
content:
application/json:
schema:
$ref: "#/components/schemas/DonationRequest"
/requests/{id}/send-whatsapp:
post:
operationId: sendWhatsapp
tags: [requests]
summary: Send thank-you WhatsApp to donor via OpenClaw
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
description: WhatsApp send result
content:
application/json:
schema:
$ref: "#/components/schemas/WhatsappResult"
/requests/{id}/close:
post:
operationId: closeRequest
tags: [requests]
summary: Close the case
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
description: Updated request
content:
application/json:
schema:
$ref: "#/components/schemas/DonationRequest"
/requests/{id}/reject:
post:
operationId: rejectRequest
tags: [requests]
summary: Reject a request
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/RejectInput"
responses:
"200":
description: Updated request
content:
application/json:
schema:
$ref: "#/components/schemas/DonationRequest"
/donors:
get:
operationId: listDonors
tags: [donors]
summary: List all donors
responses:
"200":
description: List of donors
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Donor"
/stats:
get:
operationId: getStats
tags: [stats]
summary: Dashboard statistics summary
responses:
"200":
description: Statistics
content:
application/json:
schema:
$ref: "#/components/schemas/Stats"
/whatsapp-log:
get:
operationId: listWhatsappLog
tags: [whatsapp]
summary: List all WhatsApp message log entries
responses:
"200":
description: WhatsApp log entries
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/WhatsappLogEntry"
components:
schemas:
HealthStatus:
type: object
properties:
status:
type: string
required:
- status
DonationRequest:
type: object
required: [id, caseId, beneficiaryName, nationalId, phone, source, sourceName, needType, requestedAmount, collectedAmount, description, status, currentStep, createdAt, updatedAt]
properties:
id:
type: string
caseId:
type: string
beneficiaryName:
type: string
nationalId:
type: string
phone:
type: string
source:
type: string
enum: [beneficiary, charity, official]
sourceName:
type: string
needType:
type: string
enum: [electricity, water, food, health, housing, refrigerator, air_conditioner, court_order]
requestedAmount:
type: number
collectedAmount:
type: number
description:
type: string
status:
type: string
enum: [new, pending_review, verified, published, donated, delivered, receipt_confirmed, thank_you_submitted, whatsapp_sent, closed, rejected]
currentStep:
type: integer
donorId:
type: ["string", "null"]
donorName:
type: ["string", "null"]
thankYouMessage:
type: ["string", "null"]
whatsappStatus:
type: ["string", "null"]
enum: ["pending", "sent", "failed", null]
whatsappSentAt:
type: ["string", "null"]
rejectionReason:
type: ["string", "null"]
createdAt:
type: string
updatedAt:
type: string
DonationRequestInput:
type: object
required: [beneficiaryName, nationalId, phone, source, sourceName, needType, requestedAmount, description]
properties:
beneficiaryName:
type: string
nationalId:
type: string
phone:
type: string
source:
type: string
enum: [beneficiary, charity, official]
sourceName:
type: string
needType:
type: string
enum: [electricity, water, food, health, housing, refrigerator, air_conditioner, court_order]
requestedAmount:
type: number
description:
type: string
DonationInput:
type: object
required: [donorName, donorPhone, amount]
properties:
donorName:
type: string
donorPhone:
type: string
donorEmail:
type: ["string", "null"]
amount:
type: number
ThankYouInput:
type: object
required: [beneficiaryName, message]
properties:
beneficiaryName:
type: string
message:
type: string
RejectInput:
type: object
properties:
reason:
type: string
WhatsappResult:
type: object
required: [success, message, simulated]
properties:
success:
type: boolean
message:
type: string
simulated:
type: boolean
sentAt:
type: ["string", "null"]
Donor:
type: object
required: [id, name, phone, totalDonated, donationCount]
properties:
id:
type: string
name:
type: string
phone:
type: string
email:
type: ["string", "null"]
totalDonated:
type: number
donationCount:
type: integer
Stats:
type: object
required: [totalRequests, totalDonated, totalCollected, totalClosed, byStatus, byNeedType]
properties:
totalRequests:
type: integer
totalDonated:
type: integer
totalCollected:
type: number
totalClosed:
type: integer
pendingVerification:
type: integer
activeOpportunities:
type: integer
byStatus:
type: array
items:
$ref: "#/components/schemas/StatusCount"
byNeedType:
type: array
items:
$ref: "#/components/schemas/NeedTypeCount"
StatusCount:
type: object
required: [status, count]
properties:
status:
type: string
count:
type: integer
NeedTypeCount:
type: object
required: [needType, count, totalAmount]
properties:
needType:
type: string
count:
type: integer
totalAmount:
type: number
WhatsappLogEntry:
type: object
required: [id, caseId, donorName, donorPhone, beneficiaryMessage, whatsappMessage, status, createdAt]
properties:
id:
type: string
caseId:
type: string
donorName:
type: string
donorPhone:
type: string
beneficiaryMessage:
type: string
whatsappMessage:
type: string
status:
type: string
enum: [pending, sent, failed]
sentAt:
type: ["string", "null"]
createdAt:
type: string