+
return (
-
WhatsApp not connected — reminders won't send
diff --git a/pledge-now-pay-later/src/app/dashboard/page.tsx b/pledge-now-pay-later/src/app/dashboard/page.tsx index a601537..665a527 100644 --- a/pledge-now-pay-later/src/app/dashboard/page.tsx +++ b/pledge-now-pay-later/src/app/dashboard/page.tsx @@ -134,7 +134,7 @@ export default function DashboardPage() { : "Friends at a charity dinner — where pledges begin" return ( -
+
)
}
diff --git a/pledge-now-pay-later/src/app/dashboard/pledges/page.tsx b/pledge-now-pay-later/src/app/dashboard/pledges/page.tsx
index bb52ace..f483619 100644
--- a/pledge-now-pay-later/src/app/dashboard/pledges/page.tsx
+++ b/pledge-now-pay-later/src/app/dashboard/pledges/page.tsx
@@ -258,10 +258,10 @@ export default function MoneyPage() {
if (loading) return
{/* ━━ HERO — Brand photography + the one thing that matters ━━━ */}
+ {/* ── Content with padding ── */}
+
@@ -202,6 +202,9 @@ export default function DashboardPage() {
+
{/* ── Empty state: Share your link ── */}
{isEmpty && pledgeLink && (
@@ -487,6 +490,8 @@ export default function DashboardPage() {
>
)}
+
+
+
)
}
diff --git a/temp_files/fix/ListDonations.php b/temp_files/fix/ListDonations.php
new file mode 100644
index 0000000..5264341
--- /dev/null
+++ b/temp_files/fix/ListDonations.php
@@ -0,0 +1,95 @@
+ $q->whereNotNull('confirmed_at'))
+ ->whereDate('created_at', today())
+ ->count();
+ $todayAmount = Donation::whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
+ ->whereDate('created_at', today())
+ ->sum('amount') / 100;
+
+ return "Today: {$todayCount} confirmed (£" . number_format($todayAmount, 0) . ")";
+ }
+
+ public function getTabs(): array
+ {
+ $incompleteCount = Donation::whereDoesntHave('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
+ ->where('created_at', '>=', now()->subDays(7))
+ ->count();
+
+ $recurring = Donation::where('reoccurrence', '!=', -1)
+ ->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
+ ->count();
+
+ return [
+ 'today' => Tab::make('Today')
+ ->icon('heroicon-o-clock')
+ ->modifyQueryUsing(fn (Builder $query) => $query
+ ->whereDate('created_at', today())
+ ->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
+ ),
+
+ 'all_confirmed' => Tab::make('All Confirmed')
+ ->icon('heroicon-o-check-circle')
+ ->modifyQueryUsing(fn (Builder $query) => $query
+ ->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
+ ),
+
+ 'incomplete' => Tab::make('Incomplete')
+ ->icon('heroicon-o-exclamation-triangle')
+ ->badge($incompleteCount > 0 ? $incompleteCount : null)
+ ->badgeColor('danger')
+ ->modifyQueryUsing(fn (Builder $query) => $query
+ ->whereDoesntHave('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
+ ->where('created_at', '>=', now()->subDays(7))
+ ),
+
+ 'zakat' => Tab::make('Zakat')
+ ->icon('heroicon-o-star')
+ ->modifyQueryUsing(fn (Builder $query) => $query
+ ->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
+ ->whereHas('donationPreferences', fn ($q) => $q->where('is_zakat', true))
+ ),
+
+ 'gift_aid' => Tab::make('Gift Aid')
+ ->icon('heroicon-o-gift')
+ ->modifyQueryUsing(fn (Builder $query) => $query
+ ->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at'))
+ ->whereHas('donationPreferences', fn ($q) => $q->where('is_gift_aid', true))
+ ),
+
+ 'recurring' => Tab::make('Recurring')
+ ->icon('heroicon-o-arrow-path')
+ ->badge($recurring > 0 ? $recurring : null)
+ ->badgeColor('info')
+ ->modifyQueryUsing(fn (Builder $q) => $q->where('reoccurrence', '!=', -1)
+ ->whereHas('donationConfirmation', fn ($sub) => $sub->whereNotNull('confirmed_at'))),
+
+ 'everything' => Tab::make('Everything')
+ ->icon('heroicon-o-squares-2x2'),
+ ];
+ }
+
+ public function getDefaultActiveTab(): string | int | null
+ {
+ return 'today';
+ }
+}
diff --git a/temp_files/fix/ScheduledGivingDashboard.php b/temp_files/fix/ScheduledGivingDashboard.php
new file mode 100644
index 0000000..be60504
--- /dev/null
+++ b/temp_files/fix/ScheduledGivingDashboard.php
@@ -0,0 +1,204 @@
+ fn ($q) => $q->where('is_active', true),
+ 'donations as cancelled_count' => fn ($q) => $q->where('is_active', false),
+ ])->get();
+
+ $result = [];
+
+ foreach ($campaigns as $c) {
+ $donationIds = $c->donations()->pluck('id');
+
+ if ($donationIds->isEmpty()) {
+ $result[] = [
+ 'campaign' => $c,
+ 'subscribers' => 0,
+ 'active' => 0,
+ 'cancelled' => 0,
+ 'total_payments' => 0,
+ 'paid_payments' => 0,
+ 'pending_payments' => 0,
+ 'failed_payments' => 0,
+ 'collected' => 0,
+ 'pending_amount' => 0,
+ 'avg_per_night' => 0,
+ 'completion_rate' => 0,
+ 'dates' => $c->dates ?? [],
+ 'next_payment' => null,
+ 'last_payment' => null,
+ ];
+ continue;
+ }
+
+ $payments = DB::table('scheduled_giving_payments')
+ ->whereIn('scheduled_giving_donation_id', $donationIds)
+ ->whereNull('deleted_at')
+ ->selectRaw("
+ COUNT(*) as total,
+ SUM(CASE WHEN is_paid = 1 THEN 1 ELSE 0 END) as paid,
+ SUM(CASE WHEN is_paid = 0 THEN 1 ELSE 0 END) as pending,
+ SUM(CASE WHEN is_paid = 0 AND attempts > 0 THEN 1 ELSE 0 END) as failed,
+ SUM(CASE WHEN is_paid = 1 THEN amount ELSE 0 END) as collected,
+ SUM(CASE WHEN is_paid = 0 THEN amount ELSE 0 END) as pending_amount,
+ AVG(CASE WHEN is_paid = 1 THEN amount ELSE NULL END) as avg_amount,
+ MIN(CASE WHEN is_paid = 0 AND expected_at > NOW() THEN expected_at ELSE NULL END) as next_payment,
+ MAX(CASE WHEN is_paid = 1 THEN expected_at ELSE NULL END) as last_payment
+ ")
+ ->first();
+
+ // Completion: subscribers who paid ALL their payments
+ $totalNights = count($c->dates ?? []);
+ $fullyPaid = 0;
+ if ($totalNights > 0 && $donationIds->isNotEmpty()) {
+ $ids = $donationIds->implode(',');
+ $row = DB::selectOne("SELECT COUNT(*) as cnt FROM (SELECT scheduled_giving_donation_id FROM scheduled_giving_payments WHERE scheduled_giving_donation_id IN ({$ids}) AND deleted_at IS NULL GROUP BY scheduled_giving_donation_id HAVING SUM(is_paid) >= {$totalNights}) sub");
+ $fullyPaid = $row->cnt ?? 0;
+ }
+
+ $result[] = [
+ 'campaign' => $c,
+ 'subscribers' => $c->donations_count,
+ 'active' => $c->active_count,
+ 'cancelled' => $c->cancelled_count,
+ 'total_payments' => (int) $payments->total,
+ 'paid_payments' => (int) $payments->paid,
+ 'pending_payments' => (int) $payments->pending,
+ 'failed_payments' => (int) $payments->failed,
+ 'collected' => ($payments->collected ?? 0) / 100,
+ 'pending_amount' => ($payments->pending_amount ?? 0) / 100,
+ 'avg_per_night' => ($payments->avg_amount ?? 0) / 100,
+ 'completion_rate' => $c->active_count > 0
+ ? round($fullyPaid / $c->active_count * 100, 1)
+ : 0,
+ 'fully_completed' => $fullyPaid,
+ 'dates' => $c->dates ?? [],
+ 'total_nights' => $totalNights,
+ 'next_payment' => $payments->next_payment,
+ 'last_payment' => $payments->last_payment,
+ ];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Global totals across all campaigns.
+ */
+ public function getGlobalStats(): array
+ {
+ $row = DB::table('scheduled_giving_payments')
+ ->whereNull('deleted_at')
+ ->selectRaw("
+ COUNT(*) as total,
+ SUM(CASE WHEN is_paid = 1 THEN 1 ELSE 0 END) as paid,
+ SUM(CASE WHEN is_paid = 0 AND attempts > 0 THEN 1 ELSE 0 END) as failed,
+ SUM(CASE WHEN is_paid = 1 THEN amount ELSE 0 END) / 100 as collected,
+ SUM(CASE WHEN is_paid = 0 THEN amount ELSE 0 END) / 100 as pending
+ ")
+ ->first();
+
+ $totalSubs = ScheduledGivingDonation::count();
+ $activeSubs = ScheduledGivingDonation::where('is_active', true)->count();
+
+ return [
+ 'total_subscribers' => $totalSubs,
+ 'active_subscribers' => $activeSubs,
+ 'total_payments' => (int) ($row->total ?? 0),
+ 'paid_payments' => (int) ($row->paid ?? 0),
+ 'failed_payments' => (int) ($row->failed ?? 0),
+ 'collected' => (float) ($row->collected ?? 0),
+ 'pending' => (float) ($row->pending ?? 0),
+ 'collection_rate' => $row->total > 0
+ ? round($row->paid / $row->total * 100, 1)
+ : 0,
+ ];
+ }
+
+ /**
+ * Recent failed payments needing attention.
+ */
+ public function getFailedPayments(): array
+ {
+ return DB::table('scheduled_giving_payments as p')
+ ->join('scheduled_giving_donations as d', 'd.id', '=', 'p.scheduled_giving_donation_id')
+ ->join('scheduled_giving_campaigns as c', 'c.id', '=', 'd.scheduled_giving_campaign_id')
+ ->leftJoin('customers as cu', 'cu.id', '=', 'd.customer_id')
+ ->where('p.is_paid', false)
+ ->where('p.attempts', '>', 0)
+ ->whereNull('p.deleted_at')
+ ->orderByDesc('p.updated_at')
+ ->limit(15)
+ ->get([
+ 'p.id as payment_id',
+ 'p.amount',
+ 'p.expected_at',
+ 'p.attempts',
+ 'd.id as donation_id',
+ 'c.title as campaign',
+ DB::raw("CONCAT(cu.first_name, ' ', cu.last_name) as donor_name"),
+ 'cu.email as donor_email',
+ ])
+ ->toArray();
+ }
+
+ /**
+ * Upcoming payments in the next 48 hours.
+ */
+ public function getUpcomingPayments(): array
+ {
+ return DB::table('scheduled_giving_payments as p')
+ ->join('scheduled_giving_donations as d', 'd.id', '=', 'p.scheduled_giving_donation_id')
+ ->join('scheduled_giving_campaigns as c', 'c.id', '=', 'd.scheduled_giving_campaign_id')
+ ->where('p.is_paid', false)
+ ->where('p.attempts', 0)
+ ->where('d.is_active', true)
+ ->whereNull('p.deleted_at')
+ ->whereBetween('p.expected_at', [now(), now()->addHours(48)])
+ ->selectRaw("
+ c.title as campaign,
+ COUNT(*) as payment_count,
+ SUM(p.amount) / 100 as total_amount,
+ MIN(p.expected_at) as earliest
+ ")
+ ->groupBy('c.title')
+ ->orderBy('earliest')
+ ->get()
+ ->toArray();
+ }
+}
{/* ━━ HERO — Dark stats panel like landing page ━━━━━━━━━━━━ */}
-
)
}
diff --git a/pledge-now-pay-later/src/app/dashboard/settings/page.tsx b/pledge-now-pay-later/src/app/dashboard/settings/page.tsx
index c073ec9..54c15b3 100644
--- a/pledge-now-pay-later/src/app/dashboard/settings/page.tsx
+++ b/pledge-now-pay-later/src/app/dashboard/settings/page.tsx
@@ -186,10 +186,10 @@ export default function SettingsPage() {
: `${totalCount - doneCount} thing${totalCount - doneCount > 1 ? "s" : ""} left before you go live.`
return (
-
+
+ {/* ── Content with padding ── */}
+
+
+
+
{/* ── Stats bar (clickable filters) ── */}
{[
@@ -789,6 +792,8 @@ export default function MoneyPage() {
+
{/* ── Header — human progress, not a form page ── */}
-
+
+
+
@@ -208,6 +208,9 @@ export default function SettingsPage() {
+ {/* ── Content with padding ── */}
+ Settings
+
{error &&
{error}
}
{/* ━━ TWO-COLUMN: Checklist left, Education right ━━━━━━ */}
@@ -584,6 +587,8 @@ export default function SettingsPage() {