diff --git a/resources/views/livewire/customer/press-releases/show.blade.php b/resources/views/livewire/customer/press-releases/show.blade.php index bcbd2c7f..968bfa93 100644 --- a/resources/views/livewire/customer/press-releases/show.blade.php +++ b/resources/views/livewire/customer/press-releases/show.blade.php @@ -51,12 +51,19 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends /** * Kontext fürs Kauf-Bestätigungs-Modal (Entscheidung 10.07.): Add-ons * buchen erst nach explizitem „Kostenpflichtig buchen", damit ein Klick - * auf eine Laufzeit nicht sofort Credits abbucht. + * auf eine Laufzeit nicht sofort Credits abbucht. Läuft bereits ein + * Boost, tragen extendsUntil/newEndsAt den Verlängerungs-Hinweis. * - * @var array{type: 'boost'|'proof', label: string, credits: int, days: int|null}|null + * @var array{type: 'boost'|'proof', label: string, credits: int, days: int|null, extendsUntil: string|null, newEndsAt: string|null}|null */ public ?array $pendingPurchase = null; + /** + * Pflicht-Schalter im Kauf-Modal: ohne aktive Bestätigung führt + * buyPendingAddOn keine Abbuchung aus. + */ + public bool $purchaseConfirmed = false; + public function mount(int $id): void { $this->id = $id; @@ -139,11 +146,18 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends return; } + // Läuft bereits ein Boost, verlängert die Buchung dessen Laufzeit — + // das Modal weist ausdrücklich darauf hin (Entscheidung 10.07.). + $activeUntil = app(BoostService::class)->activeUntil($this->getMyPR()); + + $this->purchaseConfirmed = false; $this->pendingPurchase = [ 'type' => 'boost', 'label' => __('Boost — :days Tage Platzierung', ['days' => $days]), 'credits' => $options[$days], 'days' => $days, + 'extendsUntil' => $activeUntil?->format('d.m.Y H:i'), + 'newEndsAt' => $activeUntil?->copy()->addDays($days)->format('d.m.Y H:i'), ]; Flux::modal('confirm-addon-purchase')->show(); @@ -154,11 +168,14 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends */ public function confirmProof(): void { + $this->purchaseConfirmed = false; $this->pendingPurchase = [ 'type' => 'proof', 'label' => __('Veröffentlichungsnachweis (PDF)'), 'credits' => app(ProofPdfService::class)->priceCredits(), 'days' => null, + 'extendsUntil' => null, + 'newEndsAt' => null, ]; Flux::modal('confirm-addon-purchase')->show(); @@ -170,8 +187,20 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends */ public function buyPendingAddOn(): void { + // Serverseitige Absicherung des Pflicht-Schalters (der Button ist + // clientseitig zusätzlich deaktiviert). + if ($this->pendingPurchase !== null && ! $this->purchaseConfirmed) { + Flux::toast( + text: __('Bitte bestätigen Sie die kostenpflichtige Buchung über den Schalter.'), + variant: 'warning', + ); + + return; + } + $pending = $this->pendingPurchase; $this->pendingPurchase = null; + $this->purchaseConfirmed = false; Flux::modal('confirm-addon-purchase')->close(); @@ -1086,6 +1115,19 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends @if ($pendingPurchase) + @if ($pendingPurchase['extendsUntil']) + {{-- Verlängerungs-Hinweis: für diese PM läuft bereits ein Boost. --}} +
+

{{ __('Diese Buchung verlängert den laufenden Boost.') }}

+

+ {{ __('Aktuell aktiv bis :until — die neue Laufzeit schließt daran an und endet am :end.', [ + 'until' => $pendingPurchase['extendsUntil'], + 'end' => $pendingPurchase['newEndsAt'], + ]) }} +

+
+ @endif +
{{ $pendingPurchase['label'] }} @@ -1105,13 +1147,17 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends @endif

+ + @endif
{{ __('Abbrechen') }} - + {{ __('Kostenpflichtig buchen') }}
diff --git a/tests/Feature/PressReleaseShowAddonsTest.php b/tests/Feature/PressReleaseShowAddonsTest.php index 4525df19..3bda163b 100644 --- a/tests/Feature/PressReleaseShowAddonsTest.php +++ b/tests/Feature/PressReleaseShowAddonsTest.php @@ -69,6 +69,8 @@ test('add-on buttons only debit after the purchase confirmation modal', function 'label' => 'Boost — 14 Tage Platzierung', 'credits' => 20, 'days' => 14, + 'extendsUntil' => null, + 'newEndsAt' => null, ]) ->assertSee('Kauf bestätigen') ->assertSee('Kostenpflichtig buchen'); @@ -77,13 +79,39 @@ test('add-on buttons only debit after the purchase confirmation modal', function expect($customer->fresh()->creditBalance())->toBe(40); expect($pr->fresh()->isBoosted())->toBeFalse(); + // Ohne den Pflicht-Schalter bleibt der Kauf aus und das Modal offen. $component->call('buyPendingAddOn') - ->assertSet('pendingPurchase', null); + ->assertSet('pendingPurchase.type', 'boost'); + + expect($customer->fresh()->creditBalance())->toBe(40); + expect($pr->fresh()->isBoosted())->toBeFalse(); + + $component->set('purchaseConfirmed', true) + ->call('buyPendingAddOn') + ->assertSet('pendingPurchase', null) + ->assertSet('purchaseConfirmed', false); expect($customer->fresh()->creditBalance())->toBe(20); expect($pr->fresh()->isBoosted())->toBeTrue(); }); +test('the confirmation modal warns when the purchase extends a running boost', function () { + /** @var TestCase $this */ + ['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner(); + $this->wallet->credit($customer, 40); + + $activeUntil = now()->addDays(3)->setTime(12, 0); + Boost::factory()->for($pr)->for($customer)->create(['ends_at' => $activeUntil]); + + $this->actingAs($customer); + + LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id]) + ->call('confirmBoost', 7) + ->assertSet('pendingPurchase.extendsUntil', $activeUntil->format('d.m.Y H:i')) + ->assertSet('pendingPurchase.newEndsAt', $activeUntil->copy()->addDays(7)->format('d.m.Y H:i')) + ->assertSee('Diese Buchung verlängert den laufenden Boost.'); +}); + test('the proof purchase runs through the confirmation modal as well', function () { /** @var TestCase $this */ ['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner(); @@ -95,6 +123,7 @@ test('the proof purchase runs through the confirmation modal as well', function ->call('confirmProof') ->assertSet('pendingPurchase.type', 'proof') ->assertSet('pendingPurchase.credits', 3) + ->set('purchaseConfirmed', true) ->call('buyPendingAddOn') ->assertSee('Nachweis herunterladen');