211 lines
7.7 KiB
TypeScript
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>
|
|
);
|
|
}
|