2026-06-05 17:05:27 +00:00
import { v4 as uuidv4 } from "uuid" ;
export type NeedType =
| "electricity"
| "water"
| "food"
| "health"
| "housing"
| "refrigerator"
| "air_conditioner"
| "court_order" ;
export type RequestSource = "beneficiary" | "charity" | "official" ;
export type RequestStatus =
| "new"
| "pending_review"
| "verified"
| "published"
| "donated"
| "delivered"
| "receipt_confirmed"
| "thank_you_submitted"
| "whatsapp_sent"
| "closed"
| "rejected" ;
export type WhatsappStatus = "pending" | "sent" | "failed" ;
export interface DonationRequest {
id : string ;
caseId : string ;
beneficiaryName : string ;
nationalId : string ;
phone : string ;
source : RequestSource ;
sourceName : string ;
needType : NeedType ;
requestedAmount : number ;
collectedAmount : number ;
description : string ;
status : RequestStatus ;
currentStep : number ;
donorId : string | null ;
donorName : string | null ;
thankYouMessage : string | null ;
whatsappStatus : WhatsappStatus | null ;
whatsappSentAt : string | null ;
rejectionReason : string | null ;
createdAt : string ;
updatedAt : string ;
}
export interface Donor {
id : string ;
name : string ;
phone : string ;
email : string | null ;
totalDonated : number ;
donationCount : number ;
}
export interface EligibilityRecord {
nationalId : string ;
eligible : boolean ;
}
export interface WhatsappLogEntry {
id : string ;
caseId : string ;
donorName : string ;
donorPhone : string ;
beneficiaryMessage : string ;
whatsappMessage : string ;
status : WhatsappStatus ;
sentAt : string | null ;
createdAt : string ;
}
2026-06-06 22:27:47 +03:00
// Outbound WhatsApp "thank the donor" notifications, fired right after a
// successful donation. They are queued here (in-memory) and picked up by an
// external poller (OpenClaw) that actually sends the WhatsApp message and then
// marks the row as sent. Resets on container restart like all mock data.
export interface DonorNotification {
id : string ;
caseId : string ;
donorName : string ;
donorPhone : string ; // normalized to international form, e.g. 9665XXXXXXXX
message : string ;
status : WhatsappStatus ; // pending | sent | failed
sentAt : string | null ;
createdAt : string ;
}
// Fixed Arabic confirmation message sent to the donor after a successful
// donation. This is an outbound WhatsApp message (not in-app UI text), so it is
// intentionally a single fixed string and does not go through LanguageContext.
export const DONOR_THANK_YOU_MESSAGE =
"إحسانك يُثمر وعطاؤك يعين ..\n\nتمت عملية تبرعك عبر منصة إحسان بنجاح\n\n(والله يحب المحسنين)" ;
export const donorNotifications : DonorNotification [ ] = [ ] ;
// Normalize a Saudi phone number to WhatsApp international form.
// 05XXXXXXXX -> 9665XXXXXXXX
// 5XXXXXXXX -> 9665XXXXXXXX
// 9665XXXXXXXX -> 9665XXXXXXXX (unchanged)
// +966 5XXXXXXXX -> 9665XXXXXXXX
// Returns digits only (no '+'). Falls back to the cleaned digits if the shape
// is unexpected, so we never throw inside the donation flow.
export function normalizeSaudiPhone ( raw : string ) : string {
const digits = String ( raw || "" ) . replace ( /\D/g , "" ) ;
if ( ! digits ) return "" ;
if ( digits . startsWith ( "966" ) ) return digits ;
if ( digits . startsWith ( "0" ) ) return "966" + digits . slice ( 1 ) ;
if ( digits . startsWith ( "5" ) && digits . length === 9 ) return "966" + digits ;
return digits ;
}
2026-06-05 17:05:27 +00:00
// ─── Eligibility Database ───────────────────────────────────────────────────
export const eligibilityDb : EligibilityRecord [ ] = [
{ nationalId : "1090512345" , eligible : true } ,
{ nationalId : "1023456789" , eligible : true } ,
{ nationalId : "2098765432" , eligible : true } ,
{ nationalId : "1056789012" , eligible : true } ,
{ nationalId : "2034567890" , eligible : true } ,
{ nationalId : "1099999999" , eligible : false } ,
] ;
// ─── Donors ─────────────────────────────────────────────────────────────────
export const donors : Donor [ ] = [
{
id : "donor-001" ,
name : "عبدالله المنصور" ,
phone : "0501234567" ,
email : "abdullah@example.com" ,
totalDonated : 5000 ,
donationCount : 2 ,
} ,
{
id : "donor-002" ,
name : "سارة الأحمد" ,
phone : "0556789012" ,
email : "sara@example.com" ,
totalDonated : 3000 ,
donationCount : 1 ,
} ,
{
id : "donor-003" ,
name : "محمد الشمري" ,
phone : "0589012345" ,
email : null ,
totalDonated : 2500 ,
donationCount : 1 ,
} ,
] ;
// ─── Mock Requests ───────────────────────────────────────────────────────────
const now = new Date ( ) ;
const d = ( daysAgo : number ) = >
new Date ( now . getTime ( ) - daysAgo * 86400000 ) . toISOString ( ) ;
export const requests : DonationRequest [ ] = [
{
id : "req-001" ,
caseId : "CASE-001" ,
beneficiaryName : "أحمد إبراهيم الحربي" ,
nationalId : "1090512345" ,
phone : "0501111111" ,
source : "beneficiary" ,
sourceName : "مستفيد مباشر" ,
needType : "electricity" ,
requestedAmount : 2400 ,
collectedAmount : 2400 ,
description : "فاتورة كهرباء متراكمة لمدة 6 أشهر، أب لأربعة أطفال" ,
status : "closed" ,
currentStep : 10 ,
donorId : "donor-001" ,
donorName : "عبدالله المنصور" ,
thankYouMessage : "جزاكم الله خيراً، وصلني الدعم وكان له أثر كبير عليّ وعلى أسرتي." ,
whatsappStatus : "sent" ,
whatsappSentAt : d ( 1 ) ,
rejectionReason : null ,
createdAt : d ( 30 ) ,
updatedAt : d ( 1 ) ,
} ,
{
id : "req-002" ,
caseId : "CASE-002" ,
beneficiaryName : "فاطمة علي السلمي" ,
nationalId : "1023456789" ,
phone : "0502222222" ,
source : "charity" ,
sourceName : "جمعية البر الخيرية" ,
needType : "water" ,
requestedAmount : 1800 ,
collectedAmount : 1800 ,
description : "فاتورة مياه متأخرة، أرملة تعيل ثلاثة أطفال" ,
status : "whatsapp_sent" ,
currentStep : 9 ,
donorId : "donor-002" ,
donorName : "سارة الأحمد" ,
thankYouMessage : "بارك الله فيكم، وصل الدعم في الوقت المناسب جداً." ,
whatsappStatus : "sent" ,
whatsappSentAt : d ( 2 ) ,
rejectionReason : null ,
createdAt : d ( 25 ) ,
updatedAt : d ( 2 ) ,
} ,
{
id : "req-003" ,
caseId : "CASE-003" ,
beneficiaryName : "خالد محمد الغامدي" ,
nationalId : "2098765432" ,
phone : "0503333333" ,
source : "official" ,
sourceName : "وزارة العدل" ,
needType : "food" ,
requestedAmount : 1200 ,
collectedAmount : 1200 ,
description : "سلة غذائية شهرية لعائلة من 6 أفراد" ,
status : "thank_you_submitted" ,
currentStep : 8 ,
donorId : "donor-003" ,
donorName : "محمد الشمري" ,
thankYouMessage : "شكراً جزيلاً، السلة الغذائية أفادتنا كثيراً." ,
whatsappStatus : "pending" ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 20 ) ,
updatedAt : d ( 3 ) ,
} ,
{
id : "req-004" ,
caseId : "CASE-004" ,
beneficiaryName : "مريم سالم العتيبي" ,
nationalId : "1056789012" ,
phone : "0504444444" ,
source : "charity" ,
sourceName : "الهلال الأحمر السعودي" ,
needType : "health" ,
requestedAmount : 5000 ,
collectedAmount : 5000 ,
description : "مصاريف علاج ومستلزمات طبية لمريضة مزمنة" ,
status : "receipt_confirmed" ,
currentStep : 7 ,
donorId : "donor-001" ,
donorName : "عبدالله المنصور" ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 18 ) ,
updatedAt : d ( 4 ) ,
} ,
{
id : "req-005" ,
caseId : "CASE-005" ,
beneficiaryName : "عمر عبدالرحمن الدوسري" ,
nationalId : "2034567890" ,
phone : "0505555555" ,
source : "official" ,
sourceName : "شركة المياه الوطنية" ,
needType : "housing" ,
requestedAmount : 8000 ,
collectedAmount : 8000 ,
description : "إصلاحات طارئة في المسكن بعد تسرب المياه" ,
status : "delivered" ,
currentStep : 6 ,
donorId : "donor-002" ,
donorName : "سارة الأحمد" ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 15 ) ,
updatedAt : d ( 5 ) ,
} ,
{
id : "req-006" ,
caseId : "CASE-006" ,
beneficiaryName : "نورة سعد القحطاني" ,
nationalId : "1090512345" ,
phone : "0506666666" ,
source : "beneficiary" ,
sourceName : "مستفيد مباشر" ,
needType : "refrigerator" ,
requestedAmount : 1500 ,
collectedAmount : 1500 ,
description : "ثلاجة منزلية لعائلة تفتقر لوسيلة حفظ الغذاء" ,
status : "donated" ,
currentStep : 5 ,
donorId : "donor-003" ,
donorName : "محمد الشمري" ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 10 ) ,
updatedAt : d ( 6 ) ,
} ,
{
id : "req-007" ,
caseId : "CASE-007" ,
beneficiaryName : "سليمان ناصر الزهراني" ,
nationalId : "1023456789" ,
phone : "0507777777" ,
source : "charity" ,
sourceName : "جمعية التنمية الأسرية" ,
needType : "air_conditioner" ,
requestedAmount : 2000 ,
collectedAmount : 0 ,
description : "مكيف لمنزل في منطقة حارة، وجود أطفال ومسنين" ,
status : "published" ,
currentStep : 4 ,
donorId : null ,
donorName : null ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 8 ) ,
updatedAt : d ( 7 ) ,
} ,
{
id : "req-008" ,
caseId : "CASE-008" ,
beneficiaryName : "حسن عبدالله الرشيدي" ,
nationalId : "2056789012" ,
phone : "0508888888" ,
source : "official" ,
sourceName : "وزارة العدل" ,
needType : "court_order" ,
requestedAmount : 3500 ,
collectedAmount : 0 ,
description : "سداد غرامة قضائية لتجنب الحجز على الممتلكات" ,
status : "verified" ,
currentStep : 3 ,
donorId : null ,
donorName : null ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 6 ) ,
updatedAt : d ( 5 ) ,
} ,
{
id : "req-009" ,
caseId : "CASE-009" ,
beneficiaryName : "رنا طارق المالكي" ,
nationalId : "9999999999" ,
phone : "0509999999" ,
source : "beneficiary" ,
sourceName : "مستفيد مباشر" ,
needType : "food" ,
requestedAmount : 900 ,
collectedAmount : 0 ,
description : "سلة غذائية لأسرة محتاجة" ,
status : "pending_review" ,
currentStep : 2 ,
donorId : null ,
donorName : null ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 2 ) ,
updatedAt : d ( 1 ) ,
} ,
{
id : "req-010" ,
caseId : "CASE-010" ,
beneficiaryName : "بدر محمد الجهني" ,
nationalId : "1099999999" ,
phone : "0511111111" ,
source : "charity" ,
sourceName : "الجمعية الخيرية" ,
needType : "electricity" ,
requestedAmount : 1800 ,
collectedAmount : 0 ,
description : "فاتورة كهرباء متراكمة" ,
status : "rejected" ,
currentStep : 2 ,
donorId : null ,
donorName : null ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : "المستفيد غير مؤهل وفق قاعدة بيانات الاستحقاق" ,
createdAt : d ( 5 ) ,
updatedAt : d ( 4 ) ,
} ,
{
id : "req-011" ,
caseId : "CASE-011" ,
beneficiaryName : "أميرة خالد السبيعي" ,
nationalId : "1056789012" ,
phone : "0512222222" ,
source : "official" ,
sourceName : "شركة الكهرباء السعودية" ,
needType : "electricity" ,
requestedAmount : 3200 ,
collectedAmount : 0 ,
description : "فاتورة كهرباء لمنزل أرملة ذات أطفال صغار" ,
status : "new" ,
currentStep : 1 ,
donorId : null ,
donorName : null ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 1 ) ,
updatedAt : d ( 0 ) ,
} ,
2026-06-05 17:45:17 +00:00
{
id : "req-012" ,
caseId : "CASE-012" ,
beneficiaryName : "هند فيصل العنزي" ,
nationalId : "1067890123" ,
phone : "0513333333" ,
source : "charity" ,
sourceName : "جمعية كافل الخيرية" ,
needType : "food" ,
requestedAmount : 1200 ,
collectedAmount : 780 ,
description : "سلة غذائية شهرية لأسرة مكونة من خمسة أفراد" ,
status : "published" ,
currentStep : 4 ,
donorId : null ,
donorName : null ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 9 ) ,
updatedAt : d ( 2 ) ,
} ,
{
id : "req-013" ,
caseId : "CASE-013" ,
beneficiaryName : "صالح عوض الحارثي" ,
nationalId : "2078901234" ,
phone : "0514444444" ,
source : "official" ,
sourceName : "شركة الكهرباء السعودية" ,
needType : "electricity" ,
requestedAmount : 2600 ,
collectedAmount : 1850 ,
description : "سداد فاتورة كهرباء متراكمة لمنزل أسرة متعففة" ,
status : "published" ,
currentStep : 4 ,
donorId : null ,
donorName : null ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 7 ) ,
updatedAt : d ( 1 ) ,
} ,
{
id : "req-014" ,
caseId : "CASE-014" ,
beneficiaryName : "لطيفة عبدالله الشهري" ,
nationalId : "1089012345" ,
phone : "0515555555" ,
source : "charity" ,
sourceName : "الهلال الأحمر السعودي" ,
needType : "health" ,
requestedAmount : 6000 ,
collectedAmount : 1500 ,
description : "مستلزمات طبية وأجهزة منزلية لمريضة مزمنة" ,
status : "published" ,
currentStep : 4 ,
donorId : null ,
donorName : null ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 6 ) ,
updatedAt : d ( 1 ) ,
} ,
{
id : "req-015" ,
caseId : "CASE-015" ,
beneficiaryName : "ماجد سعيد القرني" ,
nationalId : "2090123456" ,
phone : "0516666666" ,
source : "beneficiary" ,
sourceName : "مستفيد مباشر" ,
needType : "water" ,
requestedAmount : 1400 ,
collectedAmount : 1120 ,
description : "سداد فاتورة مياه متأخرة لأسرة تعيلها أرملة" ,
status : "published" ,
currentStep : 4 ,
donorId : null ,
donorName : null ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 5 ) ,
updatedAt : d ( 1 ) ,
} ,
{
id : "req-016" ,
caseId : "CASE-016" ,
beneficiaryName : "ريم ناصر المطيري" ,
nationalId : "1078901234" ,
phone : "0517777777" ,
source : "charity" ,
sourceName : "جمعية الإسكان التنموي" ,
needType : "housing" ,
requestedAmount : 9000 ,
collectedAmount : 3200 ,
description : "ترميم وحدة سكنية متضررة لأسرة ذات دخل محدود" ,
status : "published" ,
currentStep : 4 ,
donorId : null ,
donorName : null ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 4 ) ,
updatedAt : d ( 1 ) ,
} ,
{
id : "req-017" ,
caseId : "CASE-017" ,
beneficiaryName : "عبدالعزيز فهد الرشيد" ,
nationalId : "2012345678" ,
phone : "0518888888" ,
source : "official" ,
sourceName : "جمعية تكافل" ,
needType : "air_conditioner" ,
requestedAmount : 2200 ,
collectedAmount : 2100 ,
description : "تأمين مكيف لمنزل يقطنه مسنون في منطقة شديدة الحرارة" ,
status : "published" ,
currentStep : 4 ,
donorId : null ,
donorName : null ,
thankYouMessage : null ,
whatsappStatus : null ,
whatsappSentAt : null ,
rejectionReason : null ,
createdAt : d ( 3 ) ,
updatedAt : d ( 1 ) ,
} ,
2026-06-05 17:05:27 +00:00
] ;
// ─── WhatsApp Log ────────────────────────────────────────────────────────────
export const whatsappLog : WhatsappLogEntry [ ] = [
{
id : "wa-001" ,
caseId : "CASE-001" ,
donorName : "عبدالله المنصور" ,
donorPhone : "0501234567" ,
beneficiaryMessage :
"جزاكم الله خيراً، وصلني الدعم وكان له أثر كبير عليّ وعلى أسرتي." ,
whatsappMessage :
"السلام عليكم، نشكركم على تبرعكم عبر منصة إحسان.\nتم إيصال الدعم للمستفيد، وهذه رسالة الشكر من المستفيد:\n\"جزاكم الله خيراً، وصلني الدعم وكان له أثر كبير عليّ وعلى أسرتي.\"\nرقم الحالة: CASE-001" ,
status : "sent" ,
sentAt : d ( 1 ) ,
createdAt : d ( 2 ) ,
} ,
{
id : "wa-002" ,
caseId : "CASE-002" ,
donorName : "سارة الأحمد" ,
donorPhone : "0556789012" ,
beneficiaryMessage : "بارك الله فيكم، وصل الدعم في الوقت المناسب جداً." ,
whatsappMessage :
"السلام عليكم، نشكركم على تبرعكم عبر منصة إحسان.\nتم إيصال الدعم للمستفيد، وهذه رسالة الشكر من المستفيد:\n\"بارك الله فيكم، وصل الدعم في الوقت المناسب جداً.\"\nرقم الحالة: CASE-002" ,
status : "sent" ,
sentAt : d ( 2 ) ,
createdAt : d ( 3 ) ,
} ,
{
id : "wa-003" ,
caseId : "CASE-003" ,
donorName : "محمد الشمري" ,
donorPhone : "0589012345" ,
beneficiaryMessage : "شكراً جزيلاً، السلة الغذائية أفادتنا كثيراً." ,
whatsappMessage :
"السلام عليكم، نشكركم على تبرعكم عبر منصة إحسان.\nتم إيصال الدعم للمستفيد، وهذه رسالة الشكر من المستفيد:\n\"شكراً جزيلاً، السلة الغذائية أفادتنا كثيراً.\"\nرقم الحالة: CASE-003" ,
status : "pending" ,
sentAt : null ,
createdAt : d ( 3 ) ,
} ,
] ;
// ─── Helper: Determine status after eligibility check ───────────────────────
export function checkEligibility ( nationalId : string ) : {
eligible : boolean | null ;
} {
const record = eligibilityDb . find ( ( r ) = > r . nationalId === nationalId ) ;
if ( ! record ) return { eligible : null } ;
return { eligible : record.eligible } ;
}
// ─── Helper: Status → Step mapping ──────────────────────────────────────────
export const STATUS_STEP : Record < RequestStatus , number > = {
new : 1 ,
pending_review : 2 ,
verified : 3 ,
published : 4 ,
donated : 5 ,
delivered : 6 ,
receipt_confirmed : 7 ,
thank_you_submitted : 8 ,
whatsapp_sent : 9 ,
closed : 10 ,
rejected : 2 ,
} ;