videofolxtv/client/src/pages/VideoPage.tsx
sebastjanartic dd8ec301e2 Integrate ads to enhance platform monetization and user engagement
Introduce AdSenseAd component and integrate it across multiple pages (VideoPage, Home) to display various ad formats, including header, sidebar, and mid-content placements.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 946a0075-7e32-454b-b348-9e7f576d7f45
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/60d372ff-2c10-46c7-b01b-10c3435136b0/946a0075-7e32-454b-b348-9e7f576d7f45/tiAdL5T
2025-09-04 16:24:43 +00:00

858 lines
35 KiB
TypeScript

import { useState, useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
import { useRoute, useLocation } from "wouter";
import { type Video } from "@shared/schema";
import go4LogoPath from "@assets/go4_1756394900352.png";
// Helper functions
const formatViews = (views: number): string => {
if (views >= 1000000) return `${(views / 1000000).toFixed(1)}M`;
if (views >= 1000) return `${(views / 1000).toFixed(1)}K`;
return views.toString();
};
const formatDuration = (seconds: number): string => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${minutes}:${secs.toString().padStart(2, '0')}`;
};
const formatDate = (date: Date | string): string => {
const d = typeof date === 'string' ? new Date(date) : date;
return d.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
import { Button } from "@/components/ui/button";
import { Share2, X, Edit3, Menu, Search, ChevronLeft, ChevronRight } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Link } from "wouter";
import { apiRequest } from "@/lib/queryClient";
import {
FacebookShareButton,
TwitterShareButton,
WhatsappShareButton,
FacebookIcon,
TwitterIcon,
WhatsappIcon,
} from "react-share";
import AdSenseAd from "@/components/adsense-ad";
interface VideosResponse {
videos: Video[];
total: number;
hasMore: boolean;
}
export default function VideoPage() {
const [, params] = useRoute("/video/:id");
const [, setLocation] = useLocation();
const videoId = params?.id;
const [showShareMenu, setShowShareMenu] = useState(false);
const [touchStart, setTouchStart] = useState(0);
const [touchEnd, setTouchEnd] = useState(0);
const [activeDot, setActiveDot] = useState<'left' | 'center' | 'right'>('center');
const [searchQuery, setSearchQuery] = useState("");
const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
// Fetch current video
const { data: currentVideo, isLoading: videoLoading, error: videoError } = useQuery<Video>({
queryKey: [`/api/videos/${videoId}`],
queryFn: async () => {
const response = await fetch(`/api/videos/${videoId}`);
if (!response.ok) {
throw new Error(`Failed to fetch video: ${response.status}`);
}
return response.json();
},
enabled: !!videoId,
retry: 3,
retryDelay: 1000,
// Real-time refresh for view counts - refresh every 2 minutes for better performance
refetchInterval: 120000,
refetchOnWindowFocus: true,
refetchOnReconnect: true,
});
// Fetch recommended videos (excluding current video)
const { data: recommendedResponse } = useQuery<VideosResponse>({
queryKey: ["/api/videos", "limit=150"],
queryFn: () => fetch("/api/videos?limit=150&offset=0").then(res => res.json()),
enabled: !!videoId,
});
const recommendedVideos = recommendedResponse?.videos?.filter(v => v.id !== currentVideo?.id) || [];
// Filter videos based on current video category for navigation
const getFilteredVideosForNavigation = () => {
const allVideos = recommendedResponse?.videos || [];
if (!currentVideo) return allVideos;
// Check if current video belongs to a specific category
const videoTitle = currentVideo.title || '';
if (videoTitle.includes("FOLX STADL") || videoTitle.includes("FOLXSTADL")) {
return allVideos.filter(video =>
video.title?.includes("FOLX STADL") || video.title?.includes("FOLXSTADL")
);
}
if (videoTitle.includes("Gipfelstammtisch") ||
videoTitle.includes("GIPFELSTAMMTISCH") ||
videoTitle.includes("Gipfel Stammtisch")) {
return allVideos.filter(video =>
video.title?.includes("Gipfelstammtisch") ||
video.title?.includes("GIPFELSTAMMTISCH") ||
video.title?.includes("Gipfel Stammtisch")
);
}
if (videoTitle.includes("Geschichte des Liedes")) {
const filtered = allVideos.filter(video =>
video.title?.includes("Geschichte des Liedes")
);
return filtered;
}
// If not in any specific category, return all videos
return allVideos;
};
const allVideos = getFilteredVideosForNavigation();
// Navigation functions
const getCurrentVideoIndex = () => {
if (!currentVideo || !allVideos.length) return -1;
return allVideos.findIndex((v) => v.id === currentVideo.id);
};
const navigateToVideo = (direction: 'next' | 'prev') => {
const currentIndex = getCurrentVideoIndex();
if (currentIndex === -1) return;
// Show direction feedback on dots
setActiveDot(direction === 'next' ? 'right' : 'left');
// Return to center after 300ms
setTimeout(() => {
setActiveDot('center');
}, 300);
let newIndex;
if (direction === 'next') {
newIndex = currentIndex + 1 >= allVideos.length ? 0 : currentIndex + 1;
} else {
newIndex = currentIndex - 1 < 0 ? allVideos.length - 1 : currentIndex - 1;
}
const newVideo = allVideos[newIndex];
if (newVideo) {
// Generate short ID for navigation
const shortId = newVideo.id.replace(/-/g, '').substring(0, 8);
setLocation(`/video/${shortId}`);
}
};
// Update page meta tags for social sharing
useEffect(() => {
if (currentVideo) {
// Update page title
document.title = `${currentVideo.title} | video.folx.tv`;
// Update meta description with original or rich content
const metaDescription = document.querySelector('meta[name="description"]');
if (metaDescription) {
const descriptionContent = currentVideo.description && currentVideo.description.trim() !== ''
? currentVideo.description
: `${currentVideo.title} - Professionelle Video-Streaming-Plattform mit exklusivem Content von FOLX STADL, Geschichte des Liedes und weiteren Premium-Inhalten auf video.folx.tv.`;
metaDescription.setAttribute('content', descriptionContent);
}
// Update Open Graph tags
const updateMetaTag = (property: string, content: string) => {
let meta = document.querySelector(`meta[property="${property}"]`);
if (!meta) {
meta = document.createElement('meta');
meta.setAttribute('property', property);
document.head.appendChild(meta);
}
meta.setAttribute('content', content);
};
updateMetaTag('og:title', currentVideo.title);
// Use original description if available, otherwise create rich description
const richDescription = currentVideo.description && currentVideo.description.trim() !== ''
? currentVideo.description
: `${currentVideo.title} - Professionelle Video-Streaming-Plattform mit exklusivem Content von FOLX STADL, Geschichte des Liedes und weiteren Premium-Inhalten auf video.folx.tv.`;
updateMetaTag('og:description', richDescription);
// For social media, use direct Bunny.net thumbnail if available, otherwise fallback to our endpoint
const timestamp = new Date().getTime();
const currentDomain = window.location.origin;
// Use original Bunny thumbnail with cache-busting for social media
const socialThumbnail = currentVideo.thumbnailUrl
? `${currentVideo.thumbnailUrl}?t=${timestamp}`
: `${currentDomain}/api/video-thumbnail/${currentVideo.id}?v=${timestamp}`;
updateMetaTag('og:image', socialThumbnail);
updateMetaTag('og:image:width', '1200');
updateMetaTag('og:image:height', '630');
updateMetaTag('og:image:type', 'image/png');
updateMetaTag('og:image:alt', `${currentVideo.title} - video.folx.tv`);
updateMetaTag('og:url', window.location.href);
updateMetaTag('og:type', 'video.other');
updateMetaTag('og:video:duration', currentVideo.duration.toString());
updateMetaTag('og:site_name', 'video.folx.tv');
updateMetaTag('og:locale', 'de_DE');
// Update Twitter Card tags
const updateTwitterTag = (name: string, content: string) => {
let meta = document.querySelector(`meta[name="${name}"]`);
if (!meta) {
meta = document.createElement('meta');
meta.setAttribute('name', name);
document.head.appendChild(meta);
}
meta.setAttribute('content', content);
};
updateTwitterTag('twitter:card', 'summary_large_image');
updateTwitterTag('twitter:title', currentVideo.title);
updateTwitterTag('twitter:description', richDescription);
updateTwitterTag('twitter:image', socialThumbnail);
updateTwitterTag('twitter:image:alt', `${currentVideo.title} - video.folx.tv`);
updateTwitterTag('twitter:site', '@go4video');
}
}, [currentVideo]);
// Track video view
// Handle touch swipe for navigation
const handleTouchStart = (e: React.TouchEvent) => {
setTouchEnd(0);
setTouchStart(e.targetTouches[0].clientX);
};
const handleTouchMove = (e: React.TouchEvent) => {
setTouchEnd(e.targetTouches[0].clientX);
};
const handleTouchEnd = () => {
if (!touchStart || !touchEnd) return;
const distance = touchStart - touchEnd;
const isLeftSwipe = distance > 20;
const isRightSwipe = distance < -20;
if (isLeftSwipe) {
navigateToVideo('next');
}
if (isRightSwipe) {
navigateToVideo('prev');
}
};
const handleVideoPlay = async () => {
if (currentVideo) {
try {
// Use short ID for view tracking
const shortId = currentVideo.id.replace(/-/g, '').substring(0, 8);
await apiRequest("POST", `/api/videos/${shortId}/view`);
} catch (error) {
console.error("Failed to track video view:", error);
}
}
};
const getShareUrl = () => {
if (!currentVideo?.id) return window.location.origin;
// Use custom domain if set, otherwise current domain
const baseUrl = import.meta.env.VITE_SHARE_DOMAIN || window.location.origin;
return `${baseUrl}/video/${currentVideo.id}`;
};
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(getShareUrl());
const notification = document.createElement('div');
notification.textContent = 'Link kopiert!';
notification.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg z-50 transition-opacity duration-300';
document.body.appendChild(notification);
setTimeout(() => {
notification.style.opacity = '0';
setTimeout(() => document.body.removeChild(notification), 300);
}, 2000);
setShowShareMenu(false);
} catch (error) {
console.error('Failed to copy link:', error);
}
};
if (videoLoading) {
return (
<div className="min-h-screen bg-bunny-dark flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 bg-[#da234d] rounded-lg flex items-center justify-center shadow-lg animate-pulse mb-4 mx-auto">
<div className="w-0 h-0 border-l-[12px] border-l-white border-y-[9px] border-y-transparent ml-1"></div>
</div>
<h3 className="text-white text-xl font-bold mb-2">video.folx.tv</h3>
<p className="text-bunny-light">Video wird geladen...</p>
</div>
</div>
);
}
if (!currentVideo) {
return (
<div className="min-h-screen bg-bunny-dark flex items-center justify-center">
<div className="text-center">
<div className="w-12 h-12 bg-[#da234d] rounded-lg flex items-center justify-center shadow-lg mb-4 mx-auto opacity-50">
<div className="w-0 h-0 border-l-[9px] border-l-white border-y-[7px] border-y-transparent ml-1"></div>
</div>
<h3 className="text-white text-lg font-bold mb-2">video.folx.tv</h3>
<p className="text-bunny-light">Video nicht gefunden</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bunny-dark static-triangles has-fixed-header">
{/* Header */}
<div className="header-sticky bg-transparent overflow-hidden">
<div className="container py-6">
<div className="flex items-center justify-between">
{/* Left side - Logo */}
<div className="flex items-center space-x-6 flex-1">
<Link href="/" className="flex items-center space-x-3 hover:opacity-80 transition-opacity py-4">
<div className="w-10 h-10 bg-[#da234d] rounded-lg flex items-center justify-center shadow-lg">
<div className="w-0 h-0 border-l-[11px] border-l-white border-y-[8px] border-y-transparent ml-1"></div>
</div>
<h1 className="text-2xl font-bold text-white tracking-wide">video.folx.tv</h1>
</Link>
</div>
{/* 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="relative text-bunny-light hover:text-white transition-colors group">
Startseite
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-[#da234d] transition-all duration-300 group-hover:w-full"></span>
</Link>
<Link href="/folx-stadl" className="relative text-bunny-light hover:text-white transition-colors group">
FOLX STADL
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-[#da234d] transition-all duration-300 group-hover:w-full"></span>
</Link>
<Link href="/geschichte-lied" className="relative text-bunny-light hover:text-white transition-colors group">
DIE GESCHICHTE DES LIEDES
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-[#da234d] transition-all duration-300 group-hover:w-full"></span>
</Link>
<Link href="/gipfelstammtisch" className="relative text-bunny-light hover:text-white transition-colors group">
GIPFELSTAMMTISCH
<span className="absolute bottom-0 left-0 w-0 h-0.5 bg-[#da234d] transition-all duration-300 group-hover:w-full"></span>
</Link>
</nav>
<div className="relative">
<Input
type="search"
placeholder="Suchen..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="bg-white border border-gray-300 rounded-lg px-4 py-1.5 pl-10 text-sm text-gray-900 placeholder-gray-500 focus:outline-none focus:border-[#da234d] transition-colors w-56"
/>
<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 rounded-lg bg-white/10 hover:bg-white/20 transition-colors"
data-testid="button-mobile-menu-video"
>
{isMobileMenuOpen ? (
<X className="w-6 h-6 text-white" />
) : (
<Menu className="w-6 h-6 text-white" />
)}
</button>
</div>
</div>
</div>
{/* Mobile menu dropdown - kompakten */}
{isMobileMenuOpen && (
<div className="md:hidden border-t border-white/20 bg-bunny-dark/95 backdrop-blur-md">
<div className="px-6 py-4">
{/* Navigation Section */}
<div className="mb-6">
<h3 className="text-white text-xs font-semibold uppercase tracking-wider mb-3 opacity-70">Navigation</h3>
<nav className="flex flex-col space-y-4">
<Link
href="/"
className="text-bunny-light hover:text-white transition-colors font-medium py-1 border-l-2 border-transparent hover:border-[#da234d] pl-3"
onClick={() => setIsMobileMenuOpen(false)}
>
Startseite
</Link>
<Link
href="/folx-stadl"
className="text-bunny-light hover:text-white transition-colors font-medium py-1 border-l-2 border-transparent hover:border-[#da234d] pl-3"
onClick={() => setIsMobileMenuOpen(false)}
>
FOLX STADL
</Link>
<Link
href="/geschichte-lied"
className="text-bunny-light hover:text-white transition-colors font-medium py-1 border-l-2 border-transparent hover:border-[#da234d] pl-3"
onClick={() => setIsMobileMenuOpen(false)}
>
DIE GESCHICHTE DES LIEDES
</Link>
<Link
href="/gipfelstammtisch"
className="text-bunny-light hover:text-white transition-colors font-medium py-1 border-l-2 border-transparent hover:border-[#da234d] pl-3"
onClick={() => setIsMobileMenuOpen(false)}
>
GIPFELSTAMMTISCH
</Link>
</nav>
</div>
{/* Separator */}
<div className="border-t border-white/10 mb-4"></div>
{/* Search Section */}
<div>
<h3 className="text-white text-xs font-semibold uppercase tracking-wider mb-3 opacity-70">Suchen</h3>
<div className="relative">
<Input
type="search"
placeholder="Suchen..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="bg-white/10 border border-white/20 rounded-lg px-4 py-2.5 pl-10 text-sm text-white placeholder-white/60 focus:outline-none focus:border-[#da234d] focus:bg-white/15 transition-all w-full"
/>
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/60 w-4 h-4" />
</div>
</div>
</div>
</div>
)}
</div>
<div className="container p-4 lg:p-6 relative">
{/* Background logo decorations */}
<div
style={{
position: 'absolute',
top: '20%',
right: '8%',
transform: 'rotate(-15deg)',
width: 'clamp(200px, 20vw, 400px)',
height: 'clamp(100px, 10vw, 200px)',
backgroundImage: `url(${go4LogoPath})`,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
pointerEvents: 'none',
zIndex: 0,
opacity: 0.15,
filter: 'blur(1px)'
}}
/>
<div
style={{
position: 'absolute',
top: '60%',
left: '5%',
transform: 'rotate(20deg)',
width: 'clamp(150px, 15vw, 300px)',
height: 'clamp(75px, 7.5vw, 150px)',
backgroundImage: `url(${go4LogoPath})`,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
pointerEvents: 'none',
zIndex: 0,
opacity: 0.12,
filter: 'blur(0.5px)'
}}
/>
<div
style={{
position: 'absolute',
top: '80%',
right: '30%',
transform: 'rotate(-25deg)',
width: 'clamp(100px, 10vw, 200px)',
height: 'clamp(50px, 5vw, 100px)',
backgroundImage: `url(${go4LogoPath})`,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
pointerEvents: 'none',
zIndex: 0,
opacity: 0.08,
filter: 'blur(0.5px)'
}}
/>
<div
style={{
position: 'absolute',
top: '5%',
left: '75%',
transform: 'rotate(40deg)',
width: 'clamp(150px, 15vw, 300px)',
height: 'clamp(75px, 7.5vw, 150px)',
backgroundImage: `url(${go4LogoPath})`,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
pointerEvents: 'none',
zIndex: 0,
opacity: 0.12,
filter: 'blur(0.5px)'
}}
/>
<div className="flex flex-col lg:flex-row gap-6 relative z-[40]">
{/* Main video section */}
<div className="flex-1">
{/* Video player */}
<div
className="group relative w-full h-0 pb-[56.25%] bg-black rounded-lg overflow-hidden mb-4"
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
style={{ touchAction: 'pan-y pinch-zoom' }}
>
{/* Navigation arrows - hidden on mobile, visible on desktop */}
{allVideos.length > 1 && (
<>
<Button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
navigateToVideo('prev');
}}
className="absolute left-2 top-1/2 transform -translate-y-1/2 bg-rose-600 hover:bg-rose-700 text-white border-none p-2 rounded-full z-20 shadow-lg hidden md:group-hover:flex items-center justify-center w-10 h-10 transition-all duration-200 opacity-0 group-hover:opacity-75"
size="sm"
data-testid="button-prev-video"
>
<ChevronLeft className="w-5 h-5" />
</Button>
<Button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
navigateToVideo('next');
}}
className="absolute right-2 top-1/2 transform -translate-y-1/2 bg-rose-600 hover:bg-rose-700 text-white border-none p-2 rounded-full z-20 shadow-lg hidden md:group-hover:flex items-center justify-center w-10 h-10 transition-all duration-200 opacity-0 group-hover:opacity-75"
size="sm"
data-testid="button-next-video"
>
<ChevronRight className="w-5 h-5" />
</Button>
</>
)}
{/* Touch overlay for mobile swipe detection - positioned on edges only, avoiding bottom controls */}
<div
className="absolute left-0 top-0 w-16 h-3/4 z-10 md:hidden"
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
style={{
pointerEvents: 'auto',
touchAction: 'none'
}}
/>
<div
className="absolute right-0 top-0 w-16 h-3/4 z-10 md:hidden"
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
style={{
pointerEvents: 'auto',
touchAction: 'none'
}}
/>
{currentVideo.videoUrlIframe ? (
<iframe
src={currentVideo.videoUrlIframe}
className="absolute inset-0 w-full h-full"
style={{ pointerEvents: 'auto' }}
frameBorder="0"
allowFullScreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
sandbox="allow-scripts allow-same-origin allow-presentation"
onLoad={handleVideoPlay}
title={currentVideo.title}
/>
) : (
<div className="absolute inset-0 flex items-center justify-center text-white">
<p>Video nicht verfügbar</p>
</div>
)}
</div>
{/* Mobile navigation dots - only visible on mobile */}
{allVideos.length > 1 && (
<div className="md:hidden flex justify-center mt-4 mb-2 gap-1 py-1">
{/* Left dot */}
<button
onClick={() => navigateToVideo('prev')}
className={`rounded-full transition-colors duration-300 ease-in-out ${
activeDot === 'left'
? 'bg-gradient-to-r from-[#da234d] to-[#da234d]'
: 'bg-white/25 hover:bg-white/40'
}`}
style={{
width: '8px',
height: '8px',
minWidth: '8px',
minHeight: '8px'
}}
aria-label="Previous video"
/>
{/* Center dot */}
<button
className={`rounded-full transition-colors duration-300 ease-in-out ${
activeDot === 'center'
? 'bg-gradient-to-r from-[#da234d] to-[#da234d]'
: 'bg-white/25'
}`}
style={{
width: '8px',
height: '8px',
minWidth: '8px',
minHeight: '8px'
}}
aria-label="Current video"
/>
{/* Right dot */}
<button
onClick={() => navigateToVideo('next')}
className={`rounded-full transition-colors duration-300 ease-in-out ${
activeDot === 'right'
? 'bg-gradient-to-r from-[#da234d] to-[#da234d]'
: 'bg-white/25 hover:bg-white/40'
}`}
style={{
width: '8px',
height: '8px',
minWidth: '8px',
minHeight: '8px'
}}
aria-label="Next video"
/>
</div>
)}
{/* Video info */}
<div className="bg-bunny-gray/50 rounded-lg p-4">
<div className="flex justify-between items-start mb-4">
<h1 className="text-xl font-bold text-bunny-light flex-1 pr-4">
{currentVideo.title}
</h1>
<div className="relative">
<Button
variant="ghost"
size="sm"
onClick={() => setShowShareMenu(!showShareMenu)}
className="text-white hover:bg-gray-700"
>
<Share2 className="w-4 h-4 mr-2" />
Share
</Button>
{showShareMenu && (
<div className="absolute right-0 top-full mt-2 bg-gray-800 rounded-lg shadow-lg py-2 z-50 min-w-[200px]">
<FacebookShareButton url={getShareUrl()}>
<div className="w-full px-4 py-2 text-left text-white hover:bg-gray-700 flex items-center gap-2 cursor-pointer">
<FacebookIcon size={16} round />
Facebook
</div>
</FacebookShareButton>
<TwitterShareButton url={getShareUrl()} title={`Watch "${currentVideo.title}" on video.folx.tv`}>
<div className="w-full px-4 py-2 text-left text-white hover:bg-gray-700 flex items-center gap-2 cursor-pointer">
<TwitterIcon size={16} round />
Twitter
</div>
</TwitterShareButton>
<WhatsappShareButton url={getShareUrl()} title={`Watch "${currentVideo.title}" on video.folx.tv`}>
<div className="w-full px-4 py-2 text-left text-white hover:bg-gray-700 flex items-center gap-2 cursor-pointer">
<WhatsappIcon size={16} round />
WhatsApp
</div>
</WhatsappShareButton>
<button
onClick={copyToClipboard}
className="w-full px-4 py-2 text-left text-white hover:bg-gray-700"
>
Copy Link
</button>
</div>
)}
</div>
</div>
<div className="flex flex-wrap gap-4 text-sm text-bunny-muted mb-4">
<span>{formatViews(currentVideo.views)} views</span>
<span>{formatDuration(currentVideo.duration)}</span>
<span>{formatDate(currentVideo.createdAt)}</span>
</div>
{currentVideo.description ? (
<div className="text-bunny-light">
<p className="text-sm leading-relaxed">{currentVideo.description}</p>
</div>
) : (
<div className="text-bunny-muted text-sm">
<p>Video description not available.</p>
</div>
)}
</div>
</div>
{/* Recommended videos sidebar */}
<div className="w-full lg:w-96">
{/* Sidebar Ad */}
<div className="mb-6">
<AdSenseAd
adSlot="1234567893"
adFormat="rectangle"
width={300}
height={250}
className="w-full"
/>
</div>
<h2 className="text-lg font-semibold text-bunny-light mb-4">Empfohlene Videos</h2>
<div className="space-y-2">
{recommendedVideos.slice(0, 10).map((video) => (
<div
key={video.id}
onClick={() => {
const shortId = video.id.replace(/-/g, '').substring(0, 8);
setLocation(`/video/${shortId}`);
}}
className="flex gap-3 p-2 bg-bunny-gray/30 hover:bg-bunny-gray/50 rounded-lg cursor-pointer transition-colors"
>
<div className="relative w-24 h-16 bg-gray-700 rounded overflow-hidden flex-shrink-0">
<img
src={video.thumbnailUrl}
alt={video.title}
className="w-full h-full object-cover"
onError={(e) => {
const target = e.target as HTMLImageElement;
console.log('Sidebar thumbnail failed:', target.src);
target.style.display = 'none';
if (target.parentElement && !target.parentElement.querySelector('.thumbnail-placeholder')) {
target.parentElement.style.background = 'linear-gradient(135deg, #1f2937, #374151)';
const placeholder = document.createElement('div');
placeholder.className = 'thumbnail-placeholder absolute inset-0 flex items-center justify-center text-white text-xs';
placeholder.innerHTML = '<div>🎬</div>';
target.parentElement.appendChild(placeholder);
}
}}
/>
<div className="absolute bottom-1 right-1 bg-black/70 text-white text-xs px-1 py-0.5 rounded">
{formatDuration(video.duration)}
</div>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-sm font-medium text-bunny-light mb-1 line-clamp-2"
style={{
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden'
}}>
{video.title}
</h3>
<div className="text-xs text-bunny-muted">
<div>{formatViews(video.views)} views {formatDate(video.createdAt)}</div>
</div>
</div>
</div>
))}
</div>
{/* Bottom Sidebar Ad */}
<div className="mt-6">
<AdSenseAd
adSlot="1234567894"
adFormat="rectangle"
width={300}
height={250}
className="w-full"
/>
</div>
</div>
</div>
</div>
{/* Footer - same as home page */}
<footer className="bunny-gray/50 border-t border-white/10 mt-16 relative overflow-hidden">
{/* Triangle decorations in footer - manjši in bolje pozicionirani */}
<div className="absolute top-2 right-10 w-0 h-0 border-l-[20px] border-l-transparent border-r-[20px] border-r-transparent border-b-[25px] border-b-[#da234d]/5 rotate-12 z-0"></div>
<div className="absolute top-1 left-10 w-0 h-0 border-l-[15px] border-l-transparent border-r-[15px] border-r-transparent border-b-[20px] border-b-[#da234d]/4 -rotate-12 z-0"></div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 relative z-10">
<div className="flex flex-col items-center justify-center space-y-4">
{/* Logo */}
<div className="flex items-center space-x-2">
<div className="w-8 h-8 bg-[#da234d] rounded-lg flex items-center justify-center shadow-md">
<div className="w-0 h-0 border-l-[8px] border-l-white border-y-[6px] border-y-transparent ml-0.5"></div>
</div>
<span className="text-lg font-semibold text-white">video.folx.tv</span>
</div>
{/* Legal Links */}
<div className="flex items-center space-x-6 text-sm">
<Link href="/impressum" className="text-bunny-muted hover:text-white transition-colors">
Impressum
</Link>
<Link href="/privacy" className="text-bunny-muted hover:text-white transition-colors">
Datenschutz
</Link>
<Link href="/terms" className="text-bunny-muted hover:text-white transition-colors">
Nutzungsbedingungen
</Link>
</div>
{/* Copyright */}
<div className="text-sm text-bunny-muted text-center">
© 2025 video.folx.tv. Alle Rechte vorbehalten.
</div>
</div>
</div>
</footer>
</div>
);
}