313 lines
14 KiB
PHP
313 lines
14 KiB
PHP
<div>
|
|
<form wire:submit.prevent="showConfirmPaymentModal">
|
|
@csrf
|
|
<div class="bg-theme-background px-4 py-4 shadow sm:rounded-lg sm:p-6">
|
|
@php
|
|
$hasNoAccounts = false;
|
|
$isAdminProfile = session('activeProfileType') === 'App\Models\Admin';
|
|
|
|
if ($isAdminProfile) {
|
|
$hasNoAccounts = true;
|
|
} elseif (getActiveProfile() && method_exists(getActiveProfile(), 'accounts')) {
|
|
$hasNoAccounts = getActiveProfile()->accounts()->notRemoved()->count() === 0;
|
|
}
|
|
@endphp
|
|
|
|
@if($hasNoAccounts)
|
|
<div class="mb-6 rounded-lg border border-theme-border bg-theme-surface p-4 text-theme-primary">
|
|
<div class="flex items-center">
|
|
<div>
|
|
<h3 class="font-semibold">{{ __('No accounts available') }}</h3>
|
|
<p class="mt-1 text-sm">
|
|
@if($isAdminProfile)
|
|
{{ __('Admin profiles do not have accounts and cannot make payments. Please switch to a different profile to make payments.') }}
|
|
@else
|
|
{{ __('Your profile does not have any accounts to make payments from. Please contact an administrator to set up an account.') }}
|
|
@endif
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
<div class="grid grid-cols-6 gap-6">
|
|
<div class="col-span-6 sm:col-span-4">
|
|
|
|
<!--- Amount --->
|
|
@livewire('amount', [
|
|
'maxLengthHoursInput' => timebank_config('maxLengthHoursInput.user'),
|
|
'hours' => $hours,
|
|
'minutes' => $minutes,
|
|
'amount' => $amount,
|
|
])
|
|
@error('amount')
|
|
<div class="mb-3 text-sm text-red-700" role="alert">
|
|
{{ __($message) }}
|
|
</div>
|
|
@enderror
|
|
|
|
<!--- From account --->
|
|
@livewire('from-account')
|
|
@error('fromAccountId')
|
|
<div class="mb-3 text-sm text-red-700" role="alert">
|
|
{{ __($message) }}
|
|
</div>
|
|
@enderror
|
|
|
|
<!--- To Account --->
|
|
@livewire('to-account', ['toHolderName' => $toHolderName, 'toAccountId' => $toAccountId])
|
|
@error('toAccountId')
|
|
<div class="mb-3 text-sm text-red-700" role="alert">
|
|
{{ __($message) }}
|
|
</div>
|
|
@enderror
|
|
|
|
<!--- Description --->
|
|
<div class="mt-4">
|
|
<label for="description" class="mt-2 block text-sm font-medium text-theme-primary"> {{ __('Description') }}</label>
|
|
<textarea
|
|
wire:model.live.debounce.500ms="description"
|
|
id="description"
|
|
placeholder=" {{ __('Payment description') }}"
|
|
rows="5"
|
|
class="mt-1 placeholder-theme-light focus:ring-theme-accent focus:border-theme-accent block w-full shadow-sm sm:text-sm border-theme-primary rounded-md @error('description') is-invalid @enderror" name="description">
|
|
</textarea>
|
|
</div>
|
|
@error('description')
|
|
<div class="mb-3 text-sm text-red-700" role="alert">
|
|
{{ __($message) }}
|
|
</div>
|
|
@enderror
|
|
|
|
<!--- Transaction type --->
|
|
@livewire('transaction-type-radio', ['type' => $type, 'typeOptions' => $typeOptions])
|
|
@error('transactionTypeSelected.name')
|
|
<div class="mb-3 text-sm text-red-700" role="alert">
|
|
{{ __($message) }}
|
|
</div>
|
|
@enderror
|
|
|
|
<!--- Remember payment data checkbox ----->
|
|
<div class="my-4">
|
|
<x-checkbox id="rememberPaymentData" secondary label="{{ __('Remember payment data for next payment') }}" wire:model="rememberPaymentData"/>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<x-jetstream.button type="submit" wire:target="showModal" :disabled="!canActiveProfileCreatePayments()">
|
|
{{ __('Pay') }}
|
|
</x-jetstream.button>
|
|
</div>
|
|
</div>
|
|
|
|
<!----Transfer limit error Modal ---->
|
|
<style>
|
|
/* Ensure Pay modals render above the fixed navigation bar (nav is z-[80]) */
|
|
#payment-limit-error-dialog,
|
|
#new-recipient-dialog,
|
|
#confirm-payment-dialog {
|
|
z-index: 90;
|
|
}
|
|
</style>
|
|
<x-jetstream.dialog-modal id="payment-limit-error-dialog" wire:model.live="isPaymentLimitErrorModalOpen">
|
|
<x-slot name="title">
|
|
{{ __('Payment limit') }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
{{ $limitError }}
|
|
</x-slot>
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="$toggle('isPaymentLimitErrorModalOpen')" wire:loading.attr="disabled">
|
|
{{ __('Back') }}
|
|
</x-jetstream.secondary-button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
|
|
<!----New recipient confirmation Modal ---->
|
|
<x-jetstream.dialog-modal id="new-recipient-dialog" wire:model.live="isNewRecipientModalOpen">
|
|
<x-slot name="title">
|
|
{{ __('New recipient') }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
{{ __('You previously never had any chat conversation or payments with :name (:name) from :location. Are you sure you want to select this account?', [
|
|
'name' => $pendingToAccountDetails['holderName'] ?? '',
|
|
'location' => $pendingToAccountDetails['holderLocation'] ?? '',
|
|
]) }}
|
|
</x-slot>
|
|
<x-slot name="footer">
|
|
<x-jetstream.secondary-button wire:click="cancelNewRecipient" wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
<x-jetstream.button class="ml-3" wire:click="confirmNewRecipient" wire:loading.attr="disabled">
|
|
{{ __('Continue') }}
|
|
</x-jetstream.button>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
|
|
<!---- Confirmation Modal ---->
|
|
@if (!$limitError && !empty($transactionTypeSelected))
|
|
<x-jetstream.dialog-modal id="confirm-payment-dialog" wire:model.live="isConfirmPaymentModalOpen">
|
|
<x-slot name="title">
|
|
{{ __('Confirm your payment') }}
|
|
</x-slot>
|
|
|
|
<x-slot name="content">
|
|
|
|
<div class="pb-3 px-1 sm:px-3">
|
|
<div class="font-semibold text-sm sm:text-base">
|
|
{{ __('Description') }}
|
|
</div>
|
|
<div class="text-sm sm:text-base">{{ $description }}</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col py-3 gap-0">
|
|
<!-- From account row -->
|
|
<div class="flex items-center gap-4">
|
|
<div class="flex w-16 flex-shrink-0 justify-end">
|
|
<img alt="{{ session('activeProfileName') }}"
|
|
class="h-16 w-16 rounded-full profile-photo object-cover outline outline-1 outline-offset-2 outline-theme-primary"
|
|
src="{{ Storage::url(session('activeProfilePhoto')) }}">
|
|
</div>
|
|
<div class="leading-tight">
|
|
<div class="font-semibold text-sm">{{ session('activeProfileName') }}</div>
|
|
<div class="text-theme-secondary text-xs">{{ __(ucfirst(strtolower($fromAccountName))) }} {{ __('bank account') }}</div>
|
|
</div>
|
|
</div>
|
|
<!-- Connector: line, icon, amount, arrow -->
|
|
<div class="flex items-stretch gap-4">
|
|
<!-- Left: continuous line with icon centered and arrow at bottom -->
|
|
<div class="relative flex w-16 flex-shrink-0 justify-center">
|
|
<!-- Full-height vertical line -->
|
|
<div class="absolute inset-x-1/2 top-0 bottom-0 w-px bg-theme-primary"></div>
|
|
<!-- Icon centered vertically -->
|
|
<div class="relative flex h-full flex-col items-center justify-center">
|
|
<div class="flex h-6 w-6 items-center justify-center rounded-full bg-white text-theme-primary outline outline-1 outline-offset-1 outline-theme-primary">
|
|
<x-icon class="{{ ($transactionTypeSelected['icon'] ?? '') === 'arrow-path' ? '-scale-x-100' : '' }}" mini name="{{ $transactionTypeSelected['icon'] }}" />
|
|
</div>
|
|
</div>
|
|
<!-- Arrow at the bottom -->
|
|
<div class="absolute bottom-0 flex justify-center">
|
|
<svg fill="none" height="10" viewBox="0 0 15 10" width="15" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M7.5 0 L7.5 8 M7.5 8 L0 2 M7.5 8 L15 2" stroke-width="1" stroke="#4B5563" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<!-- Right: amount + label -->
|
|
<div class="flex items-center leading-tight py-2">
|
|
<div>
|
|
<div class="font-semibold text-sm">{{ tbFormat($amount) }}</div>
|
|
<div class="text-theme-secondary text-xs">{{ __($transactionTypeSelected['label']) }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- To account row -->
|
|
<div class="flex items-center gap-4">
|
|
<div class="flex w-16 flex-shrink-0 justify-end">
|
|
<img alt="{{ $toHolderName }}"
|
|
class="h-16 w-16 rounded-full object-cover outline outline-1 outline-offset-2 outline-theme-primary"
|
|
src="{{ $toHolderPhoto }}">
|
|
</div>
|
|
<div class="leading-tight">
|
|
<div class="font-semibold text-sm">{{ $toHolderName }}</div>
|
|
<div class="text-theme-secondary text-xs">{{ __(ucfirst($toAccountName)) }} {{ __('bank account') }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@if (session('error'))
|
|
<div class="alert alert-danger text-red-500">
|
|
{{ session('error') }}
|
|
</div>
|
|
@endif
|
|
</x-slot>
|
|
<x-slot name="footer">
|
|
@if (session('error'))
|
|
<x-jetstream.secondary-button wire:click="$toggle('isConfirmPaymentModalOpen')" wire:loading.attr="disabled">
|
|
{{ __('Back') }}
|
|
</x-jetstream.secondary-button>
|
|
@else
|
|
<x-jetstream.secondary-button class="w-32 justify-center" wire:click="$toggle('isConfirmPaymentModalOpen')"
|
|
wire:loading.attr="disabled">
|
|
{{ __('Cancel') }}
|
|
</x-jetstream.secondary-button>
|
|
|
|
<x-jetstream.button class="ml-3 w-32 justify-center" wire:click="doTransfer()"
|
|
wire:loading.attr="disabled" :disabled="!canActiveProfileCreatePayments()">
|
|
{{ __('Pay') }}
|
|
</x-jetstream.button>
|
|
@endif
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
@endif
|
|
|
|
</form>
|
|
|
|
{{-- Payment success modal --}}
|
|
<style>
|
|
/* Constrain success modal to viewport with margin, keep footer always visible */
|
|
#payment-success-dialog .mb-6 {
|
|
max-height: calc(100svh - 3rem);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
#payment-success-dialog .mb-6 > div {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
flex: 1;
|
|
}
|
|
#payment-success-dialog .mb-6 > div > .m-6 {
|
|
overflow-y: auto;
|
|
min-height: 0;
|
|
flex: 1;
|
|
}
|
|
</style>
|
|
<x-jetstream.dialog-modal id="payment-success-dialog" wire:model.live="isPaymentSuccessModalOpen" closeButton="true" maxWidth="sm">
|
|
<x-slot name="title">{{ __('Transaction done!') }}</x-slot>
|
|
<x-slot name="content">
|
|
<div class="flex flex-col items-center gap-3 py-2">
|
|
@if ($successHolderPhoto)
|
|
<img alt="{{ $successHolderName }}"
|
|
src="{{ $successHolderPhoto }}"
|
|
class="h-20 w-20 shrink-0 rounded-full object-cover outline outline-1 outline-offset-2 outline-theme-primary">
|
|
@endif
|
|
<div class="text-center leading-tight">
|
|
<div class="text-sm text-theme-secondary">{{ __('Payment sent to') }}</div>
|
|
<div class="mt-1 font-semibold text-theme-primary">{{ $successHolderName }}</div>
|
|
@if ($successAccountName)
|
|
<div class="text-xs text-theme-secondary">{{ $successAccountName }} {{ __('bank account') }}</div>
|
|
@endif
|
|
<div class="mt-2 font-bold text-theme-primary">{{ $successAmount }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if ($successQrSvg)
|
|
{{-- QR hidden via Alpine when rendered size would drop below 200px --}}
|
|
<div x-data="{ showQr: true }"
|
|
x-init="$nextTick(() => { showQr = $el.offsetWidth >= 200 })"
|
|
x-show="showQr"
|
|
class="mt-4 flex flex-col items-center gap-2">
|
|
<a href="{{ $successTransactionUrl }}"
|
|
class="block w-full max-w-[200px] [&>svg]:h-auto [&>svg]:w-full">
|
|
{!! $successQrSvg !!}
|
|
</a>
|
|
<a href="{{ $successTransactionUrl }}"
|
|
class="text-xs text-theme-secondary underline hover:text-theme-primary">
|
|
{{ __('View transaction statement') }}
|
|
</a>
|
|
</div>
|
|
@endif
|
|
|
|
</x-slot>
|
|
<x-slot name="footer">
|
|
<div class="w-full flex justify-center">
|
|
<x-jetstream.button wire:click="$set('isPaymentSuccessModalOpen', false)">
|
|
{{ __('OK') }}
|
|
</x-jetstream.button>
|
|
</div>
|
|
</x-slot>
|
|
</x-jetstream.dialog-modal>
|
|
|
|
</div>
|