Update home page to use a new grid layout and improve search functionality
Refactors the home page to replace VideoCard with NetflixGrid, removes unused video categorization logic, and enhances search functionality by implementing conditional refetching based on search query changes. 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
7651f730fd
commit
040ea52a25
@ -1,11 +1,10 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { type Video } from "@shared/schema";
|
||||
import VideoCard from "@/components/video-card";
|
||||
import NetflixGrid from "@/components/netflix-grid";
|
||||
import { Link } from "wouter";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Search, Menu, X, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Search, Menu, X } from "lucide-react";
|
||||
|
||||
interface VideosResponse {
|
||||
videos: Video[];
|
||||
@ -13,18 +12,13 @@ interface VideosResponse {
|
||||
hasMore: boolean;
|
||||
}
|
||||
|
||||
interface VideoCategory {
|
||||
title: string;
|
||||
videos: Video[];
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [allVideos, setAllVideos] = useState<Video[]>([]);
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||||
|
||||
// Fetch videos with optimized loading
|
||||
const { data: videosResponse, isLoading } = useQuery<VideosResponse>({
|
||||
const { data: videosResponse, isLoading, refetch } = useQuery<VideosResponse>({
|
||||
queryKey: ["/api/videos", {
|
||||
limit: 150,
|
||||
offset: 0,
|
||||
@ -59,70 +53,38 @@ export default function Home() {
|
||||
}
|
||||
}, [videosResponse]);
|
||||
|
||||
const handleVideoClick = (video: Video) => {
|
||||
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)
|
||||
}
|
||||
];
|
||||
};
|
||||
// Only refetch when search changes
|
||||
useEffect(() => {
|
||||
if (searchQuery !== undefined) {
|
||||
refetch();
|
||||
}
|
||||
}, [searchQuery, refetch]);
|
||||
|
||||
return (
|
||||
<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/30 backdrop-blur-md border-b border-white/10">
|
||||
<div className="has-fixed-header" style={{minHeight: '100vh', background: 'linear-gradient(135deg, hsl(250, 50%, 15%) 0%, hsl(240, 30%, 25%) 100%)', color: 'white'}}>
|
||||
{/* STICKY HEADER */}
|
||||
<div className="header-sticky bg-transparent overflow-hidden">
|
||||
<div className="container py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Logo */}
|
||||
<Link href="/" className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
|
||||
<div className="w-0 h-0 border-l-[12px] border-l-white border-y-[8px] border-y-transparent ml-1"></div>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-white">go4.video</h1>
|
||||
</Link>
|
||||
{/* Left side - Logo */}
|
||||
<div className="flex items-center space-x-4">
|
||||
<Link href="/" className="flex items-center space-x-2 hover:opacity-80 transition-opacity">
|
||||
<div className="w-9 h-9 gradient-primary rounded-lg flex items-center justify-center shadow-lg">
|
||||
<div className="w-0 h-0 border-l-[10px] border-l-white border-y-[7px] border-y-transparent ml-1"></div>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-white tracking-wide">go4.video</h1>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Navigation & Search */}
|
||||
<div className="flex items-center gap-6">
|
||||
{/* Desktop Navigation */}
|
||||
{/* Right side - Navigation + Search */}
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Desktop navigation */}
|
||||
<div className="hidden md:flex items-center space-x-6">
|
||||
<nav className="flex space-x-6">
|
||||
<Link href="/" className="text-white/80 hover:text-white transition-colors">
|
||||
<Link href="/" className="text-bunny-light hover:text-bunny-blue transition-colors">
|
||||
Home
|
||||
</Link>
|
||||
<Link href="/folx-stadl" className="text-white/80 hover:text-white transition-colors">
|
||||
<Link href="/folx-stadl" className="text-bunny-light hover:text-bunny-blue transition-colors">
|
||||
FOLX STADL
|
||||
</Link>
|
||||
</nav>
|
||||
@ -133,151 +95,72 @@ export default function Home() {
|
||||
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"
|
||||
className="bg-white border border-gray-300 rounded-lg px-4 py-2 pl-10 text-sm text-gray-900 placeholder-gray-500 focus:outline-none focus:border-bunny-blue transition-colors w-64"
|
||||
/>
|
||||
<Search className="absolute right-3 top-1/2 transform -translate-y-1/2 text-white/50 w-4 h-4" />
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile menu button */}
|
||||
<button
|
||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||
className="md:hidden p-2 text-white"
|
||||
className="md:hidden p-2 rounded-lg bg-white/10 hover:bg-white/20 transition-colors"
|
||||
data-testid="button-mobile-menu"
|
||||
>
|
||||
{isMobileMenuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
||||
{isMobileMenuOpen ? (
|
||||
<X className="w-6 h-6 text-white" />
|
||||
) : (
|
||||
<Menu className="w-6 h-6 text-white" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{isMobileMenuOpen && (
|
||||
<div className="md:hidden mt-4 pt-4 border-t border-white/20">
|
||||
<nav className="flex flex-col space-y-3 mb-4">
|
||||
<Link href="/" className="text-white/80 hover:text-white">Home</Link>
|
||||
<Link href="/folx-stadl" className="text-white/80 hover:text-white">FOLX STADL</Link>
|
||||
{/* Mobile menu dropdown */}
|
||||
{isMobileMenuOpen && (
|
||||
<div className="md:hidden border-t border-white/20 bg-bunny-dark/95 backdrop-blur-md">
|
||||
<div className="px-4 py-3">
|
||||
<nav className="flex space-x-6 mb-3">
|
||||
<Link
|
||||
href="/"
|
||||
className="text-bunny-light hover:text-bunny-blue transition-colors text-sm font-medium"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
>
|
||||
Home
|
||||
</Link>
|
||||
<Link
|
||||
href="/folx-stadl"
|
||||
className="text-bunny-light hover:text-bunny-blue transition-colors text-sm font-medium"
|
||||
onClick={() => setIsMobileMenuOpen(false)}
|
||||
>
|
||||
FOLX STADL
|
||||
</Link>
|
||||
</nav>
|
||||
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="search"
|
||||
placeholder="Suchen..."
|
||||
placeholder="Search..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="bg-white/10 border-white/20 text-white placeholder-white/50 w-full"
|
||||
className="bg-white border border-gray-300 rounded-lg px-3 py-2 pl-9 text-sm text-gray-900 placeholder-gray-500 focus:outline-none focus:border-bunny-blue transition-colors w-full"
|
||||
/>
|
||||
<Search className="absolute right-3 top-1/2 transform -translate-y-1/2 text-white/50 w-4 h-4" />
|
||||
<Search className="absolute left-2.5 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<main className="w-full pt-0 pb-8 relative">
|
||||
<div className="container">
|
||||
<NetflixGrid
|
||||
videos={allVideos}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="container py-8">
|
||||
{isLoading ? (
|
||||
<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="space-y-8">
|
||||
{getCategories().map((category, categoryIndex) => (
|
||||
<CategorySection
|
||||
key={categoryIndex}
|
||||
category={category}
|
||||
onVideoClick={handleVideoClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{allVideos.length === 0 && !isLoading && (
|
||||
<div className="text-center py-12">
|
||||
<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