Add kiosk join/leave pages, waiting room, and meeting feedback.
Deploy Ladill Meet / deploy (push) Successful in 47s

Redesign public join flows on the Frontdesk kiosk template, enforce host
admission when waiting room is enabled, and collect star ratings after leave.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-01 13:33:48 +00:00
co-authored by Cursor
parent 5e34d71de0
commit 7ae26a467c
26 changed files with 865 additions and 124 deletions
+70
View File
@@ -0,0 +1,70 @@
import Alpine from 'alpinejs';
Alpine.data('waitingRoom', () => ({
message: 'Waiting for the host to let you in…',
denied: false,
pollTimer: null,
init() {
this.pollTimer = setInterval(() => this.poll(), 2500);
this.poll();
},
destroy() {
if (this.pollTimer) {
clearInterval(this.pollTimer);
}
},
async poll() {
const url = this.$el.dataset.statusUrl;
if (!url) return;
try {
const res = await fetch(url, { headers: { Accept: 'application/json' } });
const data = await res.json();
if (data.redirect) {
window.location.href = data.redirect;
return;
}
if (data.denied) {
this.denied = true;
this.message = 'The host declined your request to join.';
clearInterval(this.pollTimer);
}
} catch {
// ignore transient errors
}
},
}));
Alpine.data('leaveFeedback', () => ({
rating: 0,
audioRating: 0,
videoRating: 0,
hoverRating: 0,
hoverAudio: 0,
hoverVideo: 0,
comment: '',
setRating(value) {
this.rating = value;
},
setAudioRating(value) {
this.audioRating = value;
},
setVideoRating(value) {
this.videoRating = value;
},
canSubmit() {
return this.rating > 0;
},
}));
window.Alpine = Alpine;
Alpine.start();
+32 -3
View File
@@ -85,6 +85,7 @@ function meetRoom() {
whiteboardUrl: el.dataset.whiteboardUrl,
whiteboardSaveUrl: el.dataset.whiteboardSaveUrl,
csrf: el.dataset.csrf,
sessionUuid: el.dataset.sessionUuid,
};
this.joinUrl = el.dataset.joinUrl || '';
@@ -334,7 +335,7 @@ function meetRoom() {
},
hostParticipant() {
const hosts = this.sessionParticipants.filter((p) => p.role === 'host' || p.role === 'co_host');
const hosts = this.inCallParticipants().filter((p) => p.role === 'host' || p.role === 'co_host');
if (hosts.length) {
return hosts[0];
}
@@ -352,13 +353,21 @@ function meetRoom() {
},
participantCount() {
return Math.max(this.sessionParticipants.length, this.participants.length);
return this.inCallParticipants().length;
},
inCallParticipants() {
return this.sessionParticipants.filter((p) => p.status === 'joined');
},
waitingParticipants() {
return this.sessionParticipants.filter((p) => p.status === 'waiting');
},
sortedParticipants() {
const order = { host: 0, co_host: 1, panelist: 2, participant: 3, guest: 4, attendee: 5 };
return [...this.sessionParticipants].sort((a, b) => {
return [...this.inCallParticipants()].sort((a, b) => {
const diff = (order[a.role] ?? 9) - (order[b.role] ?? 9);
return diff !== 0 ? diff : a.display_name.localeCompare(b.display_name);
@@ -378,6 +387,26 @@ function meetRoom() {
return labels[role] || 'Participant';
},
async admitParticipant(uuid) {
if (!this.isHost || !this.config.sessionUuid) return;
await fetch(`/room/${this.config.sessionUuid}/participants/${uuid}/admit`, {
method: 'POST',
headers: this.headers(),
});
await this.poll();
},
async denyParticipant(uuid) {
if (!this.isHost || !this.config.sessionUuid) return;
await fetch(`/room/${this.config.sessionUuid}/participants/${uuid}/deny`, {
method: 'POST',
headers: this.headers(),
});
await this.poll();
},
getOrCreateParticipantTile(participant) {
const grid = document.getElementById('video-grid');
if (!grid) return null;
@@ -0,0 +1,45 @@
@props([
'title' => 'Ladill Meet',
'organization' => null,
'eyebrow' => null,
])
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $title }}</title>
@include('partials.favicon')
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600,700&display=swap" rel="stylesheet" />
@vite(['resources/css/app.css', 'resources/js/meet-public.js'])
@stack('head')
</head>
<body {{ $attributes->merge(['class' => 'min-h-screen bg-slate-100 font-sans text-slate-900 antialiased']) }}>
<div class="pointer-events-none fixed inset-0 overflow-hidden" aria-hidden="true">
<div class="absolute -left-24 top-0 h-72 w-72 rounded-full bg-indigo-200/40 blur-3xl"></div>
<div class="absolute -right-16 bottom-0 h-80 w-80 rounded-full bg-violet-200/40 blur-3xl"></div>
</div>
<div class="fixed inset-x-0 top-0 z-10 h-1.5 bg-gradient-to-r from-indigo-600 to-violet-600"></div>
@if ($organization)
@include('meet.partials.kiosk-brand', ['organization' => $organization])
@else
<div class="fixed left-0 top-0 z-20 p-6">
<img src="{{ asset(\App\Support\OrganizationBranding::DEFAULT_LOGO) }}?v={{ @filemtime(public_path(\App\Support\OrganizationBranding::DEFAULT_LOGO)) ?: '1' }}"
alt="Ladill Meet"
class="h-8 w-auto max-w-[220px] object-contain object-left">
</div>
@endif
<div class="relative mx-auto flex min-h-screen max-w-3xl flex-col pt-20 px-6 pb-12">
@if ($eyebrow)
<p class="text-center text-sm font-semibold uppercase tracking-wider text-indigo-600">{{ $eyebrow }}</p>
@endif
{{ $slot }}
</div>
</body>
</html>
+7 -15
View File
@@ -1,16 +1,8 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Meeting cancelled · Ladill Meet</title>
@include('partials.favicon')
@vite(['resources/css/app.css'])
</head>
<body class="flex min-h-screen items-center justify-center bg-slate-100 p-4 font-sans">
<div class="rounded-2xl border border-slate-200 bg-white p-8 text-center shadow-sm">
<h1 class="text-xl font-semibold text-slate-900">Meeting cancelled</h1>
<p class="mt-2 text-sm text-slate-600">{{ $room->title }} is no longer available.</p>
<x-meet-kiosk-layout :title="'Meeting cancelled'" :organization="$organization ?? null" eyebrow="Ladill Meet">
<div class="flex min-h-[calc(100vh-8rem)] flex-col items-center justify-center text-center">
<div class="w-full max-w-md rounded-3xl border border-slate-200 bg-white p-8 shadow-sm">
<h1 class="text-2xl font-bold text-slate-900">{{ $room->title }}</h1>
<p class="mt-3 text-sm text-slate-600">This meeting has been cancelled by the host.</p>
</div>
</div>
</body>
</html>
</x-meet-kiosk-layout>
+15 -16
View File
@@ -1,17 +1,16 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Access denied · Ladill Meet</title>
@include('partials.favicon')
@vite(['resources/css/app.css'])
</head>
<body class="flex min-h-screen items-center justify-center bg-slate-100 p-4 font-sans">
<div class="max-w-md rounded-2xl border border-slate-200 bg-white p-8 text-center shadow-sm">
<h1 class="text-xl font-semibold text-slate-900">Unable to join</h1>
<p class="mt-2 text-sm text-slate-600">{{ $reason ?? 'You do not have access to this meeting.' }}</p>
<a href="{{ route('sso.connect', ['redirect' => route('meet.join', $room), 'interactive' => 1]) }}" class="btn-primary mt-6 inline-block">Sign in</a>
<x-meet-kiosk-layout :title="'Access denied · '.$room->title" :organization="$organization" eyebrow="Ladill Meet">
<div class="flex min-h-[calc(100vh-8rem)] flex-col items-center justify-center text-center">
<div class="w-full max-w-md rounded-3xl border border-slate-200 bg-white p-8 shadow-sm">
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-amber-50 text-amber-600">
<svg class="h-7 w-7" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 1 0-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 0 0 2.25-2.25v-6.75a2.25 2.25 0 0 0-2.25-2.25H6.75a2.25 2.25 0 0 0-2.25 2.25v6.75a2.25 2.25 0 0 0 2.25 2.25Z"/>
</svg>
</div>
<h1 class="mt-6 text-2xl font-bold text-slate-900">Unable to join</h1>
<p class="mt-3 text-sm text-slate-600">{{ $reason ?? 'You do not have access to this meeting.' }}</p>
<a href="{{ route('sso.connect', ['redirect' => route('meet.join', $room), 'interactive' => 1]) }}" class="btn-primary btn-primary-lg mt-8 inline-flex w-full items-center justify-center py-3.5 font-semibold">
Sign in with Ladill
</a>
</div>
</div>
</body>
</html>
</x-meet-kiosk-layout>
+22 -25
View File
@@ -1,27 +1,24 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Join · {{ $room->title }} · Ladill Meet</title>
@include('partials.favicon')
@vite(['resources/css/app.css'])
</head>
<body class="flex min-h-screen items-center justify-center bg-slate-900 p-4 font-sans text-white">
<div class="w-full max-w-md rounded-2xl bg-slate-800 p-8 shadow-xl">
<h1 class="text-xl font-semibold">{{ $room->title }}</h1>
<p class="mt-2 text-sm text-slate-400">Enter the meeting passcode to continue.</p>
<x-meet-kiosk-layout :title="'Passcode · '.$room->title" :organization="$organization" eyebrow="Secure meeting">
<div class="flex min-h-[calc(100vh-8rem)] flex-col items-center justify-center">
<div class="w-full max-w-md rounded-3xl border border-slate-200 bg-white p-8 shadow-sm">
<h1 class="text-2xl font-bold text-slate-900">{{ $room->title }}</h1>
<p class="mt-2 text-sm text-slate-500">This meeting is protected. Enter the passcode to continue.</p>
<form method="POST" action="{{ route('meet.join.passcode', $room) }}" class="mt-6 space-y-4">
@csrf
<input type="password" name="passcode" required autofocus placeholder="Passcode"
class="w-full rounded-lg border-slate-600 bg-slate-700 px-4 py-3 text-white placeholder:text-slate-400">
@error('passcode')
<p class="text-sm text-red-400">{{ $message }}</p>
@enderror
<button type="submit" class="btn-primary w-full">Continue</button>
</form>
@if ($errors->any())
<div class="mt-4 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
{{ $errors->first() }}
</div>
@endif
<form method="POST" action="{{ route('meet.join.passcode', $room) }}" class="mt-6 space-y-4">
@csrf
<div>
<label class="block text-sm font-medium text-slate-700">Passcode</label>
<input type="password" name="passcode" required autofocus
class="mt-2 w-full rounded-2xl border-slate-200 bg-white px-4 py-3.5 text-slate-900 shadow-sm ring-1 ring-slate-200 focus:border-indigo-500 focus:ring-indigo-500">
</div>
<button type="submit" class="btn-primary btn-primary-lg w-full py-3.5 font-semibold">Continue</button>
</form>
</div>
</div>
</body>
</html>
</x-meet-kiosk-layout>
+61 -41
View File
@@ -1,46 +1,66 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Join · {{ $room->title }} · Ladill Meet</title>
@include('partials.favicon')
@vite(['resources/css/app.css'])
</head>
<body class="flex min-h-screen items-center justify-center bg-slate-900 p-4 font-sans text-white">
<div class="w-full max-w-md rounded-2xl bg-slate-800 p-8 shadow-xl">
<h1 class="text-xl font-semibold">{{ $room->title }}</h1>
<p class="mt-2 text-sm text-slate-400">Ready to join?</p>
<x-meet-kiosk-layout :title="'Join · '.$room->title" :organization="$organization" eyebrow="Ladill Meet">
<div class="flex min-h-[calc(100vh-8rem)] flex-col items-center justify-center text-center">
<div class="w-full max-w-lg">
<h1 class="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">{{ $room->title }}</h1>
@if ($errors->any())
<div class="mt-4 rounded-lg border border-red-500/40 bg-red-500/10 px-4 py-3 text-sm text-red-200">
{{ $errors->first() }}
</div>
@endif
@if ($room->scheduled_at)
<p class="mt-3 text-base text-slate-500">
{{ $room->scheduled_at->timezone($room->timezone)->format('l, j F Y · g:i A T') }}
</p>
@endif
<form method="POST" action="{{ route('meet.join.enter', $room) }}" class="mt-6 space-y-4">
@csrf
@guest
<div>
<label class="block text-sm text-slate-300">Your name</label>
<input type="text" name="display_name" required placeholder="Display name"
class="mt-1 w-full rounded-lg border-slate-600 bg-slate-700 px-4 py-3 text-white placeholder:text-slate-400">
@if ($room->host_user_ref && ($host = \App\Models\User::query()->where('public_id', $room->host_user_ref)->first()))
<p class="mt-2 text-sm text-slate-500">Hosted by <span class="font-medium text-slate-700">{{ $host->name }}</span></p>
@endif
@if ($room->description)
<p class="mt-4 text-sm leading-relaxed text-slate-600">{{ $room->description }}</p>
@endif
@if ($errors->any())
<div class="mt-6 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-left text-sm text-red-700">
{{ $errors->first() }}
</div>
@if ($isWebinar ?? false)
<div>
<label class="block text-sm text-slate-300">Email</label>
<input type="email" name="email" required placeholder="you@example.com"
class="mt-1 w-full rounded-lg border-slate-600 bg-slate-700 px-4 py-3 text-white placeholder:text-slate-400">
</div>
@endif
@endguest
<button type="submit" class="btn-primary w-full">Join meeting</button>
</form>
@endif
@auth
<p class="mt-4 text-center text-xs text-slate-500">Joining as {{ auth()->user()->name }}</p>
@endauth
<form method="POST" action="{{ route('meet.join.enter', $room) }}" class="mt-10 text-left">
@csrf
@guest
<label class="block text-sm font-medium text-slate-700">Your name</label>
<input type="text" name="display_name" required placeholder="How should we show your name?"
value="{{ old('display_name') }}"
class="mt-2 w-full rounded-2xl border-slate-200 bg-white px-4 py-3.5 text-slate-900 shadow-sm ring-1 ring-slate-200 placeholder:text-slate-400 focus:border-indigo-500 focus:ring-indigo-500">
@if ($isWebinar ?? false)
<label class="mt-4 block text-sm font-medium text-slate-700">Email</label>
<input type="email" name="email" required placeholder="you@example.com"
value="{{ old('email') }}"
class="mt-2 w-full rounded-2xl border-slate-200 bg-white px-4 py-3.5 text-slate-900 shadow-sm ring-1 ring-slate-200 placeholder:text-slate-400 focus:border-indigo-500 focus:ring-indigo-500">
@endif
@endguest
@auth
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-600 shadow-sm">
Joining as <span class="font-semibold text-slate-900">{{ auth()->user()->name }}</span>
</div>
@endauth
@if ($room->setting('waiting_room', true) && ! ($user && $user->ownerRef() === $room->host_user_ref))
<p class="mt-4 text-xs text-slate-500">The host will admit you when the meeting is ready.</p>
@endif
<button type="submit" class="btn-primary btn-primary-lg mt-8 w-full py-4 text-base font-semibold">
Join meeting
</button>
</form>
@guest
<p class="mt-6 text-center text-sm text-slate-500">
Have a Ladill account?
<a href="{{ route('sso.connect', ['redirect' => route('meet.join', $room), 'interactive' => 1]) }}" class="font-medium text-indigo-600 hover:text-indigo-700">Sign in</a>
</p>
@endguest
</div>
</div>
</body>
</html>
</x-meet-kiosk-layout>
@@ -0,0 +1,29 @@
<x-meet-kiosk-layout :title="'Waiting · '.$room->title" :organization="$organization" eyebrow="Waiting room">
<div class="flex min-h-[calc(100vh-8rem)] flex-col items-center justify-center text-center"
x-data="waitingRoom"
x-init="init()"
data-status-url="{{ route('meet.join.waiting.status', $room) }}">
<div class="w-full max-w-md rounded-3xl border border-slate-200 bg-white p-8 shadow-sm">
<div class="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-indigo-50 text-indigo-600" x-show="!denied">
<svg class="h-8 w-8 animate-pulse" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
</svg>
</div>
<div class="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-red-50 text-red-600" x-show="denied" x-cloak>
<svg class="h-8 w-8" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
</svg>
</div>
<h1 class="mt-6 text-2xl font-bold text-slate-900">{{ $room->title }}</h1>
<p class="mt-2 text-sm text-slate-500">Hi {{ $participant->display_name }}, please wait.</p>
<p class="mt-4 text-base text-slate-700" x-text="message"></p>
<p class="mt-6 text-xs text-slate-400" x-show="!denied">You will join automatically once the host admits you.</p>
<a href="{{ route('meet.join', $room) }}" class="mt-8 inline-block text-sm font-medium text-indigo-600 hover:text-indigo-700" x-show="denied" x-cloak>
Back to join page
</a>
</div>
</div>
</x-meet-kiosk-layout>
+91
View File
@@ -0,0 +1,91 @@
<x-meet-kiosk-layout :title="'You left · '.$room->title" :organization="$organization" eyebrow="Meeting ended">
<div class="flex min-h-[calc(100vh-8rem)] flex-col items-center justify-center">
<form method="POST" action="{{ route('meet.left.feedback', $session) }}"
x-data="leaveFeedback"
class="w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-8 shadow-sm">
@csrf
<div class="text-center">
<h1 class="text-2xl font-bold text-slate-900">You left the meeting</h1>
<p class="mt-2 text-sm text-slate-500">{{ $room->title }}</p>
@if (! empty($feedback['display_name']))
<p class="mt-1 text-sm text-slate-600">Thanks, {{ $feedback['display_name'] }}.</p>
@endif
</div>
<div class="mt-8">
<p class="text-sm font-medium text-slate-700">How was the meeting overall?</p>
<div class="mt-3 flex justify-center gap-2">
@foreach (range(1, 5) as $star)
<button type="button"
@click="setRating({{ $star }})"
@mouseenter="hoverRating = {{ $star }}"
@mouseleave="hoverRating = 0"
class="rounded-lg p-1 transition-transform hover:scale-110"
aria-label="Rate {{ $star }} stars">
<svg class="h-9 w-9"
:class="(hoverRating || rating) >= {{ $star }} ? 'text-amber-400' : 'text-slate-300'"
fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22 12 18.56 5.82 22 7 14.14l-5-4.87 6.91-1.01L12 2z"/>
</svg>
</button>
@endforeach
</div>
<input type="hidden" name="rating" :value="rating" required>
</div>
<div class="mt-8 grid gap-6 sm:grid-cols-2">
<div>
<p class="text-sm font-medium text-slate-700">Audio quality</p>
<div class="mt-2 flex gap-1">
@foreach (range(1, 5) as $star)
<button type="button" @click="setAudioRating({{ $star }})"
class="rounded p-0.5">
<svg class="h-6 w-6"
:class="(hoverAudio || audioRating) >= {{ $star }} ? 'text-amber-400' : 'text-slate-300'"
fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22 12 18.56 5.82 22 7 14.14l-5-4.87 6.91-1.01L12 2z"/>
</svg>
</button>
@endforeach
</div>
<input type="hidden" name="audio_rating" :value="audioRating || ''">
</div>
<div>
<p class="text-sm font-medium text-slate-700">Video quality</p>
<div class="mt-2 flex gap-1">
@foreach (range(1, 5) as $star)
<button type="button" @click="setVideoRating({{ $star }})"
class="rounded p-0.5">
<svg class="h-6 w-6"
:class="(hoverVideo || videoRating) >= {{ $star }} ? 'text-amber-400' : 'text-slate-300'"
fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22 12 18.56 5.82 22 7 14.14l-5-4.87 6.91-1.01L12 2z"/>
</svg>
</button>
@endforeach
</div>
<input type="hidden" name="video_rating" :value="videoRating || ''">
</div>
</div>
<div class="mt-8">
<label class="block text-sm font-medium text-slate-700">Anything else we should know? <span class="font-normal text-slate-400">(optional)</span></label>
<textarea name="comment" rows="3" x-model="comment" placeholder="Share feedback for the host or Ladill Meet…"
class="mt-2 w-full rounded-2xl border-slate-200 bg-white px-4 py-3 text-sm text-slate-900 shadow-sm ring-1 ring-slate-200 placeholder:text-slate-400 focus:border-indigo-500 focus:ring-indigo-500"></textarea>
</div>
<div class="mt-8 flex flex-col gap-3 sm:flex-row">
<button type="submit"
class="btn-primary btn-primary-lg flex-1 py-3.5 font-semibold disabled:cursor-not-allowed disabled:opacity-50"
:disabled="!canSubmit()">
Submit feedback
</button>
<a href="{{ route('meet.left.thanks', $session) }}"
class="inline-flex flex-1 items-center justify-center rounded-2xl border border-slate-200 bg-white px-4 py-3.5 text-sm font-medium text-slate-600 hover:bg-slate-50">
Skip
</a>
</div>
</form>
</div>
</x-meet-kiosk-layout>
@@ -0,0 +1,27 @@
<x-meet-kiosk-layout :title="'Thank you'" :organization="$organization" eyebrow="Ladill Meet">
<div class="flex min-h-[calc(100vh-8rem)] flex-col items-center justify-center text-center">
<div class="w-full max-w-md rounded-3xl border border-slate-200 bg-white p-8 shadow-sm">
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-emerald-50 text-emerald-600">
<svg class="h-7 w-7" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/>
</svg>
</div>
<h1 class="mt-6 text-2xl font-bold text-slate-900">
@if (session('rated'))
Thanks for your feedback
@else
You have left the meeting
@endif
</h1>
<p class="mt-3 text-sm text-slate-600">{{ $room->title }}</p>
<div class="mt-8 flex flex-col gap-3">
@auth
<a href="{{ route('meet.dashboard') }}" class="btn-primary btn-primary-lg py-3.5 font-semibold">Back to Meet</a>
@else
<a href="{{ route('meet.join', $room) }}" class="btn-primary btn-primary-lg py-3.5 font-semibold">Rejoin meeting</a>
@endauth
</div>
</div>
</div>
</x-meet-kiosk-layout>
@@ -0,0 +1,8 @@
@php
use App\Support\OrganizationBranding;
@endphp
<div class="fixed left-0 top-0 z-20 p-6">
<img src="{{ OrganizationBranding::logoUrl($organization) }}"
alt="{{ OrganizationBranding::logoAlt($organization) }}"
class="h-8 w-auto max-w-[220px] object-contain object-left">
</div>
+25 -3
View File
@@ -13,7 +13,7 @@
use App\Models\User;
use App\Services\Meet\ParticipantPresenter;
$joined = $session->participants->where('status', 'joined');
$joined = $session->participants->whereIn('status', ['joined', 'waiting']);
$participantAvatars = ParticipantPresenter::avatarMap($joined, $room->host_user_ref);
$joinedParticipants = $joined
->map(fn ($p) => ParticipantPresenter::toArray($p, $participantAvatars, $room->host_user_ref))
@@ -61,6 +61,7 @@
data-room-title="{{ $room->title }}"
data-passcode="{{ $room->passcode ?? '' }}"
data-csrf="{{ csrf_token() }}"
data-session-uuid="{{ $session->uuid }}"
data-initial-participants='@json($joinedParticipants)'
data-room-host='@json($roomHost)'
hidden></div>
@@ -384,7 +385,28 @@
</button>
</div>
<div class="flex min-h-0 flex-1 flex-col overflow-y-auto rounded-2xl bg-slate-900/90 p-2">
<template x-for="person in sortedParticipants()" :key="person.uuid">
<template x-if="isHost && waitingParticipants().length">
<div class="mb-3 border-b border-slate-800 pb-3">
<p class="px-2 pb-2 text-xs font-semibold uppercase tracking-wide text-amber-300">Waiting to join</p>
<template x-for="person in waitingParticipants()" :key="'wait-'+person.uuid">
<div class="flex items-center gap-3 rounded-xl px-2 py-2.5">
<span class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-amber-500/20 text-sm font-semibold text-amber-200"
x-text="participantInitials(person.display_name)"></span>
<span class="min-w-0 flex-1">
<span class="block truncate text-sm font-medium text-white" x-text="person.display_name"></span>
<span class="block text-xs text-amber-200/80">Waiting for admission</span>
</span>
<span class="flex shrink-0 gap-1">
<button type="button" @click="admitParticipant(person.uuid)"
class="rounded-lg bg-emerald-600 px-2.5 py-1 text-xs font-medium text-white hover:bg-emerald-500">Admit</button>
<button type="button" @click="denyParticipant(person.uuid)"
class="rounded-lg bg-slate-700 px-2.5 py-1 text-xs font-medium text-slate-200 hover:bg-slate-600">Deny</button>
</span>
</div>
</template>
</div>
</template>
<template x-for="person in inCallParticipants()" :key="person.uuid">
<div class="flex items-center gap-3 rounded-xl px-2 py-2.5">
<span class="relative flex h-10 w-10 shrink-0">
<img x-show="person.avatar_url"
@@ -413,7 +435,7 @@
</span>
</div>
</template>
<p x-show="sessionParticipants.length === 0" class="px-2 py-4 text-center text-xs text-slate-500">No participants yet.</p>
<p x-show="inCallParticipants().length === 0 && waitingParticipants().length === 0" class="px-2 py-4 text-center text-xs text-slate-500">No participants yet.</p>
</div>
</aside>
</div>
+7 -1
View File
@@ -87,7 +87,13 @@
<fieldset class="space-y-2">
<legend class="text-sm font-medium text-slate-700">Options</legend>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="waiting_room" value="1" @checked(old('waiting_room', $defaultSettings['waiting_room'] ?? true)) class="rounded border-slate-300"> Waiting room</label>
<label class="flex items-start gap-2 text-sm">
<input type="checkbox" name="waiting_room" value="1" @checked(old('waiting_room', $defaultSettings['waiting_room'] ?? true)) class="mt-0.5 rounded border-slate-300">
<span>
<span class="font-medium text-slate-800">Waiting room</span>
<span class="mt-0.5 block text-xs text-slate-500">Guests wait until the host admits them. Uncheck to let guests join directly.</span>
</span>
</label>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="join_before_host" value="1" @checked(old('join_before_host')) class="rounded border-slate-300"> Allow join before host</label>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="mute_on_join" value="1" @checked(old('mute_on_join', $defaultSettings['mute_on_join'] ?? true)) class="rounded border-slate-300"> Mute participants on join</label>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="auto_record" value="1" @checked(old('auto_record')) class="rounded border-slate-300"> Auto-record</label>
@@ -12,7 +12,7 @@
<input type="number" name="duration_minutes" value="60" min="15" max="480" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
</div>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="is_default" value="1" class="rounded border-slate-300"> Default template</label>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="waiting_room" value="1" checked class="rounded border-slate-300"> Waiting room</label>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="waiting_room" value="1" checked class="rounded border-slate-300"> Waiting room (host admits guests)</label>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="auto_record" value="1" class="rounded border-slate-300"> Auto-record</label>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="live_captions" value="1" class="rounded border-slate-300"> Live captions</label>
<button type="submit" class="btn-primary w-full">Save template</button>