Standardize settings pages on shared centered card layout.
Deploy Ladill Frontdesk / deploy (push) Successful in 46s

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-05 21:11:11 +00:00
co-authored by Cursor
parent d64a754227
commit c68f164ba4
4 changed files with 92 additions and 133 deletions
@@ -0,0 +1,15 @@
@props(['title' => null, 'description' => null])
<div {{ $attributes->merge(['class' => 'rounded-xl border border-slate-200 bg-white shadow-sm']) }}>
@if ($title)
<div class="border-b border-slate-100 px-6 py-4">
<h2 class="text-base font-semibold text-slate-900">{{ $title }}</h2>
@if ($description)
<p class="mt-1 text-sm text-slate-500">{{ $description }}</p>
@endif
</div>
@endif
<div class="px-6 py-5">
{{ $slot }}
</div>
</div>
@@ -0,0 +1,11 @@
@props(['title' => 'Settings', 'description' => null])
<div {{ $attributes->merge(['class' => 'mx-auto max-w-3xl space-y-6']) }}>
<div>
<h1 class="text-2xl font-semibold text-slate-900">{{ $title }}</h1>
@if ($description)
<p class="mt-1 text-sm text-slate-500">{{ $description }}</p>
@endif
</div>
{{ $slot }}
</div>
@@ -1,44 +1,43 @@
<x-app-layout title="Badge template"> <x-app-layout title="Badge template">
<h1 class="text-xl font-semibold text-slate-900 dark:text-slate-100">Badge template</h1> <x-settings.page title="Badge template" description="Configure fields shown on printed visitor badges.">
<p class="text-sm text-slate-500 dark:text-slate-400">Configure fields shown on printed visitor badges.</p> <form method="POST" action="{{ route('frontdesk.settings.badge.update') }}" class="space-y-6">
@csrf @method('PUT')
<form method="POST" action="{{ route('frontdesk.settings.badge.update') }}" class="mt-6 max-w-2xl space-y-6"> <x-settings.card title="Layout" description="Choose what appears on visitor badges and set accent styling.">
@csrf @method('PUT') <div class="grid gap-3 sm:grid-cols-2">
@foreach ([
'show_photo' => 'Visitor photo',
'show_qr' => 'QR code',
'show_host' => 'Host name',
'show_company' => 'Company',
'show_type' => 'Visitor type',
] as $key => $label)
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="{{ $key }}" value="1" @checked(old($key, $template[$key] ?? true))>
{{ $label }}
</label>
@endforeach
</div>
<div class="mt-4">
<label class="block text-sm font-medium text-slate-700">Accent color</label>
<input type="color" name="primary_color" value="{{ old('primary_color', $template['primary_color'] ?? '#0d9488') }}" class="mt-1 h-10 w-20 rounded border-slate-300">
</div>
<div class="mt-4">
<label class="block text-sm font-medium text-slate-700">Footer text</label>
<input type="text" name="footer_text" value="{{ old('footer_text', $template['footer_text'] ?? '') }}" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
</div>
@if ($canManage)
<div class="mt-6 flex justify-end">
<button type="submit" class="btn-primary">Save template</button>
</div>
@endif
</x-settings.card>
</form>
<section class="rounded-2xl border border-slate-200 bg-white p-5 space-y-4 dark:border-slate-700 dark:bg-slate-900"> @if ($sampleVisit)
<h2 class="text-sm font-semibold text-slate-900 dark:text-slate-100">Layout</h2> <x-settings.card title="Live preview" description="Preview how badges render with your current template.">
<div class="grid gap-3 sm:grid-cols-2"> <iframe src="{{ route('frontdesk.visits.badge.preview', $sampleVisit) }}" class="h-96 w-full max-w-md rounded-xl border border-slate-200"></iframe>
@foreach ([ </x-settings.card>
'show_photo' => 'Visitor photo', @endif
'show_qr' => 'QR code', </x-settings.page>
'show_host' => 'Host name',
'show_company' => 'Company',
'show_type' => 'Visitor type',
] as $key => $label)
<label class="flex items-center gap-2 text-sm text-slate-700 dark:text-slate-300">
<input type="checkbox" name="{{ $key }}" value="1" @checked(old($key, $template[$key] ?? true))>
{{ $label }}
</label>
@endforeach
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300">Accent color</label>
<input type="color" name="primary_color" value="{{ old('primary_color', $template['primary_color'] ?? '#0d9488') }}" class="mt-1 h-10 w-20 rounded border-slate-300">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300">Footer text</label>
<input type="text" name="footer_text" value="{{ old('footer_text', $template['footer_text'] ?? '') }}" class="mt-1 w-full rounded-lg border-slate-300 text-sm dark:border-slate-600 dark:bg-slate-800">
</div>
@if ($canManage)
<button type="submit" class="btn-primary">Save template</button>
@endif
</section>
</form>
@if ($sampleVisit)
<div class="mt-8">
<h2 class="text-sm font-semibold text-slate-900 dark:text-slate-100">Live preview</h2>
<iframe src="{{ route('frontdesk.visits.badge.preview', $sampleVisit) }}" class="mt-3 h-96 w-full max-w-md rounded-2xl border border-slate-200 dark:border-slate-700"></iframe>
</div>
@endif
</x-app-layout> </x-app-layout>
@@ -3,30 +3,17 @@
$settings = $organization->settings ?? []; $settings = $organization->settings ?? [];
@endphp @endphp
<div class="mx-auto max-w-3xl space-y-6"> <x-settings.page description="Configure branding, reception workflows, host alerts, and integrations for {{ $organization->name }}.">
<x-frontdesk.page-hero
badge="Organization · Kiosk · Notifications"
title="Settings"
description="Configure branding, reception workflows, host alerts, and integrations for {{ $organization->name }}."
:stats="[
['value' => $isPro ? ($plan['label'] ?? 'Pro') : 'Free', 'label' => 'Plan'],
['value' => number_format($branchCount), 'label' => 'Branches'],
['value' => number_format($monthlyUsage->email_count), 'label' => 'Emails this month'],
]" />
@if ($canManage) @if ($canManage)
<section class="rounded-2xl border border-slate-200 bg-white p-5 sm:p-6"> <x-settings.card title="Plan &amp; usage" description="Subscription limits and notification billing for {{ $organization->name }}.">
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"> <div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div> <p class="text-sm leading-6 text-slate-600">
<h2 class="text-sm font-semibold text-slate-900">Plan &amp; usage</h2> @if ($isPro)
<p class="mt-1 text-sm leading-6 text-slate-600"> <span class="font-medium text-indigo-700">{{ $plan['label'] ?? 'Pro' }}</span> unlimited branches and kiosks, integrations, and unlimited host emails (SMS still billed per segment).
@if ($isPro) @else
<span class="font-medium text-indigo-700">{{ $plan['label'] ?? 'Pro' }}</span> unlimited branches and kiosks, integrations, and unlimited host emails (SMS still billed per segment). <span class="font-medium text-slate-900">Free</span> one branch, one kiosk, core check-in and badges. Host alerts billed after {{ number_format($freeEmailAllowance) }} free emails per month.
@else @endif
<span class="font-medium text-slate-900">Free</span> one branch, one kiosk, core check-in and badges. Host alerts billed after {{ number_format($freeEmailAllowance) }} free emails per month. </p>
@endif
</p>
</div>
@if (! $isPro) @if (! $isPro)
<a href="{{ route('frontdesk.pro.index') }}" class="btn-primary shrink-0 text-sm">Upgrade to Pro (GHS {{ number_format($proPriceMinor / 100, 0) }}/mo)</a> <a href="{{ route('frontdesk.pro.index') }}" class="btn-primary shrink-0 text-sm">Upgrade to Pro (GHS {{ number_format($proPriceMinor / 100, 0) }}/mo)</a>
@endif @endif
@@ -50,17 +37,15 @@
<p class="mt-4 text-xs leading-5 text-slate-500"> <p class="mt-4 text-xs leading-5 text-slate-500">
Email {{ $emailCostFormatted }} each after free allowance · SMS from {{ $smsCostFormatted }} per segment charged to your Email {{ $emailCostFormatted }} each after free allowance · SMS from {{ $smsCostFormatted }} per segment charged to your
<a href="{{ route('frontdesk.wallet') }}" class="font-medium text-indigo-600 underline">Ladill wallet</a>. <a href="{{ route('frontdesk.wallet') }}" class="font-medium text-indigo-600 underline">Ladill wallet</a>.
Hosts are notified via email or phone on their profile; linking a Ladill account is optional.
</p> </p>
</section> </x-settings.card>
@endif @endif
<form method="POST" action="{{ route('frontdesk.settings.update') }}" enctype="multipart/form-data" class="space-y-6"> <form method="POST" action="{{ route('frontdesk.settings.update') }}" enctype="multipart/form-data" class="space-y-6">
@csrf @method('PUT') @csrf @method('PUT')
<section class="rounded-2xl border border-slate-200 bg-white p-5 sm:p-6"> <x-settings.card title="Organization" description="Name, timezone, and branding shown on kiosks and badges.">
<h2 class="text-sm font-semibold text-slate-900">Organization</h2> <div class="space-y-4">
<div class="mt-4 space-y-4">
@if ($canManage) @if ($canManage)
<div> <div>
<label class="block text-sm font-medium text-slate-700">Name</label> <label class="block text-sm font-medium text-slate-700">Name</label>
@@ -76,7 +61,7 @@
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-slate-700">Company logo</label> <label class="block text-sm font-medium text-slate-700">Company logo</label>
<p class="mt-1 text-xs text-slate-500">Shown on the visitor kiosk and employee badges. PNG, JPG, WebP, or SVG up to 2&nbsp;MB.</p> <p class="mt-1 text-xs text-slate-500">PNG, JPG, WebP, or SVG up to 2&nbsp;MB.</p>
@if (\App\Support\OrganizationBranding::hasCustomLogo($organization)) @if (\App\Support\OrganizationBranding::hasCustomLogo($organization))
<img src="{{ \App\Support\OrganizationBranding::logoUrl($organization) }}" <img src="{{ \App\Support\OrganizationBranding::logoUrl($organization) }}"
alt="{{ $organization->name }}" alt="{{ $organization->name }}"
@@ -102,12 +87,11 @@
</dl> </dl>
@endif @endif
</div> </div>
</section> </x-settings.card>
@if ($canManage) @if ($canManage)
<section class="rounded-2xl border border-slate-200 bg-white p-5 sm:p-6"> <x-settings.card title="Reception &amp; kiosk" description="Badge timing, visitor policy, and kiosk behaviour.">
<h2 class="text-sm font-semibold text-slate-900">Reception &amp; kiosk</h2> <div class="space-y-5">
<div class="mt-4 space-y-5">
<div class="grid gap-4 sm:grid-cols-2"> <div class="grid gap-4 sm:grid-cols-2">
<div> <div>
<label class="block text-sm font-medium text-slate-700">Badge expiry (hours)</label> <label class="block text-sm font-medium text-slate-700">Badge expiry (hours)</label>
@@ -122,39 +106,33 @@
<input type="number" name="contractor_badge_expiry_hours" value="{{ old('contractor_badge_expiry_hours', data_get($settings, 'type_badge_expiry_hours.contractor', 12)) }}" min="1" max="24" class="mt-1 w-full max-w-xs rounded-lg border-slate-300 text-sm"> <input type="number" name="contractor_badge_expiry_hours" value="{{ old('contractor_badge_expiry_hours', data_get($settings, 'type_badge_expiry_hours.contractor', 12)) }}" min="1" max="24" class="mt-1 w-full max-w-xs rounded-lg border-slate-300 text-sm">
</div> </div>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-slate-700">Visitor policy</label> <label class="block text-sm font-medium text-slate-700">Visitor policy</label>
<p class="mt-1 text-xs text-slate-500">Shown to guests during kiosk check-in when policies must be accepted.</p>
<textarea name="visitor_policy" rows="4" class="mt-2 w-full rounded-lg border-slate-300 text-sm">{{ old('visitor_policy', $settings['visitor_policy'] ?? '') }}</textarea> <textarea name="visitor_policy" rows="4" class="mt-2 w-full rounded-lg border-slate-300 text-sm">{{ old('visitor_policy', $settings['visitor_policy'] ?? '') }}</textarea>
</div> </div>
<label class="flex items-start gap-3 rounded-xl border border-slate-100 bg-slate-50 px-4 py-3 text-sm">
<label class="flex items-start gap-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-700">
<input type="checkbox" name="employee_kiosk_enabled" value="1" class="mt-0.5" <input type="checkbox" name="employee_kiosk_enabled" value="1" class="mt-0.5"
@checked(old('employee_kiosk_enabled', $settings['employee_kiosk_enabled'] ?? true))> @checked(old('employee_kiosk_enabled', $settings['employee_kiosk_enabled'] ?? true))>
<span> <span>
<span class="font-medium text-slate-900">Employee sign-in on kiosk</span> <span class="font-semibold text-slate-900">Employee sign-in on kiosk</span>
<span class="mt-0.5 block text-xs text-slate-500">Allow staff to sign in with employee code and PIN.</span> <span class="mt-0.5 block text-slate-600">Allow staff to sign in with employee code and PIN.</span>
</span> </span>
</label> </label>
<div class="rounded-xl border border-slate-100 bg-slate-50 px-4 py-3">
<div class="rounded-xl border border-slate-200 p-4"> <p class="font-semibold text-slate-900">Ladill Queue</p>
<h3 class="text-sm font-semibold text-slate-900">Ladill Queue</h3> <p class="mt-0.5 text-sm text-slate-600">Enable queue management in Frontdesk. Also turn on Frontdesk integration in Ladill Queue.</p>
<p class="mt-1 text-xs leading-5 text-slate-500">Manage service queues and counters in Frontdesk. You must also enable Frontdesk integration in Ladill Queue settings.</p>
<label class="mt-3 flex items-center gap-2 text-sm text-slate-700"> <label class="mt-3 flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="queue_integration_enabled" value="1" @checked(data_get($settings, 'queue_integration_enabled'))> <input type="checkbox" name="queue_integration_enabled" value="1" @checked(data_get($settings, 'queue_integration_enabled'))>
Enable Ladill Queue integration Enable Ladill Queue integration
</label> </label>
</div> </div>
</div> </div>
</section> </x-settings.card>
<section class="rounded-2xl border border-slate-200 bg-white p-5 sm:p-6"> <x-settings.card title="Notifications" description="How hosts are alerted when visitors arrive.">
<h2 class="text-sm font-semibold text-slate-900">Notifications</h2> <div class="space-y-5">
<div class="mt-4 space-y-5">
<div> <div>
<p class="text-sm font-medium text-slate-700">Channels</p> <p class="text-sm font-medium text-slate-700">Channels</p>
<p class="mt-1 text-xs text-slate-500">Alerts go to each host's email and/or phone no Ladill account required.</p>
<div class="mt-3 flex flex-wrap gap-4"> <div class="mt-3 flex flex-wrap gap-4">
@foreach ($notificationChannels as $key => $label) @foreach ($notificationChannels as $key => $label)
<label class="flex items-center gap-2 text-sm text-slate-700"> <label class="flex items-center gap-2 text-sm text-slate-700">
@@ -165,7 +143,6 @@
@endforeach @endforeach
</div> </div>
</div> </div>
<div class="border-t border-slate-100 pt-5"> <div class="border-t border-slate-100 pt-5">
<p class="text-sm font-medium text-slate-700">Events</p> <p class="text-sm font-medium text-slate-700">Events</p>
<div class="mt-3 grid gap-3 sm:grid-cols-2"> <div class="mt-3 grid gap-3 sm:grid-cols-2">
@@ -178,60 +155,17 @@
@endforeach @endforeach
</div> </div>
</div> </div>
<div class="border-t border-slate-100 pt-5"> <div class="border-t border-slate-100 pt-5">
<label class="block text-sm font-medium text-slate-700">Daily report recipients</label> <label class="block text-sm font-medium text-slate-700">Daily report recipients</label>
<input type="text" name="report_daily_recipients" value="{{ old('report_daily_recipients', implode(', ', $settings['report_daily_recipients'] ?? [])) }}" placeholder="admin@example.com, security@example.com" class="mt-2 w-full rounded-lg border-slate-300 text-sm"> <input type="text" name="report_daily_recipients" value="{{ old('report_daily_recipients', implode(', ', $settings['report_daily_recipients'] ?? [])) }}" placeholder="admin@example.com, security@example.com" class="mt-2 w-full rounded-lg border-slate-300 text-sm">
<p class="mt-1 text-xs text-slate-500">Comma-separated emails for the daily summary report.</p>
</div> </div>
</div> </div>
</section> </x-settings.card>
<div class="flex justify-end"> <div class="flex justify-end">
<button type="submit" class="btn-primary">Save settings</button> <button type="submit" class="btn-primary">Save settings</button>
</div> </div>
@endif @endif
</form> </form>
</x-settings.page>
<section class="overflow-hidden rounded-2xl border border-slate-200 bg-white">
<div class="border-b border-slate-100 px-5 py-4 sm:px-6">
<h2 class="text-sm font-semibold text-slate-900">Related settings</h2>
<p class="mt-1 text-xs text-slate-500">Branches, hardware, templates, and integrations.</p>
</div>
<div class="grid gap-px bg-slate-100 sm:grid-cols-2">
@if ($canManage)
<a href="{{ route('frontdesk.branches.index') }}" class="flex flex-col bg-white px-5 py-4 transition hover:bg-indigo-50/40 sm:px-6">
<span class="font-medium text-slate-900">Branches</span>
<span class="mt-1 text-sm text-slate-500">{{ $branchCount }} branch{{ $branchCount === 1 ? '' : 'es' }}</span>
</a>
<a href="{{ route('frontdesk.members.index') }}" class="flex flex-col bg-white px-5 py-4 transition hover:bg-indigo-50/40 sm:px-6">
<span class="font-medium text-slate-900">Team members</span>
<span class="mt-1 text-sm text-slate-500">Roles and access</span>
</a>
@endif
<a href="{{ route('frontdesk.kiosk') }}" class="flex flex-col bg-white px-5 py-4 transition hover:bg-indigo-50/40 sm:px-6">
<span class="font-medium text-slate-900">Kiosk mode</span>
<span class="mt-1 text-sm text-slate-500">Open self-service check-in</span>
</a>
@if ($canManage)
<a href="{{ route('frontdesk.settings.badge') }}" class="flex flex-col bg-white px-5 py-4 transition hover:bg-indigo-50/40 sm:px-6">
<span class="font-medium text-slate-900">Badge template</span>
<span class="mt-1 text-sm text-slate-500">Layout, photo, and QR settings</span>
</a>
<a href="{{ route('frontdesk.integrations.edit') }}" class="flex flex-col bg-white px-5 py-4 transition hover:bg-indigo-50/40 sm:px-6">
<span class="font-medium text-slate-900">Integrations</span>
<span class="mt-1 text-sm text-slate-500">Webhooks and calendar feeds</span>
</a>
<a href="{{ route('frontdesk.employees.index') }}" class="flex flex-col bg-white px-5 py-4 transition hover:bg-indigo-50/40 sm:px-6">
<span class="font-medium text-slate-900">Employees</span>
<span class="mt-1 text-sm text-slate-500">Staff roster and kiosk PINs</span>
</a>
<a href="{{ route('frontdesk.devices.index') }}" class="flex flex-col bg-white px-5 py-4 transition hover:bg-indigo-50/40 sm:px-6">
<span class="font-medium text-slate-900">Devices</span>
<span class="mt-1 text-sm text-slate-500">Kiosks, printers, and hardware</span>
</a>
@endif
</div>
</section>
</div>
</x-app-layout> </x-app-layout>