Files
ladill-meet/resources/views/meet/room/show.blade.php
T
isaaccladandCursor 8b43b01dfa
Deploy Ladill Meet / deploy (push) Successful in 1m13s
Use bottom sheet for More menu and add copy-button feedback.
Shared copy-button component shows Copied state on meeting details; mobile More is icon-only and opens the same panel pattern as chat.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 14:56:46 +00:00

479 lines
35 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-ended-url="{{ route('meet.ended', $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-mute-on-join="{{ $room->setting('mute_on_join', true) ? '1' : '0' }}"
data-video-off-on-join="{{ $room->setting('video_off_on_join', false) ? '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="relative 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"
:class="isHost && waitingCount > 0 ? 'ring-2 ring-amber-400/60' : ''"
title="View participants">
<span x-show="isHost && waitingCount > 0"
x-text="waitingCount > 9 ? '9+' : waitingCount"
class="absolute -right-1 -top-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-amber-500 px-1 text-[10px] font-bold text-amber-950"></span>
<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="isHost && waitingCount > 0" x-cloak
class="flex shrink-0 items-center justify-between gap-3 border-b border-amber-500/30 bg-amber-500/15 px-4 py-2.5">
<div class="flex min-w-0 items-center gap-2 text-sm text-amber-100">
<span class="flex h-2 w-2 shrink-0 animate-pulse rounded-full bg-amber-400"></span>
<span class="truncate"
x-text="waitingCount === 1 ? '1 person is waiting to be admitted' : waitingCount + ' people are waiting to be admitted'"></span>
</div>
<button @click="participantsOpen = true" type="button"
class="shrink-0 rounded-lg bg-amber-500 px-3 py-1.5 text-xs font-semibold text-amber-950 transition-colors hover:bg-amber-400">
Review
</button>
</div>
<div x-show="audioPlaybackBlocked || needsMediaUnlock" x-cloak
class="flex shrink-0 items-center justify-between gap-3 border-b border-indigo-500/30 bg-indigo-500/15 px-4 py-2.5">
<p class="text-sm text-indigo-100"
x-text="needsMediaUnlock ? 'Microphone blocked — click to allow speaking and hearing others.' : 'Audio playback blocked — click to hear other participants.'"></p>
<button @click="unlockAllAudio()" type="button"
class="shrink-0 rounded-lg bg-indigo-500 px-3 py-1.5 text-xs font-semibold text-white transition-colors hover:bg-indigo-400">
Enable audio
</button>
</div>
<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 py-4 sm:px-4">
<div class="meet-toolbar mx-auto max-w-5xl">
<div class="meet-toolbar__track sm:px-0">
<div class="meet-toolbar__primary sm:contents">
<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="toggleFeatures()" type="button"
class="rounded-full p-3 transition-colors sm:hidden"
:class="featuresOpen ? 'bg-indigo-600 hover:bg-indigo-500' : 'bg-slate-700 hover:bg-slate-600'"
title="More options">
<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.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>
</button>
</div>
<button @click="raiseHand()" type="button" class="hidden rounded-full bg-slate-700 p-3 hover:bg-slate-600 sm:inline-flex" title="Raise hand">
@include('meet.room.partials.meet-icon', ['icon' => 'raise-hand'])
</button>
<button @click="sendReaction('thumbs')" type="button" class="hidden rounded-full bg-slate-700 p-3 hover:bg-slate-600 sm:inline-flex" title="Applause">
@include('meet.room.partials.meet-icon', ['icon' => 'applause'])
</button>
<button @click="sendReaction('heart')" type="button" class="hidden rounded-full bg-slate-700 p-3 hover:bg-slate-600 sm:inline-flex" title="React">
@include('meet.room.partials.meet-icon', ['icon' => 'react'])
</button>
<button @click="toggleChat()" type="button"
class="hidden rounded-full p-3 transition-colors sm:inline-flex"
: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="hidden items-center gap-1.5 rounded-full px-3.5 py-3 text-xs font-medium transition-colors sm:inline-flex"
: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>
<div class="meet-toolbar__divider sm:hidden" aria-hidden="true"></div>
<div class="meet-toolbar__actions sm:contents">
<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>
</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>
<x-meet.room.panel show="chatOpen" title="Meeting chat">
<x-slot:subtitle>
<p>Messages are visible to everyone in the call</p>
</x-slot:subtitle>
<div class="flex min-h-0 flex-1 flex-col">
<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>
</x-meet.room.panel>
<x-meet.room.panel show="participantsOpen" title="Participants">
<x-slot:subtitle>
<p x-text="participantCount() + (participantCount() === 1 ? ' person' : ' people') + ' in this call'"></p>
</x-slot:subtitle>
<div class="flex min-h-0 flex-1 flex-col overflow-y-auto">
<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
<span class="ml-1 rounded-full bg-amber-500/20 px-1.5 py-0.5 text-[10px] text-amber-200"
x-text="waitingParticipants().length"></span>
</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>
</x-meet.room.panel>
<x-meet.room.panel show="featuresOpen" title="More options">
<x-slot:subtitle>
<p>Meeting tools and extras</p>
</x-slot:subtitle>
<div class="flex min-h-0 flex-1 flex-col overflow-y-auto">
<div class="space-y-1">
<div class="space-y-1 sm:hidden">
<x-meet.room.menu-item icon="raise-hand" title="Raise hand" subtitle="Let the host know you have a question"
@click="raiseHand(); featuresOpen = false" />
<x-meet.room.menu-item icon="applause" title="Applause" subtitle="Send a quick reaction to everyone"
@click="sendReaction('thumbs'); featuresOpen = false" />
<x-meet.room.menu-item icon="react" title="React" subtitle="Send a heart to the room"
@click="sendReaction('heart'); featuresOpen = false" />
<x-meet.room.menu-item icon="chat" title="Chat" subtitle="Open the meeting chat"
@click="toggleChat(); featuresOpen = false" />
<div class="my-1 border-t border-slate-800"></div>
</div>
<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="mt-4 border-t border-slate-800 pt-4">
<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="mt-3 rounded-xl bg-amber-950/40 px-3 py-3 text-sm text-amber-100" x-text="breakoutBroadcast"></p>
</div>
</x-meet.room.panel>
</div>
</body>
</html>