Deploy Ladill Events / deploy (push) Successful in 41s
Events can define hybrid/virtual sessions synced to Meet rooms; SMS and email use wallet-billed platform APIs instead of Termii and Laravel mail. Co-authored-by: Cursor <cursoragent@cursor.com>
1503 lines
108 KiB
PHP
1503 lines
108 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 }}"
|
|
style="width:100%; height: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: '',
|
|
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;
|
|
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) { this.errorMsg = data.error; this.loading = false; return; }
|
|
if (window.innerWidth < 768) {
|
|
this.checkoutUrl = data.checkout_url;
|
|
this.showSheet = true;
|
|
this.loading = false;
|
|
} else {
|
|
window.location.href = data.checkout_url;
|
|
}
|
|
} catch(e) {
|
|
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 }}"
|
|
style="width:100%; height: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')
|
|
</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();
|
|
@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: {},
|
|
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.phone.trim()) { this.errorMsg = 'Enter your phone number.'; return; }
|
|
this.errorMsg = ''; this.loading = true;
|
|
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 }),
|
|
});
|
|
const data = await res.json();
|
|
if (data.error) { this.errorMsg = data.error; this.loading = false; return; }
|
|
if (!data.paid) { window.location.href = data.success_url; return; }
|
|
if (window.innerWidth < 768) { this.checkoutUrl = data.checkout_url; this.showSheet = true; this.loading = false; }
|
|
else { window.location.href = data.checkout_url; }
|
|
} catch(e) { 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="mb-3 h-16 w-16 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';
|
|
$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)
|
|
<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
|
|
<p class="mt-2 text-xs font-semibold text-indigo-600">Join virtual session →</p>
|
|
</a>
|
|
@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="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')
|
|
</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="mx-auto h-20 w-20 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: '',
|
|
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;
|
|
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) { this.errorMsg = data.error; this.loading = false; return; }
|
|
if (window.innerWidth < 768) {
|
|
this.checkoutUrl = data.checkout_url;
|
|
this.showSheet = true;
|
|
this.loading = false;
|
|
} else {
|
|
window.location.href = data.checkout_url;
|
|
}
|
|
} catch(e) {
|
|
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="h-16 w-16 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')
|
|
@endif
|
|
|
|
</div>{{-- end x-data --}}
|
|
|
|
@endif
|
|
|
|
|
|
<script src="//cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
|
</body>
|
|
</html>
|