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
281 lines
9.2 KiB
TypeScript
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>
|
|
);
|
|
} |