Initial commit
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user