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
+88 -1
View File
@@ -7,7 +7,7 @@ use App\Support\Qr\QrTypeCatalog;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/** Per-account QR Plus preferences (notifications and new-code defaults). */
/** Per-account Ladill Events preferences (notifications, event defaults, QR style). */
class QrSetting extends Model
{
protected $fillable = [
@@ -15,16 +15,36 @@ class QrSetting extends Model
'notify_email',
'product_updates',
'low_balance_alerts',
'notify_registrations',
'notify_payouts',
'default_type',
'default_style',
'event_defaults',
];
protected $casts = [
'product_updates' => 'boolean',
'low_balance_alerts' => 'boolean',
'notify_registrations' => 'boolean',
'notify_payouts' => 'boolean',
'default_style' => 'array',
'event_defaults' => 'array',
];
/** @return list<string> */
public static function storableEventDefaultKeys(): array
{
return [
'currency',
'mode',
'badge_size',
'brand_color',
'organizer',
'registration_open',
'badge_fields',
];
}
/** @return list<string> */
public static function storableStyleKeys(): array
{
@@ -58,6 +78,73 @@ class QrSetting extends Model
return in_array($type, QrTypeCatalog::eventsTypes(), true) ? $type : QrCode::TYPE_EVENT;
}
/** @return array<string, mixed> */
public function resolvedEventDefaults(): array
{
$defaults = [
'currency' => 'GHS',
'mode' => 'ticketing',
'badge_size' => '4x3',
'brand_color' => '#4f46e5',
'organizer' => '',
'registration_open' => true,
'badge_fields' => ['Company', 'Role'],
];
$stored = collect($this->event_defaults ?? [])
->only(self::storableEventDefaultKeys())
->filter(fn ($value) => $value !== null && $value !== '')
->all();
if (isset($stored['badge_fields']) && is_array($stored['badge_fields'])) {
$stored['badge_fields'] = array_values(array_filter(
array_map('strval', $stored['badge_fields']),
fn (string $field) => trim($field) !== '',
));
if ($stored['badge_fields'] === []) {
unset($stored['badge_fields']);
}
}
return array_merge($defaults, $stored);
}
/** @param array<string, mixed>|null $input */
public static function sanitizeEventDefaults(?array $input): ?array
{
if ($input === null) {
return null;
}
$allowedModes = ['ticketing', 'contributions', 'free'];
$allowedSizes = ['4x3', '4x6', 'cr80'];
$allowedCurrencies = ['GHS', 'USD', 'NGN', 'KES'];
$mode = (string) ($input['mode'] ?? 'ticketing');
$badgeSize = (string) ($input['badge_size'] ?? '4x3');
$currency = strtoupper(trim((string) ($input['currency'] ?? 'GHS')));
$badgeFields = array_values(array_filter(
array_map(fn ($field) => mb_substr(trim((string) $field), 0, 40), (array) ($input['badge_fields'] ?? [])),
fn (string $field) => $field !== '',
));
$brandColor = (string) ($input['brand_color'] ?? '#4f46e5');
if (! preg_match('/^#[0-9a-fA-F]{6}$/', $brandColor)) {
$brandColor = '#4f46e5';
}
return [
'currency' => in_array($currency, $allowedCurrencies, true) ? $currency : 'GHS',
'mode' => in_array($mode, $allowedModes, true) ? $mode : 'ticketing',
'badge_size' => in_array($badgeSize, $allowedSizes, true) ? $badgeSize : '4x3',
'brand_color' => $brandColor,
'organizer' => mb_substr(trim((string) ($input['organizer'] ?? '')), 0, 120),
'registration_open' => filter_var($input['registration_open'] ?? true, FILTER_VALIDATE_BOOL),
'badge_fields' => $badgeFields !== [] ? $badgeFields : ['Company', 'Role'],
];
}
/** @return array<string, mixed> */
public function resolvedDefaultStyle(): array
{