Organize videos into categories with improved navigation and search functionality
Introduces video categorization on the home page, including "Meist Angesehen", "FOLX STADL Shows", "Neue Videos", and "Alle Videos". Adds pagination buttons (ChevronLeft, ChevronRight) and updates placeholder text to Slovenian ("Videos suchen...", "Suchen..."). Refactors the header background and gradient for the main content. Replaces skeleton loading with a grid layout for fetched videos.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 890577b1-c154-40a4-a177-a0c6d55320c3
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/890577b1-c154-40a4-a177-a0c6d55320c3/aC8PHqS
This commit is contained in:
parent
901a5685b6
commit
7651f730fd
@ -1,10 +1,11 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { type Video } from "@shared/schema";
|
||||
import VideoCard from "@/components/video-card";
|
||||
import { Link } from "wouter";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Search, Menu, X } from "lucide-react";
|
||||
import { Search, Menu, X, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface VideosResponse {
|
||||
videos: Video[];
|
||||
@ -12,6 +13,11 @@ interface VideosResponse {
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
interface VideoCategory {
|
||||
title: string;
|
||||
videos: Video[];
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [allVideos, setAllVideos] = useState<Video[]>([]);
|
||||
@ -57,10 +63,47 @@ export default function Home() {
|
||||
window.location.href = `/video/${video.id}`;
|
||||
};
|
||||
|
||||
// Organize videos into categories like ZDF
|
||||
const getCategories = (): VideoCategory[] => {
|
||||
if (!allVideos.length) return [];
|
||||
|
||||
// Sort by views for popular content
|
||||
const sortedByViews = [...allVideos].sort((a, b) => (b.views || 0) - (a.views || 0));
|
||||
|
||||
// Sort by date for recently added
|
||||
const sortedByDate = [...allVideos].sort((a, b) =>
|
||||
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
);
|
||||
|
||||
// FOLX STADL videos
|
||||
const folxStadlVideos = allVideos.filter(video =>
|
||||
video.title.includes("FOLX STADL") || video.title.includes("FOLXSTADL")
|
||||
);
|
||||
|
||||
return [
|
||||
{
|
||||
title: "Meist Angesehen",
|
||||
videos: sortedByViews.slice(0, 12)
|
||||
},
|
||||
...(folxStadlVideos.length > 0 ? [{
|
||||
title: "FOLX STADL Shows",
|
||||
videos: folxStadlVideos.slice(0, 12)
|
||||
}] : []),
|
||||
{
|
||||
title: "Neue Videos",
|
||||
videos: sortedByDate.slice(0, 12)
|
||||
},
|
||||
{
|
||||
title: "Alle Videos",
|
||||
videos: allVideos.slice(0, 15)
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-purple-900 via-blue-900 to-indigo-900">
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-blue-900 to-indigo-900">
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-50 bg-black/20 backdrop-blur-md border-b border-white/10">
|
||||
<header className="sticky top-0 z-50 bg-black/30 backdrop-blur-md border-b border-white/10">
|
||||
<div className="container py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Logo */}
|
||||
@ -87,7 +130,7 @@ export default function Home() {
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search videos..."
|
||||
placeholder="Videos suchen..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="bg-white/10 border-white/20 text-white placeholder-white/50 w-64"
|
||||
@ -116,7 +159,7 @@ export default function Home() {
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Search..."
|
||||
placeholder="Suchen..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="bg-white/10 border-white/20 text-white placeholder-white/50 w-full"
|
||||
@ -131,25 +174,31 @@ export default function Home() {
|
||||
{/* Main Content */}
|
||||
<main className="container py-8">
|
||||
{isLoading ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6">
|
||||
{Array.from({ length: 15 }).map((_, index) => (
|
||||
<div key={index} className="animate-pulse">
|
||||
<div className="bg-white/10 aspect-video rounded-lg mb-3"></div>
|
||||
<div className="space-y-2">
|
||||
<div className="h-4 bg-white/10 rounded w-3/4"></div>
|
||||
<div className="h-3 bg-white/10 rounded w-1/2"></div>
|
||||
<div className="space-y-8">
|
||||
{Array.from({ length: 3 }).map((_, categoryIndex) => (
|
||||
<div key={categoryIndex} className="space-y-4">
|
||||
<div className="h-6 bg-white/10 rounded w-48 animate-pulse"></div>
|
||||
<div className="flex space-x-4 overflow-hidden">
|
||||
{Array.from({ length: 6 }).map((_, index) => (
|
||||
<div key={index} className="flex-shrink-0 w-[280px] animate-pulse">
|
||||
<div className="bg-white/10 aspect-video rounded-lg mb-3"></div>
|
||||
<div className="space-y-2">
|
||||
<div className="h-4 bg-white/10 rounded w-3/4"></div>
|
||||
<div className="h-3 bg-white/10 rounded w-1/2"></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6">
|
||||
{allVideos.map((video) => (
|
||||
<VideoCard
|
||||
key={video.id}
|
||||
video={video}
|
||||
onClick={handleVideoClick}
|
||||
className="hover:scale-105 transition-transform duration-200"
|
||||
<div className="space-y-8">
|
||||
{getCategories().map((category, categoryIndex) => (
|
||||
<CategorySection
|
||||
key={categoryIndex}
|
||||
category={category}
|
||||
onVideoClick={handleVideoClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@ -157,10 +206,78 @@ export default function Home() {
|
||||
|
||||
{allVideos.length === 0 && !isLoading && (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-white/60 text-lg">No videos found</div>
|
||||
<div className="text-white/60 text-lg">Keine Videos gefunden</div>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface CategorySectionProps {
|
||||
category: VideoCategory;
|
||||
onVideoClick: (video: Video) => void;
|
||||
}
|
||||
|
||||
function CategorySection({ category, onVideoClick }: CategorySectionProps) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scroll = (direction: 'left' | 'right') => {
|
||||
if (scrollRef.current) {
|
||||
const scrollAmount = 320; // Width of one card + gap
|
||||
const newScrollLeft = scrollRef.current.scrollLeft + (direction === 'right' ? scrollAmount : -scrollAmount);
|
||||
scrollRef.current.scrollTo({
|
||||
left: newScrollLeft,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative group">
|
||||
<h2 className="text-xl font-semibold text-white mb-4 px-4">
|
||||
{category.title}
|
||||
</h2>
|
||||
|
||||
<div className="relative">
|
||||
{/* Left scroll button */}
|
||||
<Button
|
||||
onClick={() => scroll('left')}
|
||||
className="absolute left-2 top-1/2 -translate-y-1/2 z-40 bg-black/60 hover:bg-black/80 text-white border-none w-12 h-12 rounded-full transition-all duration-300 opacity-0 group-hover:opacity-100"
|
||||
size="sm"
|
||||
>
|
||||
<ChevronLeft className="w-6 h-6" />
|
||||
</Button>
|
||||
|
||||
{/* Right scroll button */}
|
||||
<Button
|
||||
onClick={() => scroll('right')}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 z-40 bg-black/60 hover:bg-black/80 text-white border-none w-12 h-12 rounded-full transition-all duration-300 opacity-0 group-hover:opacity-100"
|
||||
size="sm"
|
||||
>
|
||||
<ChevronRight className="w-6 h-6" />
|
||||
</Button>
|
||||
|
||||
{/* Scrollable video row */}
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="flex gap-4 overflow-x-auto scrollbar-hide pb-4 px-4"
|
||||
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
|
||||
>
|
||||
{category.videos.map((video) => (
|
||||
<div
|
||||
key={video.id}
|
||||
className="flex-shrink-0 w-[280px] hover:scale-105 transition-transform duration-300"
|
||||
>
|
||||
<VideoCard
|
||||
video={video}
|
||||
onClick={onVideoClick}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user