Deploy Ladill Hosting / deploy (push) Successful in 49s
Standardize customer-facing Ladill Pay checkout shells across products, open them on all viewports, and escape iframe callbacks back to the product flow.
148 lines
7.1 KiB
PHP
148 lines
7.1 KiB
PHP
{{--
|
|
Responsive payment checkout shell: bottom sheet on mobile, centered modal on desktop.
|
|
|
|
Audiences:
|
|
- buyer (default): public ticket buyers, donors, shoppers — self-serve checkout
|
|
- operator: signed-in staff flows such as wallet top-up
|
|
|
|
Optional overrides: $sheetTitle, $sheetSubtitle, $sheetFooter, $sheetAria, $iframeTitle
|
|
Requires Alpine ancestor with: showSheet (bool), checkoutUrl (string).
|
|
|
|
Loads the provider authorization URL in-frame (verified via production Events/POS).
|
|
MoMo and other non-frameable providers should redirect instead of opening this sheet.
|
|
--}}
|
|
@php
|
|
$audience = $audience ?? 'buyer';
|
|
$defaults = match ($audience) {
|
|
'operator' => [
|
|
'title' => 'Complete payment',
|
|
'subtitle' => null,
|
|
'aria' => 'Complete payment',
|
|
'iframe' => 'Payment checkout',
|
|
'footer' => null,
|
|
],
|
|
default => [
|
|
'title' => 'Complete your payment',
|
|
'subtitle' => 'Pay securely. You\'ll get a confirmation when it succeeds.',
|
|
'aria' => 'Complete your payment',
|
|
'iframe' => 'Secure payment checkout',
|
|
'footer' => 'Your payment is processed securely. Do not close this window until finished.',
|
|
],
|
|
};
|
|
$sheetTitle = $sheetTitle ?? $defaults['title'];
|
|
$sheetSubtitle = $sheetSubtitle ?? $defaults['subtitle'];
|
|
$sheetAria = $sheetAria ?? $defaults['aria'];
|
|
$iframeTitle = $iframeTitle ?? $defaults['iframe'];
|
|
$sheetFooter = $sheetFooter ?? $defaults['footer'];
|
|
@endphp
|
|
<template x-teleport="body">
|
|
<div x-show="showSheet"
|
|
x-cloak
|
|
x-effect="document.documentElement.style.overflow = showSheet ? 'hidden' : ''"
|
|
@keydown.escape.window="if (showSheet) showSheet = false"
|
|
class="fixed inset-0 z-[9999] flex items-end justify-center md:items-center md:p-6"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
:aria-hidden="(!showSheet).toString()"
|
|
aria-label="{{ $sheetAria }}">
|
|
|
|
<div class="absolute inset-0 bg-black/60"
|
|
x-show="showSheet"
|
|
x-transition:enter="transition-opacity duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition-opacity duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="showSheet = false">
|
|
</div>
|
|
|
|
{{-- Mobile bottom sheet --}}
|
|
<div class="relative flex w-full flex-col overflow-hidden rounded-t-2xl bg-white md:hidden"
|
|
style="height:90dvh; padding-bottom: env(safe-area-inset-bottom, 0px)"
|
|
x-show="showSheet"
|
|
x-transition:enter="transition-transform duration-300 ease-out"
|
|
x-transition:enter-start="translate-y-full"
|
|
x-transition:enter-end="translate-y-0"
|
|
x-transition:leave="transition-transform duration-200 ease-in"
|
|
x-transition:leave-start="translate-y-0"
|
|
x-transition:leave-end="translate-y-full"
|
|
@click.stop>
|
|
<div class="relative flex shrink-0 flex-col gap-0.5 border-b border-slate-100 px-4 pb-3 pt-5"
|
|
style="padding-top: max(1.25rem, env(safe-area-inset-top, 0px))">
|
|
<div class="absolute left-1/2 top-2 h-1 w-10 -translate-x-1/2 rounded-full bg-slate-200"></div>
|
|
<div class="flex items-start justify-between gap-3">
|
|
<div class="min-w-0">
|
|
<p class="text-sm font-semibold text-slate-800">{{ $sheetTitle }}</p>
|
|
@if (! empty($sheetSubtitle))
|
|
<p class="mt-0.5 text-xs leading-snug text-slate-500">{{ $sheetSubtitle }}</p>
|
|
@endif
|
|
</div>
|
|
<button type="button"
|
|
x-ref="checkoutCloseMobile"
|
|
@click="showSheet = false"
|
|
class="shrink-0 rounded-full p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-700"
|
|
aria-label="Close">
|
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
|
<iframe :src="showSheet ? checkoutUrl : ''"
|
|
class="h-full min-h-[60dvh] w-full border-0"
|
|
allow="payment *"
|
|
title="{{ $iframeTitle }}"></iframe>
|
|
</div>
|
|
@if (! empty($sheetFooter))
|
|
<p class="shrink-0 border-t border-slate-100 bg-slate-50 px-4 py-2 text-center text-[11px] text-slate-500">
|
|
{{ $sheetFooter }}
|
|
</p>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Desktop centered modal --}}
|
|
<div class="relative hidden w-full max-w-lg overflow-hidden rounded-2xl bg-white shadow-2xl md:flex md:h-[min(720px,85vh)] md:flex-col"
|
|
x-show="showSheet"
|
|
x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-150"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
@click.stop>
|
|
<div class="flex shrink-0 items-start justify-between gap-3 border-b border-slate-100 px-5 py-3">
|
|
<div class="min-w-0">
|
|
<p class="text-sm font-semibold text-slate-800">{{ $sheetTitle }}</p>
|
|
@if (! empty($sheetSubtitle))
|
|
<p class="mt-0.5 text-xs leading-snug text-slate-500">{{ $sheetSubtitle }}</p>
|
|
@endif
|
|
</div>
|
|
<button type="button"
|
|
x-ref="checkoutCloseDesktop"
|
|
x-init="$watch('showSheet', value => { if (value) { $nextTick(() => ($refs.checkoutCloseDesktop || $refs.checkoutCloseMobile)?.focus()) } })"
|
|
@click="showSheet = false"
|
|
class="shrink-0 rounded-full p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-700"
|
|
aria-label="Close">
|
|
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="min-h-0 flex-1 overflow-y-auto overscroll-contain">
|
|
<iframe :src="showSheet ? checkoutUrl : ''"
|
|
class="h-full min-h-0 w-full border-0"
|
|
style="min-height: 28rem"
|
|
allow="payment *"
|
|
title="{{ $iframeTitle }}"></iframe>
|
|
</div>
|
|
@if (! empty($sheetFooter))
|
|
<p class="shrink-0 border-t border-slate-100 bg-slate-50 px-5 py-2 text-center text-[11px] text-slate-500">
|
|
{{ $sheetFooter }}
|
|
</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</template>
|