diff --git a/app/Http/Controllers/Public/BookingController.php b/app/Http/Controllers/Public/BookingController.php index 538fb25..533af45 100644 --- a/app/Http/Controllers/Public/BookingController.php +++ b/app/Http/Controllers/Public/BookingController.php @@ -42,8 +42,8 @@ class BookingController extends Controller $validated = $request->validate([ 'customer_name' => ['required', 'string', 'max:120'], - 'customer_email' => ['required', 'email', 'max:200'], - 'customer_phone' => ['nullable', 'string', 'max:30'], + 'customer_email' => ['nullable', 'email', 'max:200'], + 'customer_phone' => ['required', 'string', 'max:30'], 'service_index' => ['required', 'integer', 'min:0'], 'starts_at' => ['required', 'date'], ]); diff --git a/app/Http/Controllers/Public/SaleOrderController.php b/app/Http/Controllers/Public/SaleOrderController.php index 64dab7f..fda2128 100644 --- a/app/Http/Controllers/Public/SaleOrderController.php +++ b/app/Http/Controllers/Public/SaleOrderController.php @@ -34,7 +34,7 @@ class SaleOrderController extends Controller $validated = $request->validate([ 'customer_name' => ['required', 'string', 'max:120'], - 'customer_email' => ['required', 'email', 'max:200'], + 'customer_email' => ['nullable', 'email', 'max:200'], 'customer_phone' => ['required', 'string', 'max:30'], 'shipping_fee' => ['nullable', 'numeric', 'min:0', 'max:10000'], 'items' => ['required', 'array', 'min:1'], diff --git a/app/Services/Merchant/GiveDonationService.php b/app/Services/Merchant/GiveDonationService.php index 40358b6..dcfa53e 100644 --- a/app/Services/Merchant/GiveDonationService.php +++ b/app/Services/Merchant/GiveDonationService.php @@ -56,7 +56,7 @@ class MerchantSaleService 'amount_minor' => $amountMinor, 'currency' => $qrCode->content()['currency'] ?? 'GHS', 'payer_name' => trim((string) ($data['customer_name'] ?? '')), - 'payer_email' => trim((string) ($data['customer_email'] ?? '')), + 'payer_email' => ($email = trim((string) ($data['customer_email'] ?? ''))) !== '' ? $email : null, 'payer_phone' => trim((string) ($data['customer_phone'] ?? '')), 'status' => QrSaleOrder::STATUS_PENDING, 'payment_reference' => null, diff --git a/resources/views/public/qr/book-landing.blade.php b/resources/views/public/qr/book-landing.blade.php index d9b28c7..fe773d9 100644 --- a/resources/views/public/qr/book-landing.blade.php +++ b/resources/views/public/qr/book-landing.blade.php @@ -6,7 +6,7 @@ $price = number_format((float) ($c['price_ghs'] ?? 0), 2); $fileType = strtoupper($c['file_type'] ?? 'PDF'); $hasCover = !empty($c['cover_path']); - $orderUrl = route('qr.public.book.checkout', $qrCode->short_code); + $orderUrl = $qrCode->publicPath('book/order'); $csrfToken = csrf_token(); @endphp @@ -35,7 +35,7 @@ customerPhone: '', async submitOrder() { if (!this.customerName.trim()) { this.errorMsg = 'Enter your full name.'; return; } - if (!this.customerEmail.trim() || !this.customerEmail.includes('@')) { this.errorMsg = 'Enter a valid email address.'; return; } + if (!this.customerPhone.trim()) { this.errorMsg = 'Enter your phone number.'; return; } this.loading = true; this.errorMsg = ''; try { const res = await fetch(@js($orderUrl), { @@ -63,7 +63,7 @@ {{-- Left column: cover panel (desktop only) --}} diff --git a/resources/views/public/qr/landing.blade.php b/resources/views/public/qr/landing.blade.php index a248c5c..493ae11 100644 --- a/resources/views/public/qr/landing.blade.php +++ b/resources/views/public/qr/landing.blade.php @@ -3,8 +3,8 @@ - @include('partials.favicon') {{ $qrCode->label }} + @include('partials.favicon') @vite(['resources/css/app.css']) @@ -24,7 +24,7 @@ - @@ -32,7 +32,7 @@ View document - @@ -62,7 +62,7 @@ {{-- ===== VCARD ===== --}} @elseif($type === \App\Models\QrCode::TYPE_VCARD) @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'] ?? []; $social = []; foreach ($rawSocial as $platform => $url) { @@ -159,8 +159,8 @@ {{-- Save contact button --}}
- + Save Contact @@ -178,8 +178,8 @@ $bizColor = $content['brand_color'] ?? '#1e3a5f'; $bizHasLogo = !empty($content['logo_path']); $bizHasCover = !empty($content['cover_path']); - $bizLogoUrl = $bizHasLogo ? route('qr.public.business.logo', $qrCode->short_code) : null; - $bizCoverUrl = $bizHasCover ? route('qr.public.business.cover', $qrCode->short_code) : null; + $bizLogoUrl = $bizHasLogo ? $qrCode->publicPath('business-logo') : null; + $bizCoverUrl = $bizHasCover ? $qrCode->publicPath('business-cover') : null; $bizInitials = strtoupper(substr($bizName, 0, 1)); $bizRawSocial = $content['social'] ?? []; $bizSocial = []; @@ -307,25 +307,25 @@
{{-- ===== CHURCH ===== --}} -@elseif($type === \App\Models\QrCode::TYPE_SHOP) +@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']); - $menuLogoUrl = $churchHasLogo ? route('qr.public.church.logo', $qrCode->short_code) : null; - $menuCoverUrl = $churchHasCover ? route('qr.public.church.cover', $qrCode->short_code) : null; + $churchLogoUrl = $churchHasLogo ? $qrCode->publicPath('church-logo') : null; + $churchCoverUrl = $churchHasCover ? $qrCode->publicPath('church-cover') : null; $churchInitials = strtoupper(substr($churchName, 0, 2)); - $churchAcceptsPay = true; + $churchAcceptsPay = !empty($content['accepts_payment']); $churchCurrency = $content['currency'] ?? 'GHS'; - $churchTypes = $content['collection_types'] ?? ['offering', 'tithe', 'order', 'harvest']; + $churchTypes = $content['collection_types'] ?? ['offering', 'tithe', 'donation', 'harvest']; // Normalise legacy lowercase slugs → display strings - $legacyLabels = ['offering'=>'Offering','tithe'=>'Tithe','order'=>'Order','harvest'=>'Harvest']; + $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 = route('qr.public.order', $qrCode->short_code); + $churchOrderRoute = $qrCode->publicPath('order'); $churchCsrf = csrf_token(); @endphp
@if($churchHasCover) - {{ $churchName }}
@else @@ -389,7 +388,7 @@
@if($churchHasLogo)
- {{ $churchName }}
@else @@ -484,7 +483,7 @@ $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', - 'order' => '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', + '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', @@ -494,7 +493,7 @@ '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 order' => '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', + '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', @@ -512,8 +511,8 @@
-

I'm merchant for

-

Choose a merchant category

+

I'm giving for

+

Choose a giving category

@@ -592,9 +591,6 @@ Give -

Your merchant is safe & secure

+

Your giving is safe & secure

@endif @@ -631,8 +627,8 @@ $evColor = $content['brand_color'] ?? '#4f46e5'; $evHasLogo = !empty($content['logo_path']); $evHasCover = !empty($content['cover_path']); - $evLogoUrl = $evHasLogo ? route('qr.public.event.logo', $qrCode->short_code) : null; - $evCoverUrl = $evHasCover ? route('qr.public.event.cover', $qrCode->short_code) : null; + $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'] ?? []; @@ -642,7 +638,7 @@ $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 = route('qr.public.event.register', $qrCode->short_code); + $evRegRoute = $qrCode->publicPath('register'); $evCsrf = csrf_token(); @endphp
-

I'm merchant for

+

I'm giving for

Choose a category

@@ -855,9 +851,7 @@

Your details

- - @foreach($evFields as $field) short_code) : null; + $itinCoverUrl = $itinHasCover ? $qrCode->publicPath('itinerary-cover') : null; $itinTitle = $content['title'] ?? $qrCode->label; $itinDays = $content['days'] ?? []; $multiDay = count($itinDays) > 1; @@ -1029,7 +1023,7 @@

{{ $qrCode->label }}

@foreach($content['images'] ?? [] as $index => $image) - {{ $image['title'] ?? 'Image ' . ($index + 1) }} @@ -1039,7 +1033,7 @@ {{-- ===== 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
@if($appIconUrl) @@ -1132,14 +1126,14 @@ $sections = $content['sections'] ?? []; $title = $content['title'] ?? $qrCode->label; $acceptsPayment = (bool) ($content['accepts_payment'] ?? false); - $orderRoute = route('qr.public.order', $qrCode->short_code); + $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 ? route('qr.public.menu.logo', $qrCode->short_code) : null; - $coverUrl = $hasCover ? route('qr.public.menu.cover', $qrCode->short_code) : null; + $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); @@ -1186,7 +1180,6 @@ 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.email.trim() || !this.email.includes('@')) { this.errorMsg = 'Enter a valid email address.'; return } if (!this.phone.trim()) { this.errorMsg = 'Enter your phone number.'; return } this.errorMsg = ''; this.loading = true; @@ -1291,7 +1284,7 @@
{{-- Item image --}} @if($hasImage) - {{ $item['name'] }} @endif @@ -1451,8 +1444,6 @@

Your details

-
diff --git a/resources/views/public/qr/storefront.blade.php b/resources/views/public/qr/storefront.blade.php index 394d39a..9cab98f 100644 --- a/resources/views/public/qr/storefront.blade.php +++ b/resources/views/public/qr/storefront.blade.php @@ -11,9 +11,9 @@ $isShop = $qrCode->type === \App\Models\QrCode::TYPE_SHOP; $hasLogo = ! empty($c['logo_path']); $hasCover = ! empty($c['cover_path']); - $logoUrl = $hasLogo ? route('qr.public.menu.logo', $qrCode->short_code) : null; - $coverUrl = $hasCover ? route('qr.public.menu.cover', $qrCode->short_code) : null; - $orderUrl = route('qr.public.order', $qrCode->short_code); + $logoUrl = $hasLogo ? $qrCode->publicPath('menu-logo') : null; + $coverUrl = $hasCover ? $qrCode->publicPath('menu-cover') : null; + $orderUrl = $qrCode->publicPath('order'); @endphp @@ -75,7 +75,7 @@ @endphp
@if($img) - + @endif

{{ $item['name'] ?? '' }}

@@ -139,7 +139,6 @@
-
@@ -180,7 +179,7 @@ function storefront(cfg) { get total() { return this.subtotal + this.shipping; }, async checkout() { this.errorMsg = ''; - if (!this.name.trim() || !this.email.trim() || !this.phone.trim()) { this.errorMsg = 'Please fill in your name, email and phone.'; return; } + if (!this.name.trim() || !this.phone.trim()) { this.errorMsg = 'Please fill in your name and phone.'; return; } if (this.count === 0) { this.errorMsg = 'Your cart is empty.'; return; } this.loading = true; try {