dashboard/errors: Onboarding-Card „Erste Schritte" & Hub-Styling der Fehlerseiten

Neue aufklappbare "Erste Schritte"-Card als erste Card im Kunden-Dashboard,
die neue Nutzer durch Profil, Rechnungsadresse, Firma und erste PM führt und
sich ausblendet, sobald alle Schritte erledigt sind. Zusätzlich die
gemeinsame Fehlerseiten-Vorlage (404/403/419/500/503) an das Hub-Design
angepasst (Palette, Gold-Akzent, Hub-Blau-Button), weiterhin self-contained
ohne Vite-Assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin Adametz 2026-07-15 13:07:36 +00:00
parent 6f6cb13f2e
commit a54637835d
4 changed files with 307 additions and 28 deletions

View file

@ -50,9 +50,20 @@ new #[Layout('components.layouts.app'), Title('Mein Dashboard')] class extends C
$profile = $user->profile;
$billingAddress = $user->billingAddress;
$profilePercent = $this->profileCompleteness($profile);
$billingPercent = $this->billingCompleteness($billingAddress);
$pressReleaseTotal = (int) ($stats->total ?? 0);
$companiesTotal = $context->companyCountFor($user);
$onboardingSteps = $this->onboardingSteps($profilePercent, $billingPercent, $selectedCompany, $companiesTotal, $pressReleaseTotal);
$onboardingCompleted = collect($onboardingSteps)->where('done', true)->count();
return [
'user' => $user,
'selectedCompany' => $selectedCompany,
'onboardingSteps' => $onboardingSteps,
'onboardingCompleted' => $onboardingCompleted,
'onboardingTotal' => count($onboardingSteps),
// Launch-Gate (WS-5): Solange billing.enforce_booking aus ist, ist
// dies immer false und der Hinweis bleibt unsichtbar. Bei scharfem
// Gate sehen Bestandskunden (grandfathered) den Banner nicht.
@ -64,12 +75,12 @@ new #[Layout('components.layouts.app'), Title('Mein Dashboard')] class extends C
'draft' => (int) ($stats->draft ?? 0),
'deltaMonth' => (int) ($stats->current_month ?? 0) - (int) ($stats->previous_month ?? 0),
],
'profileCompleteness' => $this->profileCompleteness($profile),
'billingCompleteness' => $this->billingCompleteness($billingAddress),
'profileCompleteness' => $profilePercent,
'billingCompleteness' => $billingPercent,
'qualityHints' => $this->qualityHints($user, $profile, $billingAddress, $selectedCompany, $pressReleaseQuery),
'recent' => $recent,
'companies' => $context->latestCompaniesFor($user),
'companiesTotal' => $context->companyCountFor($user),
'companiesTotal' => $companiesTotal,
'bridgeStatus' => [
/* Heute hardcoded — perspektivisch aus echtem Sync-Service. */
'presseecho' => ['state' => 'connected', 'subline' => __('Archiv · Branchen-Tiefe')],
@ -104,6 +115,54 @@ new #[Layout('components.layouts.app'), Title('Mein Dashboard')] class extends C
return $address?->completenessPercent() ?? 0;
}
/**
* Geführte „Erste Schritte" für neue Kunden. Jeder Schritt gilt als
* erledigt, sobald die zugehörigen Stammdaten vorhanden sind.
*
* @return list<array{num: string, icon: string, title: string, description: string, href: string, action: string, done: bool}>
*/
private function onboardingSteps(int $profilePercent, int $billingPercent, ?Company $selectedCompany, int $companiesTotal, int $pressReleaseTotal): array
{
return [
[
'num' => '01',
'icon' => 'user',
'title' => __('Profil vervollständigen'),
'description' => __('Hinterlegen Sie Anrede, Telefon und Adresse für eine saubere Kundenakte.'),
'href' => route('me.profile') . '#profil',
'action' => __('Profil öffnen'),
'done' => $profilePercent >= 100,
],
[
'num' => '02',
'icon' => 'archive-box',
'title' => __('Rechnungsadresse hinterlegen'),
'description' => __('Damit spätere Buchungen sauber abgerechnet werden können.'),
'href' => route('me.profile') . '#rechnungsadresse',
'action' => __('Adresse ergänzen'),
'done' => $billingPercent >= 100,
],
[
'num' => '03',
'icon' => 'building-office',
'title' => __('Firma zuordnen'),
'description' => __('Ordnen Sie Ihrem Konto eine Firma bzw. Pressestelle zu, für die Sie veröffentlichen.'),
'href' => route('me.press-kits.index'),
'action' => __('Firmen öffnen'),
'done' => $selectedCompany !== null || $companiesTotal > 0,
],
[
'num' => '04',
'icon' => 'newspaper',
'title' => __('Erste Pressemitteilung erstellen'),
'description' => __('Verfassen Sie Ihre erste Mitteilung und senden Sie sie zur Prüfung.'),
'href' => route('me.press-releases.create'),
'action' => __('Mitteilung erstellen'),
'done' => $pressReleaseTotal > 0,
],
];
}
/**
* @return list<array{icon: string, title: string, description: string, href: string, action: string, percent?: int}>
*/
@ -205,6 +264,11 @@ new #[Layout('components.layouts.app'), Title('Mein Dashboard')] class extends C
</div>
</header>
{{-- ============== ERSTE SCHRITTE (Onboarding, aufklappbar) ============== --}}
@if ($onboardingCompleted < $onboardingTotal)
<x-portal.onboarding-guide :steps="$onboardingSteps" :completed="$onboardingCompleted" :total="$onboardingTotal" />
@endif
{{-- ============== BUCHUNGS-GATE (WS-5) ============== --}}
@if ($bookingRequired)
<section