Translate UI elements and messages from English to German across various components and pages, including video availability, loading states, and recommendations. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 051a65da-1176-4478-a61c-c662f2a15536 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/051a65da-1176-4478-a61c-c662f2a15536/9NQBiz8
113 lines
3.3 KiB
TypeScript
113 lines
3.3 KiB
TypeScript
import { useState } from "react";
|
|
import { type Video } from "@shared/schema";
|
|
import VideoCard from "./video-card";
|
|
import BunnyVideoModal from "./bunny-video-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 handleVideoClick = (video: Video) => {
|
|
// Navigate to individual video page instead of modal
|
|
window.location.href = `/video/${video.id}`;
|
|
};
|
|
|
|
const handleCloseModal = () => {
|
|
setIsModalOpen(false);
|
|
setSelectedVideo(null);
|
|
};
|
|
|
|
const handleVideoChange = (video: Video) => {
|
|
setSelectedVideo(video);
|
|
};
|
|
|
|
|
|
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}
|
|
/>
|
|
))}
|
|
</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>Wird geladen...</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<span>Weitere Videos laden</span>
|
|
<ChevronDown className="w-4 h-4" />
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
|
|
<BunnyVideoModal
|
|
video={selectedVideo}
|
|
isOpen={isModalOpen}
|
|
onClose={handleCloseModal}
|
|
videos={videos}
|
|
onVideoChange={handleVideoChange}
|
|
/>
|
|
</>
|
|
);
|
|
}
|