226 lines
7.5 KiB
TypeScript
226 lines
7.5 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import Image from 'next/image';
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from '@/components/ui/table';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { AppSidebar } from '@/components/app-sidebar';
|
|
import { SiteHeader } from '@/components/site-header';
|
|
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
interface LikedVideoItem {
|
|
id: string;
|
|
videoId: string;
|
|
createdAt: string;
|
|
video: {
|
|
id: string;
|
|
title: string;
|
|
thumbnail?: string;
|
|
durationSec?: number;
|
|
url: string;
|
|
playlist: {
|
|
title: string;
|
|
course: {
|
|
id: string;
|
|
title: string;
|
|
};
|
|
};
|
|
};
|
|
}
|
|
|
|
export default function LikedVideosClient() {
|
|
const router = useRouter();
|
|
const [likes, setLikes] = React.useState<LikedVideoItem[]>([]);
|
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
|
|
React.useEffect(() => {
|
|
const fetchLikes = async () => {
|
|
try {
|
|
const res = await fetch('/api/likes/all');
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
setLikes(data);
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to fetch liked videos', err);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchLikes();
|
|
}, []);
|
|
|
|
const handleVideoClick = (videoId: string) => {
|
|
router.push(`/videoplayer?videoId=${videoId}`);
|
|
};
|
|
|
|
const formatTime = (seconds?: number) => {
|
|
if (!seconds) return '0:00';
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
};
|
|
|
|
const formatDate = (dateString: string) => {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('en-US', {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
year: 'numeric',
|
|
});
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<SidebarProvider
|
|
style={
|
|
{
|
|
'--sidebar-width': 'calc(var(--spacing) * 72)',
|
|
'--header-height': 'calc(var(--spacing) * 12)',
|
|
} as React.CSSProperties
|
|
}
|
|
>
|
|
<AppSidebar variant="inset" />
|
|
<SidebarInset>
|
|
<SiteHeader />
|
|
<div className="flex flex-1 flex-col gap-4 p-4 md:gap-8 md:p-8">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Liked Videos</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-center justify-center py-8">
|
|
<p className="text-muted-foreground">Loading...</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</SidebarInset>
|
|
</SidebarProvider>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<SidebarProvider
|
|
style={
|
|
{
|
|
'--sidebar-width': 'calc(var(--spacing) * 72)',
|
|
'--header-height': 'calc(var(--spacing) * 12)',
|
|
} as React.CSSProperties
|
|
}
|
|
>
|
|
<AppSidebar variant="inset" />
|
|
<SidebarInset>
|
|
<SiteHeader />
|
|
<div className="flex flex-1 flex-col gap-4 p-4 md:gap-8 md:p-8">
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Liked Videos</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{likes.length === 0 ? (
|
|
<div className="flex items-center justify-center py-8">
|
|
<p className="text-muted-foreground">No liked videos yet</p>
|
|
</div>
|
|
) : (
|
|
<div className="overflow-x-auto">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Thumbnail</TableHead>
|
|
<TableHead>Title</TableHead>
|
|
<TableHead>Playlist</TableHead>
|
|
<TableHead>Course</TableHead>
|
|
<TableHead>Duration</TableHead>
|
|
<TableHead>Liked On</TableHead>
|
|
<TableHead>Action</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{likes.map((item) => (
|
|
<TableRow key={item.id}>
|
|
<TableCell>
|
|
<div
|
|
className="cursor-pointer hover:opacity-80 transition-opacity"
|
|
onClick={() => handleVideoClick(item.video.id)}
|
|
>
|
|
{item.video.thumbnail ? (
|
|
<div className="relative w-24 h-14">
|
|
<Image
|
|
src={item.video.thumbnail}
|
|
alt={item.video.title}
|
|
fill
|
|
unoptimized
|
|
className="object-cover rounded"
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div className="w-24 h-14 bg-muted rounded flex items-center justify-center">
|
|
<span className="text-xs text-muted-foreground">
|
|
No image
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<p
|
|
className="font-medium max-w-xs truncate cursor-pointer hover:underline"
|
|
onClick={() => handleVideoClick(item.video.id)}
|
|
>
|
|
{item.video.title}
|
|
</p>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="text-sm">
|
|
{item.video.playlist.title}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="text-sm">
|
|
{item.video.playlist.course.title}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="text-sm">
|
|
{formatTime(item.video.durationSec)}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<span className="text-sm text-muted-foreground">
|
|
{formatDate(item.createdAt)}
|
|
</span>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => handleVideoClick(item.video.id)}
|
|
>
|
|
Watch
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</SidebarInset>
|
|
</SidebarProvider>
|
|
);
|
|
}
|