Improve conferences, guest entry, Afia, and cross-app scheduling.
Deploy Ladill Meet / deploy (push) Successful in 50s
Deploy Ladill Meet / deploy (push) Successful in 50s
Route guests through silent SSO to the Meet product page, fix Afia context query, add conference green-room UX and copy, and extend service API for Care/CRM/Invoice calendar integration. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -38,6 +38,10 @@ function meetRoom() {
|
||||
needsMediaUnlock: false,
|
||||
waitingCount: 0,
|
||||
isWebinar: false,
|
||||
isConference: false,
|
||||
isGreenRoom: false,
|
||||
goingLive: false,
|
||||
presenterRefs: [],
|
||||
audioOnly: false,
|
||||
isSpace: false,
|
||||
qaItems: [],
|
||||
@@ -96,6 +100,8 @@ function meetRoom() {
|
||||
filesUploadUrl: el.dataset.filesUploadUrl,
|
||||
whiteboardUrl: el.dataset.whiteboardUrl,
|
||||
whiteboardSaveUrl: el.dataset.whiteboardSaveUrl,
|
||||
goLiveUrl: el.dataset.goLiveUrl,
|
||||
addPresenterUrl: el.dataset.addPresenterUrl,
|
||||
csrf: el.dataset.csrf,
|
||||
sessionUuid: el.dataset.sessionUuid,
|
||||
};
|
||||
@@ -107,6 +113,8 @@ function meetRoom() {
|
||||
this.isHost = el.dataset.isHost === '1';
|
||||
this.canPublish = el.dataset.canPublish !== '0';
|
||||
this.isWebinar = el.dataset.isWebinar === '1';
|
||||
this.isConference = el.dataset.isConference === '1';
|
||||
this.isGreenRoom = el.dataset.isGreenRoom === '1';
|
||||
this.audioOnly = el.dataset.audioOnly === '1';
|
||||
this.isSpace = el.dataset.isSpace === '1';
|
||||
this.muteOnJoin = el.dataset.muteOnJoin === '1';
|
||||
@@ -117,6 +125,12 @@ function meetRoom() {
|
||||
this.liveCaptions = el.dataset.liveCaptions === '1';
|
||||
this.displayName = el.dataset.displayName || '';
|
||||
|
||||
try {
|
||||
this.presenterRefs = JSON.parse(el.dataset.presenterRefs || '[]');
|
||||
} catch {
|
||||
this.presenterRefs = [];
|
||||
}
|
||||
|
||||
try {
|
||||
this.sessionParticipants = JSON.parse(el.dataset.initialParticipants || '[]');
|
||||
} catch {
|
||||
@@ -888,10 +902,10 @@ function meetRoom() {
|
||||
if (blob && recordingUuid) {
|
||||
const uploaded = await this.uploadRecording(blob, recordingUuid);
|
||||
if (!uploaded) {
|
||||
window.alert('Recording upload failed. Open Recordings and try again, or re-record the meeting.');
|
||||
window.alert(`Recording upload failed. Open Recordings and try again, or re-record the ${this.sessionNoun()}.`);
|
||||
}
|
||||
} else if (recordingUuid) {
|
||||
window.alert('No recording was captured from this meeting. Start recording again while the meeting is live.');
|
||||
window.alert(`No recording was captured from this ${this.sessionNoun()}. Start recording again while the ${this.sessionNoun()} is live.`);
|
||||
}
|
||||
|
||||
this.activeRecordingUuid = null;
|
||||
@@ -1202,6 +1216,52 @@ function meetRoom() {
|
||||
});
|
||||
},
|
||||
|
||||
async goLive() {
|
||||
if (!this.config.goLiveUrl || this.goingLive) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.goingLive = true;
|
||||
|
||||
try {
|
||||
const res = await fetch(this.config.goLiveUrl, {
|
||||
method: 'POST',
|
||||
headers: this.headers(),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error('Go live failed');
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
window.location.href = data.room_url || `/room/${data.session_uuid}`;
|
||||
} catch {
|
||||
this.goingLive = false;
|
||||
}
|
||||
},
|
||||
|
||||
async makeSpeaker(userRef) {
|
||||
if (!this.config.addPresenterUrl || !userRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch(this.config.addPresenterUrl, {
|
||||
method: 'POST',
|
||||
headers: this.headers(),
|
||||
body: JSON.stringify({ user_ref: userRef }),
|
||||
});
|
||||
|
||||
if (! res.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
this.presenterRefs = data.presenter_refs || this.presenterRefs;
|
||||
this.sessionParticipants = this.sessionParticipants.map((person) => (
|
||||
person.user_ref === userRef ? { ...person, role: 'panelist' } : person
|
||||
));
|
||||
},
|
||||
|
||||
toggleChat() {
|
||||
this.chatOpen = !this.chatOpen;
|
||||
if (this.chatOpen) {
|
||||
@@ -1238,7 +1298,7 @@ function meetRoom() {
|
||||
try {
|
||||
await navigator.share({
|
||||
title: this.roomTitle || 'Ladill Meet',
|
||||
text: 'Join my meeting on Ladill Meet',
|
||||
text: `Join my ${this.sessionNoun()} on Ladill Meet`,
|
||||
url: this.joinUrl,
|
||||
});
|
||||
|
||||
@@ -1329,6 +1389,14 @@ function meetRoom() {
|
||||
}
|
||||
},
|
||||
|
||||
sessionNoun() {
|
||||
return this.isConference ? 'conference' : 'meeting';
|
||||
},
|
||||
|
||||
sessionNounTitle() {
|
||||
return this.isConference ? 'Conference' : 'Meeting';
|
||||
},
|
||||
|
||||
redirectToMeetingEnded(url) {
|
||||
if (this.pollTimer) {
|
||||
clearInterval(this.pollTimer);
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Timezone</label>
|
||||
<select name="timezone" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
@foreach (['UTC', 'America/New_York', 'America/Chicago', 'America/Los_Angeles', 'Europe/London', 'Africa/Lagos'] as $tz)
|
||||
@foreach (config('meet.schedule_timezones') as $tz)
|
||||
<option value="{{ $tz }}" @selected(old('timezone', $organization->timezone) === $tz)>{{ $tz }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@@ -49,10 +49,25 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Invite by email (comma-separated)</label>
|
||||
<label class="block text-sm font-medium text-slate-700">Invite speakers by email (comma-separated)</label>
|
||||
<input type="text" name="invite_emails" value="{{ old('invite_emails') }}" placeholder="speaker@example.com" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
</div>
|
||||
|
||||
@if ($members->isNotEmpty())
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Assigned speakers</label>
|
||||
<p class="mt-0.5 text-xs text-slate-500">Team members who can speak on stage. You are always the host.</p>
|
||||
<div class="mt-2 max-h-40 space-y-2 overflow-y-auto rounded-lg border border-slate-200 p-3">
|
||||
@foreach ($members as $member)
|
||||
<label class="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" name="presenter_refs[]" value="{{ $member->user_ref }}" @checked(in_array($member->user_ref, old('presenter_refs', []))) class="rounded border-slate-300">
|
||||
<span>{{ $member->user_ref }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Passcode (optional)</label>
|
||||
<input type="text" name="passcode" value="{{ old('passcode') }}" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
|
||||
@@ -12,14 +12,6 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-4 rounded-xl border border-violet-100 bg-violet-50 px-4 py-3 text-sm text-violet-900">
|
||||
Multi-speaker event with assigned hosts and presenters.
|
||||
Billed at <span class="font-medium">GHS {{ number_format(config('meet.billing.price_per_participant_ghs', 0.30), 2) }} per participant</span>.
|
||||
@if ($room->setting('green_room'))
|
||||
Green room is enabled — presenters rehearse before the audience joins live.
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mt-6 rounded-2xl border border-slate-200 bg-white p-6">
|
||||
<h2 class="text-sm font-medium text-slate-700">Join link</h2>
|
||||
<div class="mt-2 flex gap-2">
|
||||
@@ -59,8 +51,30 @@
|
||||
</p>
|
||||
@endif
|
||||
|
||||
@if ($room->description)
|
||||
<p class="mt-3 text-sm text-slate-600">{{ $room->description }}</p>
|
||||
@if ($room->setting('green_room'))
|
||||
<p class="mt-3 text-sm text-violet-700">Green room is on — when you start, you and assigned speakers rehearse before going live to the audience.</p>
|
||||
@endif
|
||||
|
||||
@if ($speakers->isNotEmpty())
|
||||
<div class="mt-4">
|
||||
<h3 class="text-sm font-medium text-slate-700">Assigned speakers</h3>
|
||||
<ul class="mt-2 space-y-1 text-sm text-slate-600">
|
||||
@foreach ($speakers as $speaker)
|
||||
<li>{{ $speaker->user_ref }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($room->invitations->where('role', 'panelist')->isNotEmpty())
|
||||
<div class="mt-4">
|
||||
<h3 class="text-sm font-medium text-slate-700">Invited speakers</h3>
|
||||
<ul class="mt-2 space-y-1 text-sm text-slate-600">
|
||||
@foreach ($room->invitations->where('role', 'panelist') as $invite)
|
||||
<li>{{ $invite->email }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<x-meet-kiosk-layout :title="'Meeting ended · '.$room->title" :organization="$organization">
|
||||
<x-meet-kiosk-layout :title="($room->isConference() ? 'Conference ended' : 'Meeting ended').' · '.$room->title" :organization="$organization">
|
||||
<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-slate-100 text-slate-600">
|
||||
@@ -6,8 +6,8 @@
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0 0 13.5 3h-6a2.25 2.25 0 0 0-2.25 2.25v13.5A2.25 2.25 0 0 0 7.5 21h6a2.25 2.25 0 0 0 2.25-2.25V15m3 0 3-3m0 0-3-3m3 3H9"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 class="mt-6 text-2xl font-bold text-slate-900">Meeting ended</h1>
|
||||
<p class="mt-3 text-sm text-slate-600">The host has ended this meeting.</p>
|
||||
<h1 class="mt-6 text-2xl font-bold text-slate-900">{{ $room->isConference() ? 'Conference ended' : 'Meeting ended' }}</h1>
|
||||
<p class="mt-3 text-sm text-slate-600">The host has ended this {{ $room->isConference() ? 'conference' : 'meeting' }}.</p>
|
||||
<p class="mt-1 text-sm font-medium text-slate-700">{{ $room->title }}</p>
|
||||
|
||||
<div class="mt-8 flex flex-col gap-3">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<x-meet-kiosk-layout :title="'Meeting cancelled'" :organization="$organization ?? null">
|
||||
<x-meet-kiosk-layout :title="($room->isConference() ? 'Conference cancelled' : 'Meeting cancelled')" :organization="$organization ?? null">
|
||||
<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>
|
||||
<p class="mt-3 text-sm text-slate-600">This {{ $room->isConference() ? 'conference' : 'meeting' }} has been cancelled by the host.</p>
|
||||
</div>
|
||||
</div>
|
||||
</x-meet-kiosk-layout>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</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>
|
||||
<p class="mt-3 text-sm text-slate-600">{{ $reason ?? 'You do not have access to this '.($room->isConference() ? 'conference' : '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>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<x-meet-kiosk-layout :title="'Passcode · '.$room->title" :organization="$organization" eyebrow="Secure meeting">
|
||||
<x-meet-kiosk-layout :title="'Passcode · '.$room->title" :organization="$organization" eyebrow="{{ $room->isConference() ? 'Secure conference' : '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>
|
||||
<p class="mt-2 text-sm text-slate-500">This {{ $room->isConference() ? 'conference' : 'meeting' }} is protected. Enter the passcode to continue.</p>
|
||||
|
||||
@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">
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<x-meet-kiosk-layout :title="'Join · '.$room->title" :organization="$organization">
|
||||
@php
|
||||
$sessionNoun = $room->isConference() ? 'conference' : 'meeting';
|
||||
@endphp
|
||||
<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>
|
||||
@@ -47,11 +50,11 @@
|
||||
@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>
|
||||
<p class="mt-4 text-xs text-slate-500">The host will admit you when the {{ $sessionNoun }} 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
|
||||
Join {{ $sessionNoun }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
@csrf
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="text-2xl font-bold text-slate-900">You left the meeting</h1>
|
||||
<h1 class="text-2xl font-bold text-slate-900">You left the {{ $room->isConference() ? 'conference' : '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>
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<p class="text-sm font-medium text-slate-700">How was the meeting overall?</p>
|
||||
<p class="text-sm font-medium text-slate-700">How was the {{ $room->isConference() ? 'conference' : 'meeting' }} overall?</p>
|
||||
<div class="mt-3 flex justify-center gap-2">
|
||||
@foreach (range(1, 5) as $star)
|
||||
<button type="button"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
@if (session('rated'))
|
||||
Thanks for your feedback
|
||||
@else
|
||||
You have left the meeting
|
||||
You have left the {{ $room->isConference() ? 'conference' : 'meeting' }}
|
||||
@endif
|
||||
</h1>
|
||||
<p class="mt-3 text-sm text-slate-600">{{ $room->title }}</p>
|
||||
@@ -19,7 +19,7 @@
|
||||
@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>
|
||||
<a href="{{ route('meet.join', $room) }}" class="btn-primary btn-primary-lg py-3.5 font-semibold">Rejoin {{ $room->isConference() ? 'conference' : 'meeting' }}</a>
|
||||
@endauth
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
@if ($room->scheduled_at)
|
||||
{{ $room->scheduled_at->timezone($room->timezone)->format('M j, Y g:i A T') }}
|
||||
@elseif ($room->isConference())
|
||||
Instant conference
|
||||
@else
|
||||
Instant meeting
|
||||
@endif
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
@php
|
||||
$room = $recording->session->room;
|
||||
$sessionNoun = $room->isConference() ? 'conference' : 'meeting';
|
||||
$roomDetailsRoute = match (true) {
|
||||
$room->isConference() => route('meet.conferences.show', $room),
|
||||
$room->isWebinar() => route('meet.webinars.show', $room),
|
||||
$room->isSpace() => route('meet.spaces.show', $room),
|
||||
default => route('meet.rooms.show', $room),
|
||||
};
|
||||
$statusLabel = config('meet.recording_statuses')[$recording->status] ?? ucfirst($recording->status);
|
||||
$statusClass = match ($recording->status) {
|
||||
'ready' => 'bg-emerald-50 text-emerald-700',
|
||||
@@ -80,7 +87,7 @@
|
||||
</svg>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-amber-900">Processing recording</p>
|
||||
<p class="mt-0.5 text-sm text-amber-800">This usually takes a moment after the meeting ends. Refresh shortly.</p>
|
||||
<p class="mt-0.5 text-sm text-amber-800">This usually takes a moment after the {{ $sessionNoun }} ends. Refresh shortly.</p>
|
||||
</div>
|
||||
</div>
|
||||
@elseif ($recording->status === 'failed')
|
||||
@@ -98,7 +105,7 @@
|
||||
Download
|
||||
</a>
|
||||
@endif
|
||||
<a href="{{ route('meet.rooms.show', $room) }}" class="btn-secondary btn-secondary-sm">View meeting</a>
|
||||
<a href="{{ $roomDetailsRoute }}" class="btn-secondary btn-secondary-sm">View {{ $sessionNoun }}</a>
|
||||
@if ($summary)
|
||||
<a href="{{ route('meet.summaries.show', $summary) }}" class="btn-secondary btn-secondary-sm">AI summary</a>
|
||||
@endif
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
'display_name' => $roomHostUser?->name ?? 'Host',
|
||||
'avatar_url' => $roomHostUser?->avatarUrl(),
|
||||
];
|
||||
$isConferenceSession = $isConference ?? false;
|
||||
$sessionNoun = $isConferenceSession ? 'conference' : 'meeting';
|
||||
$sessionNounTitle = $isConferenceSession ? 'Conference' : 'Meeting';
|
||||
@endphp
|
||||
<div id="meet-config"
|
||||
data-token="{{ $mediaToken }}"
|
||||
@@ -53,6 +56,12 @@
|
||||
data-locked="{{ $session->is_locked ? '1' : '0' }}"
|
||||
data-recording-active="{{ $activeRecording ? '1' : '0' }}"
|
||||
data-is-webinar="{{ ($isWebinar ?? false) ? '1' : '0' }}"
|
||||
data-is-conference="{{ ($isConference ?? false) ? '1' : '0' }}"
|
||||
data-is-green-room="{{ ($isGreenRoom ?? false) ? '1' : '0' }}"
|
||||
data-session-mode="{{ $session->session_mode }}"
|
||||
data-go-live-url="{{ ($isConference ?? false) && ($isGreenRoom ?? false) && $participant->isHost() ? route('meet.room.townhall.live', $session) : '' }}"
|
||||
data-add-presenter-url="{{ ($isConference ?? false) && $participant->isHost() ? route('meet.room.townhall.presenters', $session) : '' }}"
|
||||
data-presenter-refs='@json($presenterRefs ?? [])'
|
||||
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) }}"
|
||||
@@ -84,6 +93,10 @@
|
||||
<h1 class="truncate text-sm font-semibold">{{ $room->title }}</h1>
|
||||
@if ($isAudioOnly ?? false)
|
||||
<p class="text-[10px] font-medium uppercase tracking-wide text-emerald-400/90">Audio room</p>
|
||||
@elseif ($isConference ?? false)
|
||||
<p class="text-[10px] font-medium uppercase tracking-wide"
|
||||
:class="isGreenRoom ? 'text-violet-400/90' : 'text-sky-400/90'"
|
||||
x-text="isGreenRoom ? 'Green room' : 'Conference live'"></p>
|
||||
@endif
|
||||
<p class="text-xs text-slate-400" x-text="connectionStatus"></p>
|
||||
<p class="min-h-4 truncate text-xs font-medium leading-4"
|
||||
@@ -94,7 +107,7 @@
|
||||
<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">
|
||||
title="Share {{ $sessionNoun }} link">
|
||||
@include('meet.room.partials.meet-icon', ['icon' => 'share', 'class' => 'h-4 w-4'])
|
||||
Share
|
||||
</button>
|
||||
@@ -122,6 +135,18 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div x-show="isConference && isGreenRoom" x-cloak
|
||||
class="flex shrink-0 items-center justify-between gap-3 border-b border-violet-500/30 bg-violet-500/15 px-4 py-2.5">
|
||||
<div class="min-w-0 text-sm text-violet-100">
|
||||
<p class="font-medium">Green room</p>
|
||||
<p class="truncate text-xs text-violet-200/90">Only hosts and speakers are visible. Attendees join after you go live.</p>
|
||||
</div>
|
||||
<button x-show="isHost" @click="goLive()" type="button" :disabled="goingLive"
|
||||
class="shrink-0 rounded-lg bg-violet-500 px-3 py-1.5 text-xs font-semibold text-white transition-colors hover:bg-violet-400 disabled:opacity-60">
|
||||
<span x-text="goingLive ? 'Starting…' : 'Go live'">Go live</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
@@ -151,7 +176,7 @@
|
||||
<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>
|
||||
<p class="mt-0.5 text-xs text-slate-400">Share this link so people can join the {{ $sessionNoun }}.</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>
|
||||
@@ -254,8 +279,8 @@
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="rounded-full bg-red-600 p-3 font-medium hover:bg-red-500 sm:px-5 sm:py-3 sm:text-sm"
|
||||
title="Leave meeting"
|
||||
aria-label="Leave meeting">
|
||||
title="Leave {{ $sessionNoun }}"
|
||||
aria-label="Leave {{ $sessionNoun }}">
|
||||
<span class="sm:hidden">
|
||||
@include('meet.room.partials.meet-icon', ['icon' => 'leave'])
|
||||
</span>
|
||||
@@ -265,8 +290,8 @@
|
||||
@if ($participant->isHost())
|
||||
<button @click="openEndMeetingConfirm()" type="button"
|
||||
class="rounded-full bg-red-950 p-3 text-red-400 hover:bg-red-900 sm:px-4 sm:py-3 sm:text-sm"
|
||||
title="End meeting for everyone"
|
||||
aria-label="End meeting for everyone">
|
||||
title="End {{ $sessionNoun }} for everyone"
|
||||
aria-label="End {{ $sessionNoun }} for everyone">
|
||||
<span class="sm:hidden">
|
||||
@include('meet.room.partials.meet-icon', ['icon' => 'end'])
|
||||
</span>
|
||||
@@ -307,19 +332,19 @@
|
||||
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>
|
||||
<h3 class="text-base font-semibold text-white">End {{ $sessionNoun }} 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>
|
||||
class="rounded-xl bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-500">End {{ $sessionNoun }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<x-meet.room.panel show="chatOpen" title="Meeting chat">
|
||||
<x-meet.room.panel show="chatOpen" title="{{ $sessionNounTitle }} chat">
|
||||
<x-slot:subtitle>
|
||||
<p>Messages are visible to everyone in the call</p>
|
||||
</x-slot:subtitle>
|
||||
@@ -403,6 +428,13 @@
|
||||
<span x-show="isSpeaking(person.uuid)" class="block text-xs font-medium text-emerald-400">Speaking</span>
|
||||
<span x-show="!isSpeaking(person.uuid)" class="block text-xs text-slate-400" x-text="roleLabel(person.role)"></span>
|
||||
</span>
|
||||
<span class="flex shrink-0 items-center gap-1">
|
||||
<button x-show="isConference && isHost && isGreenRoom && person.user_ref && !['host', 'co_host', 'panelist'].includes(person.role)"
|
||||
type="button" @click="makeSpeaker(person.user_ref)"
|
||||
class="rounded-lg bg-violet-600 px-2 py-1 text-[10px] font-medium text-white hover:bg-violet-500">
|
||||
Make speaker
|
||||
</button>
|
||||
</span>
|
||||
<span class="flex shrink-0 items-center gap-1 text-slate-400">
|
||||
<span x-show="isSpeaking(person.uuid)" class="meet-speaking-badge rounded-full bg-emerald-500/20 p-1 text-emerald-300" title="Speaking">
|
||||
<svg class="h-3.5 w-3.5" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
||||
@@ -429,7 +461,7 @@
|
||||
|
||||
<x-meet.room.panel show="featuresOpen" title="More options">
|
||||
<x-slot:subtitle>
|
||||
<p>Meeting tools and extras</p>
|
||||
<p>{{ $sessionNounTitle }} tools and extras</p>
|
||||
</x-slot:subtitle>
|
||||
|
||||
<div class="flex min-h-0 flex-1 flex-col overflow-y-auto">
|
||||
@@ -441,7 +473,7 @@
|
||||
@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"
|
||||
<x-meet.room.menu-item icon="chat" title="Chat" subtitle="Open the {{ $sessionNoun }} chat"
|
||||
@click="toggleChat(); featuresOpen = false" />
|
||||
<div class="my-1 border-t border-slate-800"></div>
|
||||
</div>
|
||||
@@ -465,7 +497,7 @@
|
||||
</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 class="block text-xs text-slate-400">Save this {{ $sessionNoun }} to the cloud</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -475,7 +507,7 @@
|
||||
@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 font-medium" x-text="isLocked ? 'Unlock ' + sessionNoun() : 'Lock ' + sessionNoun()"></span>
|
||||
<span class="block text-xs text-slate-400">Control who can join</span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Timezone</label>
|
||||
<select name="timezone" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
@foreach (['UTC', 'America/New_York', 'America/Chicago', 'America/Los_Angeles', 'Europe/London', 'Africa/Lagos'] as $tz)
|
||||
@foreach (config('meet.schedule_timezones') as $tz)
|
||||
<option value="{{ $tz }}" @selected(old('timezone', $organization->timezone) === $tz)>{{ $tz }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-slate-700">Timezone</label>
|
||||
<select name="timezone" class="mt-1 w-full rounded-lg border-slate-300 text-sm">
|
||||
@foreach (['UTC', 'America/New_York', 'America/Chicago', 'America/Los_Angeles', 'Europe/London', 'Africa/Lagos'] as $tz)
|
||||
@foreach (config('meet.schedule_timezones') as $tz)
|
||||
<option value="{{ $tz }}" @selected(old('timezone', $organization->timezone) === $tz)>{{ $tz }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
Reference in New Issue
Block a user