Restored to 'e087324c769fdb5e778619a362ad06f5e61119bf'
Replit-Restored-To: e087324c76
This commit is contained in:
@@ -1,268 +0,0 @@
|
||||
import { useParams } from "wouter";
|
||||
import { useLocation } from "wouter";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { type Video } from "@shared/schema";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ArrowLeft, Share2, Eye, Clock } from "lucide-react";
|
||||
import { Link } from "wouter";
|
||||
import { useState, useEffect } from "react";
|
||||
import ShareModal from "@/components/share-modal";
|
||||
|
||||
function formatDuration(seconds: number): string {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function formatViews(views: number): string {
|
||||
if (views >= 1000000) {
|
||||
return `${(views / 1000000).toFixed(1)}M ogledov`;
|
||||
} else if (views >= 1000) {
|
||||
return `${(views / 1000).toFixed(1)}K ogledov`;
|
||||
}
|
||||
return `${views} ogledov`;
|
||||
}
|
||||
|
||||
function formatDate(date: Date | string): string {
|
||||
const now = new Date();
|
||||
const createdDate = typeof date === 'string' ? new Date(date) : date;
|
||||
|
||||
if (!createdDate || isNaN(createdDate.getTime())) {
|
||||
return "Neznano";
|
||||
}
|
||||
|
||||
const diffTime = Math.abs(now.getTime() - createdDate.getTime());
|
||||
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 0) return "Danes";
|
||||
if (diffDays === 1) return "Pred 1 dnem";
|
||||
if (diffDays < 7) return `Pred ${diffDays} dnevi`;
|
||||
if (diffDays < 30) return `Pred ${Math.floor(diffDays / 7)} tednom${Math.floor(diffDays / 7) > 1 ? 'i' : ''}`;
|
||||
return `Pred ${Math.floor(diffDays / 30)} mesec${Math.floor(diffDays / 30) > 1 ? 'i' : 'em'}`;
|
||||
}
|
||||
|
||||
export default function VideoPage() {
|
||||
const params = useParams();
|
||||
const [location] = useLocation();
|
||||
const videoId = params.id;
|
||||
const [isShareModalOpen, setIsShareModalOpen] = useState(false);
|
||||
const [viewTracked, setViewTracked] = useState(false);
|
||||
|
||||
// Fetch video data
|
||||
const { data: video, isLoading, error } = useQuery<Video>({
|
||||
queryKey: ["/api/videos", videoId],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`/api/videos/${videoId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Video not found');
|
||||
}
|
||||
return response.json();
|
||||
},
|
||||
enabled: !!videoId
|
||||
});
|
||||
|
||||
// Track video view
|
||||
useEffect(() => {
|
||||
if (video && !viewTracked) {
|
||||
fetch(`/api/videos/${video.id}/view`, { method: 'POST' })
|
||||
.catch(console.error);
|
||||
setViewTracked(true);
|
||||
}
|
||||
}, [video, viewTracked]);
|
||||
|
||||
// Set page title and meta tags
|
||||
useEffect(() => {
|
||||
if (video) {
|
||||
document.title = `${video.title} - VideoStream`;
|
||||
|
||||
// Update meta tags for social sharing
|
||||
const updateMeta = (name: string, content: string, property = true) => {
|
||||
const selector = property ? `meta[property="${name}"]` : `meta[name="${name}"]`;
|
||||
let meta = document.querySelector(selector) as HTMLMetaElement;
|
||||
if (!meta) {
|
||||
meta = document.createElement('meta');
|
||||
if (property) {
|
||||
meta.setAttribute('property', name);
|
||||
} else {
|
||||
meta.setAttribute('name', name);
|
||||
}
|
||||
document.head.appendChild(meta);
|
||||
}
|
||||
meta.content = content;
|
||||
};
|
||||
|
||||
const shareUrl = `${window.location.origin}/video/${video.id}`;
|
||||
|
||||
// Open Graph tags for Facebook
|
||||
updateMeta('og:title', video.title);
|
||||
updateMeta('og:description', video.description || `Oglej si ta video na VideoStream`);
|
||||
updateMeta('og:image', video.thumbnailUrl);
|
||||
updateMeta('og:image:width', '1200');
|
||||
updateMeta('og:image:height', '630');
|
||||
updateMeta('og:image:type', 'image/jpeg');
|
||||
updateMeta('og:url', shareUrl);
|
||||
updateMeta('og:type', 'video.other');
|
||||
updateMeta('og:site_name', 'VideoStream');
|
||||
|
||||
// Standard meta tags
|
||||
updateMeta('description', video.description || `Oglej si ta video na VideoStream`, false);
|
||||
|
||||
// Twitter Card tags
|
||||
updateMeta('twitter:card', 'summary_large_image', false);
|
||||
updateMeta('twitter:title', video.title, false);
|
||||
updateMeta('twitter:description', video.description || `Oglej si ta video na VideoStream`, false);
|
||||
updateMeta('twitter:image', video.thumbnailUrl, false);
|
||||
}
|
||||
}, [video]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-bunny-dark flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-bunny-blue"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !video) {
|
||||
return (
|
||||
<div className="min-h-screen bg-bunny-dark flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold text-bunny-light mb-4">Video ni najden</h1>
|
||||
<p className="text-bunny-muted mb-6">Video, ki ga iščete, ne obstaja ali je bil odstranjen.</p>
|
||||
<Link href="/">
|
||||
<Button className="bg-bunny-blue hover:bg-blue-600 text-white">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Nazaj na domačo stran
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-bunny-dark">
|
||||
{/* Header */}
|
||||
<header className="bg-bunny-gray/80 backdrop-blur-sm border-b border-gray-700">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex items-center justify-between h-16">
|
||||
<Link href="/">
|
||||
<Button variant="ghost" className="text-bunny-light hover:text-bunny-blue">
|
||||
<ArrowLeft className="w-5 h-5 mr-2" />
|
||||
Nazaj na seznam
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
onClick={() => setIsShareModalOpen(true)}
|
||||
className="bg-bunny-blue hover:bg-blue-600 text-white"
|
||||
data-testid="button-share-video"
|
||||
>
|
||||
<Share2 className="w-4 h-4 mr-2" />
|
||||
Deli
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Video Player */}
|
||||
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<div className="lg:col-span-2">
|
||||
<div className="bg-black rounded-lg overflow-hidden aspect-video">
|
||||
{video.videoUrl.includes('iframe.mediadelivery.net') ? (
|
||||
<iframe
|
||||
src={video.videoUrl}
|
||||
className="w-full h-full"
|
||||
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture; fullscreen;"
|
||||
allowFullScreen={true}
|
||||
frameBorder="0"
|
||||
data-testid="video-iframe"
|
||||
/>
|
||||
) : (
|
||||
<video
|
||||
className="w-full h-full"
|
||||
controls
|
||||
preload="metadata"
|
||||
data-testid="video-player"
|
||||
>
|
||||
<source src={video.videoUrl} type="video/mp4" />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<h1 className="text-2xl font-bold text-bunny-light mb-4" data-testid="text-video-title">
|
||||
{video.title}
|
||||
</h1>
|
||||
|
||||
<div className="flex items-center space-x-6 text-bunny-muted mb-6">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Eye className="w-4 h-4" />
|
||||
<span data-testid="text-video-views">{formatViews(video.views)}</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span data-testid="text-video-duration">{formatDuration(video.duration)}</span>
|
||||
</div>
|
||||
<span data-testid="text-video-date">{formatDate(video.createdAt)}</span>
|
||||
{video.category && (
|
||||
<span className="bg-bunny-gray px-3 py-1 rounded-full text-sm" data-testid="text-video-category">
|
||||
{video.category}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{video.description && (
|
||||
<div className="bg-bunny-gray rounded-lg p-4">
|
||||
<h3 className="font-semibold text-bunny-light mb-2">Opis</h3>
|
||||
<p className="text-bunny-muted leading-relaxed" data-testid="text-video-description">
|
||||
{video.description}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:col-span-1">
|
||||
<div className="bg-bunny-gray rounded-lg p-6">
|
||||
<h3 className="font-semibold text-bunny-light mb-4">Deli ta video</h3>
|
||||
<Button
|
||||
onClick={() => setIsShareModalOpen(true)}
|
||||
className="w-full bg-bunny-blue hover:bg-blue-600 text-white mb-4"
|
||||
data-testid="button-share-sidebar"
|
||||
>
|
||||
<Share2 className="w-4 h-4 mr-2" />
|
||||
Deli video
|
||||
</Button>
|
||||
|
||||
<div className="space-y-3 text-sm text-bunny-muted">
|
||||
<div>
|
||||
<strong className="text-bunny-light">Trajanje:</strong> {formatDuration(video.duration)}
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-bunny-light">Ogledi:</strong> {formatViews(video.views)}
|
||||
</div>
|
||||
<div>
|
||||
<strong className="text-bunny-light">Dodano:</strong> {formatDate(video.createdAt)}
|
||||
</div>
|
||||
{video.category && (
|
||||
<div>
|
||||
<strong className="text-bunny-light">Kategorija:</strong> {video.category}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<ShareModal
|
||||
video={video}
|
||||
isOpen={isShareModalOpen}
|
||||
onClose={() => setIsShareModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user