192 lines
8.6 KiB
TypeScript
192 lines
8.6 KiB
TypeScript
"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>
|
||
);
|
||
}
|