Expand settings beyond QR into a full Events preferences page.
Deploy Ladill Events / deploy (push) Successful in 51s

Add event notification toggles, new-event defaults that pre-fill creation, and tuck QR styling into a secondary section.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-07 13:31:22 +00:00
co-authored by Cursor
parent 8a1d5708a9
commit 30528a0c7f
7 changed files with 340 additions and 73 deletions
+177 -64
View File
@@ -1,9 +1,14 @@
<x-user-layout>
<x-slot name="title">Settings</x-slot>
@php
$ed = $eventDefaults;
$badgeFieldLabels = old('event_defaults.badge_fields', $ed['badge_fields'] ?? ['Company', 'Role']);
@endphp
<div class="mx-auto max-w-2xl">
<h1 class="text-xl font-semibold tracking-tight text-slate-900">Settings</h1>
<p class="mt-0.5 text-sm text-slate-500">QR Plus notifications and defaults for new codes.</p>
<p class="mt-0.5 text-sm text-slate-500">Notifications, defaults for new events, and QR preferences.</p>
@if (session('success'))
<div class="mt-4 rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800">{{ session('success') }}</div>
@@ -15,7 +20,7 @@
<div class="rounded-2xl border border-slate-200 bg-white p-5">
<h2 class="text-sm font-semibold text-slate-900">Notifications</h2>
<p class="mt-1 text-xs text-slate-500">Where we send QR Plus billing reminders and product updates.</p>
<p class="mt-1 text-xs text-slate-500">Choose what we email you about your events and account.</p>
<div class="mt-4">
<label for="notify_email" class="block text-[11px] font-medium text-slate-500">Notifications email</label>
@@ -27,8 +32,28 @@
<label class="mt-4 flex items-center justify-between gap-4">
<span>
<span class="block text-sm font-medium text-slate-800">Low balance alerts</span>
<span class="block text-xs text-slate-400">Email when your wallet is too low to create a new code.</span>
<span class="block text-sm font-medium text-slate-800">New registrations</span>
<span class="block text-xs text-slate-400">Email when someone registers or buys a ticket.</span>
</span>
<input type="checkbox" name="notify_registrations" value="1"
@checked(old('notify_registrations', $settings->notify_registrations ?? true))
class="h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
</label>
<label class="mt-4 flex items-center justify-between gap-4">
<span>
<span class="block text-sm font-medium text-slate-800">Payouts & settlements</span>
<span class="block text-xs text-slate-400">Email when ticket revenue is ready to withdraw.</span>
</span>
<input type="checkbox" name="notify_payouts" value="1"
@checked(old('notify_payouts', $settings->notify_payouts ?? true))
class="h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
</label>
<label class="mt-4 flex items-center justify-between gap-4">
<span>
<span class="block text-sm font-medium text-slate-800">Low QR balance</span>
<span class="block text-xs text-slate-400">Email when your wallet is too low to publish a new event QR.</span>
</span>
<input type="checkbox" name="low_balance_alerts" value="1"
@checked(old('low_balance_alerts', $settings->low_balance_alerts ?? true))
@@ -37,8 +62,8 @@
<label class="mt-4 flex items-center justify-between gap-4">
<span>
<span class="block text-sm font-medium text-slate-800">Product updates</span>
<span class="block text-xs text-slate-400">Occasional emails about new QR Plus features.</span>
<span class="block text-sm font-medium text-slate-800">Ladill Events updates</span>
<span class="block text-xs text-slate-400">Occasional emails about new features and improvements.</span>
</span>
<input type="checkbox" name="product_updates" value="1"
@checked(old('product_updates', $settings->product_updates ?? true))
@@ -47,93 +72,181 @@
</div>
<div class="rounded-2xl border border-slate-200 bg-white p-5">
<h2 class="text-sm font-semibold text-slate-900">New code defaults</h2>
<p class="mt-1 text-xs text-slate-500">Pre-fill the create screen so every new QR starts with your brand look.</p>
<h2 class="text-sm font-semibold text-slate-900">New event defaults</h2>
<p class="mt-1 text-xs text-slate-500">Pre-fill the create flow so every new event starts with your usual setup.</p>
<div class="mt-4 grid gap-4 sm:grid-cols-2">
<div>
<label for="foreground" class="block text-[11px] font-medium text-slate-500">Foreground</label>
<input type="color" id="foreground" name="default_style[foreground]"
value="{{ old('default_style.foreground', $style['foreground']) }}"
class="mt-1 h-10 w-full cursor-pointer rounded-lg border border-slate-200 bg-white p-1">
</div>
<div>
<label for="background" class="block text-[11px] font-medium text-slate-500">Background</label>
<input type="color" id="background" name="default_style[background]"
value="{{ old('default_style.background', $style['background']) }}"
class="mt-1 h-10 w-full cursor-pointer rounded-lg border border-slate-200 bg-white p-1">
</div>
</div>
<div class="mt-4 grid gap-4 sm:grid-cols-2">
<div>
<label for="module_style" class="block text-[11px] font-medium text-slate-500">Module style</label>
<select id="module_style" name="default_style[module_style]"
<label for="default_currency" class="block text-[11px] font-medium text-slate-500">Currency</label>
<select id="default_currency" name="event_defaults[currency]"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
@foreach ($moduleStyles as $key => $meta)
<option value="{{ $key }}" @selected(old('default_style.module_style', $style['module_style']) === $key)>{{ $meta['label'] }}</option>
@foreach (['GHS' => 'GHS (Ghanaian Cedi)', 'USD' => 'USD (US Dollar)', 'NGN' => 'NGN (Nigerian Naira)', 'KES' => 'KES (Kenyan Shilling)'] as $code => $label)
<option value="{{ $code }}" @selected(old('event_defaults.currency', $ed['currency']) === $code)>{{ $label }}</option>
@endforeach
</select>
</div>
<div>
<label for="error_correction" class="block text-[11px] font-medium text-slate-500">Error correction</label>
<select id="error_correction" name="default_style[error_correction]"
<label for="default_mode" class="block text-[11px] font-medium text-slate-500">Collection type</label>
<select id="default_mode" name="event_defaults[mode]"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
@foreach (['L' => 'Low', 'M' => 'Medium', 'Q' => 'Quartile', 'H' => 'High'] as $key => $label)
<option value="{{ $key }}" @selected(old('default_style.error_correction', $style['error_correction']) === $key)>{{ $label }} ({{ $key }})</option>
@endforeach
<option value="ticketing" @selected(old('event_defaults.mode', $ed['mode']) === 'ticketing')>Ticketed event</option>
<option value="contributions" @selected(old('event_defaults.mode', $ed['mode']) === 'contributions')>Contributions / gifts</option>
<option value="free" @selected(old('event_defaults.mode', $ed['mode']) === 'free')>Free registration</option>
</select>
</div>
</div>
<div class="mt-4 grid gap-4 sm:grid-cols-2">
<div>
<label for="finder_outer" class="block text-[11px] font-medium text-slate-500">Corner outer</label>
<select id="finder_outer" name="default_style[finder_outer]"
<label for="default_badge_size" class="block text-[11px] font-medium text-slate-500">Badge size</label>
<select id="default_badge_size" name="event_defaults[badge_size]"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
@foreach ($cornerOuterStyles as $key => $meta)
<option value="{{ $key }}" @selected(old('default_style.finder_outer', $style['finder_outer']) === $key)>{{ $meta['label'] }}</option>
@foreach (['4x3' => '4 × 3 in (landscape)', '4x6' => '4 × 6 in (lanyard)', 'cr80' => 'CR80 card'] as $size => $label)
<option value="{{ $size }}" @selected(old('event_defaults.badge_size', $ed['badge_size']) === $size)>{{ $label }}</option>
@endforeach
</select>
</div>
<div>
<label for="finder_inner" class="block text-[11px] font-medium text-slate-500">Corner inner</label>
<select id="finder_inner" name="default_style[finder_inner]"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
@foreach ($cornerInnerStyles as $key => $meta)
<option value="{{ $key }}" @selected(old('default_style.finder_inner', $style['finder_inner']) === $key)>{{ $meta['label'] }}</option>
@endforeach
</select>
</div>
</div>
<div class="mt-4 grid gap-4 sm:grid-cols-2">
<div>
<label for="frame_style" class="block text-[11px] font-medium text-slate-500">Frame</label>
<select id="frame_style" name="default_style[frame_style]"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
@foreach ($frameStyles as $key => $meta)
<option value="{{ $key }}" @selected(old('default_style.frame_style', $style['frame_style']) === $key)>{{ $meta['label'] }}</option>
@endforeach
</select>
</div>
<div>
<label for="frame_color" class="block text-[11px] font-medium text-slate-500">Frame color</label>
<input type="color" id="frame_color" name="default_style[frame_color]"
value="{{ old('default_style.frame_color', $style['frame_color']) }}"
<label for="default_brand_color" class="block text-[11px] font-medium text-slate-500">Brand color</label>
<input type="color" id="default_brand_color" name="event_defaults[brand_color]"
value="{{ old('event_defaults.brand_color', $ed['brand_color']) }}"
class="mt-1 h-10 w-full cursor-pointer rounded-lg border border-slate-200 bg-white p-1">
</div>
</div>
<div class="mt-4">
<label for="frame_text" class="block text-[11px] font-medium text-slate-500">Frame label</label>
<input type="text" id="frame_text" name="default_style[frame_text]" maxlength="100"
value="{{ old('default_style.frame_text', $style['frame_text']) }}"
placeholder="SCAN ME"
<label for="default_organizer" class="block text-[11px] font-medium text-slate-500">Default organizer name</label>
<input type="text" id="default_organizer" name="event_defaults[organizer]" maxlength="120"
value="{{ old('event_defaults.organizer', $ed['organizer']) }}"
placeholder="e.g. CAPBuSS Events"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
</div>
<div class="mt-4" x-data="{ fields: @js(array_values($badgeFieldLabels)) }">
<label class="block text-[11px] font-medium text-slate-500">Default badge fields</label>
<p class="mt-0.5 text-[11px] text-slate-400">Extra attendee fields shown on badges (e.g. Company, Role).</p>
<div class="mt-2 space-y-2">
<template x-for="(field, i) in fields" :key="i">
<div class="flex items-center gap-2">
<input type="text" :name="'event_defaults[badge_fields]['+i+']'" x-model="fields[i]" maxlength="40"
class="flex-1 rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
<button type="button" x-show="fields.length > 1" @click="fields.splice(i, 1)"
class="rounded-lg border border-slate-200 px-2 py-1 text-xs text-slate-500 hover:bg-slate-50">Remove</button>
</div>
</template>
</div>
<button type="button" @click="fields.push('')"
class="mt-2 text-xs font-semibold text-indigo-600 hover:text-indigo-800">+ Add field</button>
</div>
<label class="mt-4 flex items-center justify-between gap-4 rounded-xl border border-slate-100 bg-slate-50/80 px-4 py-3">
<span>
<span class="block text-sm font-medium text-slate-800">Registration open by default</span>
<span class="block text-xs text-slate-400">New events accept sign-ups unless you turn this off.</span>
</span>
<input type="checkbox" name="event_defaults[registration_open]" value="1"
@checked(old('event_defaults.registration_open', $ed['registration_open']))
class="h-5 w-5 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
</label>
</div>
<details class="group rounded-2xl border border-slate-200 bg-white">
<summary class="cursor-pointer list-none p-5 marker:content-none">
<div class="flex items-center justify-between gap-3">
<div>
<h2 class="text-sm font-semibold text-slate-900">QR code defaults</h2>
<p class="mt-1 text-xs text-slate-500">Style applied when you design event QR codes.</p>
</div>
<svg class="h-5 w-5 shrink-0 text-slate-400 transition group-open:rotate-180" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/></svg>
</div>
</summary>
<div class="space-y-4 border-t border-slate-100 px-5 pb-5 pt-4">
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label for="foreground" class="block text-[11px] font-medium text-slate-500">Foreground</label>
<input type="color" id="foreground" name="default_style[foreground]"
value="{{ old('default_style.foreground', $style['foreground']) }}"
class="mt-1 h-10 w-full cursor-pointer rounded-lg border border-slate-200 bg-white p-1">
</div>
<div>
<label for="background" class="block text-[11px] font-medium text-slate-500">Background</label>
<input type="color" id="background" name="default_style[background]"
value="{{ old('default_style.background', $style['background']) }}"
class="mt-1 h-10 w-full cursor-pointer rounded-lg border border-slate-200 bg-white p-1">
</div>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label for="module_style" class="block text-[11px] font-medium text-slate-500">Module style</label>
<select id="module_style" name="default_style[module_style]"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
@foreach ($moduleStyles as $key => $meta)
<option value="{{ $key }}" @selected(old('default_style.module_style', $style['module_style']) === $key)>{{ $meta['label'] }}</option>
@endforeach
</select>
</div>
<div>
<label for="error_correction" class="block text-[11px] font-medium text-slate-500">Error correction</label>
<select id="error_correction" name="default_style[error_correction]"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
@foreach (['L' => 'Low', 'M' => 'Medium', 'Q' => 'Quartile', 'H' => 'High'] as $key => $label)
<option value="{{ $key }}" @selected(old('default_style.error_correction', $style['error_correction']) === $key)>{{ $label }} ({{ $key }})</option>
@endforeach
</select>
</div>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label for="finder_outer" class="block text-[11px] font-medium text-slate-500">Corner outer</label>
<select id="finder_outer" name="default_style[finder_outer]"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
@foreach ($cornerOuterStyles as $key => $meta)
<option value="{{ $key }}" @selected(old('default_style.finder_outer', $style['finder_outer']) === $key)>{{ $meta['label'] }}</option>
@endforeach
</select>
</div>
<div>
<label for="finder_inner" class="block text-[11px] font-medium text-slate-500">Corner inner</label>
<select id="finder_inner" name="default_style[finder_inner]"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
@foreach ($cornerInnerStyles as $key => $meta)
<option value="{{ $key }}" @selected(old('default_style.finder_inner', $style['finder_inner']) === $key)>{{ $meta['label'] }}</option>
@endforeach
</select>
</div>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<div>
<label for="frame_style" class="block text-[11px] font-medium text-slate-500">Frame</label>
<select id="frame_style" name="default_style[frame_style]"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
@foreach ($frameStyles as $key => $meta)
<option value="{{ $key }}" @selected(old('default_style.frame_style', $style['frame_style']) === $key)>{{ $meta['label'] }}</option>
@endforeach
</select>
</div>
<div>
<label for="frame_color" class="block text-[11px] font-medium text-slate-500">Frame color</label>
<input type="color" id="frame_color" name="default_style[frame_color]"
value="{{ old('default_style.frame_color', $style['frame_color']) }}"
class="mt-1 h-10 w-full cursor-pointer rounded-lg border border-slate-200 bg-white p-1">
</div>
</div>
<div>
<label for="frame_text" class="block text-[11px] font-medium text-slate-500">Frame label</label>
<input type="text" id="frame_text" name="default_style[frame_text]" maxlength="100"
value="{{ old('default_style.frame_text', $style['frame_text']) }}"
placeholder="SCAN ME"
class="mt-1 w-full rounded-lg border border-slate-200 bg-slate-50 px-3 py-2 text-sm focus:border-indigo-300 focus:bg-white focus:outline-none focus:ring-2 focus:ring-indigo-100">
</div>
</div>
</details>
<button type="submit" class="rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-indigo-700">
Save settings
</button>