Add-ons: Modal statt Toast bei fehlendem Credit-Guthaben
Scheiterte ein Boost- oder Nachweis-Kauf am Wallet-Guthaben, gab es nur einen flüchtigen Toast ohne Weg zur Lösung. Jetzt öffnet sich ein Modal mit Kosten, Guthaben und fehlender Differenz sowie einem Direktlink "Credits aufladen" zur Wallet-Sektion der Buchungsseite (neuer #credits-Anker). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
0f8f0b2d31
commit
9600a4f887
3 changed files with 99 additions and 13 deletions
|
|
@ -775,7 +775,7 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
{{-- ============== CREDIT-WALLET & ADD-ONS ============== --}}
|
{{-- ============== CREDIT-WALLET & ADD-ONS ============== --}}
|
||||||
<section class="space-y-4">
|
<section id="credits" class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<span class="section-eyebrow">{{ __('Credit-Wallet & Add-ons') }}</span>
|
<span class="section-eyebrow">{{ __('Credit-Wallet & Add-ons') }}</span>
|
||||||
<h2 class="text-[22px] font-bold tracking-[-0.4px] mt-1 mb-1 text-[color:var(--color-ink)]">
|
<h2 class="text-[22px] font-bold tracking-[-0.4px] mt-1 mb-1 text-[color:var(--color-ink)]">
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,14 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
||||||
|
|
||||||
public bool $depublicationConfirmed = false;
|
public bool $depublicationConfirmed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kontext fürs „Nicht genügend Credits"-Modal (QA-Befund 07.07.):
|
||||||
|
* ['label' => Add-on-Name, 'required' => int, 'available' => int].
|
||||||
|
*
|
||||||
|
* @var array{label: string, required: int, available: int}|null
|
||||||
|
*/
|
||||||
|
public ?array $creditShortfall = null;
|
||||||
|
|
||||||
public function mount(int $id): void
|
public function mount(int $id): void
|
||||||
{
|
{
|
||||||
$this->id = $id;
|
$this->id = $id;
|
||||||
|
|
@ -112,12 +120,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
);
|
);
|
||||||
} catch (InsufficientCreditsException $e) {
|
} catch (InsufficientCreditsException $e) {
|
||||||
Flux::toast(
|
$this->showCreditShortfallModal(__('Der Boost'), $e);
|
||||||
heading: __('Nicht genügend Credits'),
|
|
||||||
text: __('Der Boost kostet :need Credits, Ihr Guthaben beträgt :have. Laden Sie unter „Buchungen & Add-ons" auf.', ['need' => $e->required, 'have' => $e->available]),
|
|
||||||
variant: 'warning',
|
|
||||||
duration: 8000,
|
|
||||||
);
|
|
||||||
} catch (BoostNotAllowedException $e) {
|
} catch (BoostNotAllowedException $e) {
|
||||||
Flux::toast(text: $e->getMessage(), variant: 'warning', duration: 8000);
|
Flux::toast(text: $e->getMessage(), variant: 'warning', duration: 8000);
|
||||||
}
|
}
|
||||||
|
|
@ -140,15 +143,25 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
);
|
);
|
||||||
} catch (InsufficientCreditsException $e) {
|
} catch (InsufficientCreditsException $e) {
|
||||||
Flux::toast(
|
$this->showCreditShortfallModal(__('Der Veröffentlichungsnachweis'), $e);
|
||||||
heading: __('Nicht genügend Credits'),
|
|
||||||
text: __('Der Nachweis kostet :need Credits, Ihr Guthaben beträgt :have.', ['need' => $e->required, 'have' => $e->available]),
|
|
||||||
variant: 'warning',
|
|
||||||
duration: 8000,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statt eines flüchtigen Toasts ein Modal mit Aufladen-Link öffnen
|
||||||
|
* (QA-Befund 07.07.): der Kunde soll direkt zur Wallet finden.
|
||||||
|
*/
|
||||||
|
private function showCreditShortfallModal(string $label, InsufficientCreditsException $e): void
|
||||||
|
{
|
||||||
|
$this->creditShortfall = [
|
||||||
|
'label' => $label,
|
||||||
|
'required' => $e->required,
|
||||||
|
'available' => $e->available,
|
||||||
|
];
|
||||||
|
|
||||||
|
Flux::modal('insufficient-credits')->show();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unternehmer-Pflicht-Checkbox vor dem Depublikations-Checkout (T1neu):
|
* Unternehmer-Pflicht-Checkbox vor dem Depublikations-Checkout (T1neu):
|
||||||
* protokolliert wie im Buchungs-Modal — der Checkout-Controller verlangt
|
* protokolliert wie im Buchungs-Modal — der Checkout-Controller verlangt
|
||||||
|
|
@ -930,4 +943,41 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
{{-- ============== MODAL: NICHT GENÜGEND CREDITS ==============
|
||||||
|
Öffnet sich serverseitig, wenn ein Add-on-Kauf (Boost/Nachweis)
|
||||||
|
am Wallet-Guthaben scheitert — mit Direktlink zum Aufladen. --}}
|
||||||
|
<flux:modal name="insufficient-credits" class="w-full max-w-md">
|
||||||
|
<div class="space-y-5">
|
||||||
|
<div>
|
||||||
|
<flux:text class="eyebrow muted">{{ __('Credit-Wallet') }}</flux:text>
|
||||||
|
<flux:heading size="lg">{{ __('Nicht genügend Credits') }}</flux:heading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($creditShortfall)
|
||||||
|
<div class="rounded-[6px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-4 text-[12.5px] leading-[1.6] text-[color:var(--color-ink-2)]">
|
||||||
|
<p class="m-0">
|
||||||
|
{{ __(':label kostet :required Credits — Ihr Guthaben beträgt :available Credits. Es fehlen :missing Credits.', [
|
||||||
|
'label' => $creditShortfall['label'],
|
||||||
|
'required' => $creditShortfall['required'],
|
||||||
|
'available' => $creditShortfall['available'],
|
||||||
|
'missing' => max(0, $creditShortfall['required'] - $creditShortfall['available']),
|
||||||
|
]) }}
|
||||||
|
</p>
|
||||||
|
<p class="m-0 mt-3 pt-3 border-t border-[color:var(--color-bg-rule)] text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||||
|
{{ __('Guthaben laden Sie unter „Buchungen & Add-ons" auf — es steht sofort zur Verfügung (1 Credit = 1 €, größere Pakete enthalten Bonus-Credits).') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<flux:modal.close>
|
||||||
|
<flux:button variant="filled">{{ __('Schließen') }}</flux:button>
|
||||||
|
</flux:modal.close>
|
||||||
|
<flux:button variant="primary" icon="wallet" href="{{ route('me.bookings.index') }}#credits" wire:navigate>
|
||||||
|
{{ __('Credits aufladen') }}
|
||||||
|
</flux:button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</flux:modal>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,42 @@ test('buying a proof from the detail page unlocks the download', function () {
|
||||||
expect($customer->fresh()->creditBalance())->toBe(7);
|
expect($customer->fresh()->creditBalance())->toBe(7);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('an add-on purchase without enough credits opens the shortfall modal with a topup link', function () {
|
||||||
|
/** @var TestCase $this */
|
||||||
|
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
|
||||||
|
$this->wallet->credit($customer, 1);
|
||||||
|
|
||||||
|
$this->actingAs($customer);
|
||||||
|
|
||||||
|
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
|
||||||
|
->call('buyProof')
|
||||||
|
->assertSet('creditShortfall', [
|
||||||
|
'label' => 'Der Veröffentlichungsnachweis',
|
||||||
|
'required' => 3,
|
||||||
|
'available' => 1,
|
||||||
|
])
|
||||||
|
->assertSee('Nicht genügend Credits')
|
||||||
|
->assertSee('Es fehlen 2 Credits.')
|
||||||
|
->assertSee('Credits aufladen')
|
||||||
|
->assertSee(route('me.bookings.index').'#credits', escape: false);
|
||||||
|
|
||||||
|
expect($customer->fresh()->creditBalance())->toBe(1); // nichts abgebucht
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a boost without enough credits opens the shortfall modal instead of booking', function () {
|
||||||
|
/** @var TestCase $this */
|
||||||
|
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
|
||||||
|
|
||||||
|
$this->actingAs($customer);
|
||||||
|
|
||||||
|
LivewireVolt::test('customer.press-releases.show', ['id' => $pr->id])
|
||||||
|
->call('buyBoost', 7)
|
||||||
|
->assertSet('creditShortfall.label', 'Der Boost')
|
||||||
|
->assertSee('Nicht genügend Credits');
|
||||||
|
|
||||||
|
expect($pr->fresh()->isBoosted())->toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
test('a non-green release cannot be boosted from the detail page', function () {
|
test('a non-green release cannot be boosted from the detail page', function () {
|
||||||
/** @var TestCase $this */
|
/** @var TestCase $this */
|
||||||
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
|
['customer' => $customer, 'pr' => $pr] = publishedGreenPrForOwner();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue