119 lines
3.7 KiB
TypeScript
119 lines
3.7 KiB
TypeScript
import { AppShell } from "@/components/app-shell";
|
|
import { RoomConsole } from "@/components/room-console";
|
|
import { StatusBadge } from "@/components/status-badge";
|
|
import { canEnterRoom } from "@/lib/access";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { requireCurrentUser, userIsAdmin } from "@/lib/session";
|
|
import { requireInitialSetup } from "@/lib/setup";
|
|
import { redirect } from "next/navigation";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export default async function RoomPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
await requireInitialSetup();
|
|
const user = await requireCurrentUser();
|
|
const isAdmin = userIsAdmin(user);
|
|
const { slug } = await params;
|
|
const roomSlug = decodeURIComponent(slug);
|
|
const room = await prisma.room.findUnique({
|
|
where: { slug: roomSlug },
|
|
include: {
|
|
owner: true,
|
|
members: { include: { user: true } },
|
|
mediaSources: { include: { submitter: true }, orderBy: { createdAt: "desc" }, take: 20 }
|
|
}
|
|
});
|
|
|
|
if (!room) {
|
|
redirect("/dashboard");
|
|
}
|
|
|
|
const isOwner = room.ownerId === user.id;
|
|
const explicitMember = room.members.some((member) => member.userId === user.id);
|
|
const isFriend = room.ownerId
|
|
? Boolean(
|
|
await prisma.friendship.findFirst({
|
|
where: {
|
|
status: "ACCEPTED",
|
|
OR: [
|
|
{ requesterId: user.id, receiverId: room.ownerId },
|
|
{ requesterId: room.ownerId, receiverId: user.id }
|
|
]
|
|
},
|
|
select: { id: true }
|
|
})
|
|
)
|
|
: false;
|
|
|
|
if (
|
|
!canEnterRoom({
|
|
visibility: room.visibility,
|
|
isOwner,
|
|
isAdmin,
|
|
isFriend,
|
|
explicitMember,
|
|
hasRoomRole: explicitMember
|
|
})
|
|
) {
|
|
redirect("/dashboard");
|
|
}
|
|
|
|
const personalRoom = await prisma.room.findFirst({ where: { ownerId: user.id }, select: { slug: true } });
|
|
|
|
return (
|
|
<AppShell
|
|
active="Rooms"
|
|
isAdmin={isAdmin}
|
|
roomHref={personalRoom ? `/rooms/${encodeURIComponent(personalRoom.slug)}` : "/dashboard"}
|
|
userName={user.displayName || user.username}
|
|
>
|
|
<header className="topbar">
|
|
<div className="title-block">
|
|
<h1>{room.name}</h1>
|
|
<p>{room.owner ? `Owned by ${room.owner.displayName || room.owner.username}` : "Shared watch room"}</p>
|
|
</div>
|
|
<div className="status-row">
|
|
<StatusBadge tone="good">Online</StatusBadge>
|
|
<StatusBadge>{room.visibility}</StatusBadge>
|
|
</div>
|
|
</header>
|
|
<RoomConsole
|
|
roomId={room.id}
|
|
roomSlug={room.slug}
|
|
currentUser={user.displayName || user.username}
|
|
queue={room.mediaSources.map((item) => ({
|
|
id: item.id,
|
|
title: item.title || item.originalUrl,
|
|
provider: item.provider,
|
|
originalUrl: item.originalUrl,
|
|
playbackUrl: item.playbackUrl,
|
|
by: item.submitter?.displayName || item.submitter?.username || "Unknown",
|
|
createdAt: formatDate(item.createdAt)
|
|
}))}
|
|
participants={[
|
|
...(room.owner
|
|
? [
|
|
{
|
|
id: room.owner.id,
|
|
name: room.owner.displayName || room.owner.username,
|
|
role: "Owner",
|
|
status: room.owner.id === user.id ? "Online" : "Available"
|
|
}
|
|
]
|
|
: []),
|
|
...room.members.map((member) => ({
|
|
id: member.userId,
|
|
name: member.user.displayName || member.user.username,
|
|
role: member.canManage ? "Manager" : "Member",
|
|
status: member.userId === user.id ? "Online" : "Allowed"
|
|
}))
|
|
]}
|
|
/>
|
|
</AppShell>
|
|
);
|
|
}
|
|
|
|
function formatDate(date: Date) {
|
|
return new Intl.DateTimeFormat("en", { month: "short", day: "numeric" }).format(date);
|
|
}
|