feat: add meeting creation and dashboard with join links

Adds the management/CRUD layer for meetings: a signed JWT join-link
token service (jose), instant/scheduled meeting creation endpoints,
and a protected /dashboard page listing the host's active, upcoming,
and past meetings with a "Nueva reunión" action area (instant button +
scheduling dialog). Also fixes a Base UI console warning (missing
nativeButton={false}) on Button instances rendered as Link.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
WilandClaude Sonnet 5 committed 2026-10-29 11:57:42 -05:00
1 parent 6facce3d22
commit e26cfe624d
14 files changed
+715 -1

No files matched your search

+84
View File
@@ -0,0 +1,84 @@
import { redirect } from "next/navigation"
import { auth } from "@/auth"
import { prisma } from "@/lib/prisma"
import { createJoinLinkToken } from "@/lib/join-link"
import { DashboardContent } from "@/components/dashboard/dashboard-content"
import type { MeetingDTO } from "@/components/dashboard/types"
// This route reads the session (cookies) and queries the DB for the
// current user's meetings on every request, so it can't be part of the
// static shell under Cache Components. Opting out of instant-navigation
// validation here is the documented migration path (see
// node_modules/next/dist/docs/01-app/02-guides/authentication-with-cache-components.md)
// for a route that should always render fresh data rather than a cached
// shell — dashboard data (active/upcoming meetings) must never be stale.
export const instant = false
const PAST_MEETINGS_LIMIT = 10
export default async function DashboardPage() {
const session = await auth()
if (!session?.user?.id) {
redirect("/login?callbackUrl=/dashboard")
}
const hostUserId = Number(session.user.id)
const [activeMeetings, upcomingMeetings, pastMeetings] = await Promise.all([
prisma.meeting.findMany({
where: { hostUserId, status: "active" },
orderBy: { startedAt: "desc" },
}),
prisma.meeting.findMany({
where: {
hostUserId,
status: "scheduled",
scheduledAt: { gte: new Date() },
},
orderBy: { scheduledAt: "asc" },
}),
prisma.meeting.findMany({
where: { hostUserId, status: "ended" },
orderBy: { endedAt: "desc" },
take: PAST_MEETINGS_LIMIT,
}),
])
async function toDTO(
meeting: (typeof activeMeetings)[number],
withJoinLink: boolean
): Promise<MeetingDTO> {
return {
id: meeting.id,
uuid: meeting.uuid,
title: meeting.title,
type: meeting.type,
status: meeting.status,
scheduledAt: meeting.scheduledAt?.toISOString() ?? null,
startedAt: meeting.startedAt?.toISOString() ?? null,
endedAt: meeting.endedAt?.toISOString() ?? null,
joinUrl: withJoinLink
? `/join/${meeting.uuid}?token=${await createJoinLinkToken(meeting.uuid, {
scheduledAt: meeting.scheduledAt,
})}`
: null,
}
}
const [active, upcoming, past] = await Promise.all([
Promise.all(activeMeetings.map((meeting) => toDTO(meeting, true))),
Promise.all(upcomingMeetings.map((meeting) => toDTO(meeting, true))),
Promise.all(pastMeetings.map((meeting) => toDTO(meeting, false))),
])
return (
<DashboardContent
displayName={session.user.displayName}
active={active}
upcoming={upcoming}
past={past}
/>
)
}