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.
This commit is contained in:
Replit Agent
2026-06-05 17:05:27 +00:00
parent 2da838bb66
commit 12111a9562
117 changed files with 12366 additions and 81 deletions
@@ -1,10 +1,211 @@
/**
* Generated by orval v8.5.3 🍺
* Generated by orval v8.9.1 🍺
* Do not edit manually.
* Api
* API specification
* EHSAN Closed Donation Loop API
* OpenAPI spec version: 0.1.0
*/
export interface HealthStatus {
status: string;
}
export type DonationRequestSource = typeof DonationRequestSource[keyof typeof DonationRequestSource];
export const DonationRequestSource = {
beneficiary: 'beneficiary',
charity: 'charity',
official: 'official',
} as const;
export type DonationRequestNeedType = typeof DonationRequestNeedType[keyof typeof DonationRequestNeedType];
export const DonationRequestNeedType = {
electricity: 'electricity',
water: 'water',
food: 'food',
health: 'health',
housing: 'housing',
refrigerator: 'refrigerator',
air_conditioner: 'air_conditioner',
court_order: 'court_order',
} as const;
export type DonationRequestStatus = typeof DonationRequestStatus[keyof typeof DonationRequestStatus];
export const DonationRequestStatus = {
new: 'new',
pending_review: 'pending_review',
verified: 'verified',
published: 'published',
donated: 'donated',
delivered: 'delivered',
receipt_confirmed: 'receipt_confirmed',
thank_you_submitted: 'thank_you_submitted',
whatsapp_sent: 'whatsapp_sent',
closed: 'closed',
rejected: 'rejected',
} as const;
/**
* @nullable
*/
export type DonationRequestWhatsappStatus = typeof DonationRequestWhatsappStatus[keyof typeof DonationRequestWhatsappStatus] | null;
export const DonationRequestWhatsappStatus = {
pending: 'pending',
sent: 'sent',
failed: 'failed',
} as const;
export interface DonationRequest {
id: string;
caseId: string;
beneficiaryName: string;
nationalId: string;
phone: string;
source: DonationRequestSource;
sourceName: string;
needType: DonationRequestNeedType;
requestedAmount: number;
collectedAmount: number;
description: string;
status: DonationRequestStatus;
currentStep: number;
/** @nullable */
donorId?: string | null;
/** @nullable */
donorName?: string | null;
/** @nullable */
thankYouMessage?: string | null;
/** @nullable */
whatsappStatus?: DonationRequestWhatsappStatus;
/** @nullable */
whatsappSentAt?: string | null;
/** @nullable */
rejectionReason?: string | null;
createdAt: string;
updatedAt: string;
}
export type DonationRequestInputSource = typeof DonationRequestInputSource[keyof typeof DonationRequestInputSource];
export const DonationRequestInputSource = {
beneficiary: 'beneficiary',
charity: 'charity',
official: 'official',
} as const;
export type DonationRequestInputNeedType = typeof DonationRequestInputNeedType[keyof typeof DonationRequestInputNeedType];
export const DonationRequestInputNeedType = {
electricity: 'electricity',
water: 'water',
food: 'food',
health: 'health',
housing: 'housing',
refrigerator: 'refrigerator',
air_conditioner: 'air_conditioner',
court_order: 'court_order',
} as const;
export interface DonationRequestInput {
beneficiaryName: string;
nationalId: string;
phone: string;
source: DonationRequestInputSource;
sourceName: string;
needType: DonationRequestInputNeedType;
requestedAmount: number;
description: string;
}
export interface DonationInput {
donorName: string;
donorPhone: string;
/** @nullable */
donorEmail?: string | null;
amount: number;
}
export interface ThankYouInput {
beneficiaryName: string;
message: string;
}
export interface RejectInput {
reason?: string;
}
export interface WhatsappResult {
success: boolean;
message: string;
simulated: boolean;
/** @nullable */
sentAt?: string | null;
}
export interface Donor {
id: string;
name: string;
phone: string;
/** @nullable */
email?: string | null;
totalDonated: number;
donationCount: number;
}
export interface StatusCount {
status: string;
count: number;
}
export interface NeedTypeCount {
needType: string;
count: number;
totalAmount: number;
}
export interface Stats {
totalRequests: number;
totalDonated: number;
totalCollected: number;
totalClosed: number;
pendingVerification?: number;
activeOpportunities?: number;
byStatus: StatusCount[];
byNeedType: NeedTypeCount[];
}
export type WhatsappLogEntryStatus = typeof WhatsappLogEntryStatus[keyof typeof WhatsappLogEntryStatus];
export const WhatsappLogEntryStatus = {
pending: 'pending',
sent: 'sent',
failed: 'failed',
} as const;
export interface WhatsappLogEntry {
id: string;
caseId: string;
donorName: string;
donorPhone: string;
beneficiaryMessage: string;
whatsappMessage: string;
status: WhatsappLogEntryStatus;
/** @nullable */
sentAt?: string | null;
createdAt: string;
}
export type ListRequestsParams = {
status?: string;
needType?: string;
};
File diff suppressed because it is too large Load Diff