Initial Ladill Give extraction — online giving pages at give.ladill.com.

Donation checkout via Ladill Pay (9% fee), church/giving QR pages, SSO, and platform scan forwarding.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-09 07:25:49 +00:00
co-authored by Cursor
commit 0860b08af7
295 changed files with 37190 additions and 0 deletions
@@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@include('partials.favicon')
<title>{{ $qrCode->label }}</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
background: #f1f5f9;
font-family: system-ui, -apple-system, sans-serif;
display: flex;
flex-direction: column;
height: 100dvh;
color: #1e293b;
}
#toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1.25rem;
height: 3.25rem;
background: #fff;
border-bottom: 1px solid #e2e8f0;
flex-shrink: 0;
z-index: 20;
}
.tb-label {
font-size: 0.8rem;
font-weight: 600;
color: #64748b;
max-width: 65%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tb-download {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.9rem;
border-radius: 9999px;
background: #f1f5f9;
border: 1px solid #cbd5e1;
color: #334155;
font-size: 0.75rem;
font-weight: 500;
text-decoration: none;
transition: background 0.15s;
}
.tb-download:hover { background: #e2e8f0; }
.tb-download svg { width: 0.875rem; height: 0.875rem; flex-shrink: 0; }
#scroll {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
align-items: center;
padding: 1.25rem 0.75rem;
gap: 0.75rem;
}
#loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.875rem;
color: #94a3b8;
font-size: 0.8125rem;
padding: 4rem 0;
width: 100%;
}
.spinner {
width: 2rem; height: 2rem;
border: 2px solid #e2e8f0;
border-top-color: #94a3b8;
border-radius: 50%;
animation: spin 0.75s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.pdf-page {
display: block;
max-width: 100%;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0,0,0,0.12);
background: #fff;
}
#bottom {
flex-shrink: 0;
height: 2rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.72rem;
color: #94a3b8;
letter-spacing: 0.04em;
}
</style>
</head>
<body>
<div id="toolbar">
<span class="tb-label">{{ $qrCode->label }}</span>
@if($allowDownload)
<a href="{{ route('qr.public.file', $qrCode->short_code) }}" download class="tb-download">
<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"/>
</svg>
Download
</a>
@else
<span></span>
@endif
</div>
<div id="scroll">
<div id="loading">
<div class="spinner"></div>
Loading document…
</div>
</div>
<div id="bottom"><span id="page-info"></span></div>
<script type="module">
import * as pdfjsLib from 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.min.mjs';
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.worker.min.mjs';
const fileUrl = @json($fileUrl);
const scroll = document.getElementById('scroll');
const loading = document.getElementById('loading');
const pageInfo = document.getElementById('page-info');
async function main() {
const pdf = await pdfjsLib.getDocument(fileUrl).promise;
const n = pdf.numPages;
pageInfo.textContent = n + ' page' + (n !== 1 ? 's' : '');
loading.remove();
const maxW = scroll.clientWidth - 24;
for (let i = 1; i <= n; i++) {
const pg = await pdf.getPage(i);
const vp0 = pg.getViewport({ scale: 1 });
const scale = Math.min(maxW / vp0.width, 2);
const vp = pg.getViewport({ scale });
const dpr = window.devicePixelRatio || 1;
const canvas = document.createElement('canvas');
canvas.className = 'pdf-page';
canvas.width = vp.width * dpr;
canvas.height = vp.height * dpr;
canvas.style.width = vp.width + 'px';
canvas.style.height = vp.height + 'px';
scroll.appendChild(canvas);
await pg.render({ canvasContext: canvas.getContext('2d'), viewport: pg.getViewport({ scale: scale * dpr }) }).promise;
}
}
main().catch(err => {
loading.innerHTML = '<p style="color:#ef4444;text-align:center;padding:1rem">Failed to load document.</p>';
console.error(err);
});
</script>
</body>
</html>
@@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Thank you for giving</title>
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600,700&display=swap" rel="stylesheet" />
@vite(['resources/css/app.css'])
</head>
<body class="min-h-screen bg-emerald-50/40 font-sans antialiased">
<main class="mx-auto flex min-h-screen max-w-md flex-col justify-center px-4 py-10 text-center">
<div class="rounded-3xl border border-emerald-100 bg-white p-8 shadow-sm">
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-emerald-100 text-emerald-600">
<svg class="h-7 w-7" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/></svg>
</div>
<h1 class="mt-4 text-xl font-bold text-slate-900">Thank you!</h1>
<p class="mt-2 text-sm text-slate-600">
Your {{ $donation->currency }} {{ number_format($donation->amount_minor / 100, 2) }}
{{ $donation->collection_type ? strtolower($donation->collection_type) : 'donation' }} to
{{ $qrCode->content()['name'] ?? $qrCode->label }} was received.
</p>
<p class="mt-4 text-xs text-slate-500">Reference: {{ $donation->reference }}</p>
</div>
</main>
</body>
</html>
@@ -0,0 +1,73 @@
@php
$c = $qrCode->content();
$evColor = $c['brand_color'] ?? '#4f46e5';
$evName = $c['name'] ?? $qrCode->label;
$isContribution = ($c['mode'] ?? 'ticketing') === 'contributions';
$badgeUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=220x220&data=' . urlencode($registration->badge_code);
@endphp
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $isContribution ? 'Thank you' : "You're registered" }} {{ $evName }}</title>
@include('partials.favicon')
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-slate-100 flex items-center justify-center px-4 py-10">
<div class="w-full max-w-sm">
<div class="overflow-hidden rounded-3xl bg-white shadow-xl">
<div class="px-6 py-7 text-center" style="background:linear-gradient(160deg, {{ $evColor }} 0%, {{ $evColor }}cc 100%);">
<div class="mx-auto mb-3 flex h-14 w-14 items-center justify-center rounded-full bg-white/20">
<svg class="h-8 w-8 text-white" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/></svg>
</div>
<h1 class="text-xl font-black text-white">{{ $isContribution ? 'Thank you!' : "You're registered!" }}</h1>
<p class="mt-1 text-sm text-white/85">{{ $evName }}</p>
</div>
<div class="px-6 py-6 text-center">
@if($isContribution)
<p class="text-sm text-slate-500">Hi <span class="font-semibold text-slate-800">{{ $registration->attendee_name }}</span>, your contribution has been received.</p>
<div class="mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-5 py-6">
<p class="text-[10px] font-bold uppercase tracking-widest text-slate-400">{{ $registration->tier_name }}</p>
<p class="mt-1 text-3xl font-black" style="color:{{ $evColor }}">{{ $registration->currency }} {{ number_format($registration->amountCedis(), 2) }}</p>
<p class="mt-3 text-[10px] font-bold uppercase tracking-widest text-slate-400">Reference</p>
<p class="font-mono text-sm font-bold tracking-[0.15em] text-slate-700">{{ $registration->badge_code }}</p>
</div>
@if(($registration->badge_fields ?? []))
<div class="mt-5 space-y-2 text-left text-sm">
@foreach(($registration->badge_fields ?? []) as $label => $value)
<div class="flex justify-between border-b border-slate-100 pb-2"><span class="text-slate-400">{{ $label }}</span><span class="font-semibold text-slate-800">{{ $value }}</span></div>
@endforeach
</div>
@endif
<p class="mt-5 text-xs text-slate-400">A receipt was sent to {{ $registration->attendee_email }}.</p>
@else
<p class="text-sm text-slate-500">Hi <span class="font-semibold text-slate-800">{{ $registration->attendee_name }}</span>, your spot is confirmed.</p>
<div class="mt-5 rounded-2xl border border-slate-200 bg-slate-50 px-5 py-5">
<img src="{{ $badgeUrl }}" alt="Badge QR" class="mx-auto h-40 w-40 rounded-xl bg-white p-2 shadow-sm">
<p class="mt-3 text-[10px] font-bold uppercase tracking-widest text-slate-400">Badge code</p>
<p class="font-mono text-2xl font-black tracking-[0.2em]" style="color:{{ $evColor }}">{{ $registration->badge_code }}</p>
</div>
<div class="mt-5 space-y-2 text-left text-sm">
<div class="flex justify-between border-b border-slate-100 pb-2"><span class="text-slate-400">Ticket</span><span class="font-semibold text-slate-800">{{ $registration->tier_name }}</span></div>
@if($registration->isPaid())
<div class="flex justify-between border-b border-slate-100 pb-2"><span class="text-slate-400">Paid</span><span class="font-semibold text-slate-800">{{ $registration->currency }} {{ number_format($registration->amountCedis(), 2) }}</span></div>
@endif
@foreach(($registration->badge_fields ?? []) as $label => $value)
<div class="flex justify-between border-b border-slate-100 pb-2"><span class="text-slate-400">{{ $label }}</span><span class="font-semibold text-slate-800">{{ $value }}</span></div>
@endforeach
</div>
<p class="mt-5 text-xs text-slate-400">Show this badge code at check-in. A confirmation was sent to {{ $registration->attendee_email }}.</p>
@endif
</div>
</div>
</div>
</body>
</html>
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@include('partials.favicon')
<title>QR code inactive</title>
<style>
body { margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center; font-family: system-ui, sans-serif; background: #f8fafc; color: #334155; padding: 2rem; text-align: center; }
</style>
</head>
<body>
<div>
<h1 style="font-size: 1.25rem; margin: 0 0 0.5rem;">This QR code is not active</h1>
<p style="margin: 0; font-size: 0.875rem; color: #64748b;">The owner has paused this link. Try again later.</p>
</div>
</body>
</html>
File diff suppressed because it is too large Load Diff