Stripe integration: charity connects their own Stripe account
Model: PNPL never touches the money. Each charity connects their own Stripe account by pasting their API key in Settings. When a donor chooses card payment, they're redirected to Stripe Checkout. The money lands in the charity's Stripe balance. ## Schema - Organization.stripeSecretKey (new column) - Organization.stripeWebhookSecret (new column) ## New/rewritten files - src/lib/stripe.ts — getStripeForOrg(secretKey), per-org client - src/app/api/stripe/checkout/route.ts — uses org's key, not env var - src/app/api/stripe/webhook/route.ts — tries all org webhook secrets - src/app/p/[token]/steps/card-payment-step.tsx — redirect to Stripe Checkout (no fake card form — Stripe handles PCI) ## Settings page - New 'Card payments' section between Bank and Charity - Instructions: how to get your Stripe API key - Webhook setup in collapsed <details> (optional, for auto-confirm) - 'Card payments live' green banner when connected - Readiness bar shows Stripe status (5 columns now) ## Pledge flow - PaymentStep shows card option ONLY if org has Stripe configured - hasStripe flag passed from /api/qr/[token] → PaymentStep - Secret key never exposed to frontend (only boolean hasStripe) ## How it works 1. Charity pastes sk_live_... in Settings → Save 2. Donor opens pledge link → sees 'Bank Transfer', 'Direct Debit', 'Card' 3. Donor picks card → enters name + email → redirects to Stripe Checkout 4. Stripe processes payment → money in charity's Stripe balance 5. (Optional) Webhook auto-confirms pledge as paid Payment options: - Bank Transfer: zero fees (default, always available) - Direct Debit via GoCardless: 1% + 20p (if org configured) - Card via Stripe: standard Stripe fees (if org configured)
This commit is contained in:
88
temp_files/v4/AdminPanelProvider.php
Normal file
88
temp_files/v4/AdminPanelProvider.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers\Filament;
|
||||
|
||||
use App\Helpers;
|
||||
use Filament\Http\Middleware\Authenticate;
|
||||
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||
use Filament\Navigation\MenuItem;
|
||||
use Filament\Navigation\NavigationGroup;
|
||||
use Filament\Navigation\NavigationItem;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelProvider;
|
||||
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
public function panel(Panel $panel): Panel
|
||||
{
|
||||
return $panel
|
||||
->default()
|
||||
->id('admin')
|
||||
->path('admin')
|
||||
->login()
|
||||
->colors(['primary' => config('branding.colours')])
|
||||
->viteTheme('resources/css/filament/admin/theme.css')
|
||||
->sidebarCollapsibleOnDesktop()
|
||||
->sidebarWidth('16rem')
|
||||
->globalSearch(true)
|
||||
->globalSearchKeyBindings(['command+k', 'ctrl+k'])
|
||||
->globalSearchDebounce('300ms')
|
||||
->navigationGroups([
|
||||
// ── Daily Work (always visible, top of sidebar) ──
|
||||
NavigationGroup::make('Daily')
|
||||
->collapsible(false),
|
||||
|
||||
// ── Fundraising (campaigns, review queue) ──
|
||||
NavigationGroup::make('Fundraising')
|
||||
->icon('heroicon-o-megaphone')
|
||||
->collapsible(),
|
||||
|
||||
// ── Setup (rarely touched config) ──
|
||||
NavigationGroup::make('Setup')
|
||||
->icon('heroicon-o-cog-6-tooth')
|
||||
->collapsible()
|
||||
->collapsed(),
|
||||
])
|
||||
->brandLogo(Helpers::getCurrentLogo(true))
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
|
||||
->pages([\Filament\Pages\Dashboard::class])
|
||||
->userMenuItems([
|
||||
'profile' => MenuItem::make()
|
||||
->label('Edit profile')
|
||||
->url(url('user/profile')),
|
||||
|
||||
'back2site' => MenuItem::make()
|
||||
->label('Return to site')
|
||||
->icon('heroicon-o-home')
|
||||
->url(url('/')),
|
||||
])
|
||||
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets')
|
||||
->middleware([
|
||||
EncryptCookies::class,
|
||||
AddQueuedCookiesToResponse::class,
|
||||
StartSession::class,
|
||||
AuthenticateSession::class,
|
||||
ShareErrorsFromSession::class,
|
||||
VerifyCsrfToken::class,
|
||||
SubstituteBindings::class,
|
||||
DisableBladeIconComponents::class,
|
||||
DispatchServingFilamentEvent::class,
|
||||
])
|
||||
->authMiddleware([
|
||||
Authenticate::class,
|
||||
])
|
||||
->login(null)
|
||||
->registration(null)
|
||||
->darkMode(false)
|
||||
->databaseNotifications();
|
||||
}
|
||||
}
|
||||
86
temp_files/v4/ListAppeals.php
Normal file
86
temp_files/v4/ListAppeals.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\AppealResource\Pages;
|
||||
|
||||
use App\Filament\Resources\AppealResource;
|
||||
use App\Models\Appeal;
|
||||
use Filament\Resources\Components\Tab;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ListAppeals extends ListRecords
|
||||
{
|
||||
protected static string $resource = AppealResource::class;
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
return 'Fundraisers';
|
||||
}
|
||||
|
||||
public function getSubheading(): string
|
||||
{
|
||||
$live = Appeal::where('status', 'confirmed')->where('is_accepting_donations', true)->count();
|
||||
return "{$live} fundraisers are live right now.";
|
||||
}
|
||||
|
||||
public function getTabs(): array
|
||||
{
|
||||
$needsHelp = Appeal::where('status', 'confirmed')
|
||||
->where('is_accepting_donations', true)
|
||||
->where('amount_raised', 0)
|
||||
->where('created_at', '<', now()->subDays(7))
|
||||
->where('created_at', '>', now()->subDays(90))
|
||||
->count();
|
||||
|
||||
$almostThere = Appeal::where('status', 'confirmed')
|
||||
->where('is_accepting_donations', true)
|
||||
->where('amount_raised', '>', 0)
|
||||
->whereRaw('amount_raised >= amount_to_raise * 0.8')
|
||||
->whereRaw('amount_raised < amount_to_raise')
|
||||
->count();
|
||||
|
||||
return [
|
||||
'live' => Tab::make('Live')
|
||||
->icon('heroicon-o-signal')
|
||||
->modifyQueryUsing(fn (Builder $q) => $q
|
||||
->where('status', 'confirmed')
|
||||
->where('is_accepting_donations', true)
|
||||
),
|
||||
|
||||
'needs_help' => Tab::make('Needs Outreach')
|
||||
->icon('heroicon-o-hand-raised')
|
||||
->badge($needsHelp > 0 ? $needsHelp : null)
|
||||
->badgeColor('danger')
|
||||
->modifyQueryUsing(fn (Builder $q) => $q
|
||||
->where('status', 'confirmed')
|
||||
->where('is_accepting_donations', true)
|
||||
->where('amount_raised', 0)
|
||||
->where('created_at', '<', now()->subDays(7))
|
||||
->where('created_at', '>', now()->subDays(90))
|
||||
),
|
||||
|
||||
'almost' => Tab::make('Almost There')
|
||||
->icon('heroicon-o-fire')
|
||||
->badge($almostThere > 0 ? $almostThere : null)
|
||||
->badgeColor('warning')
|
||||
->modifyQueryUsing(fn (Builder $q) => $q
|
||||
->where('status', 'confirmed')
|
||||
->where('is_accepting_donations', true)
|
||||
->where('amount_raised', '>', 0)
|
||||
->whereRaw('amount_raised >= amount_to_raise * 0.8')
|
||||
->whereRaw('amount_raised < amount_to_raise')
|
||||
),
|
||||
|
||||
'hit_target' => Tab::make('Target Reached')
|
||||
->icon('heroicon-o-trophy')
|
||||
->modifyQueryUsing(fn (Builder $q) => $q
|
||||
->where('status', 'confirmed')
|
||||
->where('amount_raised', '>', 0)
|
||||
->whereRaw('amount_raised >= amount_to_raise')
|
||||
),
|
||||
|
||||
'all' => Tab::make('Everything')
|
||||
->icon('heroicon-o-squares-2x2'),
|
||||
];
|
||||
}
|
||||
}
|
||||
50
temp_files/v4/ListApprovalQueues.php
Normal file
50
temp_files/v4/ListApprovalQueues.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ApprovalQueueResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ApprovalQueueResource;
|
||||
use App\Models\ApprovalQueue;
|
||||
use Filament\Resources\Components\Tab;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ListApprovalQueues extends ListRecords
|
||||
{
|
||||
protected static string $resource = ApprovalQueueResource::class;
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
return 'Fundraiser Review';
|
||||
}
|
||||
|
||||
public function getSubheading(): string
|
||||
{
|
||||
$pending = ApprovalQueue::where('status', 'pending')->count();
|
||||
if ($pending === 0) return 'All caught up — no fundraisers waiting for review.';
|
||||
return "{$pending} fundraisers waiting for your review.";
|
||||
}
|
||||
|
||||
public function getTabs(): array
|
||||
{
|
||||
$pending = ApprovalQueue::where('status', 'pending')->count();
|
||||
|
||||
return [
|
||||
'pending' => Tab::make('Needs Review')
|
||||
->icon('heroicon-o-clock')
|
||||
->badge($pending > 0 ? $pending : null)
|
||||
->badgeColor('danger')
|
||||
->modifyQueryUsing(fn (Builder $q) => $q->where('status', 'pending')),
|
||||
|
||||
'approved' => Tab::make('Approved')
|
||||
->icon('heroicon-o-check-circle')
|
||||
->modifyQueryUsing(fn (Builder $q) => $q->where('status', 'confirmed')),
|
||||
|
||||
'rejected' => Tab::make('Rejected')
|
||||
->icon('heroicon-o-x-circle')
|
||||
->modifyQueryUsing(fn (Builder $q) => $q->where('status', 'change_requested')),
|
||||
|
||||
'all' => Tab::make('All')
|
||||
->icon('heroicon-o-squares-2x2'),
|
||||
];
|
||||
}
|
||||
}
|
||||
62
temp_files/v4/ListCustomers.php
Normal file
62
temp_files/v4/ListCustomers.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\CustomerResource\Pages;
|
||||
|
||||
use App\Filament\Resources\CustomerResource;
|
||||
use App\Models\Customer;
|
||||
use Filament\Resources\Components\Tab;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ListCustomers extends ListRecords
|
||||
{
|
||||
protected static string $resource = CustomerResource::class;
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
return 'Donors';
|
||||
}
|
||||
|
||||
public function getSubheading(): string
|
||||
{
|
||||
return 'Search by name, email, or phone number. Click a donor to see their full history.';
|
||||
}
|
||||
|
||||
public function getTabs(): array
|
||||
{
|
||||
return [
|
||||
'all' => Tab::make('All Donors')
|
||||
->icon('heroicon-o-users'),
|
||||
|
||||
'monthly' => Tab::make('Monthly Supporters')
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->modifyQueryUsing(fn (Builder $query) => $query
|
||||
->whereHas('scheduledGivingDonations', fn ($q) => $q->where('is_active', true))
|
||||
),
|
||||
|
||||
'major' => Tab::make('Major Donors')
|
||||
->icon('heroicon-o-star')
|
||||
->modifyQueryUsing(fn (Builder $query) => $query
|
||||
->whereIn('id', function ($sub) {
|
||||
$sub->select('customer_id')
|
||||
->from('donations')
|
||||
->join('donation_confirmations', 'donations.id', '=', 'donation_confirmations.donation_id')
|
||||
->whereNotNull('donation_confirmations.confirmed_at')
|
||||
->groupBy('customer_id')
|
||||
->havingRaw('SUM(donations.amount) >= 100000');
|
||||
})
|
||||
),
|
||||
|
||||
'recent' => Tab::make('New (30 days)')
|
||||
->icon('heroicon-o-sparkles')
|
||||
->modifyQueryUsing(fn (Builder $query) => $query
|
||||
->where('created_at', '>=', now()->subDays(30))
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
79
temp_files/v4/ListDonations.php
Normal file
79
temp_files/v4/ListDonations.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\DonationResource\Pages;
|
||||
|
||||
use App\Filament\Resources\DonationResource;
|
||||
use App\Models\Donation;
|
||||
use Filament\Resources\Components\Tab;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ListDonations extends ListRecords
|
||||
{
|
||||
protected static string $resource = DonationResource::class;
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
return 'Donations';
|
||||
}
|
||||
|
||||
public function getSubheading(): string
|
||||
{
|
||||
$todayCount = Donation::whereHas('donationConfirmation', fn ($q) => $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();
|
||||
|
||||
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))
|
||||
),
|
||||
|
||||
'everything' => Tab::make('Everything')
|
||||
->icon('heroicon-o-squares-2x2'),
|
||||
];
|
||||
}
|
||||
}
|
||||
53
temp_files/v4/ListScheduledGivingDonations.php
Normal file
53
temp_files/v4/ListScheduledGivingDonations.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ScheduledGivingDonationResource\Pages;
|
||||
|
||||
use App\Filament\Resources\ScheduledGivingDonationResource;
|
||||
use App\Models\ScheduledGivingDonation;
|
||||
use Filament\Resources\Components\Tab;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ListScheduledGivingDonations extends ListRecords
|
||||
{
|
||||
protected static string $resource = ScheduledGivingDonationResource::class;
|
||||
|
||||
public function getHeading(): string
|
||||
{
|
||||
return 'Regular Giving';
|
||||
}
|
||||
|
||||
public function getSubheading(): string
|
||||
{
|
||||
$active = ScheduledGivingDonation::where('is_active', true)->count();
|
||||
return "{$active} people giving every month.";
|
||||
}
|
||||
|
||||
public function getTabs(): array
|
||||
{
|
||||
$active = ScheduledGivingDonation::where('is_active', true)->count();
|
||||
$inactive = ScheduledGivingDonation::where('is_active', false)->count();
|
||||
|
||||
return [
|
||||
'active' => Tab::make('Active')
|
||||
->icon('heroicon-o-check-circle')
|
||||
->badge($active)
|
||||
->badgeColor('success')
|
||||
->modifyQueryUsing(fn (Builder $q) => $q->where('is_active', true)),
|
||||
|
||||
'cancelled' => Tab::make('Cancelled')
|
||||
->icon('heroicon-o-x-circle')
|
||||
->badge($inactive > 0 ? $inactive : null)
|
||||
->badgeColor('gray')
|
||||
->modifyQueryUsing(fn (Builder $q) => $q->where('is_active', false)),
|
||||
|
||||
'all' => Tab::make('All')
|
||||
->icon('heroicon-o-squares-2x2'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
183
temp_files/v4/nav_changes.py
Normal file
183
temp_files/v4/nav_changes.py
Normal file
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Reassign every Filament resource to the correct navigation group.
|
||||
|
||||
SIDEBAR STRUCTURE:
|
||||
Daily (always open, no collapse)
|
||||
├ Donors — "Find a donor, help them"
|
||||
├ Donations — "See what came in, investigate issues"
|
||||
└ Regular Giving — "Monthly supporters"
|
||||
|
||||
Fundraising
|
||||
├ Review Queue (84 pending badge)
|
||||
├ All Fundraisers
|
||||
└ Scheduled Campaigns (3 only)
|
||||
|
||||
Setup (collapsed by default — rarely touched)
|
||||
├ Causes (30)
|
||||
├ Countries (12)
|
||||
├ URL Builder
|
||||
├ Settings
|
||||
├ Users
|
||||
├ Activity Log
|
||||
└ (hidden): Snowdon, Campaign Regs, WOH, Engage dims, Fund dims
|
||||
"""
|
||||
|
||||
import re, os
|
||||
|
||||
BASE = '/home/forge/app.charityright.org.uk'
|
||||
|
||||
changes = {
|
||||
# ── DAILY (top 3 — the only pages staff use every day) ──
|
||||
'app/Filament/Resources/CustomerResource.php': {
|
||||
'navigationGroup': 'Daily',
|
||||
'navigationIcon': 'heroicon-o-user-circle',
|
||||
'navigationSort': 1,
|
||||
'navigationLabel': 'Donors',
|
||||
},
|
||||
'app/Filament/Resources/DonationResource.php': {
|
||||
'navigationGroup': 'Daily',
|
||||
'navigationIcon': 'heroicon-o-banknotes',
|
||||
'navigationSort': 2,
|
||||
'navigationLabel': 'Donations',
|
||||
},
|
||||
'app/Filament/Resources/ScheduledGivingDonationResource.php': {
|
||||
'navigationGroup': 'Daily',
|
||||
'navigationIcon': 'heroicon-o-arrow-path',
|
||||
'navigationSort': 3,
|
||||
'navigationLabel': 'Regular Giving',
|
||||
},
|
||||
|
||||
# ── FUNDRAISING ──
|
||||
'app/Filament/Resources/ApprovalQueueResource.php': {
|
||||
'navigationGroup': 'Fundraising',
|
||||
'navigationIcon': 'heroicon-o-shield-check',
|
||||
'navigationSort': 1,
|
||||
'navigationLabel': 'Review Queue',
|
||||
},
|
||||
'app/Filament/Resources/AppealResource.php': {
|
||||
'navigationGroup': 'Fundraising',
|
||||
'navigationIcon': 'heroicon-o-hand-raised',
|
||||
'navigationSort': 2,
|
||||
'navigationLabel': 'All Fundraisers',
|
||||
},
|
||||
'app/Filament/Resources/ScheduledGivingCampaignResource.php': {
|
||||
'navigationGroup': 'Fundraising',
|
||||
'navigationIcon': 'heroicon-o-calendar',
|
||||
'navigationSort': 3,
|
||||
'navigationLabel': 'Giving Campaigns',
|
||||
},
|
||||
|
||||
# ── SETUP (collapsed, rarely used) ──
|
||||
'app/Filament/Resources/DonationTypeResource.php': {
|
||||
'navigationGroup': 'Setup',
|
||||
'navigationIcon': 'heroicon-o-tag',
|
||||
'navigationSort': 1,
|
||||
'navigationLabel': 'Causes',
|
||||
},
|
||||
'app/Filament/Resources/DonationCountryResource.php': {
|
||||
'navigationGroup': 'Setup',
|
||||
'navigationIcon': 'heroicon-o-globe-alt',
|
||||
'navigationSort': 2,
|
||||
'navigationLabel': 'Countries',
|
||||
},
|
||||
'app/Filament/Resources/UserResource.php': {
|
||||
'navigationGroup': 'Setup',
|
||||
'navigationIcon': 'heroicon-o-users',
|
||||
'navigationSort': 3,
|
||||
'navigationLabel': 'Admin Users',
|
||||
},
|
||||
'app/Filament/Resources/EventLogResource.php': {
|
||||
'navigationGroup': 'Setup',
|
||||
'navigationIcon': 'heroicon-o-exclamation-triangle',
|
||||
'navigationSort': 4,
|
||||
'navigationLabel': 'Activity Log',
|
||||
},
|
||||
}
|
||||
|
||||
# Resources to HIDE from navigation entirely (dead data, 0 recent activity)
|
||||
hide = [
|
||||
'app/Filament/Resources/SnowdonRegistrationResource.php',
|
||||
'app/Filament/Resources/CampaignRegistrationResource.php',
|
||||
'app/Filament/Resources/WOHMessageResource.php',
|
||||
'app/Filament/Resources/EngageAttributionDimensionResource.php',
|
||||
'app/Filament/Resources/EngageFundDimensionResource.php',
|
||||
]
|
||||
|
||||
# Pages
|
||||
page_changes = {
|
||||
'app/Filament/Pages/DonationURLBuilder.php': {
|
||||
'navigationGroup': 'Setup',
|
||||
'navigationSort': 5,
|
||||
'navigationLabel': 'URL Builder',
|
||||
},
|
||||
'app/Filament/Pages/Settings.php': {
|
||||
'navigationGroup': 'Setup',
|
||||
'navigationSort': 6,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def update_static_property(content, prop, value):
|
||||
"""Replace a static property value in a PHP class."""
|
||||
# Match: protected static ?string $prop = 'old_value';
|
||||
# or: protected static ?int $prop = 123;
|
||||
pattern = rf"(protected\s+static\s+\?(?:string|int)\s+\${prop}\s*=\s*)('[^']*'|\d+)(\s*;)"
|
||||
|
||||
if isinstance(value, int):
|
||||
replacement = rf"\g<1>{value}\3"
|
||||
else:
|
||||
replacement = rf"\g<1>'{value}'\3"
|
||||
|
||||
new_content, count = re.subn(pattern, replacement, content)
|
||||
return new_content, count
|
||||
|
||||
|
||||
def apply_changes(filepath, props):
|
||||
full = os.path.join(BASE, filepath)
|
||||
with open(full, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
for prop, value in props.items():
|
||||
content, count = update_static_property(content, prop, value)
|
||||
if count == 0:
|
||||
print(f" WARNING: {prop} not found in {filepath}")
|
||||
|
||||
with open(full, 'w') as f:
|
||||
f.write(content)
|
||||
print(f"Updated: {filepath}")
|
||||
|
||||
|
||||
def hide_from_nav(filepath):
|
||||
full = os.path.join(BASE, filepath)
|
||||
with open(full, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# Add shouldRegisterNavigation = false if not present
|
||||
if 'shouldRegisterNavigation' not in content:
|
||||
# Insert after the class opening
|
||||
content = content.replace(
|
||||
'protected static ?string $navigationIcon',
|
||||
'protected static bool $shouldRegisterNavigation = false;\n\n protected static ?string $navigationIcon'
|
||||
)
|
||||
with open(full, 'w') as f:
|
||||
f.write(content)
|
||||
print(f"Hidden: {filepath}")
|
||||
else:
|
||||
print(f"Already hidden: {filepath}")
|
||||
|
||||
|
||||
# Apply all changes
|
||||
print("=== UPDATING NAVIGATION ===")
|
||||
for filepath, props in changes.items():
|
||||
apply_changes(filepath, props)
|
||||
|
||||
print("\n=== HIDING DEAD PAGES ===")
|
||||
for filepath in hide:
|
||||
hide_from_nav(filepath)
|
||||
|
||||
print("\n=== UPDATING PAGES ===")
|
||||
for filepath, props in page_changes.items():
|
||||
apply_changes(filepath, props)
|
||||
|
||||
print("\nDone!")
|
||||
Reference in New Issue
Block a user