- Wired the checkout wizard for Google “comfort login”: added Socialite controller + dependency, new Google env
hooks in config/services.php/.env.example, and updated wizard steps/controllers to store session payloads, attach packages, and surface localized success/error states. - Retooled payment handling for both Stripe and PayPal, adding richer status management in CheckoutController/ PayPalController, fallback flows in the wizard’s PaymentStep.tsx, and fresh feature tests for intent creation, webhooks, and the wizard CTA. - Introduced a consent-aware Matomo analytics stack: new consent context, cookie-banner UI, useAnalytics/ useCtaExperiment hooks, and MatomoTracker component, then instrumented marketing pages (Home, Packages, Checkout) with localized copy and experiment tracking. - Polished package presentation across marketing UIs by centralizing formatting in PresentsPackages, surfacing localized description tables/placeholders, tuning badges/layouts, and syncing guest/marketing translations. - Expanded docs & reference material (docs/prp/*, TODOs, public gallery overview) and added a Playwright smoke test for the hero CTA while reconciling outstanding checklist items.
This commit is contained in:
61
app/Filament/Widgets/CreditAlertsWidget.php
Normal file
61
app/Filament/Widgets/CreditAlertsWidget.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\PurchaseHistory;
|
||||
use App\Models\Tenant;
|
||||
use Filament\Widgets\StatsOverviewWidget;
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
|
||||
class CreditAlertsWidget extends StatsOverviewWidget
|
||||
{
|
||||
protected static ?int $sort = 0;
|
||||
|
||||
protected int|string|array $columnSpan = 'full';
|
||||
|
||||
protected function getCards(): array
|
||||
{
|
||||
$lowBalanceCount = Tenant::query()
|
||||
->where('is_active', true)
|
||||
->where('event_credits_balance', '<', 5)
|
||||
->count();
|
||||
|
||||
$monthStart = now()->startOfMonth();
|
||||
$monthlyRevenue = PurchaseHistory::query()
|
||||
->where('purchased_at', '>=', $monthStart)
|
||||
->sum('price');
|
||||
|
||||
$activeSubscriptions = Tenant::query()
|
||||
->whereNotNull('subscription_expires_at')
|
||||
->where('subscription_expires_at', '>', now())
|
||||
->count();
|
||||
|
||||
return [
|
||||
Stat::make(
|
||||
__('admin.widgets.credit_alerts.low_balance_label'),
|
||||
$lowBalanceCount
|
||||
)
|
||||
->description(__('admin.widgets.credit_alerts.low_balance_desc'))
|
||||
->descriptionIcon('heroicon-m-exclamation-triangle')
|
||||
->color('warning')
|
||||
->url(route('filament.superadmin.resources.tenants.index')),
|
||||
Stat::make(
|
||||
__('admin.widgets.credit_alerts.monthly_revenue_label'),
|
||||
number_format((float) $monthlyRevenue, 2).' €'
|
||||
)
|
||||
->description(__('admin.widgets.credit_alerts.monthly_revenue_desc', [
|
||||
'month' => $monthStart->translatedFormat('F'),
|
||||
]))
|
||||
->descriptionIcon('heroicon-m-currency-euro')
|
||||
->color('success'),
|
||||
Stat::make(
|
||||
__('admin.widgets.credit_alerts.active_subscriptions_label'),
|
||||
$activeSubscriptions
|
||||
)
|
||||
->description(__('admin.widgets.credit_alerts.active_subscriptions_desc'))
|
||||
->descriptionIcon('heroicon-m-arrow-trending-up')
|
||||
->color('info'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
55
app/Filament/Widgets/RevenueTrendWidget.php
Normal file
55
app/Filament/Widgets/RevenueTrendWidget.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\PurchaseHistory;
|
||||
use Filament\Widgets\LineChartWidget;
|
||||
|
||||
class RevenueTrendWidget extends LineChartWidget
|
||||
{
|
||||
|
||||
protected static ?int $sort = 1;
|
||||
|
||||
protected int|string|array $columnSpan = 'full';
|
||||
|
||||
public function getHeading(): ?string
|
||||
{
|
||||
return __('admin.widgets.revenue_trend.heading');
|
||||
}
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$start = now()->startOfMonth()->subMonths(11);
|
||||
$months = collect(range(0, 11))->map(fn (int $offset) => $start->copy()->addMonths($offset));
|
||||
|
||||
$records = PurchaseHistory::query()
|
||||
->where('purchased_at', '>=', $start)
|
||||
->get(['purchased_at', 'price']);
|
||||
|
||||
$grouped = $records->groupBy(fn (PurchaseHistory $history) => $history->purchased_at?->format('Y-m'));
|
||||
|
||||
$labels = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($months as $month) {
|
||||
$key = $month->format('Y-m');
|
||||
$labels[] = $month->translatedFormat('M Y');
|
||||
$total = $grouped->get($key, collect())->sum(fn (PurchaseHistory $history) => (float) $history->price);
|
||||
$values[] = round($total, 2);
|
||||
}
|
||||
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => __('admin.widgets.revenue_trend.series'),
|
||||
'data' => $values,
|
||||
'borderColor' => '#ec4899',
|
||||
'backgroundColor' => 'rgba(236, 72, 153, 0.2)',
|
||||
'tension' => 0.4,
|
||||
'fill' => 'origin',
|
||||
],
|
||||
],
|
||||
'labels' => $labels,
|
||||
];
|
||||
}
|
||||
}
|
||||
53
app/Filament/Widgets/TopTenantsByRevenue.php
Normal file
53
app/Filament/Widgets/TopTenantsByRevenue.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\Tenant;
|
||||
use Filament\Tables;
|
||||
use Filament\Widgets\TableWidget as BaseWidget;
|
||||
|
||||
class TopTenantsByRevenue extends BaseWidget
|
||||
{
|
||||
protected static ?string $heading = null;
|
||||
|
||||
protected static ?int $sort = 2;
|
||||
|
||||
protected ?string $pollingInterval = '120s';
|
||||
|
||||
protected function getHeading(): ?string
|
||||
{
|
||||
return __('admin.widgets.top_tenants_by_revenue.heading');
|
||||
}
|
||||
|
||||
public function table(Tables\Table $table): Tables\Table
|
||||
{
|
||||
return $table
|
||||
->query(
|
||||
Tenant::query()
|
||||
->withSum('purchases', 'price')
|
||||
->withCount('purchases')
|
||||
->orderByDesc('purchases_sum_price')
|
||||
->limit(10)
|
||||
)
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->label(__('admin.common.tenant'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('purchases_sum_price')
|
||||
->label(__('admin.widgets.top_tenants_by_revenue.total'))
|
||||
->money('EUR')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('purchases_count')
|
||||
->label(__('admin.widgets.top_tenants_by_revenue.count'))
|
||||
->badge()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('event_credits_balance')
|
||||
->label(__('admin.common.credits'))
|
||||
->badge()
|
||||
->sortable(),
|
||||
])
|
||||
->paginated(false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user