This commit is contained in:
222
src/pages/GuestJoin.jsx
Normal file
222
src/pages/GuestJoin.jsx
Normal file
@@ -0,0 +1,222 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { Video, User, Lock, Shield, ArrowRight, Loader2, Users, Radio } from 'lucide-react';
|
||||
import api from '../services/api';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
export default function GuestJoin() {
|
||||
const { uid } = useParams();
|
||||
const [roomInfo, setRoomInfo] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [joining, setJoining] = useState(false);
|
||||
const [name, setName] = useState('');
|
||||
const [accessCode, setAccessCode] = useState('');
|
||||
const [moderatorCode, setModeratorCode] = useState('');
|
||||
const [status, setStatus] = useState({ running: false });
|
||||
|
||||
useEffect(() => {
|
||||
const fetchRoom = async () => {
|
||||
try {
|
||||
const res = await api.get(`/rooms/${uid}/public`);
|
||||
setRoomInfo(res.data.room);
|
||||
setStatus({ running: res.data.running });
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.error || 'Raum nicht gefunden');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchRoom();
|
||||
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const res = await api.get(`/rooms/${uid}/status`);
|
||||
setStatus(res.data);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}, 10000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [uid]);
|
||||
|
||||
const handleJoin = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!name.trim()) {
|
||||
toast.error('Name ist erforderlich');
|
||||
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) {
|
||||
toast.error(err.response?.data?.error || 'Beitritt fehlgeschlagen');
|
||||
} finally {
|
||||
setJoining(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-th-bg">
|
||||
<Loader2 size={32} className="animate-spin text-th-accent" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-6 relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-th-bg">
|
||||
<div className="absolute inset-0 opacity-30">
|
||||
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-th-accent rounded-full blur-[128px] animate-pulse" />
|
||||
<div className="absolute bottom-1/4 right-1/4 w-80 h-80 bg-purple-500 rounded-full blur-[128px] animate-pulse" style={{ animationDelay: '2s' }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative w-full max-w-md">
|
||||
<div className="card p-8 backdrop-blur-xl bg-th-card/80 border border-th-border shadow-2xl rounded-2xl text-center">
|
||||
<div className="w-16 h-16 bg-th-error/15 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Video size={28} className="text-th-error" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold text-th-text mb-2">Zugang nicht möglich</h2>
|
||||
<p className="text-sm text-th-text-s mb-6">{error}</p>
|
||||
<Link to="/login" className="btn-primary inline-flex">
|
||||
Zum Login
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-6 relative overflow-hidden">
|
||||
{/* Animated background */}
|
||||
<div className="absolute inset-0 bg-th-bg">
|
||||
<div className="absolute inset-0 opacity-30">
|
||||
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-th-accent rounded-full blur-[128px] animate-pulse" />
|
||||
<div className="absolute bottom-1/4 right-1/4 w-80 h-80 bg-purple-500 rounded-full blur-[128px] animate-pulse" style={{ animationDelay: '2s' }} />
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-pink-500 rounded-full blur-[128px] animate-pulse" style={{ animationDelay: '4s' }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Join card */}
|
||||
<div className="relative w-full max-w-md">
|
||||
<div className="card p-8 backdrop-blur-xl bg-th-card/80 border border-th-border shadow-2xl rounded-2xl">
|
||||
{/* Logo */}
|
||||
<div className="flex items-center justify-center gap-2.5 mb-6">
|
||||
<div className="w-10 h-10 gradient-bg rounded-xl flex items-center justify-center">
|
||||
<Video size={22} className="text-white" />
|
||||
</div>
|
||||
<span className="text-2xl font-bold gradient-text">Redlight</span>
|
||||
</div>
|
||||
|
||||
{/* Room info */}
|
||||
<div className="text-center mb-6">
|
||||
<h2 className="text-xl font-bold text-th-text mb-1">{roomInfo.name}</h2>
|
||||
<p className="text-sm text-th-text-s">
|
||||
Erstellt von <span className="font-medium text-th-text">{roomInfo.owner_name}</span>
|
||||
</p>
|
||||
<div className="mt-3 inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-medium"
|
||||
style={{
|
||||
backgroundColor: status.running ? 'rgba(34, 197, 94, 0.15)' : 'rgba(100, 116, 139, 0.15)',
|
||||
color: status.running ? '#22c55e' : '#94a3b8',
|
||||
}}
|
||||
>
|
||||
{status.running ? <Radio size={10} className="animate-pulse" /> : <Users size={12} />}
|
||||
{status.running ? 'Meeting läuft' : 'Noch nicht gestartet'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Join form */}
|
||||
<form onSubmit={handleJoin} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">Ihr Name *</label>
|
||||
<div className="relative">
|
||||
<User size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
className="input-field pl-11"
|
||||
placeholder="Max Mustermann"
|
||||
required
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{roomInfo.has_access_code && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">Zugangscode</label>
|
||||
<div className="relative">
|
||||
<Lock size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
|
||||
<input
|
||||
type="text"
|
||||
value={accessCode}
|
||||
onChange={e => setAccessCode(e.target.value)}
|
||||
className="input-field pl-11"
|
||||
placeholder="Code eingeben"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">
|
||||
Moderator-Code
|
||||
<span className="text-th-text-s font-normal ml-1">(optional)</span>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Shield size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
|
||||
<input
|
||||
type="text"
|
||||
value={moderatorCode}
|
||||
onChange={e => setModeratorCode(e.target.value)}
|
||||
className="input-field pl-11"
|
||||
placeholder="Nur wenn Sie Moderator sind"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={joining || (!status.running && !roomInfo.anyone_can_start)}
|
||||
className="btn-primary w-full py-3"
|
||||
>
|
||||
{joining ? (
|
||||
<Loader2 size={18} className="animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
Meeting beitreten
|
||||
<ArrowRight size={18} />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{!status.running && (
|
||||
<p className="text-xs text-th-text-s text-center">
|
||||
Das Meeting wurde noch nicht gestartet. Bitte warten Sie, bis der Moderator es startet.
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
|
||||
<div className="mt-6 pt-4 border-t border-th-border text-center">
|
||||
<Link to="/login" className="text-sm text-th-text-s hover:text-th-accent transition-colors">
|
||||
Haben Sie ein Konto? <span className="text-th-accent font-medium">Anmelden</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user