videofolxtv/client/src/components/video-modal.tsx
sebastjanartic b77e18915f Integrate videos directly from Bunny.net CDN for faster streaming
Implements Bunny.net CDN integration for video streaming and utilizes its API to fetch and display videos.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 50814a1e-92e4-4968-856f-7bc7eedf5e8f
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/50814a1e-92e4-4968-856f-7bc7eedf5e8f/DXDnT5V
2025-08-04 18:32:51 +00:00

146 lines
4.4 KiB
TypeScript

import { useEffect, useRef } from "react";
import { X } from "lucide-react";
import { type Video } from "@shared/schema";
import { Button } from "@/components/ui/button";
import { apiRequest } from "@/lib/queryClient";
interface VideoModalProps {
video: Video | null;
isOpen: boolean;
onClose: () => void;
}
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 views`;
} else if (views >= 1000) {
return `${(views / 1000).toFixed(1)}K views`;
}
return `${views} views`;
}
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 "Unknown";
}
const diffTime = Math.abs(now.getTime() - createdDate.getTime());
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
if (diffDays === 0) return "Today";
if (diffDays === 1) return "1 day ago";
if (diffDays < 7) return `${diffDays} days ago`;
if (diffDays < 30) return `${Math.floor(diffDays / 7)} week${Math.floor(diffDays / 7) > 1 ? 's' : ''} ago`;
return `${Math.floor(diffDays / 30)} month${Math.floor(diffDays / 30) > 1 ? 's' : ''} ago`;
}
export default function VideoModal({ video, isOpen, onClose }: VideoModalProps) {
const videoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape" && isOpen) {
onClose();
}
};
if (isOpen) {
document.addEventListener("keydown", handleEscape);
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
return () => {
document.removeEventListener("keydown", handleEscape);
document.body.style.overflow = "";
};
}, [isOpen, onClose]);
const handleVideoPlay = async () => {
if (video) {
try {
await apiRequest("POST", `/api/videos/${video.id}/view`);
} catch (error) {
console.error("Failed to track video view:", error);
}
}
};
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
};
if (!isOpen || !video) return null;
return (
<div
className="fixed inset-0 bg-black/90 backdrop-blur-sm z-50 flex items-center justify-center p-4"
onClick={handleBackdropClick}
data-testid="modal-video"
>
<div className="relative w-full max-w-6xl">
<Button
variant="ghost"
size="icon"
onClick={onClose}
className="absolute -top-12 right-0 text-white hover:text-bunny-blue transition-colors z-60"
data-testid="button-close-modal"
>
<X className="text-2xl" />
</Button>
<div className="relative bg-black rounded-lg overflow-hidden">
<video
ref={videoRef}
className="w-full h-auto max-h-[80vh]"
controls
preload="metadata"
onPlay={handleVideoPlay}
data-testid="video-player"
src={video.videoUrl}
>
Your browser does not support the video tag.
</video>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-6">
<h3
className="text-xl font-semibold mb-2 text-white"
data-testid="text-modal-title"
>
{video.title}
</h3>
<div className="flex items-center space-x-4 text-sm text-gray-300">
<span data-testid="text-modal-views">
{formatViews(video.views)}
</span>
<span data-testid="text-modal-date">
{formatDate(video.createdAt)}
</span>
<span data-testid="text-modal-duration">
{formatDuration(video.duration)}
</span>
</div>
{video.description && (
<p className="mt-3 text-gray-300 text-sm" data-testid="text-modal-description">
{video.description}
</p>
)}
</div>
</div>
</div>
</div>
);
}