Files
ladill-events/resources/views/qr-codes/create.blade.php
T
isaaccladandCursor d8dbc83e2d
Deploy Ladill QR Plus / deploy (push) Successful in 28s
Extract Ladill Events as a standalone events suite at events.ladill.com.
Full control center for ticketed events, contributions, attendees, badges,
and programmes — not a QR utility clone. Includes SSO shell, import command,
and platform cutover runbook.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-07 09:39:53 +00:00

252 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<x-user-layout>
<x-slot name="title">Create QR Code</x-slot>
@php
$defaultStyle = \App\Support\Qr\QrStyleDefaults::merge(old('style') ?: ($accountDefaultStyle ?? null));
@endphp
<div class="mx-auto max-w-6xl space-y-6"
x-data="qrCustomizer({
previewUrl: @js(route('events.style-preview')),
csrf: @js(csrf_token()),
shortCode: 'preview',
style: @js($defaultStyle),
balance: @js((float) $wallet->spendableBalance()),
price: @js((float) $pricePerQr),
topupUrl: @js($topupUrl),
type: @js(old('type', $defaultType ?? 'url')),
})">
@if(session('error'))
<div class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{{ session('error') }}</div>
@endif
{{-- Mobile page header --}}
<div class="flex items-center gap-3 py-1 lg:hidden">
<a href="{{ route('events.index') }}"
class="flex h-9 w-9 items-center justify-center rounded-xl border border-slate-200 bg-white shadow-sm text-slate-500 hover:text-slate-900 transition">
<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="M6 18 18 6M6 6l12 12"/>
</svg>
</a>
<span class="text-sm font-semibold text-slate-900">Create QR code</span>
</div>
{{-- Desktop page header --}}
<div class="hidden lg:block">
<a href="{{ route('events.index') }}" class="inline-flex items-center gap-1 text-sm text-slate-500 hover:text-slate-700">
<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="M15.75 19.5 8.25 12l7.5-7.5"/></svg>
QR Codes
</a>
<h1 class="mt-2 text-2xl font-bold text-slate-900">Create QR code</h1>
<p class="mt-1 text-sm text-slate-500">Customize your code, then create.</p>
</div>
<form x-ref="createForm" action="{{ route('events.store') }}" method="POST" enctype="multipart/form-data"
class="grid grid-cols-1 gap-8 lg:grid-cols-[380px_1fr]"
@submit="submitOrTopup($event)">
@csrf
{{-- Left: Sticky preview (desktop only) --}}
<div class="hidden lg:block lg:sticky lg:top-6 lg:self-start">
@include('qr-codes.partials.qr-preview-card', ['showDownloads' => false])
</div>
{{-- Right: Controls --}}
<div class="space-y-5 pb-4 lg:pb-0">
{{-- Content first --}}
<div class="overflow-hidden rounded-2xl border border-slate-200/80 bg-white shadow-sm">
<div class="border-b border-slate-100 bg-slate-50/70 px-5 py-4">
<h2 class="text-sm font-semibold text-slate-900">Content</h2>
<p class="mt-0.5 text-xs text-slate-400">What should this QR code link to?</p>
</div>
<div class="space-y-4 p-5">
<div>
<label class="block text-xs font-semibold text-slate-600">Label</label>
<input type="text" name="label" value="{{ old('label') }}" required maxlength="120"
class="mt-1.5 w-full rounded-xl border border-slate-200 bg-white px-3.5 py-2.5 text-sm placeholder:text-slate-400 focus:border-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-400/30"
placeholder="e.g. Shop flyer, WiFi guest, Menu">
</div>
{{-- Custom link --}}
<div x-data="{
slug: @js(old('custom_short_code', '')),
status: '',
timer: null,
checkUrl: @js(route('events.check-slug')),
baseUrl: @js(\App\Models\QrCode::publicBaseUrl() . '/q'),
check() {
clearTimeout(this.timer);
const s = this.slug.trim();
if (s === '') { this.status = ''; return; }
if (!/^[a-z0-9][a-z0-9-]{1,18}[a-z0-9]$/.test(s)) { this.status = 'invalid'; return; }
this.status = 'checking';
this.timer = setTimeout(() => {
fetch(this.checkUrl + '?code=' + encodeURIComponent(s))
.then(r => r.json())
.then(d => { this.status = d.available ? 'available' : 'taken'; })
.catch(() => { this.status = ''; });
}, 400);
},
}">
<label class="block text-xs font-semibold text-slate-600">Custom link <span class="font-normal text-slate-400">(optional)</span></label>
<div class="mt-1.5 flex items-stretch rounded-xl border focus-within:border-indigo-400 focus-within:ring-1 focus-within:ring-indigo-400/30"
:class="status === 'available' ? 'border-green-400' : status === 'taken' || status === 'invalid' ? 'border-red-400' : 'border-slate-200'">
<span class="flex items-center rounded-l-xl border-r border-slate-200 bg-slate-50 px-3 text-xs text-slate-400 whitespace-nowrap select-none"
:class="status === 'available' ? 'border-green-400' : status === 'taken' || status === 'invalid' ? 'border-red-400' : 'border-slate-200'">
{{ \App\Models\QrCode::publicBaseUrl() }}/q/
</span>
<input type="text" name="custom_short_code"
x-model="slug"
@input="check()"
maxlength="20"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
class="min-w-0 flex-1 rounded-r-xl bg-white px-3 py-2.5 text-sm placeholder:text-slate-400 focus:outline-none"
placeholder="my-brand">
</div>
<div class="mt-1.5 flex items-center gap-1.5 text-[11px]">
<template x-if="status === 'checking'">
<span class="text-slate-400">Checking…</span>
</template>
<template x-if="status === 'available'">
<span class="flex items-center gap-1 text-green-600">
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/></svg>
Available your link will be <span x-text="baseUrl + '/' + slug.trim()" class="font-medium text-green-700"></span>
</span>
</template>
<template x-if="status === 'taken'">
<span class="flex items-center gap-1 text-red-500">
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
Already taken choose another
</span>
</template>
<template x-if="status === 'invalid'">
<span class="text-red-500">320 chars, letters, numbers and hyphens only (no leading/trailing hyphens)</span>
</template>
<template x-if="status === ''">
<span class="text-slate-400">Leave blank to auto-generate</span>
</template>
</div>
</div>
<div>
<label class="block text-xs font-semibold text-slate-600">Type</label>
<div class="mt-2 grid grid-cols-2 gap-2">
@foreach($types as $key => $meta)
<label class="cursor-pointer rounded-xl border-2 p-3 transition-all"
:class="type === '{{ $key }}' ? 'border-indigo-500 bg-indigo-50/60' : 'border-slate-200 hover:border-slate-300'">
<input type="radio" name="type" value="{{ $key }}" x-model="type" class="sr-only">
@if(!empty($meta['icon']))
<img src="{{ \App\Support\Qr\QrTypeCatalog::iconUrl($meta['icon']) }}" alt="" class="mb-1.5 h-5 w-5 text-slate-900">
@endif
<p class="text-sm font-semibold text-slate-900">{{ $meta['label'] }}</p>
<p class="mt-0.5 text-[11px] leading-4 text-slate-400">{{ $meta['description'] }}</p>
</label>
@endforeach
</div>
</div>
<div class="space-y-3">
@include('qr-codes.partials.type-fields-create')
</div>
<p class="rounded-xl bg-slate-50 px-3.5 py-2.5 text-xs leading-5 text-slate-400">
Your QR always points to a Ladill short link. Change the destination any time without reprinting.
</p>
</div>
</div>
{{-- Customization --}}
@include('qr-codes.partials.customization-fields', [
'style' => $defaultStyle,
'moduleStyles' => $moduleStyles,
'cornerOuterStyles' => $cornerOuterStyles,
'cornerInnerStyles' => $cornerInnerStyles,
'frameStyles' => $frameStyles,
])
{{-- Desktop submit button --}}
<button type="button"
@click="submitOrTopup($event)"
class="hidden lg:block w-full rounded-xl bg-indigo-600 py-3 text-sm font-semibold text-white shadow-sm transition hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
Create QR code
</button>
</div>
</form>
{{-- Mobile sticky action bar --}}
<div x-show="!showPreviewModal"
class="mobile-action-bar lg:hidden">
<div class="mobile-action-bar__toolbar">
<button type="button"
@click="showPreviewModal = true"
class="flex flex-1 items-center justify-center gap-2 rounded-xl border border-slate-200 bg-slate-50 py-2.5 text-sm font-semibold text-slate-700 transition hover:bg-slate-100">
<svg class="h-4 w-4" 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>
Preview
</button>
<button type="button"
@click="submitOrTopup($event)"
class="flex flex-1 items-center justify-center gap-2 rounded-xl bg-indigo-600 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-indigo-700">
Create
</button>
</div>
<div class="mobile-action-bar__inset-fill" aria-hidden="true"></div>
</div>
{{-- Preview modal --}}
<div x-show="showPreviewModal" x-cloak
x-transition:enter="transition-opacity duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition-opacity duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@keydown.escape.window="showPreviewModal = false"
class="fixed inset-0 z-[60] flex flex-col bg-black/60 backdrop-blur-sm lg:hidden"
style="padding-bottom: env(safe-area-inset-bottom, 0px)">
<div class="flex items-center justify-between px-5 py-4">
<span class="text-sm font-semibold text-white">Preview</span>
<button type="button" @click="showPreviewModal = false"
class="flex h-8 w-8 items-center justify-center rounded-full bg-white/20 text-white hover:bg-white/30 transition">
<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="M6 18 18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="flex flex-1 items-center justify-center p-8">
<div class="w-full max-w-xs overflow-hidden rounded-2xl bg-white shadow-2xl"
:style="frameStyle === 'thin'
? 'box-shadow: 0 0 0 2px ' + frameColor + ', 0 25px 50px -12px rgb(0 0 0 / 0.25)'
: 'box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25), 0 0 0 1px rgb(0 0 0 / 0.06)'">
<div :class="{
'p-[8px]': frameStyle === 'thin',
'p-[14px]': frameStyle === 'scan_me' || frameStyle === 'tap_to_scan',
'p-4': frameStyle === 'none',
}">
<div x-ref="qrPreviewModal"
class="aspect-square w-full [&>svg]:block [&>svg]:h-full [&>svg]:w-full"></div>
<div x-show="frameStyle === 'scan_me'" x-cloak
:style="'border-top: 1px solid ' + frameColor + '40; color: ' + frameColor"
class="pt-2.5 pb-0.5 text-center">
<span x-text="(frameText && frameText.trim() ? frameText : 'SCAN ME').toUpperCase()"
class="text-[11px] font-bold tracking-[0.18em]"></span>
</div>
<div x-show="frameStyle === 'tap_to_scan'" x-cloak class="mt-2.5 flex justify-center pb-0.5">
<span x-text="(frameText && frameText.trim() ? frameText : 'TAP TO SCAN').toUpperCase()"
:style="'background-color: ' + frameColor + '; color: ' + frameTextColor"
class="rounded-full px-5 py-1.5 text-[10px] font-bold tracking-widest"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</x-user-layout>