Add waiting queue for guest join with sound
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m26s

This commit is contained in:
2026-03-03 06:38:01 +01:00
parent df82316097
commit 2b8c179d03
5 changed files with 117 additions and 34 deletions

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { useParams, Link, useSearchParams } from 'react-router-dom';
import { Video, User, Lock, Shield, ArrowRight, Loader2, Users, Radio, AlertCircle, FileText } from 'lucide-react';
import { Video, User, Lock, Shield, ArrowRight, Loader2, Users, Radio, AlertCircle, FileText, Clock, X } from 'lucide-react';
import BrandLogo from '../components/BrandLogo';
import api from '../services/api';
import toast from 'react-hot-toast';
@@ -24,6 +24,33 @@ export default function GuestJoin() {
const [moderatorCode, setModeratorCode] = useState('');
const [status, setStatus] = useState({ running: false });
const [recordingConsent, setRecordingConsent] = useState(false);
const [waiting, setWaiting] = useState(false);
const prevRunningRef = useRef(false);
const joinMeeting = async () => {
setJoining(true);
try {
const res = await api.post(`/rooms/${uid}/guest-join`, {
name: name.trim(),
access_code: accessCode || undefined,
moderator_code: moderatorCode || undefined,
});
if (res.data.joinUrl) {
window.location.href = res.data.joinUrl;
}
} catch (err) {
const errStatus = err.response?.status;
if (errStatus === 403) {
toast.error(t('room.guestWrongAccessCode'));
setWaiting(false);
} else {
toast.error(t('room.guestJoinFailed'));
setWaiting(false);
}
} finally {
setJoining(false);
}
};
useEffect(() => {
const fetchRoom = async () => {
@@ -31,6 +58,7 @@ export default function GuestJoin() {
const res = await api.get(`/rooms/${uid}/public`);
setRoomInfo(res.data.room);
setStatus({ running: res.data.running });
prevRunningRef.current = res.data.running;
} catch (err) {
const status = err.response?.status;
if (status === 403) {
@@ -53,45 +81,36 @@ export default function GuestJoin() {
} catch {
// ignore
}
}, 10000);
}, 5000);
return () => clearInterval(interval);
}, [uid]);
// Auto-join when meeting starts while waiting
useEffect(() => {
if (!prevRunningRef.current && status.running && waiting) {
new Audio('/sounds/meeting-started.mp3').play().catch(() => {});
toast.success(t('room.guestMeetingStartedJoining'));
joinMeeting();
}
prevRunningRef.current = status.running;
}, [status.running]);
const handleJoin = async (e) => {
e.preventDefault();
if (!name.trim()) {
toast.error(t('room.guestNameRequired'));
return;
}
if (roomInfo?.allow_recording && !recordingConsent) {
toast.error(t('room.guestRecordingConsent'));
return;
}
setJoining(true);
try {
const res = await api.post(`/rooms/${uid}/guest-join`, {
name: name.trim(),
access_code: accessCode || undefined,
moderator_code: moderatorCode || undefined,
});
if (res.data.joinUrl) {
window.location.href = res.data.joinUrl;
}
} catch (err) {
const status = err.response?.status;
if (status === 403) {
toast.error(t('room.guestWrongAccessCode'));
} else if (status === 400) {
toast.error(t('room.guestWaitingMessage'));
} else {
toast.error(t('room.guestJoinFailed'));
}
} finally {
setJoining(false);
if (!status.running) {
setWaiting(true);
return;
}
await joinMeeting();
};
if (loading) {
@@ -164,6 +183,33 @@ export default function GuestJoin() {
</div>
{/* Join form */}
{waiting ? (
<div className="flex flex-col items-center gap-5 py-4">
<div className="w-16 h-16 rounded-full flex items-center justify-center bg-th-accent/10">
<Clock size={28} className="text-th-accent animate-pulse" />
</div>
<div className="text-center">
<p className="font-semibold text-th-text mb-1">{t('room.guestWaitingTitle')}</p>
<p className="text-sm text-th-text-s">{t('room.guestWaitingHint')}</p>
</div>
{joining && (
<div className="flex items-center gap-2 text-sm text-th-success font-medium">
<Loader2 size={16} className="animate-spin" />
{t('room.guestMeetingStartedJoining')}
</div>
)}
{!joining && (
<button
type="button"
onClick={() => setWaiting(false)}
className="btn-ghost flex items-center gap-2 text-sm"
>
<X size={16} />
{t('room.guestCancelWaiting')}
</button>
)}
</div>
) : (
<form onSubmit={handleJoin} className="space-y-4">
<div>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('room.guestYourName')} *</label>
@@ -236,7 +282,7 @@ export default function GuestJoin() {
<button
type="submit"
disabled={joining || (!status.running && !roomInfo.anyone_can_start) || (roomInfo.allow_recording && !recordingConsent)}
disabled={joining || (roomInfo.allow_recording && !recordingConsent)}
className="btn-primary w-full py-3"
>
{joining ? (
@@ -255,6 +301,7 @@ export default function GuestJoin() {
</p>
)}
</form>
)}
{!isLoggedIn && (
<div className="mt-6 pt-4 border-t border-th-border text-center">