user(); if ($user && $user->role === 'tenant_admin') { $returnTarget = $this->consumeTenantAdminTarget($request); if ($returnTarget !== null) { return redirect($returnTarget); } } // Block users with 'user' role from accessing dashboard if ($user && $user->role === 'user') { return redirect('/packages'); } $tenant = $user?->tenant; $summary = $tenant instanceof Tenant ? $summaryService->build($tenant) : null; $events = $tenant instanceof Tenant ? $this->collectUpcomingEvents($tenant) : collect(); $purchases = $tenant instanceof Tenant ? $this->collectRecentPurchases($tenant) : collect(); $activePackage = $summary['active_package'] ?? null; $onboarding = $tenant instanceof Tenant ? $this->buildOnboardingSteps($tenant, $summary ?? []) : []; return Inertia::render('dashboard', [ 'metrics' => $summary, 'upcomingEvents' => $events->values()->all(), 'recentPurchases' => $purchases->values()->all(), 'latestPurchase' => $purchases->first(), 'tenant' => $tenant ? [ 'id' => $tenant->id, 'name' => $tenant->name, 'subscriptionStatus' => $tenant->subscription_status, 'subscriptionExpiresAt' => optional($tenant->subscription_expires_at)->toIso8601String(), 'activePackage' => $activePackage ? [ 'name' => $activePackage['name'] ?? '', '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, ]); } private function consumeTenantAdminTarget(Request $request): ?string { $target = $request->session()->pull('tenant_admin.return_to'); if (! is_string($target)) { return null; } if (! str_starts_with($target, '/event-admin')) { return null; } return $target; } private function collectUpcomingEvents(Tenant $tenant): Collection { return Event::query() ->where('tenant_id', $tenant->getKey()) ->withCount(['photos', 'tasks', 'joinTokens']) ->orderByRaw('date IS NULL') ->orderBy('date') ->limit(5) ->get() ->map(function (Event $event): array { return [ 'id' => $event->id, 'name' => $this->resolveEventName($event), 'slug' => $event->slug, 'status' => $event->status, 'isActive' => (bool) $event->is_active, 'date' => optional($event->date)->toIso8601String(), 'photosCount' => (int) ($event->photos_count ?? 0), 'tasksCount' => (int) ($event->tasks_count ?? 0), 'joinTokensCount' => (int) ($event->join_tokens_count ?? 0), ]; }); } private function collectRecentPurchases(Tenant $tenant): Collection { $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 { $name = $event->name; if (is_array($name)) { $locale = app()->getLocale(); if (! empty($name[$locale])) { return (string) $name[$locale]; } foreach (['de', 'en'] as $fallback) { if (! empty($name[$fallback])) { return (string) $name[$fallback]; } } $firstTranslated = reset($name); if (is_string($firstTranslated) && $firstTranslated !== '') { return $firstTranslated; } } if (is_string($name) && $name !== '') { return $name; } return __('Untitled event'); } }