diff --git a/client/src/components/ad-networks.tsx b/client/src/components/ad-networks.tsx new file mode 100644 index 0000000..90bccda --- /dev/null +++ b/client/src/components/ad-networks.tsx @@ -0,0 +1,281 @@ +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(defaultAdNetworks); + const [editingNetwork, setEditingNetwork] = useState(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 ( +
+ {/* Revenue Overview */} +
+ + + Celotni prihodek + + + +
+ ${totalRevenue.toFixed(2)} +
+
+
+ + + + Povprečni eCPM + + + +
+ ${averageECPM.toFixed(2)} +
+
+
+ + + + Skupno prikazov + + + +
+ {totalImpressions.toLocaleString()} +
+
+
+ + + + Aktivne mreže + + + +
+ {networks.filter(n => n.enabled).length} +
+
+
+
+ + {/* Ad Networks List */} + + + + + Oglasne mreže + + + Upravljajte svoje oglasne mreže in optimizirajte prihodke + + + + {networks.map((network) => ( +
+
+ toggleNetwork(network.id)} + data-testid={`switch-${network.id}`} + /> + +
+
+

{network.name}

+ + Prioriteta {network.priority} + +
+ + {editingNetwork === network.id ? ( +
+ updateTagUrl(network.id, e.target.value)} + className="bg-gray-600 border-gray-500 text-white text-sm" + placeholder="VAST tag URL" + /> + +
+ ) : ( +

+ {network.tagUrl} +

+ )} +
+
+ +
+
+

Fill Rate

+

{network.fillRate}%

+
+ +
+

eCPM

+

${network.eCPM}

+
+ +
+

Prihodek

+

${network.revenue}

+
+ + +
+
+ ))} +
+
+ + {/* Performance Chart Placeholder */} + + + + + Učinkovitost mreže + + + +
+
+ +

Graf učinkovitosti oglasnih mrež

+

Prikazuje eCPM in fill rate trends

+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/client/src/components/vast-ad-player.tsx b/client/src/components/vast-ad-player.tsx new file mode 100644 index 0000000..e687b76 --- /dev/null +++ b/client/src/components/vast-ad-player.tsx @@ -0,0 +1,123 @@ +import { useEffect, useRef, useState } from 'react'; +import videojs from 'video.js'; +// @ts-ignore +import 'videojs-ima'; +// @ts-ignore +import 'videojs-contrib-ads'; +import 'video.js/dist/video-js.css'; + +interface VASTAdPlayerProps { + videoId: string; + videoUrl: string; + posterUrl?: string; + adTagUrl?: string; + onAdStart?: () => void; + onAdEnd?: () => void; + onAdError?: (error: any) => void; +} + +export default function VASTAdPlayer({ + videoId, + videoUrl, + posterUrl, + adTagUrl = 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=', + onAdStart, + onAdEnd, + onAdError +}: VASTAdPlayerProps) { + const videoRef = useRef(null); + const playerRef = useRef(null); + const [isAdPlaying, setIsAdPlaying] = useState(false); + + useEffect(() => { + if (!videoRef.current) return; + + // Initialize Video.js player with IMA plugin + const player = videojs(videoRef.current, { + controls: true, + responsive: true, + fluid: true, + playbackRates: [0.5, 1, 1.25, 1.5, 2], + poster: posterUrl, + sources: [{ + src: videoUrl, + type: 'video/mp4' + }] + }); + + playerRef.current = player; + + // Initialize ads + player.ready(() => { + // @ts-ignore + player.ads({ + debug: false, + timeout: 5000 + }); + + // Initialize IMA + // @ts-ignore + player.ima({ + adTagUrl: adTagUrl, + adsManagerLoadedCallback: () => { + console.log('IMA ads manager loaded'); + }, + adsManagerErrorCallback: (error: any) => { + console.error('IMA ads manager error:', error); + onAdError?.(error); + } + }); + + // Ad event listeners + player.on('ads-ad-started', () => { + console.log('Ad started'); + setIsAdPlaying(true); + onAdStart?.(); + }); + + player.on('ads-ad-ended', () => { + console.log('Ad ended'); + setIsAdPlaying(false); + onAdEnd?.(); + }); + + player.on('adserror', (event: any) => { + console.error('Ad error:', event); + onAdError?.(event); + }); + + player.on('ads-request', () => { + console.log('Ad request made'); + }); + + player.on('ads-load', () => { + console.log('Ad loaded'); + }); + }); + + return () => { + if (playerRef.current) { + playerRef.current.dispose(); + playerRef.current = null; + } + }; + }, [videoId, videoUrl, adTagUrl, posterUrl, onAdStart, onAdEnd, onAdError]); + + return ( +
+ {isAdPlaying && ( +
+ Oglas +
+ )} +
+ ); +} \ No newline at end of file diff --git a/client/src/components/video-card.tsx b/client/src/components/video-card.tsx index b0e6306..058e7b2 100644 --- a/client/src/components/video-card.tsx +++ b/client/src/components/video-card.tsx @@ -70,6 +70,11 @@ export default function VideoCard({ video, onClick }: VideoCardProps) { {formatDuration(video.duration)} + {/* VAST Ad monetization indicator */} +
+ 💰 SPOT +
+ {/* Play button overlay */}