Standardize settings pages on shared centered card layout.
Deploy Ladill Meet / deploy (push) Successful in 43s
Deploy Ladill Meet / deploy (push) Successful in 43s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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">
|
||||
<div class="mx-auto max-w-xl">
|
||||
<h1 class="text-xl font-semibold text-slate-900">Calendar integration</h1>
|
||||
<p class="mt-1 text-sm text-slate-600">Sync scheduled meetings to your calendar</p>
|
||||
|
||||
<div class="mt-6 space-y-4">
|
||||
<div class="flex items-center justify-between rounded-2xl border border-slate-200 bg-white p-6">
|
||||
<div>
|
||||
<h2 class="font-medium">Ladill Mail</h2>
|
||||
<p class="text-sm text-slate-500">
|
||||
@if ($mailConnected)
|
||||
Connected — events sync to your mailbox calendar
|
||||
@elseif (! $mailConfigured)
|
||||
Not configured on this server
|
||||
@else
|
||||
Not connected
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
@if ($mailConfigured && ! $mailConnected)
|
||||
<form method="POST" action="{{ route('meet.settings.calendar.mail') }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn-primary">Connect</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-2xl border border-slate-200 bg-white p-6">
|
||||
<div>
|
||||
<h2 class="font-medium">Google Calendar</h2>
|
||||
<p class="text-sm text-slate-500">{{ $googleConnected ? 'Connected' : 'Not connected' }}</p>
|
||||
</div>
|
||||
@if ($googleAuthUrl && ! $googleConnected)
|
||||
<a href="{{ route('meet.settings.calendar.connect', 'google') }}" class="btn-primary">Connect</a>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-2xl border border-slate-200 bg-white p-6">
|
||||
<div>
|
||||
<h2 class="font-medium">Microsoft 365</h2>
|
||||
<p class="text-sm text-slate-500">{{ $microsoftConnected ? 'Connected' : 'Not connected' }}</p>
|
||||
</div>
|
||||
@if ($microsoftAuthUrl && ! $microsoftConnected)
|
||||
<a href="{{ route('meet.settings.calendar.connect', 'microsoft') }}" class="btn-primary">Connect</a>
|
||||
@endif
|
||||
</div>
|
||||
<p class="text-sm text-slate-500">Apple Calendar: use iCal download on each meeting detail page.</p>
|
||||
</div>
|
||||
</div>
|
||||
<x-settings.page title="Calendar integration" description="Sync scheduled meetings to your calendar.">
|
||||
<x-settings.card title="Connections">
|
||||
<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>
|
||||
<p class="font-semibold text-slate-900">Ladill Mail</p>
|
||||
<p class="mt-0.5 text-slate-600">
|
||||
@if ($mailConnected)
|
||||
Connected — events sync to your mailbox calendar
|
||||
@elseif (! $mailConfigured)
|
||||
Not configured on this server
|
||||
@else
|
||||
Not connected
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
@if ($mailConfigured && ! $mailConnected)
|
||||
<form method="POST" action="{{ route('meet.settings.calendar.mail') }}">
|
||||
@csrf
|
||||
<button type="submit" class="btn-primary shrink-0 text-sm">Connect</button>
|
||||
</form>
|
||||
@endif
|
||||
</li>
|
||||
<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>
|
||||
<p class="font-semibold text-slate-900">Google Calendar</p>
|
||||
<p class="mt-0.5 text-slate-600">{{ $googleConnected ? 'Connected' : 'Not connected' }}</p>
|
||||
</div>
|
||||
@if ($googleAuthUrl && ! $googleConnected)
|
||||
<a href="{{ route('meet.settings.calendar.connect', 'google') }}" class="btn-primary shrink-0 text-sm">Connect</a>
|
||||
@endif
|
||||
</li>
|
||||
<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>
|
||||
<p class="font-semibold text-slate-900">Microsoft 365</p>
|
||||
<p class="mt-0.5 text-slate-600">{{ $microsoftConnected ? 'Connected' : 'Not connected' }}</p>
|
||||
</div>
|
||||
@if ($microsoftAuthUrl && ! $microsoftConnected)
|
||||
<a href="{{ route('meet.settings.calendar.connect', 'microsoft') }}" class="btn-primary shrink-0 text-sm">Connect</a>
|
||||
@endif
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-4 text-sm text-slate-500">Apple Calendar: use iCal download on each meeting detail page.</p>
|
||||
</x-settings.card>
|
||||
</x-settings.page>
|
||||
</x-app-layout>
|
||||
|
||||
@@ -1,56 +1,76 @@
|
||||
<x-app-layout title="Settings">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<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="mt-6 space-y-5 rounded-2xl border border-slate-200 bg-white p-6">
|
||||
<x-settings.page title="Organization settings">
|
||||
<form method="POST" action="{{ route('meet.settings.update') }}" enctype="multipart/form-data" class="space-y-6">
|
||||
@csrf @method('PUT')
|
||||
|
||||
<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>
|
||||
<x-settings.card title="Organization" description="Name, type, timezone, and logo for {{ $organization->name }}.">
|
||||
<div class="space-y-5">
|
||||
<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>
|
||||
<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">
|
||||
@foreach ($orgTypes as $value => $label)
|
||||
<option value="{{ $value }}" @selected(old('org_type', data_get($organization->settings, 'org_type', 'business')) === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<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">
|
||||
@foreach ($orgTypes as $value => $label)
|
||||
<option value="{{ $value }}" @selected(old('org_type', data_get($organization->settings, 'org_type', 'business')) === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<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">
|
||||
@foreach (timezone_identifiers_list() as $tz)
|
||||
<option value="{{ $tz }}" @selected(old('timezone', $organization->timezone) === $tz)>{{ $tz }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<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">
|
||||
@foreach (timezone_identifiers_list() as $tz)
|
||||
<option value="{{ $tz }}" @selected(old('timezone', $organization->timezone) === $tz)>{{ $tz }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@if ($canManage)
|
||||
<div>
|
||||
<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">
|
||||
@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>
|
||||
@if ($canManage)
|
||||
<div>
|
||||
<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">
|
||||
@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>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary">Save settings</button>
|
||||
@endif
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">Save settings</button>
|
||||
@endif
|
||||
</x-settings.card>
|
||||
</form>
|
||||
|
||||
@if ($isAdmin ?? false)
|
||||
<div class="mt-6 rounded-2xl border border-slate-200 bg-white p-6">
|
||||
<h2 class="text-sm font-semibold text-slate-900">Advanced</h2>
|
||||
<ul class="mt-3 space-y-2 text-sm">
|
||||
<li><a href="{{ route('meet.settings.security') }}" class="text-indigo-600 hover:text-indigo-800">Security policy</a></li>
|
||||
<li><a href="{{ route('meet.settings.calendar') }}" class="text-indigo-600 hover:text-indigo-800">Calendar connections</a></li>
|
||||
<li><a href="{{ route('meet.settings.webhooks') }}" class="text-indigo-600 hover:text-indigo-800">Webhooks</a></li>
|
||||
<x-settings.card title="Advanced" description="Security, calendar sync, and webhook integrations.">
|
||||
<ul class="space-y-3 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">
|
||||
<div>
|
||||
<p class="font-semibold text-slate-900">Security policy</p>
|
||||
<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>
|
||||
</div>
|
||||
</x-settings.card>
|
||||
@endif
|
||||
|
||||
<p class="mt-4 text-sm text-slate-500">{{ $branchCount }} branch(es) configured.</p>
|
||||
</div>
|
||||
<p class="text-sm text-slate-500">{{ $branchCount }} branch(es) configured.</p>
|
||||
</x-settings.page>
|
||||
</x-app-layout>
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
<x-app-layout title="Security settings">
|
||||
<div class="mx-auto max-w-xl">
|
||||
<h1 class="text-xl font-semibold text-slate-900">Meeting security</h1>
|
||||
<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">
|
||||
<x-settings.page title="Meeting security" description="Organization-wide defaults for {{ $organization->name }}.">
|
||||
<form method="POST" action="{{ route('meet.settings.security.update') }}">
|
||||
@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>
|
||||
<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>
|
||||
<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>
|
||||
<label class="block text-sm font-medium text-slate-700">Allowed email domains (comma-separated)</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">
|
||||
</div>
|
||||
<div>
|
||||
<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 w-full">Save policy</button>
|
||||
|
||||
<x-settings.card title="Policy defaults" description="Applied to new meetings unless overridden per meeting.">
|
||||
<div class="space-y-4">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<label class="block text-sm font-medium text-slate-700">Allowed email domains (comma-separated)</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">
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
</div>
|
||||
</x-settings.page>
|
||||
</x-app-layout>
|
||||
|
||||
@@ -1,39 +1,40 @@
|
||||
<x-app-layout title="Webhooks">
|
||||
<div class="mx-auto max-w-xl">
|
||||
<h1 class="text-xl font-semibold text-slate-900">Webhooks</h1>
|
||||
<p class="mt-1 text-sm text-slate-600">Notify sibling Ladill apps when meetings change</p>
|
||||
<x-settings.page title="Webhooks" description="Notify sibling Ladill apps when meetings change.">
|
||||
<x-settings.card title="Add endpoint">
|
||||
<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">
|
||||
@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 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-settings.card title="Configured endpoints">
|
||||
<ul class="-mx-6 divide-y divide-slate-100">
|
||||
@forelse ($endpoints as $endpoint)
|
||||
<li class="flex items-center justify-between gap-4 px-6 py-4 text-sm">
|
||||
<div class="min-w-0">
|
||||
<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 type="submit" class="shrink-0 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>
|
||||
</x-settings.card>
|
||||
</x-settings.page>
|
||||
</x-app-layout>
|
||||
|
||||
Reference in New Issue
Block a user