Add panel discussions option to webinars and widen audience strip.
Deploy Ladill Meet / deploy (push) Successful in 2m1s

Webinars can enable multi-speaker stage layout at creation or on the details page; plenary view shows 12 attendee avatars before the overflow count.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-01 21:29:22 +00:00
co-authored by Cursor
parent 1037cd6ee6
commit f98d66c9f9
6 changed files with 70 additions and 1 deletions
@@ -82,6 +82,7 @@ class WebinarController extends Controller
'auto_record' => ['sometimes', 'boolean'], 'auto_record' => ['sometimes', 'boolean'],
'live_captions' => ['sometimes', 'boolean'], 'live_captions' => ['sometimes', 'boolean'],
'webinar_auto_approve' => ['sometimes', 'boolean'], 'webinar_auto_approve' => ['sometimes', 'boolean'],
'panel_discussions' => ['sometimes', 'boolean'],
]); ]);
$settings = [ $settings = [
@@ -91,6 +92,9 @@ class WebinarController extends Controller
'auto_record' => $request->boolean('auto_record'), 'auto_record' => $request->boolean('auto_record'),
'live_captions' => $request->boolean('live_captions'), 'live_captions' => $request->boolean('live_captions'),
'webinar_auto_approve' => $request->boolean('webinar_auto_approve'), 'webinar_auto_approve' => $request->boolean('webinar_auto_approve'),
'panel_discussions' => $request->boolean('panel_discussions'),
'stage_layout' => 'plenary',
'panels' => [],
'stage_mode' => true, 'stage_mode' => true,
]; ];
@@ -154,4 +158,24 @@ class WebinarController extends Controller
return redirect()->route('meet.room', $session); return redirect()->route('meet.room', $session);
} }
public function updateSettings(Request $request, Room $room): RedirectResponse
{
$this->authorizeAbility($request, 'meetings.manage');
$this->authorizeOwner($request, $room);
abort_unless($room->isWebinar(), 404);
$request->validate([
'panel_discussions' => ['sometimes', 'boolean'],
]);
if ($request->has('panel_discussions')) {
$settings = array_merge($room->settings ?? [], [
'panel_discussions' => $request->boolean('panel_discussions'),
]);
$room->update(['settings' => $settings]);
}
return redirect()->route('meet.webinars.show', $room)->with('success', 'Webinar settings updated.');
}
} }
+1 -1
View File
@@ -697,7 +697,7 @@ function meetRoom() {
return; return;
} }
const maxVisible = 8; const maxVisible = 12;
const visible = audience.slice(0, maxVisible); const visible = audience.slice(0, maxVisible);
const overflow = audience.length - visible.length; const overflow = audience.length - visible.length;
@@ -72,6 +72,7 @@
<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> <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>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="live_captions" value="1" @checked(old('live_captions')) class="rounded border-slate-300"> Live captions</label> <label class="flex items-center gap-2 text-sm"><input type="checkbox" name="live_captions" value="1" @checked(old('live_captions')) class="rounded border-slate-300"> Live captions</label>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="webinar_auto_approve" value="1" @checked(old('webinar_auto_approve')) class="rounded border-slate-300"> Auto-approve registrations</label> <label class="flex items-center gap-2 text-sm"><input type="checkbox" name="webinar_auto_approve" value="1" @checked(old('webinar_auto_approve')) class="rounded border-slate-300"> Auto-approve registrations</label>
<label class="flex items-center gap-2 text-sm"><input type="checkbox" name="panel_discussions" value="1" @checked(old('panel_discussions')) class="rounded border-slate-300"> Enable panel discussions (multi-speaker stage layout)</label>
</fieldset> </fieldset>
<button type="submit" class="btn-primary btn-primary-lg w-full">Create webinar</button> <button type="submit" class="btn-primary btn-primary-lg w-full">Create webinar</button>
@@ -20,6 +20,12 @@
</div> </div>
@endif @endif
@if (session('success'))
<div class="mt-4 rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800">
{{ session('success') }}
</div>
@endif
<div class="mt-4 rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm text-indigo-900"> <div class="mt-4 rounded-xl border border-indigo-100 bg-indigo-50 px-4 py-3 text-sm text-indigo-900">
Billed at <span class="font-medium">GHS {{ number_format(config('meet.billing.price_per_participant_ghs', 0.30), 2) }} per participant</span> (peak attendance when the session ends). Billed at <span class="font-medium">GHS {{ number_format(config('meet.billing.price_per_participant_ghs', 0.30), 2) }} per participant</span> (peak attendance when the session ends).
</div> </div>
@@ -63,6 +69,19 @@
Scheduled: {{ $room->scheduled_at->timezone($room->timezone)->format('M j, Y g:i A T') }} Scheduled: {{ $room->scheduled_at->timezone($room->timezone)->format('M j, Y g:i A T') }}
</p> </p>
@endif @endif
@if ($room->status === 'scheduled')
<form method="POST" action="{{ route('meet.webinars.settings.update', $room) }}" class="mt-6 border-t border-slate-100 pt-6">
@csrf
<h2 class="text-sm font-medium text-slate-700">Stage layout</h2>
<label class="mt-3 flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="panel_discussions" value="1"
@checked(old('panel_discussions', $room->setting('panel_discussions'))) class="rounded border-slate-300">
Enable panel discussions (multi-speaker stage layout)
</label>
<button type="submit" class="btn-primary btn-primary-sm mt-3">Save settings</button>
</form>
@endif
</div> </div>
@php @php
+1
View File
@@ -134,6 +134,7 @@ Route::middleware(['auth', 'platform.session'])->group(function () {
Route::post('/webinars', [WebinarController::class, 'store'])->name('meet.webinars.store'); Route::post('/webinars', [WebinarController::class, 'store'])->name('meet.webinars.store');
Route::get('/webinars/{room}', [WebinarController::class, 'show'])->name('meet.webinars.show'); Route::get('/webinars/{room}', [WebinarController::class, 'show'])->name('meet.webinars.show');
Route::post('/webinars/{room}/start', [WebinarController::class, 'start'])->name('meet.webinars.start'); Route::post('/webinars/{room}/start', [WebinarController::class, 'start'])->name('meet.webinars.start');
Route::post('/webinars/{room}/settings', [WebinarController::class, 'updateSettings'])->name('meet.webinars.settings.update');
Route::get('/conferences', [ConferenceController::class, 'index'])->name('meet.conferences.index'); Route::get('/conferences', [ConferenceController::class, 'index'])->name('meet.conferences.index');
Route::get('/conferences/create', [ConferenceController::class, 'create'])->name('meet.conferences.create'); Route::get('/conferences/create', [ConferenceController::class, 'create'])->name('meet.conferences.create');
+24
View File
@@ -614,6 +614,30 @@ class MeetWebTest extends TestCase
]); ]);
} }
public function test_host_can_enable_panel_discussions_on_webinar(): void
{
$this->actingAs($this->user)
->post('/webinars', [
'title' => 'Panel webinar',
'scheduled_at' => now()->addDay()->format('Y-m-d\TH:i'),
'duration_minutes' => 60,
'panel_discussions' => '1',
])
->assertRedirect();
$room = Room::where('title', 'Panel webinar')->firstOrFail();
$this->assertTrue($room->setting('panel_discussions'));
$this->assertSame('plenary', $room->setting('stage_layout'));
$this->actingAs($this->user)
->post(route('meet.webinars.settings.update', $room), [
'panel_discussions' => '0',
])
->assertRedirect(route('meet.webinars.show', $room));
$this->assertFalse($room->fresh()->setting('panel_discussions'));
}
public function test_user_can_create_channel_and_post_message(): void public function test_user_can_create_channel_and_post_message(): void
{ {
$this->actingAs($this->user) $this->actingAs($this->user)