nomadweb/frontend/src/components/MeetupLiveClient.tsx
eric bdf0516197 Fix dating match gate and expand seed data for usable demos.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-04 06:18:25 -05:00

147 lines
4.4 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import { api } from "@/lib/api";
import { useAuth } from "@/lib/auth";
import { useI18n } from "@/lib/i18n";
import type { MeetupSession } from "@/lib/types";
import RingNext from "@/components/RingNext";
import { useRingSteps } from "@/lib/rings";
export default function MeetupLiveClient({ meetupId, title }: { meetupId: string; title: string }) {
const { token } = useAuth();
const { t } = useI18n();
const rings = useRingSteps();
const [session, setSession] = useState<MeetupSession | null>(null);
const [error, setError] = useState(false);
const [layout, setLayout] = useState<"split" | "video" | "chat">("split");
useEffect(() => {
setError(false);
setSession(null);
api
.getMeetupSession(meetupId, token || undefined)
.then(setSession)
.catch(() => {
setSession(null);
setError(true);
});
}, [meetupId, token]);
if (error) {
return (
<div className="live-page">
<div className="container">
<div className="live-gate reveal">
<h2>{t.live.unavailable}</h2>
<p>{t.live.unavailableDesc}</p>
<Link href="/meetups" className="btn btn-primary">
{t.live.back}
</Link>
</div>
</div>
</div>
);
}
if (!session) {
return (
<div className="live-page">
<div className="container">
<p className="dest-empty">{t.live.loading}</p>
</div>
</div>
);
}
if (!session.canJoin) {
const reason = session.lockReason || (session.mode === "offline" ? "offline" : "login_required");
const copy =
reason === "vip_required"
? t.live.vipRequired
: reason === "host_required"
? t.live.hostRequired
: reason === "offline"
? t.live.offlineOnly
: t.live.loginRequired;
const href =
reason === "vip_required"
? "/join"
: reason === "offline"
? "/meetups"
: `/login?next=/meetups/${meetupId}/live`;
const label =
reason === "vip_required" ? t.join.payBtn : reason === "offline" ? t.live.back : t.nav.login;
return (
<div className="live-page">
<div className="container">
<div className="live-gate reveal">
<h2>{title}</h2>
<p>{copy}</p>
<div className="dating-empty-actions">
<Link href={href} className="btn btn-primary">
{label}
</Link>
<Link href="/meetups" className="btn">
{t.live.back}
</Link>
</div>
</div>
</div>
</div>
);
}
return (
<div className="live-page">
<div className="live-toolbar">
<Link href="/meetups">← {t.live.back}</Link>
<strong>{title}</strong>
<div className="live-layout-btns">
{(["split", "video", "chat"] as const).map((m) => (
<button
key={m}
type="button"
className={`filter-btn${layout === m ? " active" : ""}`}
onClick={() => setLayout(m)}
>
{m === "split" ? t.live.layoutSplit : m === "video" ? t.live.layoutVideo : t.live.layoutChat}
</button>
))}
</div>
</div>
<div className={`live-stage layout-${layout}`}>
{(layout === "split" || layout === "video") && session.videoUrl && (
<iframe
title="video"
src={session.videoUrl}
className="live-iframe live-video"
allow="camera; microphone; display-capture"
/>
)}
{(layout === "split" || layout === "chat") && session.chatUrl && (
<iframe title="chat" src={session.chatUrl} className="live-iframe live-chat" />
)}
{!session.videoUrl && !session.chatUrl && (
<div className="notif-empty">
<p className="dest-empty">{t.live.linksMissing}</p>
<div className="dating-empty-actions">
<Link href="/meetups" className="btn btn-primary">
{t.live.back}
</Link>
<Link href="/feedback" className="btn btn-ghost">
{t.nav.feedback}
</Link>
</div>
</div>
)}
</div>
<div className="container live-exit">
<RingNext steps={rings.afterMeetups} />
</div>
</div>
);
}