completed the frontend dashboard component and bound it to the tenant admin pwa for the optimal onboarding experience.. Added a profile page.

This commit is contained in:
Codex Agent
2025-11-04 22:28:37 +01:00
parent fe380689fb
commit b32413b108
29 changed files with 1416 additions and 425 deletions

View File

@@ -5,9 +5,12 @@ namespace App\Http\Controllers;
use App\Models\Event;
use App\Models\PackagePurchase;
use App\Models\Tenant;
use App\Models\TenantPackage;
use App\Services\Tenant\DashboardSummaryService;
use App\Support\TenantOnboardingState;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Inertia\Inertia;
use Inertia\Response;
@@ -32,6 +35,9 @@ class DashboardController extends Controller
: collect();
$activePackage = $summary['active_package'] ?? null;
$onboarding = $tenant instanceof Tenant
? $this->buildOnboardingSteps($tenant, $summary ?? [])
: [];
return Inertia::render('dashboard', [
'metrics' => $summary,
@@ -41,7 +47,6 @@ class DashboardController extends Controller
'tenant' => $tenant ? [
'id' => $tenant->id,
'name' => $tenant->name,
'eventCreditsBalance' => $tenant->event_credits_balance,
'subscriptionStatus' => $tenant->subscription_status,
'subscriptionExpiresAt' => optional($tenant->subscription_expires_at)->toIso8601String(),
'activePackage' => $activePackage ? [
@@ -49,12 +54,14 @@ class DashboardController extends Controller
'price' => $activePackage['price'] ?? null,
'expiresAt' => $activePackage['expires_at'] ?? null,
'remainingEvents' => $activePackage['remaining_events'] ?? null,
'isActive' => (bool) ($activePackage['is_active'] ?? false),
] : null,
] : null,
'emailVerification' => [
'mustVerify' => $user instanceof MustVerifyEmail,
'verified' => $user?->hasVerifiedEmail() ?? false,
],
'onboarding' => $onboarding,
]);
}
@@ -84,23 +91,120 @@ class DashboardController extends Controller
private function collectRecentPurchases(Tenant $tenant): Collection
{
return $tenant->purchases()
->with('package')
->latest('purchased_at')
->limit(6)
->get()
->map(function (PackagePurchase $purchase): array {
return [
'id' => $purchase->id,
'packageName' => $purchase->package?->getNameForLocale(app()->getLocale())
?? $purchase->package?->name
?? __('Unknown package'),
'price' => $purchase->price !== null ? (float) $purchase->price : null,
'purchasedAt' => optional($purchase->purchased_at)->toIso8601String(),
'type' => $purchase->type,
'provider' => $purchase->provider,
];
});
$entries = collect(
$tenant->purchases()
->with('package')
->latest('purchased_at')
->limit(6)
->get()
->map(function (PackagePurchase $purchase): array {
return [
'id' => $purchase->id,
'packageName' => $purchase->package?->getNameForLocale(app()->getLocale())
?? $purchase->package?->name
?? __('Unknown package'),
'price' => $purchase->price !== null ? (float) $purchase->price : null,
'purchasedAt' => optional($purchase->purchased_at)->toIso8601String(),
'type' => $purchase->type,
'provider' => $purchase->provider,
'source' => 'purchase',
'isActive' => null,
];
})
->all()
);
if ($entries->isEmpty()) {
$packageEntries = collect(
$tenant->tenantPackages()
->with('package')
->orderByDesc('purchased_at')
->limit(6)
->get()
->map(function (TenantPackage $package): array {
return [
'id' => $package->id,
'packageName' => $package->package?->getNameForLocale(app()->getLocale())
?? $package->package?->name
?? __('Unknown package'),
'price' => $package->price !== null ? (float) $package->price : null,
'purchasedAt' => optional($package->purchased_at)->toIso8601String(),
'type' => 'tenant_package',
'provider' => 'internal',
'source' => 'tenant_package',
'isActive' => (bool) $package->active,
];
})
->all()
);
$entries = $entries->merge($packageEntries);
}
return $entries
->sortByDesc('purchasedAt')
->values()
->take(6);
}
private function buildOnboardingSteps(Tenant $tenant, array $summary): array
{
$status = TenantOnboardingState::status($tenant);
$settings = $tenant->settings ?? [];
$adminAppOpened = (bool) Arr::get($settings, 'onboarding.admin_app_opened_at')
|| $tenant->last_activity_at !== null;
$hasEvent = (bool) ($status['event'] ?? false);
$hasInvite = (bool) ($status['invite'] ?? false);
$hasBranding = (bool) ($status['palette'] ?? false)
|| (bool) Arr::get($settings, 'onboarding.branding_set', false);
$hasTaskPackage = (bool) ($status['packages'] ?? false)
|| ! empty(Arr::get($settings, 'onboarding.selected_packages'));
$hasPhotos = $tenant->photos()->exists() || (int) ($summary['new_photos'] ?? 0) > 0;
return [
[
'key' => 'admin_app',
'title' => trans('dashboard.onboarding.admin_app.title'),
'description' => trans('dashboard.onboarding.admin_app.description'),
'done' => $adminAppOpened,
'cta' => url('/event-admin'),
'ctaLabel' => trans('dashboard.onboarding.admin_app.cta'),
],
[
'key' => 'event_setup',
'title' => trans('dashboard.onboarding.event_setup.title'),
'description' => trans('dashboard.onboarding.event_setup.description'),
'done' => $hasEvent,
'cta' => url('/event-admin/events/new'),
'ctaLabel' => trans('dashboard.onboarding.event_setup.cta'),
],
[
'key' => 'invite_guests',
'title' => trans('dashboard.onboarding.invite_guests.title'),
'description' => trans('dashboard.onboarding.invite_guests.description'),
'done' => $hasInvite,
'cta' => url('/event-admin/events'),
'ctaLabel' => trans('dashboard.onboarding.invite_guests.cta'),
],
[
'key' => 'collect_photos',
'title' => trans('dashboard.onboarding.collect_photos.title'),
'description' => trans('dashboard.onboarding.collect_photos.description'),
'done' => $hasPhotos,
'cta' => url('/event-admin/dashboard'),
'ctaLabel' => trans('dashboard.onboarding.collect_photos.cta'),
],
[
'key' => 'branding',
'title' => trans('dashboard.onboarding.branding.title'),
'description' => trans('dashboard.onboarding.branding.description'),
'done' => $hasBranding && $hasTaskPackage,
'cta' => url('/event-admin/tasks'),
'ctaLabel' => trans('dashboard.onboarding.branding.cta'),
],
];
}
private function resolveEventName(Event $event): string