Files
meet/src/app/join/[meetingUuid]/page.tsx
T

218 lines
5.2 KiB
TypeScript

import { cookies } from "next/headers"
import { redirect } from "next/navigation"
import { auth } from "@/auth"
import { prisma } from "@/lib/prisma"
import { verifyJoinLinkToken } from "@/lib/join-link"
import { guestIdentityCookieName } from "@/lib/participant-identity"
import { AppShell } from "@/components/shell/app-shell"
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { JoinFlow } from "@/components/join/join-flow"
// Reads the session/cookies and queries the DB on every request to
// resolve "who is this visitor and what's their access state" — same
// reasoning as the dashboard page (see its own comment) for opting out
// of Cache Components' static-shell validation.
export const instant = false
function JoinShell({
title,
children,
}: {
title?: string
children: React.ReactNode
}) {
return (
<AppShell className="flex min-h-[80vh] items-center justify-center">
<div className="w-full max-w-md">
{title ? (
<h1 className="mb-4 text-center text-lg font-semibold text-foreground">
{title}
</h1>
) : null}
{children}
</div>
</AppShell>
)
}
function JoinMessage({
title,
description,
}: {
title: string
description: string
}) {
return (
<JoinShell>
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
</Card>
</JoinShell>
)
}
export default async function JoinPage({
params,
searchParams,
}: {
params: Promise<{ meetingUuid: string }>
searchParams: Promise<{ token?: string }>
}) {
const { meetingUuid } = await params
const { token } = await searchParams
if (!token) {
return (
<JoinMessage
title="Enlace inválido"
description="Falta el token de invitación en el enlace. Pide al anfitrión que te comparta el enlace completo."
/>
)
}
const verified = await verifyJoinLinkToken(token)
if (!verified || verified.meetingUuid !== meetingUuid) {
return (
<JoinMessage
title="Enlace inválido o expirado"
description="Este enlace de invitación ya no es válido. Pide al anfitrión un nuevo enlace."
/>
)
}
const meeting = await prisma.meeting.findUnique({
where: { uuid: meetingUuid },
})
if (!meeting) {
return (
<JoinMessage
title="Reunión no encontrada"
description="No encontramos esta reunión. Verifica el enlace."
/>
)
}
if (meeting.status === "ended") {
return (
<JoinMessage
title="Esta reunión ha finalizado"
description="El anfitrión ya terminó esta reunión. No es posible unirse."
/>
)
}
const session = await auth()
if (session?.user?.id) {
const userId = Number(session.user.id)
const identity = `user-${userId}`
if (meeting.hostUserId === userId) {
await prisma.meetingParticipant.upsert({
where: {
meetingId_identity: { meetingId: meeting.id, identity },
},
update: {},
create: {
meetingId: meeting.id,
userId,
identity,
role: "host",
status: "admitted",
admittedAt: new Date(),
},
})
redirect(`/room/${meetingUuid}`)
}
const participant = await prisma.meetingParticipant.findUnique({
where: {
meetingId_identity: { meetingId: meeting.id, identity },
},
})
if (participant) {
if (participant.status === "admitted") {
redirect(`/room/${meetingUuid}`)
}
if (participant.status === "denied") {
return (
<JoinShell>
<JoinFlow meetingUuid={meetingUuid} token={token} mode="denied" />
</JoinShell>
)
}
return (
<JoinShell>
<JoinFlow meetingUuid={meetingUuid} token={token} mode="waiting" />
</JoinShell>
)
}
return (
<JoinShell>
<JoinFlow
meetingUuid={meetingUuid}
token={token}
mode="confirm"
displayName={session.user.displayName}
/>
</JoinShell>
)
}
// Anonymous visitor: recognize a returning guest via cookie before
// falling back to the name-entry form.
const cookieStore = await cookies()
const guestIdentity = cookieStore.get(
guestIdentityCookieName(meetingUuid)
)?.value
if (guestIdentity) {
const participant = await prisma.meetingParticipant.findUnique({
where: {
meetingId_identity: { meetingId: meeting.id, identity: guestIdentity },
},
})
if (participant) {
if (participant.status === "admitted") {
redirect(`/room/${meetingUuid}`)
}
if (participant.status === "denied") {
return (
<JoinShell>
<JoinFlow meetingUuid={meetingUuid} token={token} mode="denied" />
</JoinShell>
)
}
return (
<JoinShell>
<JoinFlow meetingUuid={meetingUuid} token={token} mode="waiting" />
</JoinShell>
)
}
}
return (
<JoinShell>
<JoinFlow meetingUuid={meetingUuid} token={token} mode="name-entry" />
</JoinShell>
)
}