videofolxtv/client/src/components/video-grid.tsx
sebastjanartic a2b430c2e0 Enable users to easily share videos on social media platforms
Implements video sharing functionality with a dedicated /video/:id route, ShareModal component and Open Graph meta tags.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 50814a1e-92e4-4968-856f-7bc7eedf5e8f
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/50814a1e-92e4-4968-856f-7bc7eedf5e8f/k2GlI5l
2025-08-04 18:45:34 +00:00

126 lines
3.6 KiB
TypeScript

import { useState } from "react";
import { type Video } from "@shared/schema";
import VideoCard from "./video-card";
import VideoModal from "./video-modal";
import ShareModal from "./share-modal";
import { Button } from "@/components/ui/button";
import { ChevronDown } from "lucide-react";
interface VideoGridProps {
videos: Video[];
isLoading: boolean;
hasMore: boolean;
onLoadMore: () => void;
viewMode: "grid" | "list";
}
export default function VideoGrid({ videos, isLoading, hasMore, onLoadMore, viewMode }: VideoGridProps) {
const [selectedVideo, setSelectedVideo] = useState<Video | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [shareVideo, setShareVideo] = useState<Video | null>(null);
const [isShareModalOpen, setIsShareModalOpen] = useState(false);
const handleVideoClick = (video: Video) => {
setSelectedVideo(video);
setIsModalOpen(true);
};
const handleCloseModal = () => {
setIsModalOpen(false);
setSelectedVideo(null);
};
const handleShareVideo = (video: Video) => {
setShareVideo(video);
setIsShareModalOpen(true);
};
const handleCloseShareModal = () => {
setIsShareModalOpen(false);
setShareVideo(null);
};
if (isLoading && videos.length === 0) {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" data-testid="grid-loading">
{Array.from({ length: 8 }).map((_, index) => (
<div key={index} className="animate-pulse">
<div className="bg-bunny-gray aspect-video rounded-xl mb-4"></div>
<div className="space-y-2">
<div className="h-4 bg-bunny-gray rounded w-3/4"></div>
<div className="h-3 bg-bunny-gray rounded w-1/2"></div>
</div>
</div>
))}
</div>
);
}
if (videos.length === 0) {
return (
<div className="text-center py-12">
<div className="text-bunny-muted text-lg mb-4" data-testid="text-no-videos">
No videos found
</div>
<p className="text-sm text-bunny-muted">
Try adjusting your search or filter criteria
</p>
</div>
);
}
const gridClass = viewMode === "grid"
? "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"
: "grid grid-cols-1 gap-4";
return (
<>
<div className={gridClass} data-testid="grid-videos">
{videos.map((video) => (
<VideoCard
key={video.id}
video={video}
onClick={handleVideoClick}
onShare={handleShareVideo}
/>
))}
</div>
{hasMore && (
<div className="text-center mt-12">
<Button
onClick={onLoadMore}
disabled={isLoading}
className="bg-bunny-blue hover:bg-blue-600 text-white px-8 py-3 rounded-lg font-medium transition-colors inline-flex items-center space-x-2"
data-testid="button-load-more"
>
{isLoading ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
<span>Loading...</span>
</>
) : (
<>
<span>Load More Videos</span>
<ChevronDown className="w-4 h-4" />
</>
)}
</Button>
</div>
)}
<VideoModal
video={selectedVideo}
isOpen={isModalOpen}
onClose={handleCloseModal}
/>
<ShareModal
video={shareVideo}
isOpen={isShareModalOpen}
onClose={handleCloseShareModal}
/>
</>
);
}