Files
ladill-meet/resources/views/meet/room/show.blade.php
T
isaaccladandCursor 7ae26a467c
Deploy Ladill Meet / deploy (push) Successful in 47s
Add kiosk join/leave pages, waiting room, and meeting feedback.
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>
2026-07-01 13:33:48 +00:00

444 lines
32 KiB
PHP

<!DOCTYPE html>
<html lang="en" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $room->title }} · Ladill Meet</title>
@include('partials.favicon')
@vite(['resources/css/app.css', 'resources/js/meet-room.js'])
</head>
<body class="h-full overflow-hidden bg-slate-950 font-sans text-white" x-data="meetRoom()" x-init="init()">
@php
use App\Models\User;
use App\Services\Meet\ParticipantPresenter;
$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))
->values();
$roomHostUser = User::query()->where('public_id', $room->host_user_ref)->first();
$roomHost = [
'user_ref' => $room->host_user_ref,
'display_name' => $roomHostUser?->name ?? 'Host',
'avatar_url' => $roomHostUser?->avatarUrl(),
];
@endphp
<div id="meet-config"
data-token="{{ $mediaToken }}"
data-url="{{ $mediaUrl }}"
data-configured="{{ $mediaConfigured ? '1' : '0' }}"
data-participant="{{ $participant->uuid }}"
data-display-name="{{ $participant->display_name }}"
data-is-host="{{ $participant->isHost() ? '1' : '0' }}"
data-poll-url="{{ route('meet.room.poll', $session) }}"
data-chat-url="{{ route('meet.room.chat', $session) }}"
data-reaction-url="{{ route('meet.room.reaction', $session) }}"
data-raise-hand-url="{{ route('meet.room.raise-hand', $session) }}"
data-leave-url="{{ route('meet.room.leave', $session) }}"
data-end-url="{{ route('meet.room.end', $session) }}"
data-recording-start-url="{{ route('meet.room.recording.start', $session) }}"
data-recording-stop-url="{{ route('meet.room.recording.stop', $session) }}"
data-lock-url="{{ route('meet.room.lock', $session) }}"
data-unlock-url="{{ route('meet.room.unlock', $session) }}"
data-caption-url="{{ route('meet.room.caption', $session) }}"
data-live-captions="{{ $room->setting('live_captions') ? '1' : '0' }}"
data-watermark="{{ $watermark ? '1' : '0' }}"
data-locked="{{ $session->is_locked ? '1' : '0' }}"
data-recording-active="{{ $activeRecording ? '1' : '0' }}"
data-is-webinar="{{ ($isWebinar ?? false) ? '1' : '0' }}"
data-can-publish="{{ ($canPublish ?? true) ? '1' : '0' }}"
data-qa-url="{{ route('meet.room.qa.submit', $session) }}"
data-polls-create-url="{{ route('meet.room.polls.create', $session) }}"
data-breakouts-create-url="{{ route('meet.room.breakouts.create', $session) }}"
data-breakouts-close-url="{{ route('meet.room.breakouts.close', $session) }}"
data-breakouts-broadcast-url="{{ route('meet.room.breakouts.broadcast', $session) }}"
data-files-upload-url="{{ route('meet.room.files.upload', $session) }}"
data-whiteboard-url="{{ route('meet.room.whiteboard.show', $session) }}"
data-whiteboard-save-url="{{ route('meet.room.whiteboard.save', $session) }}"
data-join-url="{{ $room->joinUrl() }}"
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>
@if (! $mediaConfigured)
<div class="absolute inset-x-0 top-0 z-50 bg-amber-500 px-4 py-2 text-center text-sm font-medium text-amber-950">
LiveKit is not configured. Set LIVEKIT_URL, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET in .env for video.
</div>
@endif
<div class="flex h-full min-w-0">
<div class="relative flex min-h-0 min-w-0 flex-1 flex-col">
<header class="flex shrink-0 items-center justify-between bg-slate-950 px-4 py-3">
<div class="min-w-0">
<h1 class="truncate text-sm font-semibold">{{ $room->title }}</h1>
<p class="text-xs text-slate-400" x-text="connectionStatus"></p>
</div>
<div class="flex shrink-0 items-center gap-2 text-xs text-slate-400">
<button @click.stop="openShare()" data-share-trigger type="button"
class="inline-flex items-center gap-1.5 rounded-lg bg-slate-900 px-2.5 py-1.5 text-xs font-medium text-slate-200 transition-colors hover:bg-slate-800"
title="Share meeting link">
@include('meet.room.partials.meet-icon', ['icon' => 'share', 'class' => 'h-4 w-4'])
Share
</button>
<span x-show="isRecording" @unless($activeRecording) style="display: none" @endunless class="rounded bg-red-600 px-2 py-0.5 font-medium text-white">REC</span>
<span x-show="isLocked" @unless($session->is_locked) style="display: none" @endunless class="rounded bg-amber-600 px-2 py-0.5 font-medium text-white">Locked</span>
<button @click="toggleParticipants()" type="button"
class="inline-flex items-center gap-2 rounded-lg bg-slate-900 px-2 py-1 text-xs font-medium text-slate-200 transition-colors hover:bg-slate-800"
title="View participants">
<span x-show="hostParticipant()" class="relative flex h-7 w-7 shrink-0">
<img x-show="hostParticipant()?.avatar_url"
:src="hostParticipant()?.avatar_url"
alt=""
class="h-7 w-7 rounded-full object-cover">
<span x-show="hostParticipant() && !hostParticipant()?.avatar_url"
class="flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-semibold text-white"
:style="avatarStyle(hostParticipant()?.display_name)"
x-text="participantInitials(hostParticipant()?.display_name || '')"></span>
</span>
<span x-text="participantCount() + ' in call'"></span>
</button>
</div>
</header>
<div x-show="shareOpen" x-cloak
@click.outside="if (! $event.target.closest('[data-share-trigger]')) shareOpen = false"
class="absolute right-4 top-14 z-50 w-80 rounded-xl bg-slate-900 p-4 shadow-2xl">
<div class="flex items-start justify-between gap-2">
<div>
<p class="text-sm font-medium text-white">Invite others</p>
<p class="mt-0.5 text-xs text-slate-400">Share this link so people can join the meeting.</p>
</div>
<button @click="shareOpen = false" type="button" class="rounded p-1 text-slate-400 hover:bg-slate-800 hover:text-white" title="Close">
<svg class="h-4 w-4" 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>
</button>
</div>
<div class="mt-3 flex gap-2">
<input type="text" readonly :value="joinUrl"
class="min-w-0 flex-1 rounded-lg border-0 bg-slate-950 px-3 py-2 text-xs text-slate-200 ring-1 ring-slate-700/60">
<button @click="copyJoinLink()" type="button"
class="shrink-0 rounded-lg bg-indigo-600 px-3 py-2 text-xs font-medium text-white hover:bg-indigo-500"
x-text="shareCopied ? 'Copied' : 'Copy'"></button>
</div>
<p x-show="passcode" class="mt-3 text-xs text-slate-400">
Passcode: <span class="font-mono font-medium text-slate-200" x-text="passcode"></span>
</p>
</div>
<main class="relative min-h-0 min-w-0 flex-1 p-4">
<div id="video-grid" class="grid h-full gap-2 sm:grid-cols-2 lg:grid-cols-3"></div>
<div id="screen-share" class="absolute inset-4 hidden rounded-xl bg-black"></div>
<div x-show="watermark" class="pointer-events-none absolute right-6 top-6 rounded bg-black/40 px-3 py-1 text-xs text-white/80" x-text="displayName"></div>
<div x-show="liveCaptions && captionText" class="pointer-events-none absolute inset-x-0 bottom-20 flex justify-center px-4">
<p class="max-w-2xl rounded-lg bg-black/70 px-4 py-2 text-center text-sm text-white" x-text="captionText"></p>
</div>
<div class="pointer-events-none absolute inset-x-0 bottom-4 flex justify-center gap-2">
<template x-for="reaction in floatingReactions" :key="reaction.id">
<span class="flex h-10 w-10 animate-bounce items-center justify-center rounded-full bg-slate-800/90">
<img x-show="reaction.kind === 'thumbs'" src="{{ asset('images/meet-icons/applause.svg') }}?v={{ @filemtime(public_path('images/meet-icons/applause.svg')) ?: '1' }}" alt="" class="meet-toolbar-icon h-5 w-5">
<img x-show="reaction.kind === 'heart'" src="{{ asset('images/meet-icons/react.svg') }}?v={{ @filemtime(public_path('images/meet-icons/react.svg')) ?: '1' }}" alt="" class="meet-toolbar-icon h-5 w-5">
</span>
</template>
</div>
</main>
<footer class="relative shrink-0 bg-slate-950 px-4 py-4">
<div class="mx-auto flex max-w-5xl flex-wrap items-center justify-center gap-2 sm:gap-3">
<button @click="toggleMute()" type="button"
class="rounded-full p-3 transition-colors"
:class="isMuted ? 'bg-red-600 hover:bg-red-500' : 'bg-slate-700 hover:bg-slate-600'"
:title="isMuted ? 'Unmute' : 'Mute'">
<svg x-show="!isMuted" class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 0 1 6 0v8.25a3 3 0 0 1-3 3Z"/></svg>
<svg x-show="isMuted" x-cloak class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 0 1 6 0v8.25a3 3 0 0 1-3 3Z"/><path stroke-linecap="round" stroke-linejoin="round" d="m3 3 18 18"/></svg>
</button>
<button @click="toggleVideo()" type="button"
class="rounded-full p-3 transition-colors"
:class="isVideoOff ? 'bg-red-600 hover:bg-red-500' : 'bg-slate-700 hover:bg-slate-600'"
:title="isVideoOff ? 'Turn camera on' : 'Turn camera off'">
<svg x-show="!isVideoOff" class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z"/></svg>
<svg x-show="isVideoOff" x-cloak class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z"/><path stroke-linecap="round" stroke-linejoin="round" d="m3 3 18 18"/></svg>
</button>
<button @click="toggleScreenShare()" type="button"
class="rounded-full p-3 transition-colors"
:class="isScreenSharing ? 'bg-indigo-600 hover:bg-indigo-500' : 'bg-slate-700 hover:bg-slate-600'"
title="Share screen">
@include('meet.room.partials.meet-icon', ['icon' => 'screen-share'])
</button>
<button @click="raiseHand()" type="button" class="rounded-full bg-slate-700 p-3 hover:bg-slate-600" title="Raise hand">
@include('meet.room.partials.meet-icon', ['icon' => 'raise-hand'])
</button>
<button @click="sendReaction('thumbs')" type="button" class="rounded-full bg-slate-700 p-3 hover:bg-slate-600" title="Applause">
@include('meet.room.partials.meet-icon', ['icon' => 'applause'])
</button>
<button @click="sendReaction('heart')" type="button" class="rounded-full bg-slate-700 p-3 hover:bg-slate-600" title="React">
@include('meet.room.partials.meet-icon', ['icon' => 'react'])
</button>
<button @click="toggleChat()" type="button"
class="rounded-full p-3 transition-colors"
:class="chatOpen ? 'bg-indigo-600 hover:bg-indigo-500' : 'bg-slate-700 hover:bg-slate-600'"
title="Chat">
@include('meet.room.partials.meet-icon', ['icon' => 'chat'])
</button>
<input type="file" id="meet-file-input" class="hidden" @change="uploadFile">
<button @click="toggleFeatures()" type="button"
class="inline-flex items-center gap-1.5 rounded-full px-3.5 py-3 text-xs font-medium transition-colors"
:class="featuresOpen ? 'bg-indigo-600 hover:bg-indigo-500' : 'bg-slate-700 hover:bg-slate-600'"
title="More options">
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z"/></svg>
More
</button>
<form method="POST" action="{{ route('meet.room.leave', $session) }}" class="inline">
@csrf
<button type="submit" class="rounded-full bg-red-600 px-5 py-3 text-sm font-medium hover:bg-red-500">Leave</button>
</form>
@if ($participant->isHost())
<button @click="openEndMeetingConfirm()" type="button"
class="rounded-full bg-red-950 px-4 py-3 text-sm text-red-400 hover:bg-red-900">End</button>
<form id="meet-end-form" method="POST" action="{{ route('meet.room.end', $session) }}" class="hidden">
@csrf
</form>
@endif
</div>
<div x-show="featuresOpen" x-cloak @click.outside="featuresOpen = false"
x-transition:enter="transition ease-out duration-150"
x-transition:enter-start="translate-y-2 opacity-0"
x-transition:enter-end="translate-y-0 opacity-100"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="translate-y-0 opacity-100"
x-transition:leave-end="translate-y-2 opacity-0"
class="absolute bottom-full left-1/2 z-40 mb-3 w-[min(24rem,calc(100vw-2rem))] -translate-x-1/2 overflow-hidden rounded-2xl bg-slate-900/98 p-1 shadow-2xl backdrop-blur-md">
<div class="flex items-center justify-between px-3 py-3">
<div>
<p class="text-sm font-semibold text-white">More options</p>
<p class="text-xs text-slate-400">Meeting tools and extras</p>
</div>
<button @click="featuresOpen = false" type="button" class="rounded-lg p-1.5 text-slate-400 hover:bg-slate-800 hover:text-white" title="Close">
<svg class="h-4 w-4" 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>
</button>
</div>
<div class="space-y-1 p-2">
<button @click="openUploadPicker()" type="button"
class="flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-left text-sm text-slate-200 transition-colors hover:bg-slate-800">
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-slate-800/80">
@include('meet.room.partials.meet-icon', ['icon' => 'upload-file', 'class' => 'h-5 w-5'])
</span>
<span class="min-w-0">
<span class="block font-medium">Share files</span>
<span class="block text-xs text-slate-400">Upload a file for everyone in the call</span>
</span>
</button>
@if ($participant->isHost())
<button @click="toggleRecordingFromMenu()" type="button"
class="flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-left text-sm text-slate-200 transition-colors hover:bg-slate-800">
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-red-500/15">
<span class="h-3 w-3 rounded-full" :class="isRecording ? 'bg-red-500 animate-pulse' : 'bg-red-400'"></span>
</span>
<span class="min-w-0">
<span class="block font-medium" x-text="isRecording ? 'Stop recording' : 'Start recording'"></span>
<span class="block text-xs text-slate-400">Save this meeting to the cloud</span>
</span>
</button>
<button @click="toggleLockFromMenu()" type="button"
class="flex w-full items-center gap-3 rounded-xl px-3 py-2.5 text-left text-sm text-slate-200 transition-colors hover:bg-slate-800">
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-slate-800/80">
@include('meet.room.partials.meet-icon', ['icon' => 'lock', 'class' => 'h-5 w-5'])
</span>
<span class="min-w-0">
<span class="block font-medium" x-text="isLocked ? 'Unlock meeting' : 'Lock meeting'"></span>
<span class="block text-xs text-slate-400">Control who can join</span>
</span>
</button>
<x-meet.room.menu-item icon="start-breakout" title="Start breakouts" subtitle="Split participants into smaller rooms"
@click="createBreakouts()" />
<x-meet.room.menu-item icon="close-breakout" title="Close breakouts" subtitle="Bring everyone back to the main room"
@click="closeBreakouts()" />
@endif
</div>
@if ($isWebinar ?? false)
<div class="px-3 py-3">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500">Q&amp;A</p>
<ul class="mt-2 max-h-36 space-y-2 overflow-y-auto">
<template x-for="item in qaItems" :key="item.uuid">
<li class="rounded-lg bg-slate-800/80 px-3 py-2 text-sm">
<span class="font-medium text-sky-300" x-text="item.asker_name"></span>
<span class="text-slate-300" x-text="': ' + item.question"></span>
</li>
</template>
<li x-show="qaItems.length === 0" class="text-xs text-slate-500">No questions yet.</li>
</ul>
<form @submit.prevent="submitQuestion" class="mt-3 flex gap-2">
<input type="text" x-model="qaInput" placeholder="Ask a question…"
class="flex-1 rounded-lg border-0 bg-slate-950 px-3 py-2 text-sm text-white placeholder:text-slate-500 ring-1 ring-slate-700/60">
<button type="submit" class="rounded-lg bg-indigo-600 px-3 py-2 text-xs font-medium hover:bg-indigo-500">Send</button>
</form>
</div>
@endif
<p x-show="breakoutBroadcast" class="mx-2 mb-2 rounded-xl bg-amber-950/40 px-3 py-3 text-sm text-amber-100" x-text="breakoutBroadcast"></p>
</div>
</footer>
<div x-show="breakoutModalOpen" x-cloak
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm"
@keydown.escape.window="breakoutModalOpen = false">
<div class="w-full max-w-sm rounded-2xl bg-slate-900 p-5 shadow-2xl" @click.outside="breakoutModalOpen = false">
<h3 class="text-base font-semibold text-white">Start breakout rooms</h3>
<p class="mt-1 text-sm text-slate-400">How many breakout rooms should be created?</p>
<form @submit.prevent="submitBreakouts()" class="mt-4 space-y-4">
<div>
<label for="breakout-count" class="sr-only">Number of rooms</label>
<input id="breakout-count" type="number" min="1" max="20" x-model="breakoutCount"
class="w-full rounded-xl border-0 bg-slate-950 px-3 py-2.5 text-sm text-white ring-1 ring-slate-700/60 focus:ring-indigo-500">
</div>
<div class="flex justify-end gap-2">
<button type="button" @click="breakoutModalOpen = false"
class="rounded-xl px-4 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Cancel</button>
<button type="submit"
class="rounded-xl bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500">Create rooms</button>
</div>
</form>
</div>
</div>
<div x-show="endMeetingConfirmOpen" x-cloak
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm"
@keydown.escape.window="endMeetingConfirmOpen = false">
<div class="w-full max-w-sm rounded-2xl bg-slate-900 p-5 shadow-2xl" @click.outside="endMeetingConfirmOpen = false">
<h3 class="text-base font-semibold text-white">End meeting for everyone?</h3>
<p class="mt-2 text-sm text-slate-400">All participants will be disconnected and the session will close.</p>
<div class="mt-5 flex justify-end gap-2">
<button type="button" @click="endMeetingConfirmOpen = false"
class="rounded-xl px-4 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Cancel</button>
<button type="button" @click="submitEndMeeting()"
class="rounded-xl bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-500">End meeting</button>
</div>
</div>
</div>
</div>
<aside x-show="chatOpen" x-cloak
class="flex h-full w-80 shrink-0 flex-col bg-slate-950 px-3 py-3">
<div class="flex shrink-0 items-center justify-between px-1 pb-3">
<div>
<h2 class="text-sm font-semibold text-white">Meeting chat</h2>
<p class="text-xs text-slate-400">Messages are visible to everyone in the call</p>
</div>
<button @click="chatOpen = false" type="button"
class="rounded-lg p-2 text-slate-400 transition-colors hover:bg-slate-800 hover:text-white"
title="Close chat">
<svg class="h-5 w-5" 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>
</button>
</div>
<div class="flex min-h-0 flex-1 flex-col rounded-2xl bg-slate-900/90 p-3">
<div class="flex-1 space-y-3 overflow-y-auto" id="chat-messages">
@foreach ($messages as $message)
@php $isOwn = $message->sender_name === $participant->display_name; @endphp
<div class="meet-chat-bubble {{ $isOwn ? 'meet-chat-bubble--own' : '' }}">
<div class="rounded-2xl px-3 py-2 {{ $isOwn ? 'bg-indigo-600 text-white' : 'bg-slate-800 text-slate-100' }}">
@unless ($isOwn)
<p class="mb-0.5 text-xs font-medium text-sky-300">{{ $message->sender_name }}</p>
@endunless
<p class="text-sm leading-relaxed">{{ $message->body }}</p>
</div>
<p class="mt-1 text-[10px] text-slate-500 {{ $isOwn ? 'text-right' : '' }}">{{ $message->created_at->format('g:i A') }}</p>
</div>
@endforeach
</div>
<form @submit.prevent="sendChat" class="mt-3 shrink-0">
<div class="flex items-end gap-2">
<input type="text" x-model="chatInput" placeholder="Write a message…"
class="flex-1 rounded-xl border-0 bg-slate-800 px-3 py-2.5 text-sm text-white placeholder:text-slate-500 focus:ring-2 focus:ring-indigo-500">
<button type="submit"
class="rounded-xl bg-indigo-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-indigo-500 disabled:opacity-50"
:disabled="!chatInput.trim()">Send</button>
</div>
</form>
</div>
</aside>
<aside x-show="participantsOpen" x-cloak
class="flex h-full w-80 shrink-0 flex-col bg-slate-950 px-3 py-3">
<div class="flex shrink-0 items-center justify-between px-1 pb-3">
<div>
<h2 class="text-sm font-semibold text-white">Participants</h2>
<p class="text-xs text-slate-400" x-text="participantCount() + (participantCount() === 1 ? ' person' : ' people') + ' in this call'"></p>
</div>
<button @click="participantsOpen = false" type="button"
class="rounded-lg p-2 text-slate-400 transition-colors hover:bg-slate-800 hover:text-white"
title="Close participants">
<svg class="h-5 w-5" 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>
</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-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"
:src="person.avatar_url"
alt=""
class="h-10 w-10 rounded-full object-cover">
<span x-show="!person.avatar_url"
class="flex h-10 w-10 items-center justify-center rounded-full text-sm font-semibold text-white"
:style="avatarStyle(person.display_name)"
x-text="participantInitials(person.display_name)"></span>
</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-slate-400" x-text="roleLabel(person.role)"></span>
</span>
<span class="flex shrink-0 items-center gap-1 text-slate-400">
<span x-show="person.hand_raised" class="rounded-full bg-amber-500/20 p-1 text-amber-300" title="Hand raised">
@include('meet.room.partials.meet-icon', ['icon' => 'raise-hand', 'class' => 'h-3.5 w-3.5'])
</span>
<svg x-show="person.is_muted" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" title="Muted">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 0 1 6 0v8.25a3 3 0 0 1-3 3Z"/><path stroke-linecap="round" stroke-linejoin="round" d="m3 3 18 18"/>
</svg>
<svg x-show="person.is_video_off" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" title="Camera off">
<path stroke-linecap="round" stroke-linejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z"/><path stroke-linecap="round" stroke-linejoin="round" d="m3 3 18 18"/>
</svg>
</span>
</div>
</template>
<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>
</body>
</html>