canina/frontend/application/components/VideosPage.tsx
2026-08-26 00:12:33 +03:30

192 lines
8.6 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import React, { useState } from "react";
import { motion } from "motion/react";
import { ChevronLeft, PlayCircle, Search, Clock, User, Eye, Share2, Check } from "lucide-react";
import SafeImage from "./SafeImage";
import BackButton from "./BackButton";
import { toast } from "sonner";
import { videoService, Video } from "../lib/services/videoService";
import { useRouter } from 'next/navigation';
import VideoModalPlayer from "./VideoModalPlayer";
export default function VideosPage() {
const router = useRouter();
const [videos, setVideos] = useState<Video[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [selectedVideo, setSelectedVideo] = useState<Video | null>(null);
const [search, setSearch] = useState("");
const [isCopied, setIsCopied] = useState(false);
React.useEffect(() => {
setIsLoading(true);
videoService.getVideos({ limit: 100 })
.then((data) => {
if (data && Array.isArray(data)) {
setVideos(data);
} else {
setVideos([]);
}
})
.catch((err) => {
console.error('Failed to load videos:', err);
setVideos([]);
})
.finally(() => {
setIsLoading(false);
});
}, []);
const handleSharePage = () => {
if (typeof window !== 'undefined') {
navigator.clipboard.writeText(window.location.href);
setIsCopied(true);
toast.success("لینک آکادمی ویدئویی کپی شد.");
setTimeout(() => setIsCopied(false), 2000);
}
};
const filteredVideos = videos.filter(v =>
(v.title || '').toLowerCase().includes(search.toLowerCase()) ||
(v.doctor || '').toLowerCase().includes(search.toLowerCase()) ||
(v.description || '').toLowerCase().includes(search.toLowerCase())
);
return (
<div className="min-h-screen bg-medical-gray-900 pt-6 pb-20 px-4 font-vazir" dir="rtl">
<div className="max-w-7xl mx-auto">
{/* Standard Page Top Bar */}
<div className="flex items-center justify-between gap-4 mb-8 bg-white/5 p-4 rounded-3xl border border-white/10 backdrop-blur-md">
<div className="flex items-center gap-3">
<BackButton className="bg-white/10 text-white border-white/15 hover:bg-white/20 hover:text-white" />
<div className="hidden sm:inline-flex items-center gap-1.5 px-3 py-1 bg-canina-blue/20 text-canina-blue rounded-full text-xs font-black">
<PlayCircle className="w-3.5 h-3.5" />
<span>آکادمی تخصصی کنینا</span>
</div>
</div>
<button
type="button"
onClick={handleSharePage}
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-xl bg-white/10 hover:bg-white/20 text-white text-xs font-bold transition-all border border-white/10 cursor-pointer"
>
{isCopied ? <Check className="w-3.5 h-3.5 text-emerald-400" /> : <Share2 className="w-3.5 h-3.5" />}
<span>اشتراکگذاری صفحه</span>
</button>
</div>
{/* Title Header */}
<div className="text-right mb-10">
<h1 className="text-3xl lg:text-5xl font-black text-white italic leading-tight mb-2">
آکادمی ویدئویی <span className="text-canina-blue">کنینا</span>
</h1>
<p className="text-white/60 text-xs sm:text-sm font-medium">
ویدئوهای آموزشی، مشاورههای تخصصی دامپزشکی و راهنمای مصرف مکملها
</p>
</div>
{/* Search Bar */}
<div className="mb-12 relative max-w-2xl">
<Search className="absolute right-6 top-1/2 -translate-y-1/2 text-white/30 w-5 h-5" />
<input
type="text"
placeholder="جستجو در ویدئوهای آموزشی..."
value={search}
onChange={e => setSearch(e.target.value)}
className="w-full bg-white/5 border border-white/10 rounded-[2rem] py-4 pr-14 pl-6 text-white outline-none focus:bg-white/10 transition-all font-bold text-sm"
/>
</div>
{/* Video Grid or Skeleton */}
{isLoading ? (
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{[1, 2, 3, 4, 5, 6].map((i) => (
<div key={i} className="animate-pulse space-y-4">
<div className="aspect-video bg-white/5 rounded-[2.5rem] border border-white/5" />
<div className="h-6 bg-white/5 rounded-xl w-3/4" />
<div className="h-4 bg-white/5 rounded-xl w-1/2" />
</div>
))}
</div>
) : filteredVideos.length === 0 ? (
<div className="bg-white/5 border border-white/10 rounded-[3rem] p-16 text-center text-white/50">
<PlayCircle className="w-16 h-16 mx-auto mb-4 opacity-30 text-canina-blue" />
<h3 className="text-xl font-bold text-white mb-2">ویدئویی یافت نشد</h3>
<p className="text-sm font-medium">هیچ ویدئویی مطابق با جستجوی شما در آکادمی وجود ندارد.</p>
</div>
) : (
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{filteredVideos.map((video, idx) => (
<motion.div
key={video.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.1 }}
className="group cursor-pointer"
onClick={() => {
setSelectedVideo(video);
videoService.incrementViews(video.id);
}}
>
<div className="relative aspect-video rounded-3xl overflow-hidden mb-4 shadow-2xl border border-white/5 bg-black/40">
<SafeImage
src={video.thumbnail}
alt={video.title}
className="w-full h-full"
imgClassName="object-cover group-hover:scale-105 transition-transform duration-500 opacity-70 group-hover:opacity-100"
/>
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-14 h-14 bg-canina-blue rounded-full flex items-center justify-center text-white shadow-xl group-hover:scale-110 transition-transform">
<PlayCircle className="w-9 h-9" />
</div>
</div>
{video.duration && (
<div className="absolute bottom-3 right-3 bg-black/70 backdrop-blur-md text-white px-2.5 py-1 rounded-lg text-[10px] font-black">
{video.duration}
</div>
)}
{/* View Count Badge */}
<div className="absolute top-3 left-3 bg-black/60 backdrop-blur-md text-white/90 px-2.5 py-1 rounded-lg text-[10px] font-bold flex items-center gap-1">
<Eye className="w-3 h-3 text-canina-blue" />
<span>{(video.viewsCount ?? 0).toLocaleString('fa-IR')}</span>
</div>
</div>
<div className="px-2">
<h3 className="text-base sm:text-lg font-black text-white group-hover:text-canina-blue transition-colors mb-2 leading-snug line-clamp-2">
{video.title}
</h3>
<div className="flex items-center justify-between text-xs font-bold text-white/40 pt-1">
<div className="flex items-center gap-1">
<User className="w-3.5 h-3.5" />
<span>{video.doctor || 'کادر علمی کنینا'}</span>
</div>
<div className="flex items-center gap-3">
{video.duration && (
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
<span>{video.duration}</span>
</div>
)}
<div className="flex items-center gap-1 text-white/60">
<Eye className="w-3 h-3" />
<span>{(video.viewsCount ?? 0).toLocaleString('fa-IR')}</span>
</div>
</div>
</div>
</div>
</motion.div>
))}
</div>
)}
</div>
{/* Custom Feature-Rich Video Player Modal */}
<VideoModalPlayer
isOpen={!!selectedVideo}
onClose={() => setSelectedVideo(null)}
video={selectedVideo}
/>
</div>
);
}