diff --git a/dev/frontend/hub-flux/PROGRESS.md b/dev/frontend/hub-flux/PROGRESS.md index e433d92..b108c21 100644 --- a/dev/frontend/hub-flux/PROGRESS.md +++ b/dev/frontend/hub-flux/PROGRESS.md @@ -16,6 +16,13 @@ `me.checkout.billing-portal`), aktive Buchungen + Verlauf real. Credit-Pakete/Marktplatz/Platzierungen entfernt (→ 9I bzw. Phase 2). Stripe Tax im Dashboard aktiviert („SaaS – business use", exklusiv). + **Feinschliff nach Review (Kevin)**: Aktueller-Tarif-Card nur bei + vorhandener Buchung (kein irreführendes „Unbegrenzt" vor dem Launch; + Kontingent-Kachel nur als echte Zahl), Tarif-Cards plakativer + (Icon je Tarif, größerer Preis, Trennlinie, mehr Abstand zum Button), + „Prüfung und Veröffentlichung inklusive" ohne „KI", + „Aktive Buchungen"-Panel entfernt (Info steht im Tarif-Panel), + Verlauf als eigene, klar abgegrenzte Sektion. - **Dateien**: `resources/views/livewire/customer/bookings.blade.php` (Neufassung), `app/Http/Controllers/CheckoutController.php` + `app/Services/Billing/StripeCheckoutService.php` (Billing Portal), diff --git a/resources/views/livewire/customer/bookings.blade.php b/resources/views/livewire/customer/bookings.blade.php index b7fc5b5..6b95101 100644 --- a/resources/views/livewire/customer/bookings.blade.php +++ b/resources/views/livewire/customer/bookings.blade.php @@ -20,6 +20,17 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte return number_format($cents / 100, $cents % 100 === 0 ? 0 : 2, ',', '.').' €'; } + public function planIcon(Plan $plan): string + { + return match ($plan->slug) { + 'starter' => 'rocket-launch', + 'business' => 'briefcase', + 'pro' => 'chart-bar', + 'agency' => 'building-office-2', + default => 'megaphone', + }; + } + public function with(): array { $user = auth()->user(); @@ -125,6 +136,9 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte {{-- ============== AKTUELLER TARIF ============== --}} + {{-- Erscheint erst, wenn eine Buchung existiert — vorher würde hier nur + „kein Tarif" stehen und das Kontingent wäre irreführend. --}} + @if ($subscription || $legacyOptions->isNotEmpty() || $openPurchases->isNotEmpty())
{{ __('Aktueller Tarif') }} @@ -133,7 +147,7 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte @elseif ($legacyOptions->isNotEmpty()) {{ __('Bestandstarif') }} @else - {{ __('Kein Abo') }} + {{ __('Einzel-PM') }} @endif
@@ -180,30 +194,23 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte

{{ __('Jeder Kauf berechtigt zu genau einer Veröffentlichung — eingelöst wird er erst, wenn die Pressemitteilung live geht.') }}

- @else -
- {{ __('Noch kein aktiver Tarif') }} -
-

- {{ __('Wählen Sie unten einen Tarif oder buchen Sie eine einzelne Pressemitteilung ohne Abo.') }} -

@endif
-
-
{{ __('PM-Kontingent diesen Monat') }}
-
- @if (is_null($quotaRemaining)) - {{ __('Unbegrenzt') }} - @else + {{-- Kontingent nur als echte Zahl — „unbegrenzt" wäre vor dem + Launch-Schalter inhaltlich falsch. --}} + @if (! is_null($quotaRemaining)) +
+
{{ __('PM-Kontingent diesen Monat') }}
+
{{ $quotaRemaining }} / {{ $quotaTotal }} - @endif +
+
+ {{ __('Wird erst bei Veröffentlichung verbraucht.') }} +
-
- {{ __('Wird erst bei Veröffentlichung verbraucht.') }} -
-
+ @endif @if ($subscription) @@ -216,6 +223,7 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte
+ @endif {{-- ============== TARIF-RASTER ============== --}}
@@ -248,9 +256,15 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte @foreach ($plans as $plan) @php($isCurrent = $currentPlan && $plan->is($currentPlan))
$isCurrent]) wire:key="plan-{{ $plan->slug }}"> -
+
-

{{ $plan->name }}

+
+
+ +
+

{{ $plan->name }}

+
@if ($isCurrent) {{ __('Aktuell') }} @endif @@ -258,20 +272,20 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte
- {{ $this->formatEuro($plan->monthly_price_cents) }} - / {{ __('Monat') }} + {{ $this->formatEuro($plan->monthly_price_cents) }} + / {{ __('Monat') }}
- {{ $this->formatEuro($plan->yearly_price_cents) }} - / {{ __('Jahr') }} + {{ $this->formatEuro($plan->yearly_price_cents) }} + / {{ __('Jahr') }}
-
{{ __('netto zzgl. USt.') }}
+
{{ __('netto zzgl. USt.') }}
-
    +
    • - {{ __(':quota Pressemitteilungen pro Monat', ['quota' => $plan->press_release_quota]) }} + {{ $plan->press_release_quota }} {{ __('Pressemitteilungen pro Monat') }}
    • @@ -283,28 +297,30 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte
    • - {{ __('KI-Prüfung & Veröffentlichung inklusive') }} + {{ __('Prüfung und Veröffentlichung inklusive') }}
    - @if ($subscription) - - {{ $isCurrent ? __('Ihr aktueller Tarif') : __('Wechsel über „Abo verwalten"') }} - - @else -
    - - {{ __('Monatlich buchen') }} +
    + @if ($subscription) + + {{ $isCurrent ? __('Ihr aktueller Tarif') : __('Wechsel über „Abo verwalten"') }} -
    -
    - - {{ __('Jährlich buchen') }} - -
    - @endif + @else +
    + + {{ __('Monatlich buchen') }} + +
    +
    + + {{ __('Jährlich buchen') }} + +
    + @endif +
@endforeach @@ -350,83 +366,21 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte - {{-- ============== AKTIVE BUCHUNGEN / VERLAUF ============== --}} -
-
-
- {{ __('Aktive Buchungen') }} - {{ __('läuft aktuell') }} -
-
- @if ($subscription || $legacyOptions->isNotEmpty() || $openPurchases->isNotEmpty()) -
- @if ($subscription && $currentPlan) -
-
-
- {{ __('Abo: :plan', ['plan' => $currentPlan->name]) }} -
-
- {{ $currentInterval === 'yearly' ? __('jährliche Abrechnung') : __('monatliche Abrechnung') }} · Stripe -
-
- {{ __('aktiv') }} -
- @endif - - @foreach ($legacyOptions as $option) -
-
-
- {{ data_get($option->legacy_conditions, 'name') ?? $option->paymentOption?->article_number ?? __('Bestehende Vereinbarung') }} -
-
- {{ __('Bestandstarif · Abrechnung per Rechnung') }} - @if ($option->current_period_end) - · {{ __('nächste Rechnung :date', ['date' => $option->current_period_end->format('d.m.Y')]) }} - @endif -
-
- {{ __('aktiv') }} -
- @endforeach - - @foreach ($openPurchases as $purchase) -
-
-
- {{ $purchase->type->label() }} -
-
- {{ __('gekauft am :date', ['date' => $purchase->paid_at?->format('d.m.Y')]) }} · {{ $this->formatEuro($purchase->price_cents) }} {{ __('netto') }} -
-
- {{ __('einlösbar') }} -
- @endforeach -
- @else -
-
- -
-
- {{ __('Noch keine aktiven Buchungen') }} -
-

- {{ __('Ihr Abo, Bestandstarife und offene Einzelkäufe erscheinen hier mit Laufzeit und Abrechnungsart.') }} -

-
- @endif -
-
+ {{-- ============== VERLAUF ============== --}} + {{-- Aktive Buchungen stehen oben im Tarif-Panel — hier nur die Historie, + durch Trennlinie und Zwischenüberschrift klar abgesetzt. --}} +
+
+ {{ __('Verlauf') }} +

+ {{ __('Eingelöste Käufe') }} +

+

+ {{ __('Jeder eingelöste Einzelkauf mit der zugehörigen Pressemitteilung — die Rechnungen dazu finden Sie unter Rechnungen.') }} +

+
-
- {{ __('Verlauf') }} - {{ __('eingelöste Käufe') }} -
@if ($consumedPurchases->isNotEmpty())
diff --git a/tests/Feature/Billing/BookingsPageTest.php b/tests/Feature/Billing/BookingsPageTest.php index 6b34aa7..c4e5274 100644 --- a/tests/Feature/Billing/BookingsPageTest.php +++ b/tests/Feature/Billing/BookingsPageTest.php @@ -41,7 +41,7 @@ test('the bookings page renders the active plans with checkout links', function ->assertSee('Business') ->assertSee('49 €') ->assertSee('490 €') - ->assertSee('10 Pressemitteilungen pro Monat') + ->assertSee('Pressemitteilungen pro Monat') ->assertSee('max. 2 Veröffentlichungen pro Tag') ->assertSee('2 Monate gratis') ->assertSee(route('me.checkout.subscription', ['planSlug' => 'business', 'interval' => 'monthly']), false) @@ -61,13 +61,14 @@ test('the single pm block links to its checkout', function () { ->assertSee(route('me.checkout.single-pm'), false); }); -test('without any booking the page shows the empty state', function () { +test('without any booking the current tariff card is hidden', function () { /** @var TestCase $this */ $this->actingAs(bookingsTestCustomer()); LivewireVolt::test('customer.bookings') - ->assertSee('Noch kein aktiver Tarif') - ->assertSee('Noch keine aktiven Buchungen'); + ->assertDontSee('Aktueller Tarif') + ->assertSee('Den passenden Tarif wählen') + ->assertSee('Noch kein Buchungsverlauf'); }); test('a subscriber sees the current plan and the manage button', function () { @@ -85,7 +86,7 @@ test('a subscriber sees the current plan and the manage button', function () { LivewireVolt::test('customer.bookings') ->assertSee('Ihr aktueller Tarif') ->assertSee('Abo verwalten') - ->assertSee('Abo: Pro') + ->assertSee('Pressemitteilungen pro Monat') ->assertSee(route('me.checkout.billing-portal'), false); }); @@ -104,11 +105,10 @@ test('a grandfathered legacy user sees the bestandstarif with unlimited quota', LivewireVolt::test('customer.bookings') ->assertSee('Bestandstarif') ->assertSee('Presseverteiler Premium') - ->assertSee('Unbegrenzte Pressemitteilungen (Bestandsschutz).') - ->assertSee('Unbegrenzt'); + ->assertSee('Unbegrenzte Pressemitteilungen (Bestandsschutz).'); }); -test('open and consumed single purchases appear in bookings and history', function () { +test('open and consumed single purchases appear in the card and history', function () { /** @var TestCase $this */ $user = bookingsTestCustomer(); SinglePurchase::factory()->paid()->create(['user_id' => $user->id]); @@ -117,7 +117,7 @@ test('open and consumed single purchases appear in bookings and history', functi $this->actingAs($user); LivewireVolt::test('customer.bookings') - ->assertSee('einlösbar') + ->assertSee('Einzel-Pressemitteilung verfügbar') ->assertSee('eingelöst am'); }); diff --git a/tests/Feature/PanelConsolidationTest.php b/tests/Feature/PanelConsolidationTest.php index 5d39ef2..f1f906f 100644 --- a/tests/Feature/PanelConsolidationTest.php +++ b/tests/Feature/PanelConsolidationTest.php @@ -80,7 +80,7 @@ test('customer bookings page shows the tariff grid and single pm block', functio ->assertSee('29 €') ->assertSee('2 Monate gratis') ->assertSee('Einzel-Pressemitteilung — ohne Abo') - ->assertSee('Noch keine aktiven Buchungen'); + ->assertSee('Noch kein Buchungsverlauf'); }); test('admin can access both panel dashboards', function () {