videofolxtv/client/src/components/video-ads.tsx
sebastjanartic 4e4fe369c1 Add support for displaying video advertisement metadata
Integrate Bunny.net API to fetch and display video ad spots, including type, duration, and priority, on the video page. Define new database schema for video ads and create API endpoints for retrieving ad information.

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/jsdCVZt
2025-08-28 16:47:09 +00:00

123 lines
3.9 KiB
TypeScript

import { useState, useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
import { Card } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { PlayCircle, Clock, Tag, Network } from "lucide-react";
interface VideoAd {
adType: string;
adUrl?: string;
adTitle?: string;
adDuration?: number;
position?: number;
vastTag?: string;
adNetwork?: string;
priority: number;
}
interface VideoAdsProps {
videoId: string;
}
export default function VideoAds({ videoId }: VideoAdsProps) {
const { data, isLoading, error } = useQuery({
queryKey: [`/api/videos/${videoId}/ads`],
});
if (isLoading) {
return (
<Card className="p-4">
<div className="animate-pulse">
<div className="h-4 bg-gray-300 rounded w-3/4 mb-2"></div>
<div className="h-3 bg-gray-200 rounded w-1/2"></div>
</div>
</Card>
);
}
if (error) {
return (
<Card className="p-4 border-red-200 bg-red-50">
<p className="text-red-600 text-sm">Failed to load ad metadata</p>
</Card>
);
}
const { ads = [], totalAds = 0 } = data || {};
if (totalAds === 0) {
return (
<Card className="p-4 border-gray-200 bg-gray-50">
<p className="text-gray-600 text-sm flex items-center gap-2">
<PlayCircle className="w-4 h-4" />
No ad spots configured for this video
</p>
</Card>
);
}
return (
<div className="space-y-3">
<div className="flex items-center gap-2 mb-3">
<PlayCircle className="w-5 h-5 text-blue-600" />
<h3 className="font-semibold text-gray-900">
Video Ads ({totalAds} spots)
</h3>
</div>
{ads.map((ad: VideoAd, index: number) => (
<Card key={index} className="p-4 border border-gray-200 hover:border-blue-300 transition-colors">
<div className="flex items-start justify-between gap-3">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<Badge
variant={ad.adType === 'preroll' ? 'default' : ad.adType === 'postroll' ? 'secondary' : 'outline'}
className="text-xs"
>
{ad.adType.toUpperCase()}
</Badge>
<span className="text-sm font-medium text-gray-900">
{ad.adTitle || `${ad.adType} Advertisement`}
</span>
</div>
<div className="grid grid-cols-2 gap-3 text-sm text-gray-600">
{ad.adDuration && (
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
<span>{ad.adDuration}s duration</span>
</div>
)}
{ad.position !== undefined && ad.adType === 'midroll' && (
<div className="flex items-center gap-1">
<PlayCircle className="w-3 h-3" />
<span>At {Math.floor(ad.position / 60)}:{(ad.position % 60).toString().padStart(2, '0')}</span>
</div>
)}
{ad.adNetwork && (
<div className="flex items-center gap-1">
<Network className="w-3 h-3" />
<span>{ad.adNetwork}</span>
</div>
)}
<div className="flex items-center gap-1">
<Tag className="w-3 h-3" />
<span>Priority {ad.priority}</span>
</div>
</div>
{ad.vastTag && (
<div className="mt-2 p-2 bg-gray-50 rounded text-xs font-mono text-gray-700 break-all">
VAST: {ad.vastTag.substring(0, 80)}...
</div>
)}
</div>
</div>
</Card>
))}
</div>
);
}