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

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-05 21:11:45 +00:00
co-authored by Cursor
parent 762aba0246
commit cbbcfbb68b
6 changed files with 190 additions and 142 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,48 +1,47 @@
<x-app-layout title="Calendar"> <x-app-layout title="Calendar">
<div class="mx-auto max-w-xl"> <x-settings.page title="Calendar integration" description="Sync scheduled meetings to your calendar.">
<h1 class="text-xl font-semibold text-slate-900">Calendar integration</h1> <x-settings.card title="Connections">
<p class="mt-1 text-sm text-slate-600">Sync scheduled meetings to your calendar</p> <ul class="space-y-3 text-sm">
<li class="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-slate-100 bg-slate-50 px-4 py-3">
<div class="mt-6 space-y-4"> <div>
<div class="flex items-center justify-between rounded-2xl border border-slate-200 bg-white p-6"> <p class="font-semibold text-slate-900">Ladill Mail</p>
<div> <p class="mt-0.5 text-slate-600">
<h2 class="font-medium">Ladill Mail</h2> @if ($mailConnected)
<p class="text-sm text-slate-500"> Connected events sync to your mailbox calendar
@if ($mailConnected) @elseif (! $mailConfigured)
Connected events sync to your mailbox calendar Not configured on this server
@elseif (! $mailConfigured) @else
Not configured on this server Not connected
@else @endif
Not connected </p>
@endif </div>
</p> @if ($mailConfigured && ! $mailConnected)
</div> <form method="POST" action="{{ route('meet.settings.calendar.mail') }}">
@if ($mailConfigured && ! $mailConnected) @csrf
<form method="POST" action="{{ route('meet.settings.calendar.mail') }}"> <button type="submit" class="btn-primary shrink-0 text-sm">Connect</button>
@csrf </form>
<button type="submit" class="btn-primary">Connect</button> @endif
</form> </li>
@endif <li class="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-slate-100 bg-slate-50 px-4 py-3">
</div> <div>
<div class="flex items-center justify-between rounded-2xl border border-slate-200 bg-white p-6"> <p class="font-semibold text-slate-900">Google Calendar</p>
<div> <p class="mt-0.5 text-slate-600">{{ $googleConnected ? 'Connected' : 'Not connected' }}</p>
<h2 class="font-medium">Google Calendar</h2> </div>
<p class="text-sm text-slate-500">{{ $googleConnected ? 'Connected' : 'Not connected' }}</p> @if ($googleAuthUrl && ! $googleConnected)
</div> <a href="{{ route('meet.settings.calendar.connect', 'google') }}" class="btn-primary shrink-0 text-sm">Connect</a>
@if ($googleAuthUrl && ! $googleConnected) @endif
<a href="{{ route('meet.settings.calendar.connect', 'google') }}" class="btn-primary">Connect</a> </li>
@endif <li class="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-slate-100 bg-slate-50 px-4 py-3">
</div> <div>
<div class="flex items-center justify-between rounded-2xl border border-slate-200 bg-white p-6"> <p class="font-semibold text-slate-900">Microsoft 365</p>
<div> <p class="mt-0.5 text-slate-600">{{ $microsoftConnected ? 'Connected' : 'Not connected' }}</p>
<h2 class="font-medium">Microsoft 365</h2> </div>
<p class="text-sm text-slate-500">{{ $microsoftConnected ? 'Connected' : 'Not connected' }}</p> @if ($microsoftAuthUrl && ! $microsoftConnected)
</div> <a href="{{ route('meet.settings.calendar.connect', 'microsoft') }}" class="btn-primary shrink-0 text-sm">Connect</a>
@if ($microsoftAuthUrl && ! $microsoftConnected) @endif
<a href="{{ route('meet.settings.calendar.connect', 'microsoft') }}" class="btn-primary">Connect</a> </li>
@endif </ul>
</div> <p class="mt-4 text-sm text-slate-500">Apple Calendar: use iCal download on each meeting detail page.</p>
<p class="text-sm text-slate-500">Apple Calendar: use iCal download on each meeting detail page.</p> </x-settings.card>
</div> </x-settings.page>
</div>
</x-app-layout> </x-app-layout>
+61 -41
View File
@@ -1,56 +1,76 @@
<x-app-layout title="Settings"> <x-app-layout title="Settings">
<div class="mx-auto max-w-2xl"> <x-settings.page title="Organization settings">
<h1 class="text-xl font-semibold text-slate-900">Organization settings</h1> <form method="POST" action="{{ route('meet.settings.update') }}" enctype="multipart/form-data" class="space-y-6">
<form method="POST" action="{{ route('meet.settings.update') }}" enctype="multipart/form-data" class="mt-6 space-y-5 rounded-2xl border border-slate-200 bg-white p-6">
@csrf @method('PUT') @csrf @method('PUT')
<div> <x-settings.card title="Organization" description="Name, type, timezone, and logo for {{ $organization->name }}.">
<label class="block text-sm font-medium text-slate-700">Organization name</label> <div class="space-y-5">
<input type="text" name="name" value="{{ old('name', $organization->name) }}" @disabled(! $canManage) required class="mt-1 w-full rounded-lg border-slate-300 text-sm"> <div>
</div> <label class="block text-sm font-medium text-slate-700">Organization name</label>
<input type="text" name="name" value="{{ old('name', $organization->name) }}" @disabled(! $canManage) required class="mt-1 w-full rounded-lg border-slate-300 text-sm">
</div>
<div> <div>
<label class="block text-sm font-medium text-slate-700">Organization type</label> <label class="block text-sm font-medium text-slate-700">Organization type</label>
<select name="org_type" @disabled(! $canManage) class="mt-1 w-full rounded-lg border-slate-300 text-sm"> <select name="org_type" @disabled(! $canManage) class="mt-1 w-full rounded-lg border-slate-300 text-sm">
@foreach ($orgTypes as $value => $label) @foreach ($orgTypes as $value => $label)
<option value="{{ $value }}" @selected(old('org_type', data_get($organization->settings, 'org_type', 'business')) === $value)>{{ $label }}</option> <option value="{{ $value }}" @selected(old('org_type', data_get($organization->settings, 'org_type', 'business')) === $value)>{{ $label }}</option>
@endforeach @endforeach
</select> </select>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-slate-700">Timezone</label> <label class="block text-sm font-medium text-slate-700">Timezone</label>
<select name="timezone" @disabled(! $canManage) class="mt-1 w-full rounded-lg border-slate-300 text-sm"> <select name="timezone" @disabled(! $canManage) class="mt-1 w-full rounded-lg border-slate-300 text-sm">
@foreach (timezone_identifiers_list() as $tz) @foreach (timezone_identifiers_list() as $tz)
<option value="{{ $tz }}" @selected(old('timezone', $organization->timezone) === $tz)>{{ $tz }}</option> <option value="{{ $tz }}" @selected(old('timezone', $organization->timezone) === $tz)>{{ $tz }}</option>
@endforeach @endforeach
</select> </select>
</div> </div>
@if ($canManage) @if ($canManage)
<div> <div>
<label class="block text-sm font-medium text-slate-700">Logo</label> <label class="block text-sm font-medium text-slate-700">Logo</label>
<input type="file" name="logo" accept="image/png,image/jpeg,image/webp,image/svg+xml" class="mt-2 block w-full text-sm"> <input type="file" name="logo" accept="image/png,image/jpeg,image/webp,image/svg+xml" class="mt-2 block w-full text-sm">
@if (\App\Support\OrganizationBranding::hasCustomLogo($organization)) @if (\App\Support\OrganizationBranding::hasCustomLogo($organization))
<label class="mt-2 flex items-center gap-2 text-sm"><input type="checkbox" name="remove_logo" value="1"> Remove current logo</label> <label class="mt-2 flex items-center gap-2 text-sm"><input type="checkbox" name="remove_logo" value="1"> Remove current logo</label>
@endif
</div>
<button type="submit" class="btn-primary">Save settings</button>
@endif @endif
</div> </div>
<button type="submit" class="btn-primary">Save settings</button> </x-settings.card>
@endif
</form> </form>
@if ($isAdmin ?? false) @if ($isAdmin ?? false)
<div class="mt-6 rounded-2xl border border-slate-200 bg-white p-6"> <x-settings.card title="Advanced" description="Security, calendar sync, and webhook integrations.">
<h2 class="text-sm font-semibold text-slate-900">Advanced</h2> <ul class="space-y-3 text-sm">
<ul class="mt-3 space-y-2 text-sm"> <li class="flex flex-wrap items-start justify-between gap-3 rounded-xl border border-slate-100 bg-slate-50 px-4 py-3">
<li><a href="{{ route('meet.settings.security') }}" class="text-indigo-600 hover:text-indigo-800">Security policy</a></li> <div>
<li><a href="{{ route('meet.settings.calendar') }}" class="text-indigo-600 hover:text-indigo-800">Calendar connections</a></li> <p class="font-semibold text-slate-900">Security policy</p>
<li><a href="{{ route('meet.settings.webhooks') }}" class="text-indigo-600 hover:text-indigo-800">Webhooks</a></li> <p class="mt-0.5 text-slate-600">Meeting access rules, recording defaults, and session timeouts.</p>
</div>
<a href="{{ route('meet.settings.security') }}" class="shrink-0 font-semibold text-indigo-600 hover:text-indigo-700">Configure</a>
</li>
<li class="flex flex-wrap items-start justify-between gap-3 rounded-xl border border-slate-100 bg-slate-50 px-4 py-3">
<div>
<p class="font-semibold text-slate-900">Calendar connections</p>
<p class="mt-0.5 text-slate-600">Sync scheduled meetings to Ladill Mail, Google Calendar, or Microsoft 365.</p>
</div>
<a href="{{ route('meet.settings.calendar') }}" class="shrink-0 font-semibold text-indigo-600 hover:text-indigo-700">Manage</a>
</li>
<li class="flex flex-wrap items-start justify-between gap-3 rounded-xl border border-slate-100 bg-slate-50 px-4 py-3">
<div>
<p class="font-semibold text-slate-900">Webhooks</p>
<p class="mt-0.5 text-slate-600">Notify sibling Ladill apps when meetings are created or updated.</p>
</div>
<a href="{{ route('meet.settings.webhooks') }}" class="shrink-0 font-semibold text-indigo-600 hover:text-indigo-700">Manage</a>
</li>
</ul> </ul>
</div> </x-settings.card>
@endif @endif
<p class="mt-4 text-sm text-slate-500">{{ $branchCount }} branch(es) configured.</p> <p class="text-sm text-slate-500">{{ $branchCount }} branch(es) configured.</p>
</div> </x-settings.page>
</x-app-layout> </x-app-layout>
@@ -1,23 +1,25 @@
<x-app-layout title="Security settings"> <x-app-layout title="Security settings">
<div class="mx-auto max-w-xl"> <x-settings.page title="Meeting security" description="Organization-wide defaults for {{ $organization->name }}.">
<h1 class="text-xl font-semibold text-slate-900">Meeting security</h1> <form method="POST" action="{{ route('meet.settings.security.update') }}">
<p class="mt-1 text-sm text-slate-600">Organization-wide defaults for {{ $organization->name }}</p>
<form method="POST" action="{{ route('meet.settings.security.update') }}" class="mt-6 space-y-4 rounded-2xl border border-slate-200 bg-white p-6">
@csrf @method('PUT') @csrf @method('PUT')
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="org_only" value="1" @checked($policy['org_only'] ?? false) class="rounded border-slate-300"> Ladill accounts only</label>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="authenticated_only" value="1" @checked($policy['authenticated_only'] ?? false) class="rounded border-slate-300"> Authenticated users only</label> <x-settings.card title="Policy defaults" description="Applied to new meetings unless overridden per meeting.">
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="recording_host_only" value="1" @checked($policy['recording_host_only'] ?? true) class="rounded border-slate-300"> Host-only recording</label> <div class="space-y-4">
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="watermark_enabled" value="1" @checked($policy['watermark_enabled'] ?? false) class="rounded border-slate-300"> Show watermark in meetings</label> <label class="flex items-center gap-2 text-sm"><input type="checkbox" name="org_only" value="1" @checked($policy['org_only'] ?? false) class="rounded border-slate-300"> Ladill accounts only</label>
<div> <label class="flex items-center gap-2 text-sm"><input type="checkbox" name="authenticated_only" value="1" @checked($policy['authenticated_only'] ?? false) class="rounded border-slate-300"> Authenticated users only</label>
<label class="block text-sm font-medium text-slate-700">Allowed email domains (comma-separated)</label> <label class="flex items-center gap-2 text-sm"><input type="checkbox" name="recording_host_only" value="1" @checked($policy['recording_host_only'] ?? true) class="rounded border-slate-300"> Host-only recording</label>
<input type="text" name="allowed_domains" value="{{ implode(', ', $policy['allowed_domains'] ?? []) }}" class="mt-1 w-full rounded-lg border-slate-300 text-sm" placeholder="example.com, company.org"> <label class="flex items-center gap-2 text-sm"><input type="checkbox" name="watermark_enabled" value="1" @checked($policy['watermark_enabled'] ?? false) class="rounded border-slate-300"> Show watermark in meetings</label>
</div> <div>
<div> <label class="block text-sm font-medium text-slate-700">Allowed email domains (comma-separated)</label>
<label class="block text-sm font-medium text-slate-700">Session idle timeout (minutes)</label> <input type="text" name="allowed_domains" value="{{ implode(', ', $policy['allowed_domains'] ?? []) }}" class="mt-1 w-full rounded-lg border-slate-300 text-sm" placeholder="example.com, company.org">
<input type="number" name="session_idle_minutes" value="{{ $policy['session_idle_minutes'] ?? 120 }}" min="15" max="480" class="mt-1 w-full rounded-lg border-slate-300 text-sm"> </div>
</div> <div>
<button type="submit" class="btn-primary w-full">Save policy</button> <label class="block text-sm font-medium text-slate-700">Session idle timeout (minutes)</label>
<input type="number" name="session_idle_minutes" value="{{ $policy['session_idle_minutes'] ?? 120 }}" min="15" max="480" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
</div>
<button type="submit" class="btn-primary">Save policy</button>
</div>
</x-settings.card>
</form> </form>
</div> </x-settings.page>
</x-app-layout> </x-app-layout>
@@ -1,39 +1,40 @@
<x-app-layout title="Webhooks"> <x-app-layout title="Webhooks">
<div class="mx-auto max-w-xl"> <x-settings.page title="Webhooks" description="Notify sibling Ladill apps when meetings change.">
<h1 class="text-xl font-semibold text-slate-900">Webhooks</h1> <x-settings.card title="Add endpoint">
<p class="mt-1 text-sm text-slate-600">Notify sibling Ladill apps when meetings change</p> <form method="POST" action="{{ route('meet.settings.webhooks.store') }}" class="space-y-4">
@csrf
<div>
<label class="block text-sm font-medium text-slate-700">Endpoint URL</label>
<input type="url" name="url" required class="mt-1 w-full rounded-lg border-slate-300 text-sm" placeholder="https://care.ladill.com/webhooks/meet">
</div>
<div>
<label class="block text-sm font-medium text-slate-700">Signing secret (optional)</label>
<input type="text" name="secret" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
</div>
<fieldset class="space-y-2">
<legend class="text-sm font-medium text-slate-700">Events</legend>
@foreach ($events as $event)
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="events[]" value="{{ $event }}" checked class="rounded border-slate-300"> {{ $event }}</label>
@endforeach
</fieldset>
<button type="submit" class="btn-primary">Add endpoint</button>
</form>
</x-settings.card>
<form method="POST" action="{{ route('meet.settings.webhooks.store') }}" class="mt-6 space-y-4 rounded-2xl border border-slate-200 bg-white p-6"> <x-settings.card title="Configured endpoints">
@csrf <ul class="-mx-6 divide-y divide-slate-100">
<div> @forelse ($endpoints as $endpoint)
<label class="block text-sm font-medium text-slate-700">Endpoint URL</label> <li class="flex items-center justify-between gap-4 px-6 py-4 text-sm">
<input type="url" name="url" required class="mt-1 w-full rounded-lg border-slate-300 text-sm" placeholder="https://care.ladill.com/webhooks/meet"> <div class="min-w-0">
</div> <p class="font-mono text-slate-900">{{ $endpoint->url }}</p>
<div> <p class="text-slate-500">{{ implode(', ', $endpoint->events ?? []) }}</p>
<label class="block text-sm font-medium text-slate-700">Signing secret (optional)</label> </div>
<input type="text" name="secret" class="mt-1 w-full rounded-lg border-slate-300 text-sm"> <form method="POST" action="{{ route('meet.settings.webhooks.destroy', $endpoint) }}">@csrf @method('DELETE')<button type="submit" class="shrink-0 text-red-600 hover:underline">Remove</button></form>
</div> </li>
<fieldset class="space-y-2"> @empty
<legend class="text-sm font-medium text-slate-700">Events</legend> <li class="px-6 py-8 text-center text-slate-500">No webhook endpoints configured.</li>
@foreach ($events as $event) @endforelse
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="events[]" value="{{ $event }}" checked class="rounded border-slate-300"> {{ $event }}</label> </ul>
@endforeach </x-settings.card>
</fieldset> </x-settings.page>
<button type="submit" class="btn-primary w-full">Add endpoint</button>
</form>
<ul class="mt-6 divide-y divide-slate-100 rounded-2xl border border-slate-200 bg-white">
@forelse ($endpoints as $endpoint)
<li class="flex items-center justify-between px-6 py-4 text-sm">
<div>
<p class="font-mono text-slate-900">{{ $endpoint->url }}</p>
<p class="text-slate-500">{{ implode(', ', $endpoint->events ?? []) }}</p>
</div>
<form method="POST" action="{{ route('meet.settings.webhooks.destroy', $endpoint) }}">@csrf @method('DELETE')<button class="text-red-600 hover:underline">Remove</button></form>
</li>
@empty
<li class="px-6 py-8 text-center text-slate-500">No webhook endpoints configured.</li>
@endforelse
</ul>
</div>
</x-app-layout> </x-app-layout>