Use ladl.link URLs for all front-facing QR and short-link surfaces.
Deploy Ladill Give / deploy (push) Successful in 47s

Public landing pages, asset paths, payment callbacks, and redirects now go through QrCode::publicPath() / LadillLink instead of ladill.com/q/* routes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-27 15:46:27 +00:00
co-authored by Cursor
parent c989c1460f
commit c4e1ac19c6
5 changed files with 41 additions and 31 deletions
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Public;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\QrCode; use App\Models\QrCode;
use App\Services\Give\GiveDonationService; use App\Services\Give\GiveDonationService;
use App\Support\LadillLink;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@@ -53,15 +54,15 @@ class DonationController extends Controller
{ {
$reference = trim((string) $request->query('reference', '')); $reference = trim((string) $request->query('reference', ''));
if ($reference === '') { if ($reference === '') {
return redirect('/q/'.$shortCode)->with('error', 'Missing payment reference.'); return redirect()->away(LadillLink::url($shortCode))->with('error', 'Missing payment reference.');
} }
try { try {
$donation = $this->donations->complete($reference); $donation = $this->donations->complete($reference);
} catch (RuntimeException $e) { } catch (RuntimeException $e) {
return redirect('/q/'.$shortCode)->with('order_error', $e->getMessage()); return redirect()->away(LadillLink::url($shortCode))->with('order_error', $e->getMessage());
} catch (\Throwable) { } catch (\Throwable) {
return redirect('/q/'.$shortCode)->with('order_error', 'Payment could not be verified. Contact the organisation with reference: '.$reference); return redirect()->away(LadillLink::url($shortCode))->with('order_error', 'Payment could not be verified. Contact the organisation with reference: '.$reference);
} }
return view('public.qr.donation-confirmed', [ return view('public.qr.donation-confirmed', [
@@ -36,7 +36,7 @@ class QrScanController extends Controller
} }
if ($qrCode->isDocumentType()) { if ($qrCode->isDocumentType()) {
return redirect()->route('qr.public.view', $shortCode); return redirect()->away($qrCode->publicPath('view'));
} }
if ($qrCode->isBookType()) { if ($qrCode->isBookType()) {
@@ -62,7 +62,7 @@ class QrScanController extends Controller
return view('public.qr.document-viewer', [ return view('public.qr.document-viewer', [
'qrCode' => $qrCode, 'qrCode' => $qrCode,
'fileUrl' => route('qr.public.file', $shortCode), 'fileUrl' => $qrCode->publicPath('file'),
'allowDownload' => (bool) ($qrCode->content()['allow_download'] ?? true), 'allowDownload' => (bool) ($qrCode->content()['allow_download'] ?? true),
]); ]);
} }
@@ -111,7 +111,7 @@
<div id="toolbar"> <div id="toolbar">
<span class="tb-label">{{ $qrCode->label }}</span> <span class="tb-label">{{ $qrCode->label }}</span>
@if($allowDownload) @if($allowDownload)
<a href="{{ route('qr.public.file', $qrCode->short_code) }}" download class="tb-download"> <a href="{{ $qrCode->publicPath('file') }}" download class="tb-download">
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> <svg 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.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"/> <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> </svg>
+33 -24
View File
@@ -3,8 +3,8 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@include('partials.favicon')
<title>{{ $qrCode->label }}</title> <title>{{ $qrCode->label }}</title>
@include('partials.favicon')
<meta name="description" content="{{ $qrCode->typeLabel() }} — {{ $qrCode->label }}"> <meta name="description" content="{{ $qrCode->typeLabel() }} — {{ $qrCode->label }}">
@vite(['resources/css/app.css']) @vite(['resources/css/app.css'])
</head> </head>
@@ -24,7 +24,7 @@
<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"/> <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> </svg>
</div> </div>
<a href="{{ route('qr.public.view', $qrCode->short_code) }}" <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"> 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"> <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="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"/>
@@ -32,7 +32,7 @@
</svg> </svg>
View document View document
</a> </a>
<a href="{{ route('qr.public.file', $qrCode->short_code) }}" <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"> 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"> <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"/> <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"/>
@@ -62,7 +62,7 @@
{{-- ===== VCARD ===== --}} {{-- ===== VCARD ===== --}}
@elseif($type === \App\Models\QrCode::TYPE_VCARD) @elseif($type === \App\Models\QrCode::TYPE_VCARD)
@php @php
$avatarUrl = !empty($content['avatar_path']) ? route('qr.public.vcard.avatar', $qrCode->short_code) : null; $avatarUrl = !empty($content['avatar_path']) ? $qrCode->publicPath('avatar') : null;
$rawSocial = $content['social'] ?? []; $rawSocial = $content['social'] ?? [];
$social = []; $social = [];
foreach ($rawSocial as $platform => $url) { foreach ($rawSocial as $platform => $url) {
@@ -159,8 +159,8 @@
{{-- Save contact button --}} {{-- Save contact button --}}
<div class="px-6 pb-6 pt-2"> <div class="px-6 pb-6 pt-2">
<a href="{{ route('qr.public.vcard', $qrCode->short_code) }}" <a href="{{ $qrCode->publicPath('vcard.vcf') }}"
class="flex w-full items-center justify-center gap-2 rounded-2xl bg-indigo-600 py-3.5 text-sm font-semibold text-white shadow-sm transition hover:bg-indigo-700"> 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> <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 Save Contact
</a> </a>
@@ -178,8 +178,8 @@
$bizColor = $content['brand_color'] ?? '#1e3a5f'; $bizColor = $content['brand_color'] ?? '#1e3a5f';
$bizHasLogo = !empty($content['logo_path']); $bizHasLogo = !empty($content['logo_path']);
$bizHasCover = !empty($content['cover_path']); $bizHasCover = !empty($content['cover_path']);
$bizLogoUrl = $bizHasLogo ? route('qr.public.business.logo', $qrCode->short_code) : null; $bizLogoUrl = $bizHasLogo ? $qrCode->publicPath('business-logo') : null;
$bizCoverUrl = $bizHasCover ? route('qr.public.business.cover', $qrCode->short_code) : null; $bizCoverUrl = $bizHasCover ? $qrCode->publicPath('business-cover') : null;
$bizInitials = strtoupper(substr($bizName, 0, 1)); $bizInitials = strtoupper(substr($bizName, 0, 1));
$bizRawSocial = $content['social'] ?? []; $bizRawSocial = $content['social'] ?? [];
$bizSocial = []; $bizSocial = [];
@@ -313,10 +313,10 @@
$churchColor = $content['brand_color'] ?? '#1a3a5c'; $churchColor = $content['brand_color'] ?? '#1a3a5c';
$churchHasLogo = !empty($content['logo_path']); $churchHasLogo = !empty($content['logo_path']);
$churchHasCover = !empty($content['cover_path']); $churchHasCover = !empty($content['cover_path']);
$churchLogoUrl = $churchHasLogo ? route('qr.public.church.logo', $qrCode->short_code) : null; $churchLogoUrl = $churchHasLogo ? $qrCode->publicPath('church-logo') : null;
$churchCoverUrl = $churchHasCover ? route('qr.public.church.cover', $qrCode->short_code) : null; $churchCoverUrl = $churchHasCover ? $qrCode->publicPath('church-cover') : null;
$churchInitials = strtoupper(substr($churchName, 0, 2)); $churchInitials = strtoupper(substr($churchName, 0, 2));
$churchAcceptsPay = true; $churchAcceptsPay = !empty($content['accepts_payment']);
$churchCurrency = $content['currency'] ?? 'GHS'; $churchCurrency = $content['currency'] ?? 'GHS';
$churchTypes = $content['collection_types'] ?? ['offering', 'tithe', 'donation', 'harvest']; $churchTypes = $content['collection_types'] ?? ['offering', 'tithe', 'donation', 'harvest'];
// Normalise legacy lowercase slugs → display strings // Normalise legacy lowercase slugs → display strings
@@ -325,7 +325,7 @@
$churchTypes, $churchTypes,
array_map(fn($t) => $legacyLabels[strtolower(trim($t))] ?? trim($t), $churchTypes) array_map(fn($t) => $legacyLabels[strtolower(trim($t))] ?? trim($t), $churchTypes)
); );
$churchOrderRoute = route('qr.public.order', $qrCode->short_code); $churchOrderRoute = $qrCode->publicPath('order');
$churchCsrf = csrf_token(); $churchCsrf = csrf_token();
@endphp @endphp
<div x-data="{ <div x-data="{
@@ -341,6 +341,7 @@
async give() { async give() {
if (!this.amount || parseFloat(this.amount) <= 0) { this.errorMsg = 'Enter a valid amount.'; return; } 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.name.trim()) { this.errorMsg = 'Enter your name.'; return; }
if (!this.email.trim() || !this.email.includes('@')) { this.errorMsg = 'Enter a valid email.'; return; }
if (!this.phone.trim()) { this.errorMsg = 'Enter your phone number.'; return; } if (!this.phone.trim()) { this.errorMsg = 'Enter your phone number.'; return; }
this.errorMsg = ''; this.loading = true; this.errorMsg = ''; this.loading = true;
const labels = @js($churchTypeLabels); const labels = @js($churchTypeLabels);
@@ -591,6 +592,9 @@
<input type="text" x-model="name" placeholder="Full name *" autocomplete="name" <input type="text" x-model="name" placeholder="Full name *" autocomplete="name"
class="church-input" 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;"> 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="email" x-model="email" placeholder="Email address *" autocomplete="email"
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" <input type="tel" x-model="phone" placeholder="Phone number *" autocomplete="tel"
class="church-input" 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;"> 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;">
@@ -627,8 +631,8 @@
$evColor = $content['brand_color'] ?? '#4f46e5'; $evColor = $content['brand_color'] ?? '#4f46e5';
$evHasLogo = !empty($content['logo_path']); $evHasLogo = !empty($content['logo_path']);
$evHasCover = !empty($content['cover_path']); $evHasCover = !empty($content['cover_path']);
$evLogoUrl = $evHasLogo ? route('qr.public.event.logo', $qrCode->short_code) : null; $evLogoUrl = $evHasLogo ? $qrCode->publicPath('event-logo') : null;
$evCoverUrl = $evHasCover ? route('qr.public.event.cover', $qrCode->short_code) : null; $evCoverUrl = $evHasCover ? $qrCode->publicPath('event-cover') : null;
$evName = $content['name'] ?? $qrCode->label; $evName = $content['name'] ?? $qrCode->label;
$evInitials = strtoupper(mb_substr($evName, 0, 2)); $evInitials = strtoupper(mb_substr($evName, 0, 2));
$evTiers = $content['tiers'] ?? []; $evTiers = $content['tiers'] ?? [];
@@ -638,7 +642,7 @@
$evMode = in_array($content['mode'] ?? 'ticketing', ['contributions', 'free'], true) ? ($content['mode'] ?? 'ticketing') : 'ticketing'; $evMode = in_array($content['mode'] ?? 'ticketing', ['contributions', 'free'], true) ? ($content['mode'] ?? 'ticketing') : 'ticketing';
$evCategories = $content['contribution_categories'] ?? ['Contribution']; $evCategories = $content['contribution_categories'] ?? ['Contribution'];
$evHeading = $evMode === 'contributions' ? 'Make a contribution' : 'Register'; $evHeading = $evMode === 'contributions' ? 'Make a contribution' : 'Register';
$evRegRoute = route('qr.public.event.register', $qrCode->short_code); $evRegRoute = $qrCode->publicPath('register');
$evCsrf = csrf_token(); $evCsrf = csrf_token();
@endphp @endphp
<div x-data="{ <div x-data="{
@@ -656,7 +660,7 @@
if (!this.tier) { this.errorMsg = this.mode === 'contributions' ? 'Select a category.' : 'Select a ticket type.'; return; } 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.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.name.trim()) { this.errorMsg = 'Enter your full name.'; return; }
if (!this.phone.trim()) { this.errorMsg = 'Enter your phone number.'; return; } if (!this.email.trim() || !this.email.includes('@')) { this.errorMsg = 'Enter a valid email.'; return; }
this.errorMsg = ''; this.loading = true; this.errorMsg = ''; this.loading = true;
try { try {
const res = await fetch(@js($evRegRoute), { const res = await fetch(@js($evRegRoute), {
@@ -851,7 +855,9 @@
<p class="text-[10px] font-bold uppercase tracking-widest" style="color:{{ $evColor }}">Your details</p> <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" <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"> 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" <input type="email" x-model="email" placeholder="Email address *" autocomplete="email"
class="w-full rounded-xl border border-slate-200 px-4 py-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-1">
<input type="tel" x-model="phone" placeholder="Phone number" autocomplete="tel"
class="w-full rounded-xl border border-slate-200 px-4 py-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-1"> 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) @foreach($evFields as $field)
<input type="text" x-model="fields[@js($field)]" placeholder="{{ $field }}" <input type="text" x-model="fields[@js($field)]" placeholder="{{ $field }}"
@@ -881,7 +887,7 @@
@php @php
$itinColor = $content['brand_color'] ?? '#b45309'; $itinColor = $content['brand_color'] ?? '#b45309';
$itinHasCover = !empty($content['cover_path']); $itinHasCover = !empty($content['cover_path']);
$itinCoverUrl = $itinHasCover ? route('qr.public.itinerary.cover', $qrCode->short_code) : null; $itinCoverUrl = $itinHasCover ? $qrCode->publicPath('itinerary-cover') : null;
$itinTitle = $content['title'] ?? $qrCode->label; $itinTitle = $content['title'] ?? $qrCode->label;
$itinDays = $content['days'] ?? []; $itinDays = $content['days'] ?? [];
$multiDay = count($itinDays) > 1; $multiDay = count($itinDays) > 1;
@@ -1023,7 +1029,7 @@
<h1 class="text-2xl font-bold text-slate-900">{{ $qrCode->label }}</h1> <h1 class="text-2xl font-bold text-slate-900">{{ $qrCode->label }}</h1>
<div class="mt-6 space-y-4"> <div class="mt-6 space-y-4">
@foreach($content['images'] ?? [] as $index => $image) @foreach($content['images'] ?? [] as $index => $image)
<img src="{{ route('qr.public.image', [$qrCode->short_code, $index]) }}" <img src="{{ $qrCode->publicPath('images/'.$index) }}"
alt="{{ $image['title'] ?? 'Image ' . ($index + 1) }}" alt="{{ $image['title'] ?? 'Image ' . ($index + 1) }}"
class="w-full rounded-2xl border border-slate-200 shadow-sm" class="w-full rounded-2xl border border-slate-200 shadow-sm"
loading="lazy"> loading="lazy">
@@ -1033,7 +1039,7 @@
{{-- ===== APP ===== --}} {{-- ===== APP ===== --}}
@elseif($type === \App\Models\QrCode::TYPE_APP) @elseif($type === \App\Models\QrCode::TYPE_APP)
@php $appIconUrl = !empty($content['icon_path']) ? route('qr.public.app.icon', $qrCode->short_code) : null; @endphp @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="flex min-h-screen flex-col items-center justify-center px-4 py-12">
<div class="w-full max-w-sm text-center"> <div class="w-full max-w-sm text-center">
@if($appIconUrl) @if($appIconUrl)
@@ -1126,14 +1132,14 @@
$sections = $content['sections'] ?? []; $sections = $content['sections'] ?? [];
$title = $content['title'] ?? $qrCode->label; $title = $content['title'] ?? $qrCode->label;
$acceptsPayment = (bool) ($content['accepts_payment'] ?? false); $acceptsPayment = (bool) ($content['accepts_payment'] ?? false);
$orderRoute = route('qr.public.order', $qrCode->short_code); $orderRoute = $qrCode->publicPath('order');
$csrfToken = csrf_token(); $csrfToken = csrf_token();
$paystackPublicKey = $acceptsPayment ? app(\App\Services\Billing\PaystackService::class)->publicKey() : ''; $paystackPublicKey = $acceptsPayment ? app(\App\Services\Billing\PaystackService::class)->publicKey() : '';
$brandColor = $content['brand_color'] ?? ($isShop ? '#7c3aed' : '#f97316'); $brandColor = $content['brand_color'] ?? ($isShop ? '#7c3aed' : '#f97316');
$hasLogo = !empty($content['logo_path']); $hasLogo = !empty($content['logo_path']);
$hasCover = !empty($content['cover_path']); $hasCover = !empty($content['cover_path']);
$logoUrl = $hasLogo ? route('qr.public.menu.logo', $qrCode->short_code) : null; $logoUrl = $hasLogo ? $qrCode->publicPath('menu-logo') : null;
$coverUrl = $hasCover ? route('qr.public.menu.cover', $qrCode->short_code) : null; $coverUrl = $hasCover ? $qrCode->publicPath('menu-cover') : null;
$shippingType = $content['shipping_type'] ?? 'none'; $shippingType = $content['shipping_type'] ?? 'none';
$shippingFee = (float) ($content['shipping_fee'] ?? 0); $shippingFee = (float) ($content['shipping_fee'] ?? 0);
$freeShippingAbove = (float) ($content['free_shipping_above'] ?? 0); $freeShippingAbove = (float) ($content['free_shipping_above'] ?? 0);
@@ -1180,6 +1186,7 @@
async submitOrder() { async submitOrder() {
if (this.cartCount === 0) { this.errorMsg = 'Add at least one item.'; return } if (this.cartCount === 0) { this.errorMsg = 'Add at least one item.'; return }
if (!this.name.trim()) { this.errorMsg = 'Enter your name.'; return } if (!this.name.trim()) { this.errorMsg = 'Enter your name.'; return }
if (!this.email.trim() || !this.email.includes('@')) { this.errorMsg = 'Enter a valid email address.'; return }
if (!this.phone.trim()) { this.errorMsg = 'Enter your phone number.'; return } if (!this.phone.trim()) { this.errorMsg = 'Enter your phone number.'; return }
this.errorMsg = ''; this.errorMsg = '';
this.loading = true; this.loading = true;
@@ -1284,7 +1291,7 @@
<div class="flex gap-3 overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-slate-100"> <div class="flex gap-3 overflow-hidden rounded-2xl bg-white shadow-sm ring-1 ring-slate-100">
{{-- Item image --}} {{-- Item image --}}
@if($hasImage) @if($hasImage)
<img src="{{ route('qr.public.item-image', [$qrCode->short_code, $si, $ii]) }}" <img src="{{ $qrCode->publicPath('item-image/'.$si.'/'.$ii) }}"
alt="{{ $item['name'] }}" alt="{{ $item['name'] }}"
class="h-[100px] w-[100px] flex-shrink-0 object-cover" loading="lazy"> class="h-[100px] w-[100px] flex-shrink-0 object-cover" loading="lazy">
@endif @endif
@@ -1444,6 +1451,8 @@
<p class="text-xs font-semibold uppercase tracking-widest text-slate-400">Your details</p> <p class="text-xs font-semibold uppercase tracking-widest text-slate-400">Your details</p>
<input type="text" x-model="name" placeholder="Full name *" <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"> 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="email" x-model="email" placeholder="Email address *"
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 *" <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"> 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>
+1 -1
View File
@@ -3,7 +3,7 @@
$evColor = $c['brand_color'] ?? '#4f46e5'; $evColor = $c['brand_color'] ?? '#4f46e5';
$evName = $c['name'] ?? $qrCode->label; $evName = $c['name'] ?? $qrCode->label;
$hasLogo = !empty($c['logo_path']); $hasLogo = !empty($c['logo_path']);
$logoUrl = $hasLogo ? route('qr.public.event.logo', $qrCode->short_code) : null; $logoUrl = $hasLogo ? $qrCode->publicPath('event-logo') : null;
$size = $c['badge_size'] ?? '4x3'; $size = $c['badge_size'] ?? '4x3';
// Physical badge dimensions (inches) // Physical badge dimensions (inches)
[$bw, $bh] = match ($size) { [$bw, $bh] = match ($size) {