THE STAR OF THE SHOW — the automation engine is now visible. ## New: Unified Messaging Layer (src/lib/messaging.ts) Channel waterfall: WhatsApp → SMS → Email - sendToDonor() routes to best available channel - Respects donor consent flags (whatsappOptIn, emailOptIn) - Falls back automatically if primary channel fails - Every attempt logged to AnalyticsEvent for dashboard ## New: Email Integration (src/lib/email.ts) Bring-your-own-key: charity pastes their Resend or SendGrid API key - Resend: free 3,000 emails/month - SendGrid: free 100/day - Messages come from THEIR domain (donations@mymosque.org) - Plain text auto-converted to clean HTML ## New: SMS Integration (src/lib/sms.ts) Bring-your-own-key: charity pastes their Twilio credentials - Pay-as-you-go (~3p per SMS) - UK number normalization (07xxx → +447xxx) - Reaches donors without WhatsApp or email ## New: /dashboard/automations — the visible engine A. Dark hero stats: Messages this week per channel + delivery rate B. Live channels: WhatsApp/Email/SMS with status, features, stats C. The Pipeline: visual 4-step automation sequence - What triggers, what's sent, which channels, waterfall explanation D. Scheduled reminders: upcoming messages with timing E. Message feed: recent messages with channel icon, status, time ## New: /api/messaging/status — dashboard data endpoint Returns channels, stats (7 day), history (50 recent), pending reminders ## New: /api/messaging/test — send test message to admin ## Schema: 8 new Organization columns emailProvider, emailApiKey, emailFromAddress, emailFromName smsProvider, smsAccountSid, smsAuthToken, smsFromNumber ## Settings: 2 new channel rows in the checklist - Email: provider selector (Resend/SendGrid) + API key + from address - SMS: Twilio credentials + from number Both follow the same checklist expand/collapse pattern ## Nav: Automations added between Money and Reports Home → Collect → Money → Automations → Reports → Settings ## Stats tracking Messages logged as AnalyticsEvent: message.whatsapp.receipt.sent message.email.reminder_1.failed message.sms.reminder_2.sent Donor PII masked in logs (last 4 digits of phone, email obfuscated)
31 lines
1.2 KiB
Python
31 lines
1.2 KiB
Python
#!/usr/bin/env python3
|
|
import os
|
|
|
|
BASE = '/home/forge/app.charityright.org.uk'
|
|
|
|
pages = {
|
|
'app/Filament/Resources/CustomerResource/Pages/ListCustomers.php': 'all',
|
|
'app/Filament/Resources/DonationResource/Pages/ListDonations.php': 'today',
|
|
'app/Filament/Resources/AppealResource/Pages/ListAppeals.php': 'live',
|
|
'app/Filament/Resources/ApprovalQueueResource/Pages/ListApprovalQueues.php': 'pending',
|
|
'app/Filament/Resources/ScheduledGivingDonationResource/Pages/ListScheduledGivingDonations.php': 'active',
|
|
}
|
|
|
|
for rel_path, default_tab in pages.items():
|
|
path = os.path.join(BASE, rel_path)
|
|
with open(path, 'r') as f:
|
|
c = f.read()
|
|
|
|
if 'getDefaultActiveTab' not in c:
|
|
method = "\n public function getDefaultActiveTab(): string | int | null\n {\n return '" + default_tab + "';\n }\n"
|
|
last_brace = c.rstrip().rfind('}')
|
|
c = c[:last_brace] + method + '}\n'
|
|
|
|
with open(path, 'w') as f:
|
|
f.write(c)
|
|
print('Added default tab "' + default_tab + '" to ' + os.path.basename(path))
|
|
else:
|
|
print('Already has default tab: ' + os.path.basename(path))
|
|
|
|
print('Done')
|