videofolxtv/client/src/components/ad-networks.tsx
sebastjanartic 635f5eb197 Add monetization indicators and ad network management features
Introduce a new component for managing ad networks with detailed analytics and integrate VAST ad playback with Video.js, including an indicator for monetized content.

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/A9pVG1H
2025-08-08 21:38:22 +00:00

281 lines
9.2 KiB
TypeScript

import { useState } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { Badge } from '@/components/ui/badge';
import {
DollarSign,
TrendingUp,
Settings,
Eye,
BarChart3,
Target,
Zap
} from 'lucide-react';
interface AdNetwork {
id: string;
name: string;
enabled: boolean;
priority: number;
tagUrl: string;
fillRate: number;
eCPM: number;
revenue: number;
impressions: number;
}
const defaultAdNetworks: AdNetwork[] = [
{
id: 'publift',
name: 'Publift',
enabled: true,
priority: 1,
tagUrl: 'https://ads.publift.com/vast/tag?zone=12345',
fillRate: 85,
eCPM: 2.45,
revenue: 245.50,
impressions: 10000
},
{
id: 'vdo',
name: 'Vdo.ai',
enabled: true,
priority: 2,
tagUrl: 'https://delivery.vdo.ai/vast/tag?id=67890',
fillRate: 78,
eCPM: 1.85,
revenue: 185.20,
impressions: 8500
},
{
id: 'primis',
name: 'Primis',
enabled: true,
priority: 3,
tagUrl: 'https://tags.primis.tech/vast/tag?pid=54321',
fillRate: 72,
eCPM: 1.65,
revenue: 165.80,
impressions: 7200
},
{
id: 'adplayer',
name: 'AdPlayer.Pro',
enabled: false,
priority: 4,
tagUrl: 'https://vast.adplayer.pro/vast?id=98765',
fillRate: 68,
eCPM: 1.25,
revenue: 125.40,
impressions: 6800
},
{
id: 'aniview',
name: 'Aniview',
enabled: false,
priority: 5,
tagUrl: 'https://player.aniview.com/vast/tag?id=11111',
fillRate: 65,
eCPM: 1.15,
revenue: 115.30,
impressions: 6500
}
];
export default function AdNetworks() {
const [networks, setNetworks] = useState<AdNetwork[]>(defaultAdNetworks);
const [editingNetwork, setEditingNetwork] = useState<string | null>(null);
const toggleNetwork = (id: string) => {
setNetworks(prev =>
prev.map(network =>
network.id === id
? { ...network, enabled: !network.enabled }
: network
)
);
};
const updateTagUrl = (id: string, tagUrl: string) => {
setNetworks(prev =>
prev.map(network =>
network.id === id
? { ...network, tagUrl }
: network
)
);
};
const totalRevenue = networks.reduce((sum, network) => sum + network.revenue, 0);
const totalImpressions = networks.reduce((sum, network) => sum + network.impressions, 0);
const averageECPM = totalRevenue / (totalImpressions / 1000);
return (
<div className="space-y-6">
{/* Revenue Overview */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card className="bg-gray-800 border-gray-700">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-gray-400">Celotni prihodek</CardTitle>
<DollarSign className="h-4 w-4 text-green-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-white">
${totalRevenue.toFixed(2)}
</div>
</CardContent>
</Card>
<Card className="bg-gray-800 border-gray-700">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-gray-400">Povprečni eCPM</CardTitle>
<TrendingUp className="h-4 w-4 text-blue-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-white">
${averageECPM.toFixed(2)}
</div>
</CardContent>
</Card>
<Card className="bg-gray-800 border-gray-700">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-gray-400">Skupno prikazov</CardTitle>
<Eye className="h-4 w-4 text-purple-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-white">
{totalImpressions.toLocaleString()}
</div>
</CardContent>
</Card>
<Card className="bg-gray-800 border-gray-700">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium text-gray-400">Aktivne mreže</CardTitle>
<Zap className="h-4 w-4 text-orange-500" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-white">
{networks.filter(n => n.enabled).length}
</div>
</CardContent>
</Card>
</div>
{/* Ad Networks List */}
<Card className="bg-gray-800 border-gray-700">
<CardHeader>
<CardTitle className="text-white flex items-center gap-2">
<Target className="w-5 h-5" />
Oglasne mreže
</CardTitle>
<CardDescription className="text-gray-400">
Upravljajte svoje oglasne mreže in optimizirajte prihodke
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{networks.map((network) => (
<div
key={network.id}
className="flex items-center justify-between p-4 bg-gray-700 rounded-lg"
>
<div className="flex items-center gap-4">
<Switch
checked={network.enabled}
onCheckedChange={() => toggleNetwork(network.id)}
data-testid={`switch-${network.id}`}
/>
<div className="space-y-1">
<div className="flex items-center gap-2">
<h3 className="font-medium text-white">{network.name}</h3>
<Badge
variant={network.enabled ? "default" : "secondary"}
className={network.enabled ? "bg-green-600" : "bg-gray-600"}
>
Prioriteta {network.priority}
</Badge>
</div>
{editingNetwork === network.id ? (
<div className="flex gap-2 mt-2">
<Input
value={network.tagUrl}
onChange={(e) => updateTagUrl(network.id, e.target.value)}
className="bg-gray-600 border-gray-500 text-white text-sm"
placeholder="VAST tag URL"
/>
<Button
size="sm"
onClick={() => setEditingNetwork(null)}
className="bg-blue-600 hover:bg-blue-700"
>
Shrani
</Button>
</div>
) : (
<p className="text-sm text-gray-400 truncate max-w-md">
{network.tagUrl}
</p>
)}
</div>
</div>
<div className="flex items-center gap-6">
<div className="text-right">
<p className="text-sm text-gray-400">Fill Rate</p>
<p className="text-white font-medium">{network.fillRate}%</p>
</div>
<div className="text-right">
<p className="text-sm text-gray-400">eCPM</p>
<p className="text-white font-medium">${network.eCPM}</p>
</div>
<div className="text-right">
<p className="text-sm text-gray-400">Prihodek</p>
<p className="text-green-400 font-medium">${network.revenue}</p>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => setEditingNetwork(
editingNetwork === network.id ? null : network.id
)}
className="text-gray-400 hover:text-white"
data-testid={`button-edit-${network.id}`}
>
<Settings className="w-4 h-4" />
</Button>
</div>
</div>
))}
</CardContent>
</Card>
{/* Performance Chart Placeholder */}
<Card className="bg-gray-800 border-gray-700">
<CardHeader>
<CardTitle className="text-white flex items-center gap-2">
<BarChart3 className="w-5 h-5" />
Učinkovitost mreže
</CardTitle>
</CardHeader>
<CardContent>
<div className="h-64 flex items-center justify-center border-2 border-dashed border-gray-600 rounded-lg">
<div className="text-center text-gray-400">
<BarChart3 className="w-12 h-12 mx-auto mb-2" />
<p>Graf učinkovitosti oglasnih mrež</p>
<p className="text-sm">Prikazuje eCPM in fill rate trends</p>
</div>
</div>
</CardContent>
</Card>
</div>
);
}