Add kiosk join/leave pages, waiting room, and meeting feedback.
Deploy Ladill Meet / deploy (push) Successful in 47s
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:
@@ -3,11 +3,14 @@
|
||||
namespace App\Http\Controllers\Meet;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Participant;
|
||||
use App\Models\Room;
|
||||
use App\Models\Session;
|
||||
use App\Services\Meet\InvitationService;
|
||||
use App\Services\Meet\SecurityPolicyService;
|
||||
use App\Services\Meet\SessionService;
|
||||
use App\Services\Meet\WebinarService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
@@ -23,8 +26,13 @@ class JoinController extends Controller
|
||||
|
||||
public function show(Request $request, Room $room): View|RedirectResponse
|
||||
{
|
||||
$room->loadMissing('organization');
|
||||
|
||||
if ($room->status === 'cancelled') {
|
||||
return view('meet.join.cancelled', compact('room'));
|
||||
return view('meet.join.cancelled', [
|
||||
'room' => $room,
|
||||
'organization' => $room->organization,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($room->isWebinar() && $request->query('token')) {
|
||||
@@ -33,15 +41,39 @@ class JoinController extends Controller
|
||||
|
||||
[$allowed, $reason] = $this->security->canJoin($room, $request->user(), $request->user()?->email);
|
||||
if (! $allowed && $reason !== 'passcode_required') {
|
||||
return view('meet.join.denied', compact('room', 'reason'));
|
||||
return view('meet.join.denied', [
|
||||
'room' => $room,
|
||||
'organization' => $room->organization,
|
||||
'reason' => $reason,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($room->passcode && ! $request->session()->get("meet.passcode.{$room->uuid}")) {
|
||||
return view('meet.join.passcode', compact('room'));
|
||||
return view('meet.join.passcode', [
|
||||
'room' => $room,
|
||||
'organization' => $room->organization,
|
||||
]);
|
||||
}
|
||||
|
||||
$session = $room->activeSession();
|
||||
if ($session) {
|
||||
$participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
|
||||
$participant = $participantUuid
|
||||
? Participant::where('uuid', $participantUuid)->where('session_id', $session->id)->first()
|
||||
: null;
|
||||
|
||||
if ($participant?->status === 'waiting') {
|
||||
return redirect()->route('meet.join.waiting', $room);
|
||||
}
|
||||
|
||||
if ($participant?->status === 'joined') {
|
||||
return redirect()->route('meet.room', $session);
|
||||
}
|
||||
}
|
||||
|
||||
return view('meet.join.show', [
|
||||
'room' => $room,
|
||||
'organization' => $room->organization,
|
||||
'user' => $request->user(),
|
||||
'isWebinar' => $room->isWebinar(),
|
||||
]);
|
||||
@@ -130,12 +162,72 @@ class JoinController extends Controller
|
||||
$displayName = $reg->display_name;
|
||||
}
|
||||
|
||||
$participant = $this->sessions->joinParticipant($session, $user, $role, $displayName, $email);
|
||||
$joinStatus = $room->requiresWaitingRoom($user, $role) ? 'waiting' : 'joined';
|
||||
|
||||
$participant = $this->sessions->joinParticipant($session, $user, $role, $displayName, $email, $joinStatus);
|
||||
|
||||
app(InvitationService::class)->markAcceptedOnJoin($room, $user, $email);
|
||||
|
||||
$this->sessions->rememberParticipantSession($request, $participant, $session);
|
||||
|
||||
if ($participant->status === 'waiting') {
|
||||
return redirect()->route('meet.join.waiting', $room);
|
||||
}
|
||||
|
||||
return redirect()->route('meet.room', $session);
|
||||
}
|
||||
|
||||
public function waiting(Request $request, Room $room): View|RedirectResponse
|
||||
{
|
||||
$room->loadMissing('organization');
|
||||
|
||||
$session = $room->activeSession();
|
||||
abort_unless($session?->isLive(), 404);
|
||||
|
||||
$participant = $this->participantFromSession($request, $session);
|
||||
|
||||
if (! $participant || $participant->status === 'removed') {
|
||||
return redirect()->route('meet.join', $room);
|
||||
}
|
||||
|
||||
if ($participant->status === 'joined') {
|
||||
return redirect()->route('meet.room', $session);
|
||||
}
|
||||
|
||||
return view('meet.join.waiting', [
|
||||
'room' => $room,
|
||||
'session' => $session,
|
||||
'organization' => $room->organization,
|
||||
'participant' => $participant,
|
||||
]);
|
||||
}
|
||||
|
||||
public function waitingStatus(Request $request, Room $room): JsonResponse
|
||||
{
|
||||
$session = $room->activeSession();
|
||||
abort_unless($session, 404);
|
||||
|
||||
$participant = $this->participantFromSession($request, $session);
|
||||
|
||||
return response()->json([
|
||||
'status' => $participant?->status ?? 'unknown',
|
||||
'redirect' => ($participant && $participant->status === 'joined')
|
||||
? route('meet.room', $session)
|
||||
: null,
|
||||
'denied' => $participant?->status === 'removed',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function participantFromSession(Request $request, Session $session): ?Participant
|
||||
{
|
||||
$participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
|
||||
if (! $participantUuid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Participant::query()
|
||||
->where('uuid', $participantUuid)
|
||||
->where('session_id', $session->id)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Meet;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Participant;
|
||||
use App\Models\Room;
|
||||
use App\Models\Session;
|
||||
use App\Models\SessionFeedback;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class LeaveController extends Controller
|
||||
{
|
||||
public function show(Request $request, Session $session): View|RedirectResponse
|
||||
{
|
||||
$session->loadMissing('room.organization');
|
||||
abort_unless($session->room, 404);
|
||||
|
||||
$feedback = (array) $request->session()->get('meet.left.feedback', []);
|
||||
|
||||
if (($feedback['session_uuid'] ?? null) !== $session->uuid) {
|
||||
return redirect()->route('meet.join', $session->room);
|
||||
}
|
||||
|
||||
return view('meet.left.show', [
|
||||
'session' => $session,
|
||||
'room' => $session->room,
|
||||
'organization' => $session->room->organization,
|
||||
'feedback' => $feedback,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request, Session $session): RedirectResponse
|
||||
{
|
||||
$feedback = (array) $request->session()->get('meet.left.feedback', []);
|
||||
|
||||
abort_unless(($feedback['session_uuid'] ?? null) === $session->uuid, 403);
|
||||
|
||||
$validated = $request->validate([
|
||||
'rating' => ['required', 'integer', 'min:1', 'max:5'],
|
||||
'audio_rating' => ['nullable', 'integer', 'min:1', 'max:5'],
|
||||
'video_rating' => ['nullable', 'integer', 'min:1', 'max:5'],
|
||||
'comment' => ['nullable', 'string', 'max:1000'],
|
||||
]);
|
||||
|
||||
SessionFeedback::updateOrCreate(
|
||||
[
|
||||
'session_id' => $session->id,
|
||||
'participant_uuid' => $feedback['participant_uuid'] ?? null,
|
||||
],
|
||||
[
|
||||
'display_name' => $feedback['display_name'] ?? null,
|
||||
'user_ref' => $feedback['user_ref'] ?? null,
|
||||
'rating' => $validated['rating'],
|
||||
'audio_rating' => $validated['audio_rating'] ?? null,
|
||||
'video_rating' => $validated['video_rating'] ?? null,
|
||||
'comment' => $validated['comment'] ?? null,
|
||||
],
|
||||
);
|
||||
|
||||
$request->session()->forget('meet.left.feedback');
|
||||
|
||||
return redirect()
|
||||
->route('meet.left.thanks', $session)
|
||||
->with('rated', true);
|
||||
}
|
||||
|
||||
public function thanks(Request $request, Session $session): View
|
||||
{
|
||||
$session->loadMissing('room.organization');
|
||||
|
||||
return view('meet.left.thanks', [
|
||||
'session' => $session,
|
||||
'room' => $session->room,
|
||||
'organization' => $session->room->organization,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,12 @@ class MeetingRoomController extends Controller
|
||||
->where('session_id', $session->id)
|
||||
->firstOrFail();
|
||||
|
||||
if ($participant->status === 'waiting') {
|
||||
return redirect()->route('meet.join.waiting', $session->room);
|
||||
}
|
||||
|
||||
abort_unless($participant->status === 'joined', 403, 'You are not in this meeting.');
|
||||
|
||||
$token = $this->media->isConfigured()
|
||||
? $this->sessions->generateMediaToken($participant)
|
||||
: '';
|
||||
@@ -81,11 +87,18 @@ class MeetingRoomController extends Controller
|
||||
|
||||
if ($participant) {
|
||||
$this->sessions->leaveParticipant($participant);
|
||||
|
||||
$request->session()->put('meet.left.feedback', [
|
||||
'session_uuid' => $session->uuid,
|
||||
'participant_uuid' => $participant->uuid,
|
||||
'display_name' => $participant->display_name,
|
||||
'user_ref' => $participant->user_ref,
|
||||
]);
|
||||
}
|
||||
|
||||
$request->session()->forget("meet.participant.{$session->uuid}");
|
||||
|
||||
return redirect()->route('meet.dashboard');
|
||||
return redirect()->route('meet.left', $session);
|
||||
}
|
||||
|
||||
public function raiseHand(Request $request, Session $session): JsonResponse
|
||||
@@ -145,7 +158,7 @@ class MeetingRoomController extends Controller
|
||||
{
|
||||
$this->currentParticipant($request, $session);
|
||||
|
||||
$session->load(['participants' => fn ($q) => $q->where('status', 'joined')]);
|
||||
$session->load(['participants' => fn ($q) => $q->whereIn('status', ['joined', 'waiting'])]);
|
||||
|
||||
$avatars = ParticipantPresenter::avatarMap($session->participants, $session->room->host_user_ref);
|
||||
|
||||
@@ -167,6 +180,7 @@ class MeetingRoomController extends Controller
|
||||
'participants' => $session->participants->map(
|
||||
fn ($p) => ParticipantPresenter::toArray($p, $avatars, $session->room->host_user_ref)
|
||||
),
|
||||
'waiting_count' => $session->participants->where('status', 'waiting')->count(),
|
||||
'messages' => $messages->map(fn ($m) => [
|
||||
'uuid' => $m->uuid,
|
||||
'sender_name' => $m->sender_name,
|
||||
@@ -271,6 +285,28 @@ class MeetingRoomController extends Controller
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
public function admit(Request $request, Session $session, Participant $participant): JsonResponse
|
||||
{
|
||||
$current = $this->currentParticipant($request, $session);
|
||||
abort_unless($current->isHost(), 403);
|
||||
abort_unless($participant->session_id === $session->id, 404);
|
||||
|
||||
$this->sessions->admitParticipant($participant);
|
||||
|
||||
return response()->json(['ok' => true, 'status' => 'joined']);
|
||||
}
|
||||
|
||||
public function deny(Request $request, Session $session, Participant $participant): JsonResponse
|
||||
{
|
||||
$current = $this->currentParticipant($request, $session);
|
||||
abort_unless($current->isHost(), 403);
|
||||
abort_unless($participant->session_id === $session->id, 404);
|
||||
|
||||
$this->sessions->denyParticipant($participant);
|
||||
|
||||
return response()->json(['ok' => true, 'status' => 'removed']);
|
||||
}
|
||||
|
||||
protected function currentParticipant(Request $request, Session $session): Participant
|
||||
{
|
||||
$participantUuid = $request->session()->get("meet.participant.{$session->uuid}");
|
||||
|
||||
Reference in New Issue
Block a user