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
+3 -1
View File
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { getCurrentCustomer } from "@/lib/auth/customer";
import { getCustomerBookings } from "@/lib/booking/queries";
import { PersonalCalendar } from "@/components/booking/PersonalCalendar";
import { getMeetingSettingsGlobal } from "@/lib/payload/globals";
export const metadata: Metadata = { title: "Mein Kalender" };
@@ -10,12 +11,13 @@ export default async function KontoKalenderPage() {
if (!customer) return null;
const bookings = await getCustomerBookings(customer.id);
const meetingSettings = await getMeetingSettingsGlobal();
return (
<div>
<h2 className="font-serif text-xl font-medium text-anouma-plum">Mein Kalender</h2>
<div className="mt-6">
<PersonalCalendar bookings={bookings} />
<PersonalCalendar bookings={bookings} meetingSettings={meetingSettings} />
</div>
</div>
);
+4 -1
View File
@@ -3,6 +3,8 @@ import { getCurrentCustomer } from "@/lib/auth/customer";
import { getCustomerBookings } from "@/lib/booking/queries";
import { BookingCard } from "@/components/booking/BookingCard";
import { ArrowLink } from "@/components/ArrowLink";
import { getMeetingSettingsGlobal } from "@/lib/payload/globals";
import { getBookingMeetingStatus } from "@/lib/booking/meetingAccess";
export const metadata: Metadata = { title: "Mein Konto" };
@@ -11,6 +13,7 @@ export default async function KontoOverviewPage() {
if (!customer) return null; // Layout already redirects; keeps TS happy.
const bookings = await getCustomerBookings(customer.id);
const meetingSettings = await getMeetingSettingsGlobal();
const pending = bookings.filter((b) => b.status === "pending");
const confirmed = bookings.filter((b) => b.status === "confirmed" && new Date(b.date) >= new Date());
const past = bookings.filter((b) => new Date(b.date) < new Date() || b.status === "rejected" || b.status === "cancelled");
@@ -53,7 +56,7 @@ export default async function KontoOverviewPage() {
) : (
<div className="mt-4 grid gap-4 sm:grid-cols-2">
{confirmed.map((b) => (
<BookingCard key={b.id} booking={b} />
<BookingCard key={b.id} booking={b} meetingStatus={getBookingMeetingStatus(b, meetingSettings)} />
))}
</div>
)}
+4 -1
View File
@@ -2,6 +2,8 @@ import type { Metadata } from "next";
import { getCurrentCustomer } from "@/lib/auth/customer";
import { getCustomerBookings } from "@/lib/booking/queries";
import { BookingCard } from "@/components/booking/BookingCard";
import { getMeetingSettingsGlobal } from "@/lib/payload/globals";
import { getBookingMeetingStatus } from "@/lib/booking/meetingAccess";
export const metadata: Metadata = { title: "Meine Termine" };
@@ -10,6 +12,7 @@ export default async function KontoTerminePage() {
if (!customer) return null;
const bookings = await getCustomerBookings(customer.id);
const meetingSettings = await getMeetingSettingsGlobal();
return (
<div>
@@ -21,7 +24,7 @@ export default async function KontoTerminePage() {
) : (
<div className="mt-4 grid gap-4 sm:grid-cols-2">
{bookings.map((b) => (
<BookingCard key={b.id} booking={b} />
<BookingCard key={b.id} booking={b} meetingStatus={getBookingMeetingStatus(b, meetingSettings)} />
))}
</div>
)}
@@ -5,6 +5,7 @@ import { JoinForm } from "@/components/meeting/JoinForm";
import { OrganicBlob } from "@/components/OrganicBlob";
import { getEventForJoin } from "@/lib/payload/content";
import { combineDateAndTime } from "@/lib/meeting/status";
import { formatBerlinDate, formatBerlinTime } from "@/lib/calendar/timezone";
export const dynamic = "force-dynamic";
@@ -21,8 +22,8 @@ export default async function BeitretenPage({ params }: Args) {
if (!event || !event.isOnline) notFound();
const start = combineDateAndTime(event.date, event.startTime);
const dateLabel = start.toLocaleDateString("de-DE", { day: "2-digit", month: "long" });
const timeLabel = start.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" });
const dateLabel = formatBerlinDate(start, { day: "2-digit", month: "long" });
const timeLabel = formatBerlinTime(start, { hour: "2-digit", minute: "2-digit" });
return (
<section className="relative flex min-h-[80vh] items-center overflow-hidden py-20">
+1 -1
View File
@@ -76,7 +76,7 @@ export default async function EventDetailPage({ params }: Args) {
const { user } = await payload.auth({ headers: await headers() });
// Must check the collection, not just presence — a logged-in customer
// (collection "customers") must never be treated as host.
const isHost = user?.collection === "users";
const isHost = user?.collection === "users" && user.role === "admin";
const settings = await payload.findGlobal({ slug: "meeting-settings" });
const start = combineDateAndTime(event.date, event.startTime);
const end = event.endTime
+3 -1
View File
@@ -21,7 +21,9 @@ export default async function TerminePage() {
const events = await getAllEvents();
const today = new Date();
today.setHours(0, 0, 0, 0);
const upcoming = events.filter((e) => new Date(e.date) >= today).reverse();
const upcoming = events
.filter((e) => new Date(e.date) >= today)
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
const past = events.filter((e) => new Date(e.date) < today);
const siteUrl = await getSiteUrl();