Files
2026-06-08 06:05:31 -05:00

211 lines
7.7 KiB
TypeScript

"use client";
import { useMemo, useState } from "react";
import { Link } from "@/i18n/navigation";
import {
MIROTALK_PROVIDER_LABEL,
WEB_CHAT_PROVIDER_LABEL,
type MeetupSession,
} from "@/app/lib/meetups";
import MeetupSocialPanel from "@/app/components/meetups/MeetupSocialPanel";
interface EventLiveRoomProps {
locale: string;
meetup: {
id: string;
city: string;
emoji: string;
venue: string;
date: string;
time: string;
organizer: string;
gradientFrom: string;
gradientTo: string;
};
session: MeetupSession;
formattedDate: string;
}
const copy = {
zh: {
back: "返回活动列表",
video: "视频会议",
chat: "Web 群聊",
split: "分屏",
videoOnly: "只看视频",
chatOnly: "只看群聊",
identity: "社区身份",
guest: "访客",
vip: "VIP",
member: "成员",
channel: "活动频道",
openChat: "新窗口打开群聊",
openVideo: "新窗口打开视频",
locked: "当前权限不足,无法进入活动间",
login: "登录社区账户",
joinVip: "开通 VIP",
providers: "服务由 VPS 自建开源项目提供",
},
en: {
back: "Back to events",
video: "Video",
chat: "Web chat",
split: "Split",
videoOnly: "Video only",
chatOnly: "Chat only",
identity: "Community identity",
guest: "Guest",
vip: "VIP",
member: "Member",
channel: "Event channel",
openChat: "Open chat in new tab",
openVideo: "Open video in new tab",
locked: "You do not have access to this live room yet.",
login: "Sign in",
joinVip: "Get VIP",
providers: "Powered by self-hosted open-source services on VPS",
},
};
type PanelMode = "split" | "video" | "chat";
export default function EventLiveRoom({ locale, meetup, session, formattedDate }: EventLiveRoomProps) {
const c = locale === "zh" ? copy.zh : copy.en;
const [panel, setPanel] = useState<PanelMode>("split");
const badge = useMemo(() => {
if (session.user?.vip) return c.vip;
if (session.user) return c.member;
return c.guest;
}, [c.guest, c.member, c.vip, session.user]);
if (!session.canJoin) {
return (
<div className="mx-auto flex min-h-[60vh] max-w-2xl flex-col items-center justify-center px-4 text-center">
<p className="text-lg font-semibold text-gray-900 dark:text-gray-100">{c.locked}</p>
<div className="mt-6 flex flex-wrap justify-center gap-3">
<Link
href="/login"
className="rounded-xl bg-[#ff4d4f] px-5 py-2.5 text-sm font-semibold text-white hover:bg-[#ff7a45]"
>
{c.login}
</Link>
{session.lockReason === "vip_required" ? (
<Link
href="/join"
className="rounded-xl border border-violet-300 px-5 py-2.5 text-sm font-semibold text-violet-700 dark:border-violet-700 dark:text-violet-300"
>
{c.joinVip}
</Link>
) : null}
<Link href="/meetups" className="rounded-xl border border-gray-200 px-5 py-2.5 text-sm font-semibold text-gray-600 dark:border-gray-700 dark:text-gray-300">
{c.back}
</Link>
</div>
</div>
);
}
const showVideo = panel === "split" || panel === "video";
const showChat = panel === "split" || panel === "chat";
return (
<div className="flex min-h-screen flex-col bg-[#0b1220] text-white">
<header
className="border-b border-white/10 px-4 py-4 sm:px-6"
style={{ background: `linear-gradient(135deg, ${meetup.gradientFrom}, ${meetup.gradientTo})` }}
>
<div className="mx-auto flex max-w-[1600px] flex-wrap items-center justify-between gap-4">
<div className="min-w-0">
<Link href="/meetups" className="text-xs font-medium text-white/75 hover:text-white">
{c.back}
</Link>
<h1 className="mt-2 truncate text-xl font-bold sm:text-2xl">
{meetup.emoji} {meetup.city} · {meetup.venue}
</h1>
<p className="mt-1 text-sm text-white/85">{formattedDate}</p>
</div>
<div className="flex flex-wrap items-center gap-2">
<span className="rounded-full bg-black/20 px-3 py-1 text-xs font-semibold backdrop-blur">
{c.identity}: {session.displayName}
</span>
<span className="rounded-full bg-black/20 px-3 py-1 text-xs font-semibold backdrop-blur">{badge}</span>
<span className="rounded-full bg-black/20 px-3 py-1 text-xs font-semibold backdrop-blur">
{c.channel} {session.loungeChannel}
</span>
</div>
</div>
</header>
<div className="border-b border-white/10 bg-[#111827] px-4 py-3 sm:px-6">
<div className="mx-auto flex max-w-[1600px] flex-wrap items-center justify-between gap-3">
<div className="flex flex-wrap gap-2 text-xs text-white/70">
<span className="rounded-full bg-white/10 px-2.5 py-1">{MIROTALK_PROVIDER_LABEL}</span>
<span className="rounded-full bg-white/10 px-2.5 py-1">{WEB_CHAT_PROVIDER_LABEL}</span>
<span className="rounded-full bg-white/10 px-2.5 py-1">{c.providers}</span>
</div>
<div className="flex flex-wrap gap-2">
{(["split", "video", "chat"] as PanelMode[]).map((mode) => (
<button
key={mode}
type="button"
onClick={() => setPanel(mode)}
className={`rounded-lg px-3 py-1.5 text-xs font-semibold transition ${
panel === mode ? "bg-[#ff4d4f] text-white" : "bg-white/10 text-white/80 hover:bg-white/15"
}`}
>
{mode === "split" ? c.split : mode === "video" ? c.videoOnly : c.chatOnly}
</button>
))}
<a
href={session.videoUrl}
target="_blank"
rel="noreferrer"
className="rounded-lg border border-white/15 px-3 py-1.5 text-xs font-semibold text-white/85 hover:bg-white/10"
>
{c.openVideo}
</a>
<a
href={session.chatUrl}
target="_blank"
rel="noreferrer"
className="rounded-lg border border-white/15 px-3 py-1.5 text-xs font-semibold text-white/85 hover:bg-white/10"
>
{c.openChat}
</a>
</div>
</div>
</div>
<main className="mx-auto grid w-full max-w-[1600px] flex-1 gap-0 px-0 py-0 lg:grid-cols-[minmax(0,1.35fr)_minmax(320px,0.65fr)]">
{showVideo ? (
<section className={`relative min-h-[320px] bg-black ${panel === "split" ? "border-r border-white/10" : ""}`}>
<iframe
title={`${meetup.city} video`}
src={session.videoUrl}
className="h-[50vh] w-full lg:h-[calc(100vh-180px)]"
allow="camera; microphone; display-capture; fullscreen; clipboard-read; clipboard-write"
referrerPolicy="no-referrer-when-downgrade"
/>
</section>
) : null}
{showChat ? (
<section className="relative min-h-[320px] bg-[#0f172a]">
<iframe
title={`${meetup.city} chat`}
src={session.chatUrl}
className="h-[50vh] w-full lg:h-[calc(100vh-180px)]"
referrerPolicy="no-referrer-when-downgrade"
/>
</section>
) : null}
</main>
<footer className="border-t border-white/10 bg-[#111827] px-4 py-5 sm:px-6">
<div className="mx-auto max-w-[1600px]">
<MeetupSocialPanel meetupId={meetup.id} city={meetup.city} locale={locale} />
</div>
</footer>
</div>
);
}