'use client'; import * as React from 'react'; import { useRouter } from 'next/navigation'; import { useSession } from 'next-auth/react'; import { usePlaylists } from '@/hooks/usePlaylists'; import { Card, CardContent, CardHeader, CardTitle, } from '@/components/ui/card'; import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, } from '@/components/ui/carousel'; import { Badge } from '@/components/ui/badge'; import { SegmentedProgressBar, WatchSegment } from '@/components/segmented-progress-bar'; import { AppSidebar } from '@/components/app-sidebar'; import { SiteHeader } from '@/components/site-header'; import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; import { Lock } from 'lucide-react'; import Image from 'next/image'; type VideoItem = { id: string; title: string; durationSec?: number; thumbnail?: string; locked?: boolean; instantAccess?: boolean; uploader?: { id: string; name?: string; image?: string; }; }; type VideoProgressData = { percent: number; watchedSec: number; segments: WatchSegment[]; }; export default function DashboardClient() { const router = useRouter(); const { data: session } = useSession(); const { subjects, isLoading } = usePlaylists(); const [videoProgress, setVideoProgress] = React.useState>({}); const [userUnlocks, setUserUnlocks] = React.useState>(new Set()); const [latestVideos, setLatestVideos] = React.useState([]); const [latestVideosLoading, setLatestVideosLoading] = React.useState(true); const playlistsToShow = React.useMemo(() => { const playlistMap = new Map< string, { playlist: any; courses: Map; } >(); subjects.forEach((subject: any) => { (subject.playlists || []).forEach((playlist: any) => { const entry = playlistMap.get(playlist.id); const courseInfo = { id: subject.id, title: subject.title, code: subject.code, }; if (entry) { entry.courses.set(courseInfo.id, courseInfo); } else { const coursesMap = new Map(); coursesMap.set(courseInfo.id, courseInfo); playlistMap.set(playlist.id, { playlist, courses: coursesMap }); } }); }); return Array.from(playlistMap.values()).map(({ playlist, courses }) => ({ ...playlist, coursesForDisplay: Array.from(courses.values()), })); }, [subjects]); // Fetch user's unlocks React.useEffect(() => { const fetchUserUnlocks = async () => { try { const res = await fetch('/api/user/unlocks'); if (res.ok) { const data = await res.json(); const unlockedVideoIds = new Set((data.unlocks?.map((u: any) => u.videoId) ?? []) as string[]); setUserUnlocks(unlockedVideoIds); } } catch (err) { console.error('Failed to fetch user unlocks:', err); } }; fetchUserUnlocks(); }, []); // Fetch latest videos from enrolled courses React.useEffect(() => { const fetchLatestVideos = async () => { try { setLatestVideosLoading(true); const res = await fetch('/api/videos/latest'); if (res.ok) { const data = await res.json(); setLatestVideos(data.videos ?? []); } } catch (err) { console.error('Failed to fetch latest videos:', err); } finally { setLatestVideosLoading(false); } }; fetchLatestVideos(); }, []); // Auto-sync enrollments for allowed students on first login React.useEffect(() => { const syncEnrollments = async () => { if (!session?.user) return; const userLevels = (session.user as any)?.levels; console.log('[DASHBOARD] User levels from session:', userLevels); if (!userLevels) { console.log('[DASHBOARD] No levels found in session'); return; } const levels = userLevels .split(',') .map((level: string) => level.trim()) .filter((level: string) => level.length > 0); console.log('[DASHBOARD] Parsed levels:', levels); if (levels.length === 0) { console.log('[DASHBOARD] No valid levels after parsing'); return; } try { console.log('[DASHBOARD] Calling sync-enrollments with:', { levels }); const res = await fetch('/api/enrollments/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ levels }), }); if (!res.ok) { console.error('[DASHBOARD] Failed to sync enrollments, status:', res.status); } else { const result = await res.json(); console.log('[DASHBOARD] Sync enrollments result:', result); } } catch (err) { console.error('[DASHBOARD] Error syncing enrollments:', err); } }; syncEnrollments(); }, [session]); React.useEffect(() => { const fetchProgress = async () => { const progressMap: Record = {}; // Fetch progress for playlist videos for (const playlist of playlistsToShow) { for (const video of playlist.videos || []) { try { const [progressRes, segmentsRes] = await Promise.all([ fetch(`/api/progress?videoId=${video.id}`), fetch(`/api/progress/segments?videoId=${video.id}`), ]); if (progressRes.ok) { const progressData = await progressRes.json(); const segmentsData = segmentsRes.ok ? await segmentsRes.json() : { segments: [] }; progressMap[video.id] = { percent: progressData.percent ?? 0, watchedSec: progressData.watchedSec ?? 0, segments: segmentsData.segments ?? [], }; } } catch (err) { console.error(`Failed to fetch progress for video ${video.id}`, err); progressMap[video.id] = { percent: 0, watchedSec: 0, segments: [] }; } } } // Fetch progress for latest videos for (const video of latestVideos) { try { const [progressRes, segmentsRes] = await Promise.all([ fetch(`/api/progress?videoId=${video.id}`), fetch(`/api/progress/segments?videoId=${video.id}`), ]); if (progressRes.ok) { const progressData = await progressRes.json(); const segmentsData = segmentsRes.ok ? await segmentsRes.json() : { segments: [] }; progressMap[video.id] = { percent: progressData.percent ?? 0, watchedSec: progressData.watchedSec ?? 0, segments: segmentsData.segments ?? [], }; } } catch (err) { console.error(`Failed to fetch progress for video ${video.id}`, err); progressMap[video.id] = { percent: 0, watchedSec: 0, segments: [] }; } } setVideoProgress(progressMap); }; if (playlistsToShow.length > 0 || latestVideos.length > 0) { fetchProgress(); } }, [playlistsToShow, latestVideos]); const handleOpenVideo = (playlistId: string, videoId: string) => { // navigate to videoplayer page using query params (keeps your current structure) router.push(`/videoplayer?playlistId=${playlistId}&videoId=${videoId}`); }; const formatDuration = (s?: number) => { if (!s && s !== 0) return ''; const mins = Math.floor(s! / 60); const secs = Math.floor(s! % 60) .toString() .padStart(2, '0'); return `${mins}:${secs}`; }; return (
{isLoading && latestVideosLoading ? (
Loading…
) : (
{/* Latest Videos Section */} {!latestVideosLoading && latestVideos.length > 0 && (

Latest Videos

Recently uploaded from your enrolled courses

{latestVideos.map((v: any) => ( { if (v.playlist?.id) { handleOpenVideo(v.playlist.id, v.id); } }} >
{v.title}
{v.uploader?.image && (
{v.uploader.name
)}
{v.title} {formatDuration(v.durationSec)}
{v.uploader?.name && (

{v.uploader.name}

)} {v.playlist?.title && (

{v.playlist.title}

)}
))}
)} {/* Playlists Section */} {playlistsToShow.length === 0 && latestVideos.length === 0 ? (
No content available.
) : ( <> {playlistsToShow.length > 0 && ( <>

Playlists

Showing {playlistsToShow.length} playlist{playlistsToShow.length === 1 ? '' : 's'}

{playlistsToShow.map((pl: any) => (

{pl.title}

{pl.description}
{pl.coursesForDisplay?.length ? (
{pl.coursesForDisplay.map((course: any) => ( {course.code ?? course.title} ))}
) : null}
{pl.videos?.map((v: VideoItem) => ( { if ((v as any).locked) return; if ((v as any).instantAccess) { handleOpenVideo(pl.id, v.id); return; } if ((v as any).index === 0) { handleOpenVideo(pl.id, v.id); return; } if (userUnlocks.has(v.id)) { handleOpenVideo(pl.id, v.id); } }} >
{v.title} {(v as any).locked || (!(v as any).instantAccess && (v as any).index !== 0 && !userUnlocks.has(v.id)) ? (
) : null}
{(v as any).uploader?.image && (
{(v
)}
{v.title} {formatDuration(v.durationSec)}
{(v as any).uploader?.name && (

{(v as any).uploader.name}

)}
))}
))} )} )}
)}
); }