Integrate LiveKit SFU and harden booking workflow

This commit is contained in:
2026-09-16 19:38:28 +02:00
parent f1cd07b3ad
commit d0864e2911
55 changed files with 1648 additions and 1234 deletions
+31 -13
View File
@@ -2,7 +2,7 @@
import { useState, type FormEvent } from "react";
import { useRouter } from "next/navigation";
import { TextField, FormError } from "@/components/FormField";
import { fieldClass, TextField, FormError } from "@/components/FormField";
export const MEETING_SESSION_KEY = "anouma-meeting-session";
const MEETING_CLIENT_ID_KEY = "anouma-meeting-client-id";
@@ -28,18 +28,20 @@ function getOrCreateClientId(): string {
export type StoredMeetingSession = {
token: string;
participantToken?: string;
serverUrl?: string;
participantId: string;
role: "host" | "participant";
name: string;
eventTitle: string;
eventSlug: string;
iceServers: RTCIceServer[];
};
export function JoinForm({ slug }: { slug: string }) {
const router = useRouter();
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
@@ -63,12 +65,13 @@ export function JoinForm({ slug }: { slug: string }) {
const session: StoredMeetingSession = {
token: data.token,
participantToken: data.participantToken,
serverUrl: data.serverUrl,
participantId: data.participantId,
role: data.role,
name,
eventTitle: data.eventTitle,
eventSlug: slug,
iceServers: data.iceServers,
};
sessionStorage.setItem(MEETING_SESSION_KEY, JSON.stringify(session));
router.push(`/termine/${slug}/call`);
@@ -90,16 +93,31 @@ export function JoinForm({ slug }: { slug: string }) {
placeholder="Name eingeben"
autoComplete="name"
/>
<TextField
label="Meeting-Passwort"
id="password"
type="text"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Passwort eingeben"
autoComplete="off"
/>
<div>
<div className="mb-2 flex items-center justify-between">
<label htmlFor="password" className="block text-sm font-medium text-anouma-plum">
Meeting-Passwort
</label>
<button
type="button"
className="text-xs font-medium text-anouma-mauve-dark underline-offset-2 hover:underline"
onClick={() => setShowPassword((visible) => !visible)}
aria-pressed={showPassword}
>
{showPassword ? "Verbergen" : "Anzeigen"}
</button>
</div>
<input
id="password"
className={fieldClass}
type={showPassword ? "text" : "password"}
required
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Passwort eingeben"
autoComplete="off"
/>
</div>
{error && <FormError>{error}</FormError>}
<button
type="submit"