Files
ladill-transfer/resources/views/components/user/service-topup-modal.blade.php
T
isaaccladandClaude Opus 4.8 a0a706093b
Deploy Ladill Transfer / deploy (push) Successful in 48s
Transfer: two-step add-funds modal on insufficient balance
Creating a transfer that the wallet can't cover used to flash a bare error.
Detect the insufficient-balance case and open an in-app two-step modal (billing
explainer -> amount -> Paystack) on the create page instead, with an 'Add funds'
button to top up on demand.

- BillingClient::topup -> POST /api/billing/topup (Transfer already holds its key)
- WalletTopupController + route POST /wallet/topup (user.wallet.topup)
- store() flashes open_topup_modal on TransferWalletErrors::isInsufficientBalance
- service-topup-modal made two-step (uses <x-modal>, plain POST -> checkout)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 07:15:38 +00:00

104 lines
5.0 KiB
PHP

@props([
'id',
'title' => 'Add funds',
'topupAction',
'minTopup' => 5,
'suggestedAmount' => 10,
'pricePerAction' => null,
'actionNoun' => 'action',
'serviceBalance' => null,
'serviceBalanceLabel' => 'Wallet balance',
'description' => '',
'openOnLoad' => false,
'returnUrl' => null,
])
<x-modal :name="$id" :show="$openOnLoad" maxWidth="md">
<div x-data="{ step: 1 }"
x-on:open-modal.window="String($event.detail) === @js($id) ? step = 1 : null"
class="flex min-h-full flex-col sm:min-h-0">
{{-- Header with step indicator --}}
<div class="border-b border-slate-100 px-5 py-4 pr-12 sm:px-6 sm:pr-14">
<div class="flex items-center gap-1.5">
<span class="h-1.5 w-6 rounded-full transition-colors" :class="step >= 1 ? 'bg-indigo-600' : 'bg-slate-200'"></span>
<span class="h-1.5 w-6 rounded-full transition-colors" :class="step >= 2 ? 'bg-indigo-600' : 'bg-slate-200'"></span>
<span class="ml-2 text-xs font-medium text-slate-400" x-text="'Step ' + step + ' of 2'"></span>
</div>
<h2 class="mt-2 text-lg font-semibold text-slate-900"
x-text="step === 1 ? 'How billing works' : @js($title)">How billing works</h2>
</div>
{{-- STEP 1 billing explainer --}}
<div x-show="step === 1" class="flex flex-1 flex-col p-5 sm:p-6">
<p class="text-sm leading-6 text-slate-600">
Ladill is <span class="font-medium text-slate-800">pay-as-you-go</span>. One wallet funds
every Ladill app, and you're only charged when you perform an action.
</p>
@if($description)
<div class="mt-4 rounded-xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-600">{{ $description }}</div>
@endif
<div class="mt-4 space-y-2.5 rounded-xl border border-slate-200 bg-slate-50 p-4">
@if($pricePerAction !== null)
<div class="flex items-center justify-between text-sm">
<span class="text-slate-500">Each {{ $actionNoun }} costs</span>
<span class="font-semibold text-slate-900">GHS {{ number_format((float) $pricePerAction, 4) }}</span>
</div>
@endif
@if($serviceBalance !== null)
<div class="flex items-center justify-between text-sm">
<span class="text-slate-500">{{ $serviceBalanceLabel }}</span>
<span class="font-semibold text-slate-900">GHS {{ number_format((float) $serviceBalance, 2) }}</span>
</div>
@endif
</div>
<p class="mt-4 text-xs leading-5 text-slate-500">
Top up any amount — unused funds stay in your wallet for next time.
</p>
<div class="mt-6 flex flex-col gap-2 sm:mt-auto sm:flex-row sm:justify-end">
<button type="button" @click="$dispatch('close-modal', '{{ $id }}')"
data-close-modal="{{ $id }}"
class="hidden rounded-xl border border-slate-200 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 sm:inline-flex">
Not now
</button>
<button type="button" @click="step = 2" class="btn-primary w-full sm:w-auto">
Add funds
</button>
</div>
</div>
{{-- STEP 2 — amount + payment (plain POST -> Paystack checkout) --}}
<form x-show="step === 2" x-cloak action="{{ $topupAction }}" method="POST" class="flex flex-1 flex-col p-5 sm:p-6">
@csrf
@if($returnUrl)
<input type="hidden" name="return_url" value="{{ $returnUrl }}">
@endif
<div>
<label class="text-sm font-medium text-slate-700">Amount (GHS)</label>
<input type="number" name="amount" min="{{ $minTopup }}" max="5000" step="0.01"
value="{{ $suggestedAmount }}"
class="mt-1.5 block w-full rounded-xl border border-slate-200 px-3 py-2.5 text-sm focus:border-indigo-400 focus:ring-1 focus:ring-indigo-400"
required>
<p class="mt-1.5 text-xs text-slate-500">Minimum GHS {{ number_format($minTopup, 2) }}</p>
</div>
<input type="hidden" name="payment_method" value="paystack">
<p class="mt-4 text-xs text-slate-500">Pay securely with Paystack.</p>
<div class="mt-6 flex flex-col gap-2 sm:mt-auto sm:flex-row sm:justify-end">
<button type="button" @click="step = 1"
class="hidden rounded-xl border border-slate-200 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 sm:inline-flex">
Back
</button>
<button type="submit" class="btn-primary w-full sm:w-auto">Continue to payment</button>
</div>
</form>
</div>
</x-modal>