Deploy Ladill Events / deploy (push) Successful in 1m25s
Use the shared Inline sheet, hold busy until payment UI opens, and avoid locking page scroll during the handoff.
1555 lines
112 KiB
PHP
1555 lines
112 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>{{ $qrCode->label }}</title>
|
|
@include('partials.favicon')
|
|
<meta name="description" content="{{ $qrCode->typeLabel() }} — {{ $qrCode->label }}">
|
|
@vite(['resources/css/app.css'])
|
|
</head>
|
|
<body class="min-h-screen bg-slate-50 text-slate-900 antialiased">
|
|
|
|
@php
|
|
$content = $qrCode->content();
|
|
$type = $qrCode->type;
|
|
@endphp
|
|
|
|
{{-- ===== PDF ===== --}}
|
|
@if($type === \App\Models\QrCode::TYPE_DOCUMENT)
|
|
<div class="flex min-h-screen flex-col items-center justify-center px-4 py-12">
|
|
<div class="w-full max-w-sm">
|
|
<div class="flex h-16 w-16 items-center justify-center rounded-2xl bg-red-100">
|
|
<svg class="h-8 w-8 text-red-600" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/>
|
|
</svg>
|
|
</div>
|
|
<a href="{{ $qrCode->publicPath('view') }}"
|
|
class="mt-6 flex w-full items-center justify-center gap-2 rounded-2xl bg-red-600 px-6 py-4 text-sm font-semibold text-white shadow-sm transition hover:bg-red-700">
|
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
|
|
</svg>
|
|
View document
|
|
</a>
|
|
<a href="{{ $qrCode->publicPath('file') }}"
|
|
class="mt-3 flex w-full items-center justify-center gap-2 rounded-2xl border border-slate-200 bg-white px-6 py-4 text-sm font-semibold text-slate-700 shadow-sm transition hover:bg-slate-50">
|
|
<svg class="h-5 w-5 text-slate-500" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"/>
|
|
</svg>
|
|
Download PDF
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ===== LIST OF LINKS ===== --}}
|
|
@elseif($type === \App\Models\QrCode::TYPE_LINK_LIST)
|
|
<div class="mx-auto max-w-md px-4 py-12">
|
|
<h1 class="text-center text-2xl font-bold text-slate-900">{{ $qrCode->label }}</h1>
|
|
<div class="mt-8 space-y-3">
|
|
@foreach($content['links'] ?? [] as $link)
|
|
<a href="{{ $link['url'] }}" target="_blank" rel="noopener"
|
|
class="group flex items-center justify-between rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm transition hover:border-indigo-300 hover:shadow-md">
|
|
<span class="text-sm font-semibold text-slate-900 group-hover:text-indigo-700">{{ $link['title'] }}</span>
|
|
<svg class="h-4 w-4 shrink-0 text-slate-400 transition group-hover:translate-x-1 group-hover:text-indigo-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"/>
|
|
</svg>
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ===== VCARD ===== --}}
|
|
@elseif($type === \App\Models\QrCode::TYPE_VCARD)
|
|
@php
|
|
$avatarUrl = !empty($content['avatar_path']) ? $qrCode->publicPath('avatar') : null;
|
|
$rawSocial = $content['social'] ?? [];
|
|
$social = [];
|
|
foreach ($rawSocial as $platform => $url) {
|
|
$social[$platform] = \App\Services\Qr\QrPayloadValidator::normalizeSocialUrl($platform, trim((string) $url));
|
|
}
|
|
$fullName = trim(($content['first_name'] ?? '') . ' ' . ($content['last_name'] ?? ''));
|
|
$initials = strtoupper(substr($content['first_name'] ?? ($content['last_name'] ?? '?'), 0, 1));
|
|
|
|
$socialIcons = [
|
|
'linkedin' => 'linkedin.svg',
|
|
'twitter' => 'twitter.svg',
|
|
'instagram' => 'instagram.svg',
|
|
'facebook' => 'facebook.svg',
|
|
'tiktok' => 'tik-tok.svg',
|
|
'youtube' => 'youtube.svg',
|
|
'whatsapp' => 'whatsapp.svg',
|
|
'snapchat' => 'snapchat.svg',
|
|
];
|
|
@endphp
|
|
<div class="min-h-screen bg-slate-100 pb-10">
|
|
|
|
{{-- Colored header strip --}}
|
|
<div class="h-36 bg-gradient-to-br from-indigo-500 to-violet-600"></div>
|
|
|
|
{{-- Floating card --}}
|
|
<div class="mx-auto -mt-20 max-w-sm px-4">
|
|
<div class="overflow-hidden rounded-3xl bg-white shadow-xl">
|
|
|
|
{{-- Avatar --}}
|
|
<div class="flex justify-center pt-4">
|
|
@if($avatarUrl)
|
|
<img src="{{ $avatarUrl }}" alt="{{ $fullName }}"
|
|
class="h-28 w-28 rounded-full object-cover ring-4 ring-white shadow-md">
|
|
@else
|
|
<div class="flex h-28 w-28 items-center justify-center rounded-full bg-gradient-to-br from-indigo-500 to-violet-600 ring-4 ring-white shadow-md text-4xl font-bold text-white">
|
|
{{ $initials }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Name & company --}}
|
|
<div class="px-6 pb-5 pt-4 text-center">
|
|
<h1 class="text-xl font-bold text-slate-900">{{ $fullName }}</h1>
|
|
@if(!empty($content['company']))
|
|
<p class="mt-0.5 text-sm text-slate-500">{{ $content['company'] }}</p>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Contact rows --}}
|
|
<div class="divide-y divide-slate-100 border-t border-slate-100">
|
|
@if(!empty($content['phone']))
|
|
<a href="tel:{{ $content['phone'] }}" class="flex items-center gap-3 px-6 py-3.5 text-sm text-slate-700 transition hover:bg-slate-50 hover:text-indigo-600">
|
|
<svg class="h-4 w-4 shrink-0 text-slate-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 0 0 2.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 0 1-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 0 0-1.091-.852H4.5A2.25 2.25 0 0 0 2.25 4.5v2.25Z"/></svg>
|
|
{{ $content['phone'] }}
|
|
</a>
|
|
@endif
|
|
@if(!empty($content['email']))
|
|
<a href="mailto:{{ $content['email'] }}" class="flex items-center gap-3 px-6 py-3.5 text-sm text-slate-700 transition hover:bg-slate-50 hover:text-indigo-600">
|
|
<svg class="h-4 w-4 shrink-0 text-slate-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"/></svg>
|
|
{{ $content['email'] }}
|
|
</a>
|
|
@endif
|
|
@if(!empty($content['website']))
|
|
<a href="{{ $content['website'] }}" target="_blank" rel="noopener" class="flex items-center gap-3 px-6 py-3.5 text-sm text-slate-700 transition hover:bg-slate-50 hover:text-indigo-600">
|
|
<svg class="h-4 w-4 shrink-0 text-slate-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253M3 12a8.96 8.96 0 0 0 .735 3.547"/></svg>
|
|
{{ $content['website'] }}
|
|
</a>
|
|
@endif
|
|
@if(!empty($content['address']))
|
|
<div class="flex items-start gap-3 px-6 py-3.5 text-sm text-slate-700">
|
|
<svg class="mt-0.5 h-4 w-4 shrink-0 text-slate-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1 1 15 0Z"/></svg>
|
|
{{ $content['address'] }}
|
|
</div>
|
|
@endif
|
|
@if(!empty($content['note']))
|
|
<p class="px-6 py-3.5 text-sm italic text-slate-500">{{ $content['note'] }}</p>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Social links --}}
|
|
@if(!empty($social))
|
|
<div class="flex flex-wrap items-center justify-center gap-3 border-t border-slate-100 px-6 py-5">
|
|
@foreach($social as $platform => $url)
|
|
@if(isset($socialIcons[$platform]))
|
|
<a href="{{ $url }}" target="_blank" rel="noopener"
|
|
class="flex h-10 w-10 items-center justify-center rounded-full bg-slate-100 transition hover:bg-indigo-100"
|
|
title="{{ ucfirst($platform) }}">
|
|
<img src="/images/qr-icons/{{ $socialIcons[$platform] }}" alt="{{ ucfirst($platform) }}" class="h-5 w-5">
|
|
</a>
|
|
@endif
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Save contact button --}}
|
|
<div class="px-6 pb-6 pt-2">
|
|
<a href="{{ $qrCode->publicPath('vcard.vcf') }}"
|
|
class="btn-primary w-full">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5"/></svg>
|
|
Save Contact
|
|
</a>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ===== BUSINESS ===== --}}
|
|
@elseif($type === \App\Models\QrCode::TYPE_BUSINESS)
|
|
@php
|
|
$bizName = $content['name'] ?? $qrCode->label;
|
|
$bizTagline = $content['tagline'] ?? '';
|
|
$bizColor = $content['brand_color'] ?? '#1e3a5f';
|
|
$bizHasLogo = !empty($content['logo_path']);
|
|
$bizHasCover = !empty($content['cover_path']);
|
|
$bizLogoUrl = $bizHasLogo ? $qrCode->publicPath('business-logo') : null;
|
|
$bizCoverUrl = $bizHasCover ? $qrCode->publicPath('business-cover') : null;
|
|
$bizInitials = strtoupper(substr($bizName, 0, 1));
|
|
$bizRawSocial = $content['social'] ?? [];
|
|
$bizSocial = [];
|
|
foreach ($bizRawSocial as $platform => $url) {
|
|
$bizSocial[$platform] = \App\Services\Qr\QrPayloadValidator::normalizeSocialUrl($platform, trim((string) $url));
|
|
}
|
|
$socialIcons = [
|
|
'linkedin' => 'linkedin.svg',
|
|
'twitter' => 'twitter.svg',
|
|
'instagram' => 'instagram.svg',
|
|
'facebook' => 'facebook.svg',
|
|
'tiktok' => 'tik-tok.svg',
|
|
'youtube' => 'youtube.svg',
|
|
'whatsapp' => 'whatsapp.svg',
|
|
];
|
|
@endphp
|
|
<div class="min-h-screen bg-slate-100 pb-10">
|
|
|
|
{{-- Cover strip + logo anchor --}}
|
|
<div style="position:relative;">
|
|
{{-- Cover --}}
|
|
<div style="position:relative; overflow:hidden; height:200px;">
|
|
@if($bizHasCover)
|
|
<img src="{{ $bizCoverUrl }}" alt="{{ $bizName }}"
|
|
style="position:absolute; inset:0; width:100%; height:100%; object-fit:cover;">
|
|
<div style="position:absolute; inset:0; background:linear-gradient(to bottom,rgba(0,0,0,0.08) 0%,rgba(0,0,0,0.5) 100%);"></div>
|
|
@else
|
|
<div style="position:absolute; inset:0; background:linear-gradient(135deg,{{ $bizColor }} 0%,{{ $bizColor }}bb 100%);"></div>
|
|
<div style="position:absolute; inset:0; opacity:0.07; background-image:radial-gradient(circle, white 1px, transparent 1px); background-size:32px 32px;"></div>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Logo: floats at the cover/card seam --}}
|
|
<div style="position:absolute; bottom:-52px; left:50%; transform:translateX(-50%); z-index:10;">
|
|
@if($bizHasLogo)
|
|
<div style="width:104px; height:104px; border-radius:20px; background:#fff; box-shadow:0 8px 32px rgba(0,0,0,0.14); border:4px solid #fff; overflow:hidden; display:flex; align-items:center; justify-content:center;">
|
|
<img src="{{ $bizLogoUrl }}" alt="{{ $bizName }}"
|
|
class="public-brand-logo"
|
|
style="width:100%; height:100%; max-width:100%; object-fit:contain; padding:8px;">
|
|
</div>
|
|
@else
|
|
<div style="width:104px; height:104px; border-radius:20px; background:{{ $bizColor }}; box-shadow:0 8px 32px rgba(0,0,0,0.18); border:4px solid #fff; display:flex; align-items:center; justify-content:center; color:#fff; font-size:2.5rem; font-weight:900;">
|
|
{{ $bizInitials }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Floating card --}}
|
|
<div class="mx-auto max-w-sm px-4" style="margin-top:64px;">
|
|
<div class="overflow-hidden rounded-3xl bg-white shadow-xl">
|
|
|
|
{{-- Name & tagline centred --}}
|
|
<div class="px-6 pb-5 pt-8 text-center">
|
|
<h1 class="text-xl font-bold text-slate-900">{{ $bizName }}</h1>
|
|
@if($bizTagline)
|
|
<p class="mt-1 text-sm text-slate-500">{{ $bizTagline }}</p>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Contact rows --}}
|
|
<div class="divide-y divide-slate-100 border-t border-slate-100">
|
|
@if(!empty($content['phone']))
|
|
<a href="tel:{{ $content['phone'] }}" class="flex items-center gap-3 px-6 py-3.5 text-sm text-slate-700 transition hover:bg-slate-50"
|
|
style="--hover-color: {{ $bizColor }}">
|
|
<svg class="h-4 w-4 shrink-0 text-slate-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 0 0 2.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 0 1-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 0 0-1.091-.852H4.5A2.25 2.25 0 0 0 2.25 4.5v2.25Z"/></svg>
|
|
{{ $content['phone'] }}
|
|
</a>
|
|
@endif
|
|
@if(!empty($content['email']))
|
|
<a href="mailto:{{ $content['email'] }}" class="flex items-center gap-3 px-6 py-3.5 text-sm text-slate-700 transition hover:bg-slate-50">
|
|
<svg class="h-4 w-4 shrink-0 text-slate-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"/></svg>
|
|
{{ $content['email'] }}
|
|
</a>
|
|
@endif
|
|
@if(!empty($content['website']))
|
|
<a href="{{ $content['website'] }}" target="_blank" rel="noopener" class="flex items-center gap-3 px-6 py-3.5 text-sm text-slate-700 transition hover:bg-slate-50">
|
|
<svg class="h-4 w-4 shrink-0 text-slate-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253M3 12a8.96 8.96 0 0 0 .735 3.547"/></svg>
|
|
{{ $content['website'] }}
|
|
</a>
|
|
@endif
|
|
@if(!empty($content['address']))
|
|
<div class="flex items-start gap-3 px-6 py-3.5 text-sm text-slate-700">
|
|
<svg class="mt-0.5 h-4 w-4 shrink-0 text-slate-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1 1 15 0Z"/></svg>
|
|
{{ $content['address'] }}
|
|
</div>
|
|
@endif
|
|
@if(!empty($content['hours']))
|
|
<div class="flex items-start gap-3 px-6 py-3.5 text-sm text-slate-700">
|
|
<svg class="mt-0.5 h-4 w-4 shrink-0 text-slate-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/></svg>
|
|
<span class="whitespace-pre-line">{{ $content['hours'] }}</span>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Social links --}}
|
|
@if(!empty($bizSocial))
|
|
<div class="flex flex-wrap items-center justify-center gap-3 border-t border-slate-100 px-6 py-5">
|
|
@foreach($bizSocial as $platform => $url)
|
|
@if(isset($socialIcons[$platform]))
|
|
<a href="{{ $url }}" target="_blank" rel="noopener"
|
|
class="flex h-10 w-10 items-center justify-center rounded-full bg-slate-100 transition hover:opacity-80"
|
|
style="background-color: {{ $bizColor }}1a;"
|
|
title="{{ ucfirst($platform) }}">
|
|
<img src="/images/qr-icons/{{ $socialIcons[$platform] }}" alt="{{ ucfirst($platform) }}" class="h-5 w-5">
|
|
</a>
|
|
@endif
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
{{-- CTA: website button if present --}}
|
|
@if(!empty($content['website']))
|
|
<div class="px-6 pb-6 {{ !empty($bizSocial) ? 'pt-0' : 'pt-2' }}">
|
|
<a href="{{ $content['website'] }}" target="_blank" rel="noopener"
|
|
class="flex w-full items-center justify-center gap-2 rounded-2xl py-3.5 text-sm font-semibold text-white shadow-sm transition"
|
|
style="background: {{ $bizColor }};">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253M3 12a8.96 8.96 0 0 0 .735 3.547"/></svg>
|
|
Visit Website
|
|
</a>
|
|
</div>
|
|
@endif
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ===== CHURCH ===== --}}
|
|
@elseif($type === \App\Models\QrCode::TYPE_CHURCH)
|
|
@php
|
|
$churchName = $content['name'] ?? $qrCode->label;
|
|
$churchColor = $content['brand_color'] ?? '#1a3a5c';
|
|
$churchHasLogo = !empty($content['logo_path']);
|
|
$churchHasCover = !empty($content['cover_path']);
|
|
$churchLogoUrl = $churchHasLogo ? $qrCode->publicPath('church-logo') : null;
|
|
$churchCoverUrl = $churchHasCover ? $qrCode->publicPath('church-cover') : null;
|
|
$churchInitials = strtoupper(substr($churchName, 0, 2));
|
|
$churchAcceptsPay = !empty($content['accepts_payment']);
|
|
$churchCurrency = $content['currency'] ?? 'GHS';
|
|
$churchTypes = $content['collection_types'] ?? ['offering', 'tithe', 'donation', 'harvest'];
|
|
// Normalise legacy lowercase slugs → display strings
|
|
$legacyLabels = ['offering'=>'Offering','tithe'=>'Tithe','donation'=>'Donation','harvest'=>'Harvest'];
|
|
$churchTypeLabels = array_combine(
|
|
$churchTypes,
|
|
array_map(fn($t) => $legacyLabels[strtolower(trim($t))] ?? trim($t), $churchTypes)
|
|
);
|
|
$churchOrderRoute = $qrCode->publicPath('order');
|
|
$churchCsrf = csrf_token();
|
|
@endphp
|
|
<div x-data="{
|
|
collectionType: @js($churchTypes[0] ?? 'offering'),
|
|
amount: '',
|
|
name: '',
|
|
email: '',
|
|
phone: '',
|
|
loading: false,
|
|
errorMsg: '',
|
|
showSheet: false,
|
|
checkoutUrl: '',
|
|
accessCode: '',
|
|
publicKey: '',
|
|
returnUrl: '',
|
|
async give() {
|
|
if (!this.amount || parseFloat(this.amount) <= 0) { this.errorMsg = 'Enter a valid amount.'; return; }
|
|
if (!this.name.trim()) { this.errorMsg = 'Enter your name.'; return; }
|
|
if (!this.phone.trim()) { this.errorMsg = 'Enter your phone number.'; return; }
|
|
this.errorMsg = ''; this.loading = true; window.LadillPayCheckout?.prepare?.();
|
|
const labels = @js($churchTypeLabels);
|
|
try {
|
|
const res = await fetch(@js($churchOrderRoute), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': @js($churchCsrf) },
|
|
body: JSON.stringify({
|
|
customer_name: this.name,
|
|
customer_email: this.email,
|
|
customer_phone: this.phone,
|
|
items: [{ name: labels[this.collectionType] || this.collectionType, price: parseFloat(this.amount), qty: 1 }],
|
|
}),
|
|
});
|
|
const data = await res.json();
|
|
if (data.error) { window.LadillPayCheckout?.cancel?.(); this.errorMsg = data.error; this.loading = false; return; }
|
|
this.checkoutUrl = data.checkout_url;
|
|
this.accessCode = data.access_code || '';
|
|
this.publicKey = data.public_key || '';
|
|
this.returnUrl = data.callback_url || '';
|
|
this.showSheet = true; // loading cleared when payment UI opens (ladill-pay-opened)
|
|
} catch(e) {
|
|
window.LadillPayCheckout?.cancel?.(); this.errorMsg = 'Network error. Please try again.';
|
|
this.loading = false;
|
|
}
|
|
}
|
|
}" class="min-h-screen bg-slate-100 pb-10">
|
|
|
|
{{-- Cover strip + logo anchor --}}
|
|
<div style="position:relative;">
|
|
<div style="position:relative; overflow:hidden; height:200px;">
|
|
@if($churchHasCover)
|
|
<img src="{{ $churchCoverUrl }}" alt="{{ $churchName }}"
|
|
style="position:absolute; inset:0; width:100%; height:100%; object-fit:cover;">
|
|
<div style="position:absolute; inset:0; background:linear-gradient(to bottom,rgba(0,0,0,0.05) 0%,rgba(0,0,0,0.55) 100%);"></div>
|
|
@else
|
|
<div style="position:absolute; inset:0; background:linear-gradient(160deg,{{ $churchColor }} 0%,{{ $churchColor }}bb 100%);"></div>
|
|
<div style="position:absolute; inset:0; opacity:0.07; background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='60' viewBox='0 0 60 60'%3E%3Cpath fill='white' d='M30 2v8m-4-4h8M8 58V26l22-16 22 16v32M22 58V44a8 8 0 0116 0v14M8 26H2M58 26h-6'/%3E%3C/svg%3E\"); background-size:60px 60px;"></div>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Logo: floats at cover/card seam --}}
|
|
<div style="position:absolute; bottom:-52px; left:50%; transform:translateX(-50%); z-index:10;">
|
|
@if($churchHasLogo)
|
|
<div style="width:104px; height:104px; border-radius:20px; background:#fff; box-shadow:0 8px 32px rgba(0,0,0,0.14); border:4px solid #fff; overflow:hidden; display:flex; align-items:center; justify-content:center;">
|
|
<img src="{{ $churchLogoUrl }}" alt="{{ $churchName }}"
|
|
class="public-brand-logo"
|
|
style="width:100%; height:100%; max-width:100%; object-fit:contain; padding:8px;">
|
|
</div>
|
|
@else
|
|
<div style="width:104px; height:104px; border-radius:20px; background:{{ $churchColor }}; box-shadow:0 8px 32px rgba(0,0,0,0.18); border:4px solid #fff; display:flex; align-items:center; justify-content:center; color:#fff; font-size:2rem; font-weight:900;">
|
|
{{ $churchInitials }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Floating card --}}
|
|
<div class="mx-auto max-w-sm px-4 space-y-4" style="margin-top:64px;">
|
|
<div class="overflow-hidden rounded-3xl bg-white shadow-xl">
|
|
|
|
{{-- Name + denomination + description --}}
|
|
<div class="px-6 pb-4 pt-8 text-center">
|
|
<h1 class="text-xl font-bold text-slate-900">{{ $churchName }}</h1>
|
|
@if(!empty($content['denomination']))
|
|
<p class="mt-1 text-sm font-medium" style="color:{{ $churchColor }};">{{ $content['denomination'] }}</p>
|
|
@endif
|
|
@if(!empty($content['description']))
|
|
<p class="mt-2 text-xs text-slate-500 leading-relaxed">{{ $content['description'] }}</p>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Service times badge --}}
|
|
@if(!empty($content['service_times']))
|
|
<div class="px-6 pb-3 text-center">
|
|
<span style="display:inline-flex; align-items:center; gap:5px; background:#f8fafc; border:1px solid #e2e8f0; border-radius:999px; padding:5px 12px; font-size:0.75rem; color:#475569; font-weight:500;">
|
|
<svg width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/></svg>
|
|
{{ $content['service_times'] }}
|
|
</span>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Compact icon action row --}}
|
|
@php $hasContact = !empty($content['phone']) || !empty($content['email']) || !empty($content['address']) || !empty($content['website']); @endphp
|
|
@if($hasContact)
|
|
<div style="display:flex; justify-content:center; gap:12px; padding:12px 24px 18px; border-top:1px solid #f1f5f9;">
|
|
@if(!empty($content['phone']))
|
|
<a href="tel:{{ $content['phone'] }}" title="{{ $content['phone'] }}"
|
|
style="width:46px; height:46px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:{{ $churchColor }}12; color:{{ $churchColor }}; text-decoration:none; transition:opacity 0.15s;" onmouseover="this.style.opacity='.75'" onmouseout="this.style.opacity='1'">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.6" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 0 0 2.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 0 1-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 0 0-1.091-.852H4.5A2.25 2.25 0 0 0 2.25 4.5v2.25Z"/></svg>
|
|
</a>
|
|
@endif
|
|
@if(!empty($content['email']))
|
|
<a href="mailto:{{ $content['email'] }}" title="{{ $content['email'] }}"
|
|
style="width:46px; height:46px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:{{ $churchColor }}12; color:{{ $churchColor }}; text-decoration:none; transition:opacity 0.15s;" onmouseover="this.style.opacity='.75'" onmouseout="this.style.opacity='1'">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.6" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75"/></svg>
|
|
</a>
|
|
@endif
|
|
@if(!empty($content['address']))
|
|
<a href="https://maps.google.com/?q={{ urlencode($content['address']) }}" target="_blank" rel="noopener" title="{{ $content['address'] }}"
|
|
style="width:46px; height:46px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:{{ $churchColor }}12; color:{{ $churchColor }}; text-decoration:none; transition:opacity 0.15s;" onmouseover="this.style.opacity='.75'" onmouseout="this.style.opacity='1'">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.6" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1 1 15 0Z"/></svg>
|
|
</a>
|
|
@endif
|
|
@if(!empty($content['website']))
|
|
<a href="{{ $content['website'] }}" target="_blank" rel="noopener" title="Visit website"
|
|
style="width:46px; height:46px; border-radius:50%; display:flex; align-items:center; justify-content:center; background:{{ $churchColor }}12; color:{{ $churchColor }}; text-decoration:none; transition:opacity 0.15s;" onmouseover="this.style.opacity='.75'" onmouseout="this.style.opacity='1'">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.6" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253M3 12a8.96 8.96 0 0 0 .735 3.547"/></svg>
|
|
</a>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
|
|
</div>
|
|
|
|
@if($churchAcceptsPay && !empty($churchTypes))
|
|
{{-- Brand color CSS variable so focus rings match --}}
|
|
<style>
|
|
.church-input:focus { outline: none; border-color: {{ $churchColor }} !important; box-shadow: 0 0 0 3px {{ $churchColor }}22; }
|
|
.church-quick:hover { border-color: {{ $churchColor }}66; }
|
|
.church-amount-carousel { scrollbar-width: none; -webkit-overflow-scrolling: touch; }
|
|
.church-amount-carousel::-webkit-scrollbar { display: none; }
|
|
</style>
|
|
|
|
{{-- Giving card --}}
|
|
<div class="overflow-hidden rounded-3xl bg-white shadow-xl">
|
|
|
|
{{-- Header --}}
|
|
<div class="px-6 py-6 text-center" style="background: linear-gradient(160deg, {{ $churchColor }}0d 0%, {{ $churchColor }}1f 100%); border-bottom: 1px solid {{ $churchColor }}22;">
|
|
<h2 class="text-lg font-bold text-slate-900">Give Online</h2>
|
|
<p class="mt-0.5 text-xs text-slate-500">Secured by Paystack. Powered by Ladill Pay</p>
|
|
</div>
|
|
|
|
<div class="px-5 pb-6 pt-5 space-y-5">
|
|
|
|
{{-- Collection type icon cards --}}
|
|
@php
|
|
// Keyed by lowercase label for case-insensitive lookup
|
|
$typeIconMap = [
|
|
'offering' => 'M21 11.25v8.25a1.5 1.5 0 0 1-1.5 1.5H5.25a1.5 1.5 0 0 1-1.5-1.5v-8.25M12 4.875A2.625 2.625 0 1 0 9.375 7.5H12m0-2.625V7.5m0-2.625A2.625 2.625 0 1 1 14.625 7.5H12m0 0V21m-8.625-9.75h18c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125h-18c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z',
|
|
'tithe' => 'M2.25 18.75a60.07 60.07 0 0 1 15.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 0 1 3 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 0 0-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 0 1-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 0 0 3 15h-.75M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm3 0h.008v.008H18V10.5Zm-12 0h.008v.008H6V10.5Z',
|
|
'donation' => 'M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z',
|
|
'harvest' => 'M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z',
|
|
'school fees' => 'M4.26 10.147a60.438 60.438 0 0 0-.491 6.347A48.63 48.63 0 0 1 12 20.904a48.63 48.63 0 0 1 8.232-4.41 60.46 60.46 0 0 0-.491-6.347m-15.482 0a50.57 50.57 0 0 0-2.658-.813A59.906 59.906 0 0 1 12 3.493a59.903 59.903 0 0 1 10.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.697 50.697 0 0 1 12 13.489a50.702 50.702 0 0 1 3.741-1.342M6.75 14.25v4.5',
|
|
'pta levy' => 'M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z',
|
|
'exam fees' => 'M16.862 4.487l1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10',
|
|
'development fund' => 'M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3.75h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Z',
|
|
'sadaqah' => 'M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z',
|
|
'zakat' => 'M2.25 18.75a60.07 60.07 0 0 1 15.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 0 1 3 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 0 0-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 0 1-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 0 0 3 15h-.75M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm3 0h.008v.008H18V10.5Zm-12 0h.008v.008H6V10.5Z',
|
|
"waqf" => 'M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3.75h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Z',
|
|
"jumu'ah" => 'M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z',
|
|
'general donation' => 'M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z',
|
|
'project fund' => 'M20.25 14.15v4.25c0 1.094-.787 2.036-1.872 2.18-2.087.277-4.216.42-6.378.42s-4.291-.143-6.378-.42c-1.085-.144-1.872-1.086-1.872-2.18v-4.25m16.5 0a2.18 2.18 0 0 0 .75-1.661V8.706c0-1.081-.768-2.015-1.837-2.175a48.114 48.114 0 0 0-3.413-.387m4.5 8.006c-.194.165-.42.295-.673.38A23.978 23.978 0 0 1 12 15.75c-2.648 0-5.195-.429-7.577-1.22a2.016 2.016 0 0 1-.673-.38m0 0A2.18 2.18 0 0 1 3 12.489V8.706c0-1.081.768-2.015 1.837-2.175a48.111 48.111 0 0 1 3.413-.387m7.5 0V5.25A2.25 2.25 0 0 0 13.5 3h-3a2.25 2.25 0 0 0-2.25 2.25v.894m7.5 0a48.667 48.667 0 0 0-7.5 0M12 12.75h.008v.008H12v-.008Z',
|
|
'emergency relief' => 'M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z',
|
|
'sponsorship' => 'M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z',
|
|
'membership dues' => 'M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z',
|
|
'event fund' => 'M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5',
|
|
];
|
|
$defaultIcon = 'M9.813 15.904 9 18.75l-.813-2.846a4.5 4.5 0 0 0-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 0 0 3.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 0 0 3.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 0 0-3.09 3.09Z';
|
|
// Resolve icon for each type by lowercase key
|
|
$typeIcons = [];
|
|
foreach ($churchTypeLabels as $key => $label) {
|
|
$typeIcons[$key] = $typeIconMap[strtolower($label)] ?? $defaultIcon;
|
|
}
|
|
@endphp
|
|
@if(count($churchTypeLabels) > 1)
|
|
<div style="border-radius:24px; background:linear-gradient(180deg, #ffffff 0%, #f8fafc 100%); border:1px solid #eef2f7; padding:14px; box-shadow:0 14px 36px rgba(15,23,42,0.06);">
|
|
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:12px;">
|
|
<div>
|
|
<p class="text-[10px] font-bold uppercase tracking-widest" style="color:{{ $churchColor }}">I'm giving for</p>
|
|
<p style="margin-top:3px; font-size:0.75rem; color:#94a3b8;">Choose a giving category</p>
|
|
</div>
|
|
<span style="display:flex; height:34px; width:34px; flex-shrink:0; align-items:center; justify-content:center; border-radius:999px; background:{{ $churchColor }}12; color:{{ $churchColor }};">
|
|
<svg width="17" height="17" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21s8.25-4.875 8.25-11.25A4.875 4.875 0 0 0 12 6.227 4.875 4.875 0 0 0 3.75 9.75C3.75 16.125 12 21 12 21Z"/>
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
<div style="display:flex; flex-direction:column; gap:8px;">
|
|
@foreach($churchTypeLabels as $ct => $ctLabel)
|
|
@php $iconPath = $typeIcons[$ct] ?? $defaultIcon; @endphp
|
|
<button type="button"
|
|
@click="collectionType = @js($ct)"
|
|
:style="collectionType === @js($ct)
|
|
? 'display:flex;align-items:center;gap:12px;width:100%;border:1.5px solid {{ $churchColor }};border-radius:16px;padding:12px 14px;cursor:pointer;transition:all 0.2s ease;text-align:left;outline:none;overflow:hidden;background:linear-gradient(135deg, {{ $churchColor }}15 0%, #ffffff 70%);box-shadow:0 4px 14px {{ $churchColor }}20;'
|
|
: 'display:flex;align-items:center;gap:12px;width:100%;border:1.5px solid #edf0f7;border-radius:16px;padding:12px 14px;cursor:pointer;transition:all 0.2s ease;text-align:left;outline:none;overflow:hidden;background:#fafbfc;'">
|
|
{{-- Icon --}}
|
|
<span :style="collectionType === @js($ct)
|
|
? 'flex-shrink:0;display:flex;height:38px;width:38px;align-items:center;justify-content:center;border-radius:12px;background:{{ $churchColor }}18;transition:color 0.2s;color:{{ $churchColor }};'
|
|
: 'flex-shrink:0;display:flex;height:38px;width:38px;align-items:center;justify-content:center;border-radius:12px;background:#f1f5f9;transition:color 0.2s;color:#94a3b8;'">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.6" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="{{ $iconPath }}"/>
|
|
</svg>
|
|
</span>
|
|
{{-- Label — single line, ellipsis on overflow --}}
|
|
<span :style="collectionType === @js($ct)
|
|
? 'flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.875rem;font-weight:700;transition:color 0.2s;color:{{ $churchColor }};'
|
|
: 'flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.875rem;font-weight:600;transition:color 0.2s;color:#475569;'">
|
|
{{ $ctLabel }}
|
|
</span>
|
|
{{-- Selected indicator --}}
|
|
<span x-show="collectionType === @js($ct)" x-cloak
|
|
style="flex-shrink:0;width:22px;height:22px;color:{{ $churchColor }};">
|
|
<svg viewBox="0 0 24 24" fill="currentColor" style="display:block;width:100%;height:100%;">
|
|
<path d="m12,0C5.383,0,0,5.383,0,12s5.383,12,12,12,12-5.383,12-12S18.617,0,12,0Zm-.091,15.419c-.387.387-.896.58-1.407.58s-1.025-.195-1.416-.585l-2.782-2.696,1.393-1.437,2.793,2.707,5.809-5.701,1.404,1.425-5.793,5.707Z"/>
|
|
</svg>
|
|
</span>
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Quick amounts carousel — shows 2 full chips + peek at 3rd --}}
|
|
<div>
|
|
<p class="mb-2.5 text-[10px] font-bold uppercase tracking-widest" style="color:{{ $churchColor }}">Quick amount</p>
|
|
<div class="church-amount-carousel"
|
|
style="display:grid; grid-auto-flow:column; grid-auto-columns:calc((100% - 20px) / 2.5); gap:10px; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:2px;">
|
|
@foreach([10, 20, 50, 100, 200] as $qa)
|
|
<button type="button"
|
|
@click="amount = '{{ $qa }}'"
|
|
:style="amount == '{{ $qa }}'
|
|
? 'scroll-snap-align:start;border-radius:16px;border:1.5px solid {{ $churchColor }};padding:14px 10px;font-size:0.95rem;font-weight:800;text-align:center;transition:all 0.15s;white-space:nowrap;background:{{ $churchColor }};color:#fff;box-shadow:0 4px 12px {{ $churchColor }}40;'
|
|
: 'scroll-snap-align:start;border-radius:16px;border:1.5px solid #e2e8f0;padding:14px 10px;font-size:0.95rem;font-weight:700;text-align:center;transition:all 0.15s;white-space:nowrap;background:#fff;color:#475569;'">
|
|
{{ $churchCurrency }} {{ $qa }}
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Amount input — inline styles to prevent overflow on mobile --}}
|
|
<div>
|
|
<p class="mb-2.5 text-[10px] font-bold uppercase tracking-widest" style="color:{{ $churchColor }}">Or enter amount</p>
|
|
<div style="display:flex; align-items:center; border-radius:16px; border:1.5px solid #e2e8f0; background:#f8fafc; padding:12px 16px; box-sizing:border-box; width:100%;">
|
|
<span style="flex-shrink:0; font-size:0.875rem; font-weight:700; color:#94a3b8; margin-right:8px;">{{ $churchCurrency }}</span>
|
|
<input type="number" x-model="amount" inputmode="decimal" min="1" step="0.01" placeholder="0.00"
|
|
class="church-input"
|
|
style="flex:1; min-width:0; width:0; background:transparent; border:none; outline:none; font-size:1.75rem; font-weight:800; color:#0f172a; font-family:inherit;"
|
|
@focus="$el.parentElement.style.borderColor='{{ $churchColor }}'; $el.parentElement.style.boxShadow='0 0 0 3px {{ $churchColor }}22'"
|
|
@blur="$el.parentElement.style.borderColor='#e2e8f0'; $el.parentElement.style.boxShadow='none'">
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Contact fields --}}
|
|
<div class="space-y-2.5">
|
|
<p class="text-[10px] font-bold uppercase tracking-widest" style="color:{{ $churchColor }}">Your details</p>
|
|
<input type="text" x-model="name" placeholder="Full name *" autocomplete="name"
|
|
class="church-input"
|
|
style="display:block; width:100%; box-sizing:border-box; border-radius:14px; border:1.5px solid #e2e8f0; padding:12px 16px; font-size:0.875rem; color:#0f172a; background:#fff;">
|
|
<input type="tel" x-model="phone" placeholder="Phone number *" autocomplete="tel"
|
|
class="church-input"
|
|
style="display:block; width:100%; box-sizing:border-box; border-radius:14px; border:1.5px solid #e2e8f0; padding:12px 16px; font-size:0.875rem; color:#0f172a; background:#fff;">
|
|
</div>
|
|
|
|
{{-- Error --}}
|
|
<div x-show="errorMsg" x-cloak
|
|
class="rounded-xl border border-red-100 bg-red-50 px-4 py-3 text-sm text-red-600"
|
|
x-text="errorMsg"></div>
|
|
|
|
{{-- Give button --}}
|
|
<button type="button" @click="give()" :disabled="loading"
|
|
class="flex w-full items-center justify-center gap-2 rounded-2xl py-4 text-base font-bold text-white transition disabled:opacity-60 active:scale-[0.98]"
|
|
style="background:{{ $churchColor }}; box-shadow:0 8px 24px {{ $churchColor }}44;">
|
|
<svg x-show="loading" x-cloak class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
|
</svg>
|
|
<span x-text="loading ? 'Processing…' : ('Give ' + @js($churchCurrency) + ' ' + (parseFloat(amount)||0).toFixed(2))">Give</span>
|
|
</button>
|
|
|
|
<p class="text-center text-[11px] text-slate-400">Your giving is safe & secure</p>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@include('partials.paystack-sheet', [
|
|
'audience' => 'buyer',
|
|
'sheetTitle' => 'Complete your gift',
|
|
'sheetSubtitle' => 'Pay securely. You\'ll get a confirmation when it succeeds.',
|
|
])
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ===== EVENT ===== --}}
|
|
@elseif($type === \App\Models\QrCode::TYPE_EVENT)
|
|
@php
|
|
$evColor = $content['brand_color'] ?? '#4f46e5';
|
|
$evHasLogo = !empty($content['logo_path']);
|
|
$evHasCover = !empty($content['cover_path']);
|
|
$evLogoUrl = $evHasLogo ? $qrCode->publicPath('event-logo') : null;
|
|
$evCoverUrl = $evHasCover ? $qrCode->publicPath('event-cover') : null;
|
|
$evName = $content['name'] ?? $qrCode->label;
|
|
$evInitials = strtoupper(mb_substr($evName, 0, 2));
|
|
$evTiers = $content['tiers'] ?? [];
|
|
$evCurrency = $content['currency'] ?? 'GHS';
|
|
$evFields = $content['badge_fields'] ?? [];
|
|
$evOpen = (bool) ($content['registration_open'] ?? true);
|
|
$evMode = in_array($content['mode'] ?? 'ticketing', ['contributions', 'free'], true) ? ($content['mode'] ?? 'ticketing') : 'ticketing';
|
|
$evCategories = $content['contribution_categories'] ?? ['Contribution'];
|
|
$evHeading = $evMode === 'contributions' ? 'Make a contribution' : 'Register';
|
|
$evRegRoute = $qrCode->publicPath('register');
|
|
$evCsrf = csrf_token();
|
|
$evMeetReturn = trim((string) ($meetReturn ?? ''));
|
|
@endphp
|
|
<div x-data="{
|
|
mode: @js($evMode),
|
|
tier: @js($evMode === 'contributions' ? ($evCategories[0] ?? '') : ($evTiers[0]['name'] ?? '')),
|
|
tierPrice: @js((float) ($evTiers[0]['price'] ?? 0)),
|
|
amount: '',
|
|
name: '', email: '', phone: '',
|
|
fields: {},
|
|
meetReturn: @js($evMeetReturn),
|
|
loading: false, errorMsg: '',
|
|
showSheet: false, checkoutUrl: '',
|
|
selectTier(n, p) { this.tier = n; this.tierPrice = p; },
|
|
selectCategory(n) { this.tier = n; },
|
|
async submit() {
|
|
if (!this.tier) { this.errorMsg = this.mode === 'contributions' ? 'Select a category.' : 'Select a ticket type.'; return; }
|
|
if (this.mode === 'contributions' && !(parseFloat(this.amount) > 0)) { this.errorMsg = 'Enter a contribution amount.'; return; }
|
|
if (!this.name.trim()) { this.errorMsg = 'Enter your full name.'; return; }
|
|
if (!this.email.trim() || !this.email.includes('@')) { this.errorMsg = 'Enter a valid email address.'; return; }
|
|
if (!this.phone.trim()) { this.errorMsg = 'Enter your phone number.'; return; }
|
|
this.errorMsg = ''; this.loading = true; window.LadillPayCheckout?.prepare?.();
|
|
try {
|
|
const res = await fetch(@js($evRegRoute), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': @js($evCsrf) },
|
|
body: JSON.stringify({ tier: this.tier, amount: this.mode === 'contributions' ? this.amount : null, attendee_name: this.name, attendee_email: this.email, attendee_phone: this.phone, badge_fields: this.fields, meet_return: this.meetReturn || null }),
|
|
});
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) {
|
|
this.errorMsg = data.error || data.message || 'Registration failed. Please try again.';
|
|
this.loading = false;
|
|
return;
|
|
}
|
|
if (data.error) { window.LadillPayCheckout?.cancel?.(); this.errorMsg = data.error; this.loading = false; return; }
|
|
if (data.paid) {
|
|
if (!data.checkout_url) { this.errorMsg = 'Could not start checkout. Please try again.'; this.loading = false; return; }
|
|
this.checkoutUrl = data.checkout_url;
|
|
this.accessCode = data.access_code || '';
|
|
this.publicKey = data.public_key || '';
|
|
this.returnUrl = data.callback_url || ''; this.showSheet = true; // loading cleared when payment UI opens (ladill-pay-opened)
|
|
return;
|
|
}
|
|
if (data.success_url) { window.location.href = data.success_url; return; }
|
|
this.errorMsg = 'Registration failed. Please try again.';
|
|
this.loading = false;
|
|
} catch(e) { window.LadillPayCheckout?.cancel?.(); this.errorMsg = 'Network error. Please try again.'; this.loading = false; }
|
|
}
|
|
}" class="min-h-screen bg-slate-50 pb-12">
|
|
|
|
{{-- Hero --}}
|
|
<div class="relative overflow-hidden" style="min-height:230px;">
|
|
@if($evHasCover)
|
|
<img src="{{ $evCoverUrl }}" alt="{{ $evName }}" class="absolute inset-0 h-full w-full object-cover">
|
|
<div class="absolute inset-0" style="background:linear-gradient(to bottom, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.72) 100%);"></div>
|
|
@else
|
|
<div class="absolute inset-0" style="background:linear-gradient(150deg, {{ $evColor }} 0%, {{ $evColor }}cc 100%);"></div>
|
|
@endif
|
|
<div class="relative flex min-h-[230px] flex-col items-center justify-end px-6 pb-7 text-center text-white">
|
|
@if($evHasLogo)
|
|
<img src="{{ $evLogoUrl }}" alt="{{ $evName }}" class="public-brand-logo mb-3 h-16 w-16 max-w-[50vw] rounded-2xl bg-white object-contain p-1 shadow-lg ring-2 ring-white/40">
|
|
@else
|
|
<div class="mb-3 flex h-16 w-16 items-center justify-center rounded-2xl text-xl font-black text-white shadow-lg ring-2 ring-white/40" style="background:rgba(255,255,255,0.18); backdrop-filter:blur(6px);">{{ $evInitials }}</div>
|
|
@endif
|
|
<h1 class="text-2xl font-black leading-tight drop-shadow">{{ $evName }}</h1>
|
|
@if(!empty($content['tagline']))<p class="mt-1 text-sm font-medium text-white/85">{{ $content['tagline'] }}</p>@endif
|
|
<div class="mt-3 flex flex-wrap items-center justify-center gap-2 text-xs">
|
|
@if(!empty($content['starts_at']))
|
|
<span class="inline-flex items-center gap-1.5 rounded-full bg-white/20 px-3 py-1 font-semibold backdrop-blur-sm">
|
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5"/></svg>
|
|
{{ \App\Support\Qr\QrDateFormatter::forDisplay($content['starts_at']) }}
|
|
</span>
|
|
@endif
|
|
@if(!empty($content['location']))
|
|
<span class="inline-flex items-center gap-1.5 rounded-full bg-white/20 px-3 py-1 font-semibold backdrop-blur-sm">
|
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1 1 15 0Z"/></svg>
|
|
{{ $content['location'] }}
|
|
</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mx-auto max-w-md px-4">
|
|
@if(!empty($content['description']))
|
|
<p class="mt-6 text-center text-sm leading-relaxed text-slate-600">{{ $content['description'] }}</p>
|
|
@endif
|
|
|
|
@php
|
|
$evFormat = $content['format'] ?? 'in_person';
|
|
$meetAccess = app(\App\Services\Meet\EventMeetAccessService::class);
|
|
$evVirtualSessions = collect($content['virtual_sessions'] ?? [])
|
|
->filter(fn ($s) => is_array($s) && ! empty($s['join_url']));
|
|
@endphp
|
|
@if(in_array($evFormat, ['virtual', 'hybrid'], true) && $evVirtualSessions->isNotEmpty())
|
|
<div class="mt-6 space-y-3">
|
|
<h2 class="text-center text-sm font-bold text-slate-900">Virtual sessions</h2>
|
|
@foreach($evVirtualSessions as $session)
|
|
@php $windowOpen = $meetAccess->isJoinWindowOpen($session); @endphp
|
|
@if($windowOpen)
|
|
<a href="{{ $session['join_url'] }}" target="_blank" rel="noopener"
|
|
class="block rounded-2xl border border-indigo-100 bg-white px-4 py-3 shadow-sm transition hover:border-indigo-300">
|
|
<p class="font-semibold text-slate-900">{{ $session['title'] ?? 'Join session' }}</p>
|
|
@if(!empty($session['scheduled_at']))
|
|
<p class="mt-1 text-xs text-slate-500">{{ \App\Support\Qr\QrDateFormatter::forDisplay($session['scheduled_at']) }}</p>
|
|
@endif
|
|
@if(($session['meet_status'] ?? '') === 'live')
|
|
<p class="mt-1 text-xs font-semibold text-emerald-600">Live now</p>
|
|
@endif
|
|
<p class="mt-2 text-xs font-semibold text-indigo-600">Join virtual session →</p>
|
|
</a>
|
|
@else
|
|
<div class="block rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3">
|
|
<p class="font-semibold text-slate-700">{{ $session['title'] ?? 'Virtual session' }}</p>
|
|
@if(!empty($session['scheduled_at']))
|
|
<p class="mt-1 text-xs text-slate-500">{{ \App\Support\Qr\QrDateFormatter::forDisplay($session['scheduled_at']) }}</p>
|
|
@endif
|
|
<p class="mt-2 text-xs text-amber-700">Join opens 15 minutes before the session starts.</p>
|
|
</div>
|
|
@endif
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
@if(! $evOpen)
|
|
<div class="mt-6 rounded-2xl border border-amber-200 bg-amber-50 px-5 py-4 text-center text-sm font-medium text-amber-700">{{ $evMode === 'contributions' ? 'Contributions are currently closed.' : 'Registration is currently closed.' }}</div>
|
|
@else
|
|
<div class="mt-6 overflow-hidden rounded-3xl bg-white shadow-xl">
|
|
<div class="px-5 py-4 text-center" style="background:linear-gradient(160deg, {{ $evColor }}0d, {{ $evColor }}1f); border-bottom:1px solid {{ $evColor }}22;">
|
|
<h2 class="text-base font-bold text-slate-900">{{ $evHeading }}</h2>
|
|
</div>
|
|
<div class="px-5 pb-6 pt-5 space-y-5">
|
|
|
|
{{-- Ticket tiers --}}
|
|
@if($evMode === 'ticketing' && count($evTiers) > 0)
|
|
<div style="border-radius:24px; background:linear-gradient(180deg, #ffffff 0%, #f8fafc 100%); border:1px solid #eef2f7; padding:14px; box-shadow:0 14px 36px rgba(15,23,42,0.06);">
|
|
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:12px;">
|
|
<div>
|
|
<p class="text-[10px] font-bold uppercase tracking-widest" style="color:{{ $evColor }}">Ticket</p>
|
|
<p style="margin-top:3px; font-size:0.75rem; color:#94a3b8;">Choose a ticket type</p>
|
|
</div>
|
|
<span style="display:flex; height:34px; width:34px; flex-shrink:0; align-items:center; justify-content:center; border-radius:999px; background:{{ $evColor }}12; color:{{ $evColor }};">
|
|
<svg width="17" height="17" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 0 1 0 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 0 1 0-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375Z"/>
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
<div style="display:flex; flex-direction:column; gap:8px;">
|
|
@foreach($evTiers as $t)
|
|
@php $tPrice = (float) ($t['price'] ?? 0); @endphp
|
|
<button type="button"
|
|
@click="selectTier(@js($t['name']), {{ $tPrice }})"
|
|
:style="tier === @js($t['name'])
|
|
? 'display:flex;align-items:center;gap:12px;width:100%;border:1.5px solid {{ $evColor }};border-radius:16px;padding:12px 14px;cursor:pointer;transition:all 0.2s ease;text-align:left;outline:none;overflow:hidden;background:linear-gradient(135deg, {{ $evColor }}15 0%, #ffffff 70%);box-shadow:0 4px 14px {{ $evColor }}20;'
|
|
: 'display:flex;align-items:center;gap:12px;width:100%;border:1.5px solid #edf0f7;border-radius:16px;padding:12px 14px;cursor:pointer;transition:all 0.2s ease;text-align:left;outline:none;overflow:hidden;background:#fafbfc;'">
|
|
{{-- Icon --}}
|
|
<span :style="tier === @js($t['name'])
|
|
? 'flex-shrink:0;display:flex;height:38px;width:38px;align-items:center;justify-content:center;border-radius:12px;background:{{ $evColor }}18;transition:color 0.2s;color:{{ $evColor }};'
|
|
: 'flex-shrink:0;display:flex;height:38px;width:38px;align-items:center;justify-content:center;border-radius:12px;background:#f1f5f9;transition:color 0.2s;color:#94a3b8;'">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.6" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 6v.75m0 3v.75m0 3v.75m0 3V18m-9-5.25h5.25M7.5 15h3M3.375 5.25c-.621 0-1.125.504-1.125 1.125v3.026a2.999 2.999 0 0 1 0 5.198v3.026c0 .621.504 1.125 1.125 1.125h17.25c.621 0 1.125-.504 1.125-1.125v-3.026a2.999 2.999 0 0 1 0-5.198V6.375c0-.621-.504-1.125-1.125-1.125H3.375Z"/>
|
|
</svg>
|
|
</span>
|
|
{{-- Name + price --}}
|
|
<span style="flex:1;min-width:0;display:flex;flex-direction:column;gap:1px;">
|
|
<span :style="tier === @js($t['name'])
|
|
? 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.875rem;font-weight:700;transition:color 0.2s;color:{{ $evColor }};'
|
|
: 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.875rem;font-weight:600;transition:color 0.2s;color:#475569;'">
|
|
{{ $t['name'] }}
|
|
</span>
|
|
<span style="font-size:0.75rem;font-weight:700;color:#94a3b8;">{{ $tPrice > 0 ? $evCurrency . ' ' . number_format($tPrice, 2) : 'Free' }}</span>
|
|
</span>
|
|
{{-- Selected indicator --}}
|
|
<span x-show="tier === @js($t['name'])" x-cloak
|
|
style="flex-shrink:0;width:22px;height:22px;color:{{ $evColor }};">
|
|
<svg viewBox="0 0 24 24" fill="currentColor" style="display:block;width:100%;height:100%;">
|
|
<path d="m12,0C5.383,0,0,5.383,0,12s5.383,12,12,12,12-5.383,12-12S18.617,0,12,0Zm-.091,15.419c-.387.387-.896.58-1.407.58s-1.025-.195-1.416-.585l-2.782-2.696,1.393-1.437,2.793,2.707,5.809-5.701,1.404,1.425-5.793,5.707Z"/>
|
|
</svg>
|
|
</span>
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Contribution categories + amount --}}
|
|
@if($evMode === 'contributions')
|
|
@if(count($evCategories) > 1)
|
|
<div style="border-radius:24px; background:linear-gradient(180deg, #ffffff 0%, #f8fafc 100%); border:1px solid #eef2f7; padding:14px; box-shadow:0 14px 36px rgba(15,23,42,0.06);">
|
|
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:12px;">
|
|
<div>
|
|
<p class="text-[10px] font-bold uppercase tracking-widest" style="color:{{ $evColor }}">I'm giving for</p>
|
|
<p style="margin-top:3px; font-size:0.75rem; color:#94a3b8;">Choose a category</p>
|
|
</div>
|
|
<span style="display:flex; height:34px; width:34px; flex-shrink:0; align-items:center; justify-content:center; border-radius:999px; background:{{ $evColor }}12; color:{{ $evColor }};">
|
|
<svg width="17" height="17" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21 11.25v8.25a1.5 1.5 0 0 1-1.5 1.5H5.25a1.5 1.5 0 0 1-1.5-1.5v-8.25M12 4.875A2.625 2.625 0 1 0 9.375 7.5H12m0-2.625V7.5m0-2.625A2.625 2.625 0 1 1 14.625 7.5H12m0 0V21m-8.625-9.75h18c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125h-18c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z"/>
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
<div style="display:flex; flex-direction:column; gap:8px;">
|
|
@foreach($evCategories as $cat)
|
|
<button type="button"
|
|
@click="selectCategory(@js($cat))"
|
|
:style="tier === @js($cat)
|
|
? 'display:flex;align-items:center;gap:12px;width:100%;border:1.5px solid {{ $evColor }};border-radius:16px;padding:12px 14px;cursor:pointer;transition:all 0.2s ease;text-align:left;outline:none;overflow:hidden;background:linear-gradient(135deg, {{ $evColor }}15 0%, #ffffff 70%);box-shadow:0 4px 14px {{ $evColor }}20;'
|
|
: 'display:flex;align-items:center;gap:12px;width:100%;border:1.5px solid #edf0f7;border-radius:16px;padding:12px 14px;cursor:pointer;transition:all 0.2s ease;text-align:left;outline:none;overflow:hidden;background:#fafbfc;'">
|
|
<span :style="tier === @js($cat)
|
|
? 'flex-shrink:0;display:flex;height:38px;width:38px;align-items:center;justify-content:center;border-radius:12px;background:{{ $evColor }}18;color:{{ $evColor }};'
|
|
: 'flex-shrink:0;display:flex;height:38px;width:38px;align-items:center;justify-content:center;border-radius:12px;background:#f1f5f9;color:#94a3b8;'">
|
|
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.6" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21 11.25v8.25a1.5 1.5 0 0 1-1.5 1.5H5.25a1.5 1.5 0 0 1-1.5-1.5v-8.25M12 4.875A2.625 2.625 0 1 0 9.375 7.5H12m0-2.625V7.5m0-2.625A2.625 2.625 0 1 1 14.625 7.5H12m0 0V21m-8.625-9.75h18c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125h-18c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z"/>
|
|
</svg>
|
|
</span>
|
|
<span :style="tier === @js($cat)
|
|
? 'flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.875rem;font-weight:700;color:{{ $evColor }};'
|
|
: 'flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:0.875rem;font-weight:600;color:#475569;'">
|
|
{{ $cat }}
|
|
</span>
|
|
<span x-show="tier === @js($cat)" x-cloak style="flex-shrink:0;width:22px;height:22px;color:{{ $evColor }};">
|
|
<svg viewBox="0 0 24 24" fill="currentColor" style="display:block;width:100%;height:100%;">
|
|
<path d="m12,0C5.383,0,0,5.383,0,12s5.383,12,12,12,12-5.383,12-12S18.617,0,12,0Zm-.091,15.419c-.387.387-.896.58-1.407.58s-1.025-.195-1.416-.585l-2.782-2.696,1.393-1.437,2.793,2.707,5.809-5.701,1.404,1.425-5.793,5.707Z"/>
|
|
</svg>
|
|
</span>
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Quick amounts --}}
|
|
<div>
|
|
<p class="mb-2.5 text-[10px] font-bold uppercase tracking-widest" style="color:{{ $evColor }}">Quick amount</p>
|
|
<div style="display:grid; grid-auto-flow:column; grid-auto-columns:calc((100% - 20px) / 2.5); gap:10px; overflow-x:auto; scroll-snap-type:x mandatory; padding-bottom:2px;">
|
|
@foreach([50, 100, 200, 500, 1000] as $qa)
|
|
<button type="button"
|
|
@click="amount = '{{ $qa }}'"
|
|
:style="amount == '{{ $qa }}'
|
|
? 'scroll-snap-align:start;border-radius:16px;border:1.5px solid {{ $evColor }};padding:14px 10px;font-size:0.95rem;font-weight:800;text-align:center;white-space:nowrap;background:{{ $evColor }};color:#fff;box-shadow:0 4px 12px {{ $evColor }}40;'
|
|
: 'scroll-snap-align:start;border-radius:16px;border:1.5px solid #e2e8f0;padding:14px 10px;font-size:0.95rem;font-weight:700;text-align:center;white-space:nowrap;background:#fff;color:#475569;'">
|
|
{{ $evCurrency }} {{ $qa }}
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Custom amount --}}
|
|
<div>
|
|
<p class="mb-2.5 text-[10px] font-bold uppercase tracking-widest" style="color:{{ $evColor }}">Or enter amount</p>
|
|
<div style="display:flex; align-items:center; border-radius:16px; border:1.5px solid #e2e8f0; background:#f8fafc; padding:12px 16px; box-sizing:border-box; width:100%;">
|
|
<span style="flex-shrink:0; font-size:0.875rem; font-weight:700; color:#94a3b8; margin-right:8px;">{{ $evCurrency }}</span>
|
|
<input type="number" x-model="amount" inputmode="decimal" min="1" step="0.01" placeholder="0.00"
|
|
style="flex:1; min-width:0; width:0; background:transparent; border:none; outline:none; font-size:1.75rem; font-weight:800; color:#0f172a; font-family:inherit;"
|
|
@focus="$el.parentElement.style.borderColor='{{ $evColor }}'; $el.parentElement.style.boxShadow='0 0 0 3px {{ $evColor }}22'"
|
|
@blur="$el.parentElement.style.borderColor='#e2e8f0'; $el.parentElement.style.boxShadow='none'">
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Attendee details --}}
|
|
<div class="space-y-2.5">
|
|
<p class="text-[10px] font-bold uppercase tracking-widest" style="color:{{ $evColor }}">Your details</p>
|
|
<input type="text" x-model="name" placeholder="Full name *" autocomplete="name"
|
|
class="w-full rounded-xl border border-slate-200 px-4 py-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-1">
|
|
<input type="email" x-model="email" placeholder="Email address *" autocomplete="email"
|
|
class="w-full rounded-xl border border-slate-200 px-4 py-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-1">
|
|
<input type="tel" x-model="phone" placeholder="Phone number *" autocomplete="tel"
|
|
class="w-full rounded-xl border border-slate-200 px-4 py-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-1">
|
|
@foreach($evFields as $field)
|
|
<input type="text" x-model="fields[@js($field)]" placeholder="{{ $field }}"
|
|
class="w-full rounded-xl border border-slate-200 px-4 py-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-1">
|
|
@endforeach
|
|
</div>
|
|
|
|
<div x-show="errorMsg" x-cloak class="rounded-xl border border-red-100 bg-red-50 px-4 py-3 text-sm text-red-600" x-text="errorMsg"></div>
|
|
|
|
<button type="button" @click="submit()" :disabled="loading"
|
|
class="flex w-full items-center justify-center gap-2 rounded-2xl py-4 text-base font-bold text-white transition disabled:opacity-60 active:scale-[0.98]"
|
|
style="background:{{ $evColor }}; box-shadow:0 8px 24px {{ $evColor }}44;">
|
|
<svg x-show="loading" x-cloak class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>
|
|
<span x-text="loading ? 'Processing…' : (mode === 'contributions' ? ('Contribute ' + @js($evCurrency) + ' ' + ((parseFloat(amount)||0).toFixed(2))) : (tierPrice > 0 ? ('Pay ' + @js($evCurrency) + ' ' + tierPrice.toFixed(2)) : 'Register free'))">{{ $evHeading }}</span>
|
|
</button>
|
|
<p class="text-center text-[11px] text-slate-400">{{ $evMode === 'contributions' ? 'Your contribution is safe & secure.' : 'A badge will be issued on registration.' }}</p>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
@include('partials.paystack-sheet', [
|
|
'audience' => 'buyer',
|
|
'sheetTitle' => $evMode === 'contributions' ? 'Complete your contribution' : 'Complete your ticket payment',
|
|
'sheetSubtitle' => $evMode === 'contributions'
|
|
? 'Pay securely for your contribution. You\'ll get a confirmation when it succeeds.'
|
|
: 'Pay securely for your ticket. You\'ll get a confirmation when it succeeds.',
|
|
])
|
|
</div>
|
|
|
|
{{-- ===== ITINERARY ===== --}}
|
|
@elseif($type === \App\Models\QrCode::TYPE_ITINERARY)
|
|
@php
|
|
$itinColor = $content['brand_color'] ?? '#b45309';
|
|
$itinHasCover = !empty($content['cover_path']);
|
|
$itinCoverUrl = $itinHasCover ? $qrCode->publicPath('itinerary-cover') : null;
|
|
$itinTitle = $content['title'] ?? $qrCode->label;
|
|
$itinDays = $content['days'] ?? [];
|
|
$multiDay = count($itinDays) > 1;
|
|
@endphp
|
|
<div class="min-h-screen bg-slate-50 pb-12">
|
|
|
|
{{-- Hero --}}
|
|
<div class="relative overflow-hidden" style="min-height:240px;">
|
|
@if($itinHasCover)
|
|
<img src="{{ $itinCoverUrl }}" alt="{{ $itinTitle }}" class="absolute inset-0 h-full w-full object-cover">
|
|
<div class="absolute inset-0" style="background:linear-gradient(to bottom, rgba(0,0,0,0.25) 0%, rgba(0,0,0,0.7) 100%);"></div>
|
|
@else
|
|
<div class="absolute inset-0" style="background:linear-gradient(150deg, {{ $itinColor }} 0%, {{ $itinColor }}cc 100%);"></div>
|
|
<div class="absolute inset-0 opacity-10" style="background-image:radial-gradient(circle at 20% 30%, white 1px, transparent 1px); background-size:34px 34px;"></div>
|
|
@endif
|
|
<div class="relative flex min-h-[240px] flex-col items-center justify-end px-6 pb-7 text-center text-white">
|
|
<h1 class="text-3xl font-black leading-tight drop-shadow">{{ $itinTitle }}</h1>
|
|
@if(!empty($content['subtitle']))
|
|
<p class="mt-1.5 text-sm font-medium text-white/85">{{ $content['subtitle'] }}</p>
|
|
@endif
|
|
<div class="mt-3 flex flex-wrap items-center justify-center gap-2 text-xs">
|
|
@if(!empty($content['event_date']))
|
|
<span class="inline-flex items-center gap-1.5 rounded-full bg-white/20 px-3 py-1 font-semibold backdrop-blur-sm">
|
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5"/></svg>
|
|
{{ \App\Support\Qr\QrDateFormatter::forDisplay($content['event_date']) }}
|
|
</span>
|
|
@endif
|
|
@if(!empty($content['location']))
|
|
<span class="inline-flex items-center gap-1.5 rounded-full bg-white/20 px-3 py-1 font-semibold backdrop-blur-sm">
|
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1 1 15 0Z"/></svg>
|
|
{{ $content['location'] }}
|
|
</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mx-auto max-w-xl px-4">
|
|
@if(!empty($content['description']))
|
|
<p class="mt-6 text-center text-sm leading-relaxed text-slate-600">{{ $content['description'] }}</p>
|
|
@endif
|
|
|
|
{{-- Programme --}}
|
|
<div class="mt-6 space-y-7" x-data="{ active: 0 }">
|
|
@if($multiDay)
|
|
<div class="flex gap-2 overflow-x-auto pb-1" style="scrollbar-width:none;">
|
|
@foreach($itinDays as $di => $day)
|
|
<button type="button" @click="active = {{ $di }}"
|
|
:style="active === {{ $di }} ? 'background:{{ $itinColor }};color:#fff;' : 'background:#fff;color:#475569;'"
|
|
class="shrink-0 rounded-full border border-slate-200 px-4 py-1.5 text-xs font-bold transition">
|
|
{{ $day['label'] ?: ('Day ' . ($di + 1)) }}
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
@foreach($itinDays as $di => $day)
|
|
<div {!! $multiDay ? 'x-show="active === ' . $di . '"' : '' !!}>
|
|
<div class="mb-3 flex items-baseline justify-between">
|
|
<h2 class="text-base font-bold text-slate-900">{{ $day['label'] ?: ('Day ' . ($di + 1)) }}</h2>
|
|
@if(!empty($day['date']))<span class="text-xs font-medium text-slate-400">{{ \App\Support\Qr\QrDateFormatter::forDisplay($day['date']) }}</span>@endif
|
|
</div>
|
|
|
|
<div class="relative space-y-0">
|
|
@foreach($day['items'] ?? [] as $ii => $item)
|
|
<div class="relative flex gap-3.5 pb-5 last:pb-0">
|
|
{{-- Timeline rail --}}
|
|
<div class="flex flex-col items-center">
|
|
<span class="mt-1 flex h-3 w-3 shrink-0 rounded-full ring-4 ring-white" style="background:{{ $itinColor }};"></span>
|
|
@if(! $loop->last)<span class="w-px flex-1" style="background:{{ $itinColor }}33;"></span>@endif
|
|
</div>
|
|
<div class="flex-1 -mt-0.5 pb-1">
|
|
@if(!empty($item['time']))
|
|
<p class="text-xs font-bold" style="color:{{ $itinColor }};">{{ $item['time'] }}</p>
|
|
@endif
|
|
<p class="text-sm font-semibold text-slate-900 leading-snug">{{ $item['title'] }}</p>
|
|
@if(!empty($item['host']) || !empty($item['location']))
|
|
<p class="mt-0.5 text-xs text-slate-500">
|
|
{{ $item['host'] ?? '' }}{{ !empty($item['host']) && !empty($item['location']) ? ' · ' : '' }}{{ $item['location'] ?? '' }}
|
|
</p>
|
|
@endif
|
|
@if(!empty($item['description']))
|
|
<p class="mt-1 text-xs leading-relaxed text-slate-500">{{ $item['description'] }}</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ===== WIFI ===== --}}
|
|
@elseif($type === \App\Models\QrCode::TYPE_WIFI)
|
|
<div class="flex min-h-screen flex-col items-center justify-center px-4 py-12">
|
|
<div class="w-full max-w-sm">
|
|
<div class="flex flex-col items-center text-center">
|
|
<div class="flex h-16 w-16 items-center justify-center rounded-2xl bg-blue-100">
|
|
<svg class="h-8 w-8 text-blue-600" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M8.288 15.038a5.25 5.25 0 0 1 7.424 0M5.106 11.856c3.807-3.808 9.98-3.808 13.788 0M1.924 8.674c5.565-5.565 14.587-5.565 20.152 0M12.53 18.22l-.53.53-.53-.53a.75.75 0 0 1 1.06 0Z"/>
|
|
</svg>
|
|
</div>
|
|
<h1 class="mt-5 text-2xl font-bold text-slate-900">Join WiFi</h1>
|
|
<p class="mt-1 text-sm text-slate-500">{{ $qrCode->label }}</p>
|
|
</div>
|
|
|
|
<div class="mt-8 space-y-3">
|
|
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-sm">
|
|
<div class="px-5 py-4">
|
|
<p class="text-xs font-semibold uppercase tracking-widest text-slate-400">Network</p>
|
|
<p class="mt-1 text-lg font-bold text-slate-900">{{ $content['ssid'] ?? '' }}</p>
|
|
</div>
|
|
@if(($content['encryption'] ?? 'WPA') !== 'NOPASS' && !empty($content['password']))
|
|
<div class="border-t border-slate-100 px-5 py-4">
|
|
<p class="text-xs font-semibold uppercase tracking-widest text-slate-400">Password</p>
|
|
<div class="mt-1 flex items-center justify-between gap-3">
|
|
<p class="font-mono text-lg font-semibold text-slate-900" id="wifi-pw">{{ $content['password'] }}</p>
|
|
<button onclick="navigator.clipboard?.writeText('{{ addslashes($content['password']) }}')"
|
|
class="shrink-0 rounded-xl border border-slate-200 px-3 py-1.5 text-xs font-semibold text-slate-600 transition hover:bg-slate-50">Copy</button>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
<div class="border-t border-slate-100 px-5 py-3">
|
|
<span class="inline-flex items-center gap-1.5 rounded-full bg-slate-100 px-2.5 py-1 text-xs font-medium text-slate-600">
|
|
{{ $content['encryption'] ?? 'WPA' }}
|
|
{{ !empty($content['hidden']) ? ' · Hidden' : '' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ===== IMAGES ===== --}}
|
|
@elseif($type === \App\Models\QrCode::TYPE_IMAGE)
|
|
<div class="mx-auto max-w-lg px-4 py-10">
|
|
<h1 class="text-2xl font-bold text-slate-900">{{ $qrCode->label }}</h1>
|
|
<div class="mt-6 space-y-4">
|
|
@foreach($content['images'] ?? [] as $index => $image)
|
|
<img src="{{ $qrCode->publicPath('images/'.$index) }}"
|
|
alt="{{ $image['title'] ?? 'Image ' . ($index + 1) }}"
|
|
class="w-full rounded-2xl border border-slate-200 shadow-sm"
|
|
loading="lazy">
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ===== APP ===== --}}
|
|
@elseif($type === \App\Models\QrCode::TYPE_APP)
|
|
@php $appIconUrl = !empty($content['icon_path']) ? $qrCode->publicPath('app-icon') : null; @endphp
|
|
<div class="flex min-h-screen flex-col items-center justify-center px-4 py-12">
|
|
<div class="w-full max-w-sm text-center">
|
|
@if($appIconUrl)
|
|
<img src="{{ $appIconUrl }}" alt="{{ $content['name'] ?? $qrCode->label }}"
|
|
class="public-brand-logo mx-auto h-20 w-20 max-w-[50vw] rounded-[22px] object-cover shadow-lg">
|
|
@else
|
|
<div class="mx-auto flex h-20 w-20 items-center justify-center rounded-[22px] bg-gradient-to-br from-indigo-500 to-violet-600 shadow-lg">
|
|
<svg class="h-10 w-10 text-white" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 1.5H8.25A2.25 2.25 0 0 0 6 3.75v16.5a2.25 2.25 0 0 0 2.25 2.25h7.5A2.25 2.25 0 0 0 18 20.25V3.75a2.25 2.25 0 0 0-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3"/>
|
|
</svg>
|
|
</div>
|
|
@endif
|
|
<h1 class="mt-6 text-3xl font-bold text-slate-900">{{ $content['name'] ?? $qrCode->label }}</h1>
|
|
<div class="mt-8 space-y-3 text-left">
|
|
@if(!empty($content['ios_url']))
|
|
<a href="{{ $content['ios_url'] }}" target="_blank" rel="noopener"
|
|
class="flex items-center gap-4 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm transition hover:border-slate-300 hover:shadow-md">
|
|
<svg class="h-8 w-8 shrink-0 text-slate-900" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
|
<g>
|
|
<path d="M19.149,0.001H4.847C2.169,0.001,0,2.17,0,4.849v14.305c0,2.676,2.169,4.845,4.847,4.845h14.305c2.676,0,4.847-2.169,4.847-4.847V4.849C23.997,2.17,21.828,0.001,19.149,0.001z M6.635,19.001c-0.3,0.525-0.969,0.702-1.494,0.402c-0.525-0.3-0.702-0.969-0.402-1.494l0.78-1.35c0.882-0.273,1.598-0.063,2.166,0.621L6.635,19.001z M14.218,16.056H4.29c-0.606,0-1.092-0.486-1.092-1.092c0-0.606,0.486-1.092,1.092-1.092h2.783l3.564-6.176L9.524,5.764c-0.3-0.525-0.123-1.188,0.402-1.494c0.525-0.3,1.188-0.123,1.494,0.402l0.477,0.843l0.486-0.84c0.3-0.525,0.969-0.702,1.494-0.402c0.525,0.3,0.702,0.969,0.402,1.493l-4.682,8.105h3.387C14.08,13.872,14.695,15.162,14.218,16.056z M19.567,16.062h-1.578l1.065,1.847c0.3,0.525,0.123,1.188-0.402,1.494c-0.525,0.3-1.188,0.123-1.494-0.402c-1.793-3.111-3.141-5.438-4.034-6.989c-0.915-1.578-0.261-3.162,0.384-3.699c0.717,1.23,1.788,3.087,3.218,5.565h2.841c0.606,0,1.092,0.486,1.092,1.092C20.658,15.576,20.173,16.062,19.567,16.062z"/>
|
|
</g>
|
|
</svg>
|
|
<div>
|
|
<p class="text-xs text-slate-500">Download on the</p>
|
|
<p class="font-bold text-slate-900">App Store</p>
|
|
</div>
|
|
</a>
|
|
@endif
|
|
@if(!empty($content['android_url']))
|
|
<a href="{{ $content['android_url'] }}" target="_blank" rel="noopener"
|
|
class="flex items-center gap-4 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm transition hover:border-slate-300 hover:shadow-md">
|
|
<svg class="h-8 w-8 shrink-0 text-slate-900" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
|
<path fill-rule="evenodd" d="m12,0C5.373,0,0,5.373,0,12s5.373,12,12,12,12-5.373,12-12S18.627,0,12,0Zm-2.877,5.22l7.3,4.084-2.047,2.047-6.315-6.315c.359-.085.742-.023,1.061.184Zm-2.123,12.403V6.376c0-.304.101-.592.278-.826l6.45,6.45-6.45,6.45c-.176-.234-.278-.522-.278-.826Zm2.123,1.156c-.227.147-.486.221-.747.221-.105,0-.211-.013-.314-.038l6.314-6.314,2.047,2.047-7.3,4.084Zm9.971-5.579l-1.84,1.029-2.23-2.23,2.23-2.23,1.839,1.029c.441.247.704.696.704,1.2s-.263.954-.704,1.201Z"/>
|
|
</svg>
|
|
<div>
|
|
<p class="text-xs text-slate-500">Get it on</p>
|
|
<p class="font-bold text-slate-900">Google Play</p>
|
|
</div>
|
|
</a>
|
|
@endif
|
|
@if(!empty($content['web_url']))
|
|
<a href="{{ $content['web_url'] }}" target="_blank" rel="noopener"
|
|
class="flex items-center gap-4 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm transition hover:border-slate-300 hover:shadow-md">
|
|
<svg class="h-8 w-8 shrink-0 text-slate-500" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253M3 12a8.96 8.96 0 0 0 .735 3.547"/></svg>
|
|
<div>
|
|
<p class="text-xs text-slate-500">Open</p>
|
|
<p class="font-bold text-slate-900">Website</p>
|
|
</div>
|
|
</a>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ===== COUPON ===== --}}
|
|
@elseif($type === \App\Models\QrCode::TYPE_COUPON)
|
|
<div class="flex min-h-screen flex-col items-center justify-center px-4 py-12">
|
|
<div class="w-full max-w-sm">
|
|
<div class="overflow-hidden rounded-3xl bg-white shadow-xl">
|
|
<div class="bg-gradient-to-br from-amber-400 to-orange-500 px-6 py-10 text-center text-white">
|
|
<svg class="mx-auto h-10 w-10" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9 14.25l6-6m4.5-3.493V21.75l-3.75-1.5-3.75 1.5-3.75-1.5-3.75 1.5V4.757c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0 1 11.186 0c1.1.128 1.907 1.077 1.907 2.185ZM9.75 9h.008v.008H9.75V9Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm4.125 4.5h.008v.008h-.008V13.5Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"/>
|
|
</svg>
|
|
<h1 class="mt-4 text-xl font-bold">{{ $content['title'] ?? $qrCode->label }}</h1>
|
|
</div>
|
|
<div class="px-6 py-8 text-center">
|
|
<div class="rounded-2xl border-2 border-dashed border-orange-200 bg-orange-50 px-6 py-5">
|
|
<p class="text-xs font-semibold uppercase tracking-widest text-orange-500">Your code</p>
|
|
<p class="mt-2 text-3xl font-black tracking-[0.2em] text-orange-600">{{ $content['code'] ?? '' }}</p>
|
|
</div>
|
|
@if(!empty($content['description']))<p class="mt-5 text-sm text-slate-600">{{ $content['description'] }}</p>@endif
|
|
@if(!empty($content['expires_at']))<p class="mt-3 text-xs text-slate-400">Expires {{ $content['expires_at'] }}</p>@endif
|
|
@if(!empty($content['button_url']))
|
|
<a href="{{ $content['button_url'] }}" target="_blank" rel="noopener"
|
|
class="mt-6 flex items-center justify-center gap-2 rounded-2xl bg-orange-500 px-6 py-3.5 text-sm font-semibold text-white shadow-sm transition hover:bg-orange-600">
|
|
{{ $content['button_text'] ?? 'Shop now' }}
|
|
</a>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ===== MENU & SHOP (shared cart/order UI) ===== --}}
|
|
@elseif(in_array($type, [\App\Models\QrCode::TYPE_MENU, \App\Models\QrCode::TYPE_SHOP]))
|
|
|
|
@php
|
|
$isShop = $type === \App\Models\QrCode::TYPE_SHOP;
|
|
$currency = $content['currency'] ?? 'GHS';
|
|
$sections = $content['sections'] ?? [];
|
|
$title = $content['title'] ?? $qrCode->label;
|
|
$acceptsPayment = (bool) ($content['accepts_payment'] ?? false);
|
|
$orderRoute = $qrCode->publicPath('order');
|
|
$csrfToken = csrf_token();
|
|
$paystackPublicKey = $acceptsPayment ? app(\App\Services\Billing\PaystackService::class)->publicKey() : '';
|
|
$brandColor = $content['brand_color'] ?? ($isShop ? '#7c3aed' : '#f97316');
|
|
$hasLogo = !empty($content['logo_path']);
|
|
$hasCover = !empty($content['cover_path']);
|
|
$logoUrl = $hasLogo ? $qrCode->publicPath('menu-logo') : null;
|
|
$coverUrl = $hasCover ? $qrCode->publicPath('menu-cover') : null;
|
|
$shippingType = $content['shipping_type'] ?? 'none';
|
|
$shippingFee = (float) ($content['shipping_fee'] ?? 0);
|
|
$freeShippingAbove = (float) ($content['free_shipping_above'] ?? 0);
|
|
$itemCount = array_sum(array_map(fn($s) => count($s['items'] ?? []), $sections));
|
|
@endphp
|
|
|
|
<div x-data="{
|
|
cart: {},
|
|
name: '',
|
|
email: '',
|
|
phone: '',
|
|
showModal: false,
|
|
activeSection: 0,
|
|
loading: false,
|
|
errorMsg: '',
|
|
showSheet: false,
|
|
checkoutUrl: '',
|
|
accessCode: '',
|
|
publicKey: '',
|
|
returnUrl: '',
|
|
shippingType: @js($shippingType),
|
|
shippingFee: @js($shippingFee),
|
|
freeAbove: @js($freeShippingAbove),
|
|
get cartItems() {
|
|
const items = [];
|
|
Object.entries(this.cart).forEach(([key, qty]) => {
|
|
if (qty > 0) {
|
|
const [si, ii] = key.split('-').map(Number);
|
|
const sections = @js($sections);
|
|
const item = sections[si]?.items[ii];
|
|
if (item) items.push({ key, si, ii, qty, name: item.name, price: item.price, priceNum: parseFloat(item.price) || 0 });
|
|
}
|
|
});
|
|
return items;
|
|
},
|
|
get cartCount() { return this.cartItems.reduce((s, i) => s + i.qty, 0) },
|
|
get cartTotal() { return this.cartItems.reduce((s, i) => s + i.qty * i.priceNum, 0) },
|
|
get shippingCost() {
|
|
if (this.shippingType !== 'flat' || this.shippingFee <= 0) return 0;
|
|
if (this.freeAbove > 0 && this.cartTotal >= this.freeAbove) return 0;
|
|
return this.shippingFee;
|
|
},
|
|
get orderTotal() { return this.cartTotal + this.shippingCost },
|
|
add(si, ii) { const k = si+'-'+ii; this.cart[k] = (this.cart[k] || 0) + 1 },
|
|
remove(si, ii) { const k = si+'-'+ii; if ((this.cart[k]||0) > 0) this.cart[k]-- },
|
|
qty(si, ii) { return this.cart[si+'-'+ii] || 0 },
|
|
async submitOrder() {
|
|
if (this.cartCount === 0) { this.errorMsg = 'Add at least one item.'; return }
|
|
if (!this.name.trim()) { this.errorMsg = 'Enter your name.'; return }
|
|
if (!this.phone.trim()) { this.errorMsg = 'Enter your phone number.'; return }
|
|
this.errorMsg = '';
|
|
this.loading = true; window.LadillPayCheckout?.prepare?.();
|
|
const payload = {
|
|
customer_name: this.name,
|
|
customer_email: this.email,
|
|
customer_phone: this.phone,
|
|
shipping_fee: this.shippingCost,
|
|
items: this.cartItems.map(i => ({ name: i.name, price: i.priceNum, qty: i.qty })),
|
|
};
|
|
try {
|
|
const res = await fetch(@js($orderRoute), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': @js($csrfToken) },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
const data = await res.json();
|
|
if (data.error) { window.LadillPayCheckout?.cancel?.(); this.errorMsg = data.error; this.loading = false; return; }
|
|
this.checkoutUrl = data.checkout_url;
|
|
this.accessCode = data.access_code || '';
|
|
this.publicKey = data.public_key || '';
|
|
this.returnUrl = data.callback_url || '';
|
|
this.showSheet = true; // loading cleared when payment UI opens (ladill-pay-opened)
|
|
} catch(e) {
|
|
window.LadillPayCheckout?.cancel?.(); this.errorMsg = 'Network error. Please try again.';
|
|
this.loading = false;
|
|
}
|
|
}
|
|
}" class="min-h-screen bg-slate-50">
|
|
|
|
{{-- ===== HERO ===== --}}
|
|
<div class="relative overflow-hidden" style="min-height: 220px;">
|
|
@if($hasCover)
|
|
<img src="{{ $coverUrl }}" alt="{{ $title }}" class="absolute inset-0 h-full w-full object-cover">
|
|
@endif
|
|
{{-- gradient overlay always present --}}
|
|
<div class="absolute inset-0" style="background: linear-gradient(to bottom, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.62) 100%); {{ !$hasCover ? 'background: linear-gradient(135deg, ' . $brandColor . 'ee 0%, ' . $brandColor . '99 100%);' : '' }}"></div>
|
|
@if(!$hasCover)
|
|
<div class="absolute inset-0 opacity-10" style="background-image: radial-gradient(circle at 20% 50%, white 1px, transparent 1px), radial-gradient(circle at 80% 20%, white 1px, transparent 1px); background-size: 40px 40px;"></div>
|
|
@endif
|
|
|
|
{{-- Hero content --}}
|
|
<div class="relative flex flex-col justify-end px-5 pb-6 pt-12" style="min-height: 220px;">
|
|
<div class="flex items-end gap-4">
|
|
@if($hasLogo)
|
|
<img src="{{ $logoUrl }}" alt="{{ $title }}"
|
|
class="public-brand-logo h-16 w-16 max-w-[50vw] flex-shrink-0 rounded-2xl object-cover shadow-lg ring-2 ring-white/30">
|
|
@else
|
|
<div class="flex h-16 w-16 flex-shrink-0 items-center justify-center rounded-2xl shadow-lg ring-2 ring-white/30 text-2xl font-black text-white"
|
|
style="background: rgba(255,255,255,0.2); backdrop-filter: blur(8px);">
|
|
{{ strtoupper(substr($title, 0, 1)) }}
|
|
</div>
|
|
@endif
|
|
<div class="min-w-0 flex-1 pb-0.5">
|
|
<h1 class="text-2xl font-black text-white leading-tight drop-shadow">{{ $title }}</h1>
|
|
<div class="mt-1 flex flex-wrap items-center gap-2">
|
|
<span class="inline-flex items-center gap-1 rounded-full bg-white/20 px-2.5 py-0.5 text-[11px] font-semibold text-white backdrop-blur-sm">
|
|
{{ $isShop ? 'Shop' : 'Restaurant' }}
|
|
</span>
|
|
<span class="inline-flex items-center gap-1 rounded-full bg-white/20 px-2.5 py-0.5 text-[11px] font-semibold text-white backdrop-blur-sm">
|
|
{{ $itemCount }} {{ $isShop ? ($itemCount === 1 ? 'product' : 'products') : ($itemCount === 1 ? 'item' : 'items') }}
|
|
</span>
|
|
@if($acceptsPayment)
|
|
<span class="inline-flex items-center gap-1 rounded-full bg-green-500/80 px-2.5 py-0.5 text-[11px] font-semibold text-white backdrop-blur-sm">
|
|
<span class="h-1.5 w-1.5 rounded-full bg-white"></span> Order online
|
|
</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- ===== SECTION TABS ===== --}}
|
|
@if(count($sections) > 1)
|
|
<div class="sticky top-0 z-20 bg-white shadow-sm">
|
|
<div class="flex gap-0 overflow-x-auto scrollbar-none px-4">
|
|
@foreach($sections as $si => $section)
|
|
<button @click="activeSection = {{ $si }}"
|
|
class="shrink-0 border-b-2 px-4 py-3.5 text-sm font-semibold whitespace-nowrap transition-colors"
|
|
:class="activeSection === {{ $si }} ? 'border-current text-current' : 'border-transparent text-slate-400 hover:text-slate-700'"
|
|
:style="activeSection === {{ $si }} ? 'color: {{ $brandColor }}; border-color: {{ $brandColor }}' : ''">
|
|
{{ $section['name'] }}
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- ===== ITEMS ===== --}}
|
|
<div class="mx-auto max-w-xl px-4 pb-36 pt-5 space-y-8">
|
|
@foreach($sections as $si => $section)
|
|
<div x-show="{{ count($sections) > 1 ? 'activeSection === ' . $si : 'true' }}">
|
|
@if(count($sections) === 1)
|
|
<h2 class="mb-4 text-xs font-bold uppercase tracking-widest text-slate-400">{{ $section['name'] }}</h2>
|
|
@endif
|
|
<div class="space-y-3">
|
|
@foreach($section['items'] ?? [] as $ii => $item)
|
|
@php $hasImage = !empty($item['image_path']); @endphp
|
|
<div class="flex gap-3 overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-slate-100">
|
|
{{-- Item image --}}
|
|
@if($hasImage)
|
|
<img src="{{ $qrCode->publicPath('item-image/'.$si.'/'.$ii) }}"
|
|
alt="{{ $item['name'] }}"
|
|
class="h-[100px] w-[100px] flex-shrink-0 object-cover" loading="lazy">
|
|
@endif
|
|
|
|
{{-- Item details --}}
|
|
<div class="flex flex-1 min-w-0 flex-col justify-between py-3 pr-3 {{ $hasImage ? '' : 'pl-4' }}">
|
|
<div>
|
|
<p class="font-semibold text-slate-900 leading-snug">{{ $item['name'] }}</p>
|
|
@if(!empty($item['description']))
|
|
<p class="mt-0.5 text-xs text-slate-500 leading-relaxed line-clamp-2">{{ $item['description'] }}</p>
|
|
@endif
|
|
</div>
|
|
<div class="mt-2 flex items-center justify-between gap-2">
|
|
@if(!empty($item['price']))
|
|
<div class="flex items-baseline gap-0.5">
|
|
<span class="text-[10px] font-medium text-slate-400">{{ $currency }}</span>
|
|
<span class="text-base font-bold text-slate-900">{{ $item['price'] }}</span>
|
|
</div>
|
|
@else
|
|
<span class="text-xs font-medium text-slate-400">—</span>
|
|
@endif
|
|
|
|
@if($acceptsPayment)
|
|
<div class="flex items-center gap-1.5">
|
|
<button @click="remove({{ $si }}, {{ $ii }})"
|
|
x-show="qty({{ $si }}, {{ $ii }}) > 0" x-cloak
|
|
class="flex h-7 w-7 items-center justify-center rounded-lg border border-slate-200 bg-white text-slate-600 hover:border-slate-300 transition shadow-sm">
|
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14"/></svg>
|
|
</button>
|
|
<span x-show="qty({{ $si }}, {{ $ii }}) > 0" x-cloak
|
|
x-text="qty({{ $si }}, {{ $ii }})"
|
|
class="min-w-[1.5rem] text-center text-sm font-bold text-slate-900"></span>
|
|
<button @click="add({{ $si }}, {{ $ii }})"
|
|
class="flex h-8 w-8 items-center justify-center rounded-xl text-white shadow-md transition active:scale-95"
|
|
style="background: {{ $brandColor }}">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
|
</button>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
|
|
@if($acceptsPayment)
|
|
{{-- ===== STICKY CART BAR ===== --}}
|
|
<div x-show="cartCount > 0" x-cloak
|
|
x-transition:enter="transition ease-out duration-200"
|
|
x-transition:enter-start="translate-y-full opacity-0"
|
|
x-transition:enter-end="translate-y-0 opacity-100"
|
|
x-transition:leave="transition ease-in duration-150"
|
|
x-transition:leave-start="translate-y-0 opacity-100"
|
|
x-transition:leave-end="translate-y-full opacity-0"
|
|
class="fixed bottom-0 inset-x-0 z-30 px-4 pb-6 pt-2"
|
|
style="padding-bottom: max(1.5rem, env(safe-area-inset-bottom))">
|
|
<button @click="showModal = true"
|
|
class="flex w-full max-w-xl mx-auto items-center gap-3 rounded-2xl px-5 py-4 text-white shadow-2xl transition active:scale-[0.98]"
|
|
style="background: {{ $brandColor }}">
|
|
<span class="flex h-7 w-7 items-center justify-center rounded-xl bg-white/20 text-sm font-bold tabular-nums" x-text="cartCount"></span>
|
|
<span class="flex-1 text-left text-sm font-bold">View order</span>
|
|
<span class="text-sm font-semibold tabular-nums opacity-90" x-text="'{{ $currency }} ' + orderTotal.toFixed(2)"></span>
|
|
<svg class="h-4 w-4 opacity-80" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{{-- ===== PAYMENT MODAL (bottom sheet) ===== --}}
|
|
<div x-show="showModal" x-cloak
|
|
class="fixed inset-0 z-50 flex items-end justify-center"
|
|
style="background: rgba(0,0,0,0.5); backdrop-filter: blur(3px);"
|
|
x-transition:enter="transition-opacity ease-out duration-200"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition-opacity ease-in duration-150"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0">
|
|
|
|
<div @click.outside="if(!loading) showModal = false"
|
|
class="w-full max-w-lg overflow-hidden rounded-t-3xl bg-white shadow-2xl"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="translate-y-full"
|
|
x-transition:enter-end="translate-y-0"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="translate-y-0"
|
|
x-transition:leave-end="translate-y-full"
|
|
style="max-height: 90vh; overflow-y: auto; padding-bottom: env(safe-area-inset-bottom, 0px)">
|
|
|
|
{{-- Drag handle --}}
|
|
<div class="flex justify-center pt-3 pb-2">
|
|
<div class="h-1 w-10 rounded-full bg-slate-200"></div>
|
|
</div>
|
|
|
|
{{-- Header --}}
|
|
<div class="flex items-center justify-between border-b border-slate-100 px-5 pb-4 pt-2">
|
|
<h3 class="text-lg font-bold text-slate-900">Your order</h3>
|
|
<button @click="if(!loading) showModal = false"
|
|
class="flex h-8 w-8 items-center justify-center rounded-full bg-slate-100 text-slate-500 hover:bg-slate-200 transition">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="px-5 pb-6 pt-4 space-y-5">
|
|
{{-- Cart items --}}
|
|
<div class="space-y-2">
|
|
<template x-for="item in cartItems" :key="item.key">
|
|
<div class="flex items-center gap-3">
|
|
<div class="flex items-center gap-1.5">
|
|
<button @click="remove(item.si, item.ii)"
|
|
class="flex h-7 w-7 items-center justify-center rounded-lg border border-slate-200 text-slate-500 hover:border-slate-300 transition">
|
|
<svg class="h-3 w-3" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14"/></svg>
|
|
</button>
|
|
<span class="min-w-[1.5rem] text-center text-sm font-bold text-slate-900 tabular-nums" x-text="item.qty"></span>
|
|
<button @click="add(item.si, item.ii)"
|
|
class="flex h-7 w-7 items-center justify-center rounded-lg text-white transition"
|
|
style="background: {{ $brandColor }}">
|
|
<svg class="h-3 w-3" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
|
</button>
|
|
</div>
|
|
<span class="flex-1 text-sm text-slate-700 leading-snug" x-text="item.name"></span>
|
|
<span class="text-sm font-semibold text-slate-900 tabular-nums"
|
|
x-text="item.priceNum > 0 ? '{{ $currency }} ' + (item.qty * item.priceNum).toFixed(2) : 'Free'"></span>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
{{-- Totals --}}
|
|
<div class="rounded-2xl bg-slate-50 px-4 py-3 space-y-2">
|
|
<div class="flex justify-between text-sm text-slate-600">
|
|
<span>Subtotal</span>
|
|
<span class="tabular-nums" x-text="'{{ $currency }} ' + cartTotal.toFixed(2)"></span>
|
|
</div>
|
|
<template x-if="shippingCost > 0">
|
|
<div class="flex justify-between text-sm text-slate-600">
|
|
<span>{{ $isShop ? 'Shipping' : 'Delivery' }}</span>
|
|
<span class="tabular-nums" x-text="'{{ $currency }} ' + shippingCost.toFixed(2)"></span>
|
|
</div>
|
|
</template>
|
|
<template x-if="shippingType === 'flat' && shippingFee > 0 && freeAbove > 0 && shippingCost === 0">
|
|
<div class="flex justify-between text-sm text-green-600">
|
|
<span>{{ $isShop ? 'Shipping' : 'Delivery' }}</span>
|
|
<span>Free</span>
|
|
</div>
|
|
</template>
|
|
<div class="flex justify-between border-t border-slate-200 pt-2 text-base font-bold text-slate-900">
|
|
<span>Total</span>
|
|
<span class="tabular-nums" x-text="'{{ $currency }} ' + orderTotal.toFixed(2)"></span>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Contact fields --}}
|
|
<div class="space-y-2.5">
|
|
<p class="text-xs font-semibold uppercase tracking-widest text-slate-400">Your details</p>
|
|
<input type="text" x-model="name" placeholder="Full name *"
|
|
class="w-full rounded-xl border border-slate-200 px-4 py-3 text-sm placeholder:text-slate-400 focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-400/30">
|
|
<input type="tel" x-model="phone" placeholder="Phone number *"
|
|
class="w-full rounded-xl border border-slate-200 px-4 py-3 text-sm placeholder:text-slate-400 focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-400/30">
|
|
</div>
|
|
|
|
<div x-show="errorMsg" x-cloak
|
|
class="rounded-xl border border-red-100 bg-red-50 px-4 py-3 text-sm text-red-600"
|
|
x-text="errorMsg"></div>
|
|
|
|
<button @click="submitOrder" :disabled="loading"
|
|
class="flex w-full items-center justify-center gap-2 rounded-2xl py-4 text-sm font-bold text-white shadow-lg transition disabled:opacity-60 active:scale-[0.98]"
|
|
style="background: {{ $brandColor }}">
|
|
<svg x-show="loading" x-cloak class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
|
</svg>
|
|
<span x-text="loading ? 'Opening payment…' : ('Pay {{ $currency }} ' + orderTotal.toFixed(2))"></span>
|
|
</button>
|
|
|
|
<p class="text-center text-[11px] text-slate-400">Secured by Paystack. Powered by Ladill Pay</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@if($acceptsPayment)
|
|
@include('partials.paystack-sheet', [
|
|
'audience' => 'buyer',
|
|
'sheetTitle' => 'Complete your payment',
|
|
'sheetSubtitle' => 'Pay securely for your order. You\'ll get a confirmation when it succeeds.',
|
|
])
|
|
@endif
|
|
|
|
</div>{{-- end x-data --}}
|
|
|
|
@endif
|
|
|
|
|
|
<script src="//cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
|
</body>
|
|
</html>
|