FitnessApp/app/[tenant]/classroom/[classId]/page.tsx

118 lines
4.0 KiB
TypeScript

export const dynamic = 'force-dynamic';
import React from 'react';
import Link from 'next/link';
import { notFound } from 'next/navigation';
import { prisma } from '@/lib/prisma';
import { generateJitsiToken, JITSI_DOMAIN } from '@/lib/jitsi';
import JitsiClassroom from '@/components/classroom/JitsiClassroom';
interface PageProps {
params: Promise<{ tenant: string; classId: string }>;
searchParams: Promise<{ role?: string; userId?: string }>;
}
export default async function ClassroomLivePage({
params, searchParams }: PageProps) {
const { tenant: tenantSlug, classId } = await params;
const { role: queryRole, userId: queryUserId } = await searchParams;
// 1. Verify tenant & class session
const session = await prisma.classSession.findUnique({
where: { id: classId },
include: {
tenant: true,
bookings: {
include: { user: true },
},
},
});
if (!session || session.tenant.slug !== tenantSlug) {
notFound();
}
// 2. Resolve user & permissions
const isHostRequested = queryRole === 'INSTRUCTOR' || queryRole === 'SUPER_ADMIN';
let user = null;
if (queryUserId) {
user = await prisma.user.findUnique({
where: { id: queryUserId },
});
}
if (!user) {
// Select first appropriate user in the tenant matching requested role
user = await prisma.user.findFirst({
where: {
tenantId: session.tenantId,
role: isHostRequested ? { in: ['INSTRUCTOR', 'SUPER_ADMIN'] } : 'CLIENT',
},
});
}
// Fallback demo user if tenant has no seeded users yet
const effectiveUser = user || {
id: `guest-${Math.random().toString(36).slice(2, 8)}`,
name: isHostRequested ? 'Head Coach' : 'Fitness Member',
email: `${isHostRequested ? 'coach' : 'member'}@${tenantSlug}.local`,
role: (isHostRequested ? 'INSTRUCTOR' : 'CLIENT') as 'INSTRUCTOR' | 'CLIENT',
};
const isHost = effectiveUser.role === 'INSTRUCTOR' || effectiveUser.role === 'SUPER_ADMIN';
// 3. Generate signed Jitsi JWT token for this session
const jwtToken = generateJitsiToken({
roomName: session.jitsiRoomId,
tenantSlug: session.tenant.slug,
user: {
id: effectiveUser.id,
name: effectiveUser.name || 'Participant',
email: effectiveUser.email,
role: effectiveUser.role,
},
expiresInSeconds: 7200,
});
return (
<div className="h-screen h-dvh w-full min-h-0 min-w-0 overflow-hidden bg-neutral-950 text-neutral-100 font-sans p-2 sm:p-4 md:p-6 flex flex-col">
{/* Top Banner Navigation */}
<div className="max-w-7xl w-full min-w-0 mx-auto mb-2 sm:mb-4 shrink-0 flex flex-col sm:flex-row sm:items-center justify-between gap-2 sm:gap-3">
<div className="flex min-w-0 items-center gap-2 sm:gap-3">
<Link
href={`/${tenantSlug}/classes`}
className="shrink-0 text-xs text-neutral-400 hover:text-white transition px-2.5 py-1 rounded bg-neutral-900 border border-neutral-800"
>
← Exit to Dashboard
</Link>
<span className="min-w-0 text-sm font-semibold text-white truncate" title={session.title}>
{session.title}
</span>
<span className="hidden md:inline min-w-0 truncate text-xs font-mono text-neutral-500" title={session.tenant.name}>
• {session.tenant.name}
</span>
</div>
<div className="flex min-w-0 items-center gap-2">
<span className="truncate text-xs text-neutral-400">
Signed in as: <strong className="text-white">{effectiveUser.name}</strong> ({effectiveUser.role})
</span>
</div>
</div>
{/* Main Jitsi WebRTC Classroom Container */}
<div className="max-w-7xl w-full min-h-0 min-w-0 mx-auto flex-1 flex flex-col">
<JitsiClassroom
roomName={session.jitsiRoomId}
tenantSlug={tenantSlug}
jwtToken={jwtToken}
domain={JITSI_DOMAIN}
displayName={effectiveUser.name || 'Participant'}
email={effectiveUser.email}
isHost={isHost}
/>
</div>
</div>
);
}