nimuli/resources/views/livewire/modals/create-qr-code.blade.php

189 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<div class="grid grid-cols-1 lg:grid-cols-2 gap-0 max-h-[85vh]">
{{-- LEFT: Form --}}
<div class="p-6 overflow-y-auto border-r border-white/[.06]">
<h2 class="text-lg font-semibold text-t1 mb-4">Create QR Code</h2>
<div class="mb-4">
<label class="block text-xs font-medium text-t2 mb-1.5">Label (optional)</label>
<input wire:model.live="label" type="text" placeholder="My QR Code"
class="block w-full px-3 py-2.5 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none focus:ring-1 focus:ring-blue/50">
</div>
<div class="flex gap-0.5 mb-4 border-b border-white/[.06] overflow-x-auto -mx-6 px-6">
@foreach(['url'=>'URL','vcard'=>'vCard','wifi'=>'WiFi','email'=>'Email','phone'=>'Phone','sms'=>'SMS','text'=>'Text'] as $key => $lbl)
<button wire:click="$set('type','{{ $key }}')" type="button"
class="px-3 py-2 text-xs whitespace-nowrap border-b-2 transition-colors -mb-px
{{ $type === $key ? 'border-blue text-blue font-medium' : 'border-transparent text-t2 hover:text-t1' }}">
{{ $lbl }}
</button>
@endforeach
</div>
<div class="space-y-3 mb-4">
@if(in_array($type, ['url','text','location']))
<input wire:model.live.debounce.250ms="url" type="{{ $type === 'url' ? 'url' : 'text' }}"
placeholder="{{ $type === 'url' ? 'https://example.com' : ($type === 'location' ? '48.8566,2.3522' : 'Your text') }}"
class="block w-full px-3 py-2.5 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none focus:ring-1 focus:ring-blue/50">
@elseif($type === 'email')
<input wire:model.live.debounce.250ms="url" type="email" placeholder="email@example.com"
class="block w-full px-3 py-2.5 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
@elseif($type === 'phone')
<input wire:model.live.debounce.250ms="url" type="tel" placeholder="+49 123 456789"
class="block w-full px-3 py-2.5 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
@elseif($type === 'sms')
<input wire:model.live.debounce.250ms="url" type="tel" placeholder="+49 123 456789"
class="block w-full px-3 py-2.5 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
@elseif($type === 'vcard')
<input wire:model.live="vcardName" placeholder="Full Name" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
<input wire:model.live="vcardPhone" placeholder="Phone" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
<input wire:model.live="vcardEmail" placeholder="Email" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
<input wire:model.live="vcardCompany" placeholder="Company" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
<input wire:model.live="vcardWebsite" placeholder="Website" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
@elseif($type === 'wifi')
<input wire:model.live="wifiSsid" placeholder="Network name (SSID)" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
<input wire:model.live="wifiPassword" type="password" placeholder="Password" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
<select wire:model.live="wifiEncryption" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 focus:outline-none">
<option value="WPA">WPA/WPA2</option>
<option value="WEP">WEP</option>
<option value="nopass">Open</option>
</select>
<label class="flex items-center gap-2 text-sm text-t2 cursor-pointer">
<input wire:model.live="wifiHidden" type="checkbox"> Hidden network
</label>
@endif
</div>
<details class="border border-white/[.06] rounded-lg mb-2">
<summary class="px-4 py-3 text-sm font-medium text-t1 cursor-pointer select-none">Styling</summary>
<div class="px-4 pb-4 space-y-3 mt-2">
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs text-t2 mb-1">Foreground</label>
<input wire:model.live="fgColor" type="color" class="w-full h-9 rounded cursor-pointer border border-white/[.06]">
</div>
<div>
<label class="block text-xs text-t2 mb-1">Background</label>
<input wire:model.live="bgColor" type="color" class="w-full h-9 rounded cursor-pointer border border-white/[.06]">
</div>
</div>
<div>
<label class="block text-xs text-t2 mb-1">Error Correction</label>
<select wire:model.live="errorCorrection" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 focus:outline-none">
<option value="L">L 7%</option>
<option value="M">M 15%</option>
<option value="Q">Q 25%</option>
<option value="H">H 30%</option>
</select>
</div>
<div>
<label class="block text-xs text-t2 mb-1">Size: <span class="text-t1">{{ $size }}px</span></label>
<input wire:model.live="size" type="range" min="128" max="1024" step="32" class="w-full accent-blue">
</div>
<div>
<label class="block text-xs text-t2 mb-1">Logo URL (optional)</label>
<input wire:model.live.debounce.500ms="logoUrl" type="url" placeholder="https://..."
class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
</div>
</div>
</details>
<details class="border border-white/[.06] rounded-lg mb-2">
<summary class="px-4 py-3 text-sm font-medium text-t1 cursor-pointer select-none">Frame</summary>
<div class="px-4 pb-4 space-y-3 mt-2">
<select wire:model.live="frame" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 focus:outline-none">
<option value="none">No frame</option>
<option value="label-bottom">Label bottom</option>
<option value="label-top">Label top</option>
</select>
<input wire:model.live="frameText" placeholder='e.g. "Scan me"'
class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
<div>
<label class="block text-xs text-t2 mb-1">Frame Color</label>
<input wire:model.live="frameColor" type="color" class="w-full h-9 rounded cursor-pointer border border-white/[.06]">
</div>
</div>
</details>
<details class="border border-white/[.06] rounded-lg mb-4">
<summary class="px-4 py-3 text-sm font-medium text-t1 cursor-pointer select-none">Advanced</summary>
<div class="px-4 pb-4 space-y-3 mt-2">
<label class="flex items-center gap-2 text-sm text-t2 cursor-pointer">
<input wire:model.live="isDynamic" type="checkbox"> Dynamic QR (target changeable later)
</label>
<div>
<label class="block text-xs text-t2 mb-1">Scan limit (optional)</label>
<input wire:model.live="scanLimit" type="number" min="1" placeholder="Unlimited"
class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
</div>
<div>
<label class="block text-xs text-t2 mb-1">Expires at (optional)</label>
<input wire:model.live="expiresAt" type="datetime-local"
class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 focus:outline-none">
</div>
</div>
</details>
<div class="flex gap-3">
<button wire:click="save" wire:loading.attr="disabled" wire:target="save"
class="flex-1 px-4 py-2.5 bg-accent-gradient text-white text-sm font-medium rounded-lg hover:opacity-90 disabled:opacity-60 transition-opacity">
<span wire:loading.remove wire:target="save">Create QR Code</span>
<span wire:loading wire:target="save">Creating...</span>
</button>
<button wire:click="closeModal" type="button"
class="px-4 py-2.5 bg-s2 text-t2 text-sm font-medium rounded-lg hover:text-t1 transition-colors border border-white/[.06]">
Cancel
</button>
</div>
</div>
{{-- RIGHT: Live Preview --}}
<div class="bg-s2 p-6 flex flex-col items-center justify-center">
<div class="text-xs font-medium text-t3 uppercase tracking-wider mb-4">Live Preview</div>
<div id="qr-preview-wrap" wire:ignore class="rounded-xl p-4 shadow-inner flex items-center justify-center"
style="background: {{ $bgColor }}; min-width: 220px; min-height: 220px;">
<canvas id="qr-canvas" style="display:none;"></canvas>
<span id="qr-placeholder" class="text-xs text-gray-400">Enter content above</span>
</div>
<div class="text-xs text-t3 mt-3">{{ $size }}×{{ $size }}px · {{ strtoupper($errorCorrection) }}</div>
</div>
</div>
@script
<script>
import QRCode from 'qrcode';
const canvas = document.getElementById('qr-canvas');
const placeholder = document.getElementById('qr-placeholder');
const wrap = document.getElementById('qr-preview-wrap');
async function renderQR(payload, options) {
const empty = !payload || ['mailto:', 'tel:', 'sms:', 'geo:', ''].includes(payload.trim());
if (empty) {
canvas.style.display = 'none';
placeholder.style.display = 'block';
return;
}
try {
placeholder.style.display = 'none';
canvas.style.display = 'block';
wrap.style.background = options.bgColor || '#ffffff';
await QRCode.toCanvas(canvas, payload, {
width: Math.min(220, options.size || 220),
margin: options.margin || 4,
errorCorrectionLevel: options.errorCorrection || 'M',
color: { dark: options.fgColor || '#000000', light: options.bgColor || '#ffffff' },
});
} catch(e) { console.warn('QR:', e); }
}
$wire.on('qr-preview-update', ({ payload, options }) => renderQR(payload, options));
renderQR(@json($this->buildPayload()), {
size: @json($size), margin: @json($margin),
errorCorrection: @json($errorCorrection),
fgColor: @json($fgColor), bgColor: @json($bgColor),
});
</script>
@endscript