kv-tube/frontend/app/watch/RelatedVideos.tsx
KV-Tube Deployer 86913861f2
Some checks are pending
CI / lint (push) Waiting to run
CI / test (push) Waiting to run
CI / build (push) Blocked by required conditions
Fix missing thumbnails: Add error handling to image/video elements with fallback to YouTube defaults
2026-03-24 23:23:11 +07:00

77 lines
3.1 KiB
TypeScript

import Link from 'next/link';
import { API_BASE } from '../constants';
import NextVideoClient from './NextVideoClient';
interface VideoData {
id: string;
title: string;
uploader: string;
channel_id?: string;
thumbnail: string;
view_count: number;
duration: string;
}
async function getRelatedVideos(videoId: string, title: string, uploader: string) {
try {
const params = new URLSearchParams({ v: videoId, title: title || '', uploader: uploader || '', limit: '15' });
const res = await fetch(`${API_BASE}/api/related?${params.toString()}`, { cache: 'no-store' });
if (!res.ok) return [];
return res.json() as Promise<VideoData[]>;
} catch (e) {
console.error(e);
return [];
}
}
function formatViews(views: number): string {
if (views >= 1000000) return (views / 1000000).toFixed(1) + 'M';
if (views >= 1000) return (views / 1000).toFixed(1) + 'K';
return views.toString();
}
export default async function RelatedVideos({ videoId, title, uploader }: { videoId: string, title: string, uploader: string }) {
const relatedVideos = await getRelatedVideos(videoId, title, uploader);
if (relatedVideos.length === 0) {
return <div style={{ padding: '1rem', color: '#888' }}>No related videos found.</div>;
}
const nextVideoId = relatedVideos[0].id;
return (
<div className="watch-related-list">
<NextVideoClient videoId={nextVideoId} />
{relatedVideos.map((video, i) => {
const views = formatViews(video.view_count);
const staggerClass = `stagger-${Math.min(i + 1, 6)}`;
return (
<Link key={video.id} href={`/watch?v=${video.id}`} className={`related-video-item fade-in-up ${staggerClass}`} style={{ opacity: 0 }}>
<div className="related-thumb-container">
<img
src={video.thumbnail}
alt={video.title}
className="related-thumb-img"
onError={(e) => {
e.target.onError = null; // Prevent infinite loop
e.target.src = 'https://i.ytimg.com/vi/default/hqdefault.jpg'; // Fallback to YouTube's default thumbnail
}}
/>
{video.duration && (
<div className="duration-badge">
{video.duration}
</div>
)}
</div>
<div className="related-video-info">
<span className="related-video-title">{video.title}</span>
<span className="related-video-channel">{video.uploader}</span>
<span className="related-video-meta">{views} views</span>
</div>
</Link>
);
})}
</div>
);
}