videofolxtv/client/src/pages/VideoPage.tsx
sebastjanartic 1ede557133 Translate video page interface and sharing options into English
Update various text elements on the video page from Slovene to English, including loading messages, error messages, button labels, and social media share descriptions. Also adjusts the query key for fetching video data.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: d7424866-83d1-4486-a212-ac12b4c7becf
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/d7424866-83d1-4486-a212-ac12b4c7becf/SnoheaM
2025-08-28 11:41:11 +00:00

280 lines
11 KiB
TypeScript

import { useState, useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
import { useRoute } from "wouter";
import { type Video } from "@shared/schema";
// 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('sl-SI', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
import { Button } from "@/components/ui/button";
import { Share2, X, Edit3 } from "lucide-react";
import { apiRequest } from "@/lib/queryClient";
import {
FacebookShareButton,
TwitterShareButton,
WhatsappShareButton,
FacebookIcon,
TwitterIcon,
WhatsappIcon,
} from "react-share";
interface VideosResponse {
videos: Video[];
total: number;
hasMore: boolean;
}
export default function VideoPage() {
const [, params] = useRoute("/video/:id");
const videoId = params?.id;
const [showShareMenu, setShowShareMenu] = useState(false);
// Fetch current video
const { data: currentVideo, isLoading: videoLoading } = useQuery<Video>({
queryKey: [`/api/videos/${videoId}`],
enabled: !!videoId,
});
// Fetch recommended videos (excluding current video)
const { data: recommendedResponse } = useQuery<VideosResponse>({
queryKey: ["/api/videos", { limit: 20, offset: 0 }],
enabled: !!videoId,
});
const recommendedVideos = recommendedResponse?.videos?.filter(v => v.id !== videoId) || [];
// Track video view
const handleVideoPlay = async () => {
if (currentVideo) {
try {
await apiRequest("POST", `/api/videos/${currentVideo.id}/view`);
} catch (error) {
console.error("Failed to track video view:", error);
}
}
};
const getShareUrl = () => {
if (!currentVideo?.id) return window.location.origin;
return `${window.location.origin}/video/${currentVideo.id}`;
};
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(getShareUrl());
const notification = document.createElement('div');
notification.textContent = 'Link copied!';
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-white">Loading video...</div>
</div>
);
}
if (!currentVideo) {
return (
<div className="min-h-screen bg-bunny-dark flex items-center justify-center">
<div className="text-white">Video not found</div>
</div>
);
}
return (
<div className="min-h-screen bg-bunny-dark">
{/* Header */}
<header className="bg-bunny-gray/50 border-b border-gray-700 p-4">
<div className="max-w-7xl mx-auto flex items-center justify-between">
<div className="flex items-center space-x-2">
<div className="w-8 h-8 bg-bunny-blue rounded-full flex items-center justify-center">
<div className="w-0 h-0 border-l-[6px] border-l-white border-y-[4px] border-y-transparent ml-1"></div>
</div>
<h1 className="text-xl font-bold text-bunny-light">go4.video</h1>
</div>
<Button
onClick={() => window.history.back()}
className="bg-gray-700 hover:bg-gray-600 text-white"
>
Back
</Button>
</div>
</header>
<div className="max-w-7xl mx-auto p-4 lg:p-6">
<div className="flex flex-col lg:flex-row gap-6">
{/* Main video section */}
<div className="flex-1">
{/* Video player */}
<div className="relative w-full h-0 pb-[56.25%] bg-black rounded-lg overflow-hidden mb-4">
{currentVideo.videoUrlIframe ? (
<iframe
src={currentVideo.videoUrlIframe}
className="absolute inset-0 w-full h-full"
frameBorder="0"
allowFullScreen
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
onLoad={handleVideoPlay}
title={currentVideo.title}
/>
) : (
<div className="absolute inset-0 flex items-center justify-center text-white">
<p>Video not available</p>
</div>
)}
</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()}>
<button className="w-full px-4 py-2 text-left text-white hover:bg-gray-700 flex items-center gap-2">
<FacebookIcon size={16} round />
Facebook
</button>
</FacebookShareButton>
<TwitterShareButton url={getShareUrl()} title={`Watch "${currentVideo.title}" on go4.video`}>
<button className="w-full px-4 py-2 text-left text-white hover:bg-gray-700 flex items-center gap-2">
<TwitterIcon size={16} round />
Twitter
</button>
</TwitterShareButton>
<WhatsappShareButton url={getShareUrl()} title={`Watch "${currentVideo.title}" on go4.video`}>
<button className="w-full px-4 py-2 text-left text-white hover:bg-gray-700 flex items-center gap-2">
<WhatsappIcon size={16} round />
WhatsApp
</button>
</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.category && (
<div className="mb-4">
<span className="inline-block bg-bunny-blue text-white px-3 py-1 rounded-full text-sm">
{currentVideo.category}
</span>
</div>
)}
{currentVideo.description && (
<div className="text-bunny-light">
<p className="text-sm leading-relaxed">{currentVideo.description}</p>
</div>
)}
</div>
</div>
{/* Recommended videos sidebar */}
<div className="lg:w-96">
<h2 className="text-lg font-semibold text-bunny-light mb-4">Recommended videos</h2>
<div className="space-y-3">
{recommendedVideos.slice(0, 10).map((video) => (
<div
key={video.id}
onClick={() => window.location.href = `/video/${video.id}`}
className="flex gap-3 p-3 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;
target.style.display = 'none';
}}
/>
<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 space-y-0.5">
<div>{formatViews(video.views)} views</div>
<div>{formatDate(video.createdAt)}</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
}