Replace existing simple loading indicators with a new animated `LoadingSpinner` component across various pages and components, including `HLSPreviewThumbnail`, `NetflixGrid`, `ThumbnailGenerator`, `VideoAds`, `VideoGrid`, `FolxStadlPage`, `GeschichteLiedPage`, `GipfelstammtischPage`, and `VideoPage`. The new component features a customizable size, text, and a pulsing gradient animation, improving the visual feedback during loading states. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 890577b1-c154-40a4-a177-a0c6d55320c3 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/890577b1-c154-40a4-a177-a0c6d55320c3/UbX8tN8
126 lines
4.2 KiB
TypeScript
126 lines
4.2 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-6 border-gray-700 bg-gray-800">
|
|
<div className="text-center">
|
|
<div className="w-10 h-10 gradient-primary rounded-lg flex items-center justify-center shadow-lg animate-pulse mb-3 mx-auto">
|
|
<div className="w-0 h-0 border-l-[8px] border-l-white border-y-[6px] border-y-transparent ml-1"></div>
|
|
</div>
|
|
<div className="text-white text-sm font-medium mb-1">go4.video</div>
|
|
<div className="text-gray-400 text-xs">Loading ads...</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 as any) || {};
|
|
|
|
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>
|
|
);
|
|
} |