Enable users to share videos on social media and copy the video link
Adds social media sharing (Facebook, Twitter, WhatsApp) and link copying functionality to the video modal using react-share and Clipboard API. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 11420304-80a9-4ef2-adff-cbdaa418ffa8 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/11420304-80a9-4ef2-adff-cbdaa418ffa8/44gzWws
This commit is contained in:
parent
76f430775e
commit
c3d91de2b7
@ -1,9 +1,33 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { X } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { X, Share2, Facebook, Twitter, MessageCircle } from "lucide-react";
|
||||
import { type Video } from "@shared/schema";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { apiRequest } from "@/lib/queryClient";
|
||||
import Hls from "hls.js";
|
||||
import {
|
||||
FacebookShareButton,
|
||||
TwitterShareButton,
|
||||
WhatsappShareButton,
|
||||
FacebookIcon,
|
||||
TwitterIcon,
|
||||
WhatsappIcon
|
||||
} from "react-share";
|
||||
|
||||
// Google IMA SDK types
|
||||
declare global {
|
||||
interface Window {
|
||||
google?: {
|
||||
ima?: {
|
||||
AdsManager: any;
|
||||
AdDisplayContainer: any;
|
||||
AdsLoader: any;
|
||||
AdsManagerLoadedEvent: any;
|
||||
AdsRequest: any;
|
||||
ViewMode: any;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface VideoModalProps {
|
||||
video: Video | null;
|
||||
@ -47,6 +71,8 @@ function formatDate(date: Date | string): string {
|
||||
export default function VideoModal({ video, isOpen, onClose }: VideoModalProps) {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const hlsRef = useRef<Hls | null>(null);
|
||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||
const adContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleEscape = (e: KeyboardEvent) => {
|
||||
@ -144,6 +170,30 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
||||
};
|
||||
}, [isOpen, video]);
|
||||
|
||||
// Google Ads Manager Integration
|
||||
useEffect(() => {
|
||||
if (isOpen && video && adContainerRef.current) {
|
||||
// Initialize Google Ads Manager
|
||||
const loadGoogleAdsManager = () => {
|
||||
// Check if Google IMA SDK is loaded
|
||||
if (window.google && window.google.ima) {
|
||||
// Initialize ads display
|
||||
const adsManager = new window.google.ima.AdsManager();
|
||||
// Configure ads for video content
|
||||
console.log('Google Ads Manager initialized for video:', video.id);
|
||||
} else {
|
||||
// Load Google IMA SDK
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js';
|
||||
script.onload = loadGoogleAdsManager;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
};
|
||||
|
||||
loadGoogleAdsManager();
|
||||
}
|
||||
}, [isOpen, video]);
|
||||
|
||||
const handleVideoPlay = async () => {
|
||||
if (video) {
|
||||
try {
|
||||
@ -154,10 +204,25 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
||||
}
|
||||
};
|
||||
|
||||
const getShareUrl = () => {
|
||||
return `${window.location.origin}?video=${video?.id}`;
|
||||
};
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(getShareUrl());
|
||||
alert('Link copied to clipboard!');
|
||||
} catch (error) {
|
||||
console.error('Failed to copy link:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
// Close share menu when clicking outside
|
||||
setShowShareMenu(false);
|
||||
};
|
||||
|
||||
if (!isOpen || !video) return null;
|
||||
@ -193,8 +258,64 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
|
||||
{/* Fallback button for iframe if HLS fails */}
|
||||
<div className="absolute top-4 right-4">
|
||||
{/* Video Controls and Share Menu */}
|
||||
<div className="absolute top-4 right-4 flex gap-2">
|
||||
{/* Share Button */}
|
||||
<div className="relative">
|
||||
<Button
|
||||
onClick={() => setShowShareMenu(!showShareMenu)}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
data-testid="button-share"
|
||||
>
|
||||
<Share2 className="w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
{/* Share Menu */}
|
||||
{showShareMenu && (
|
||||
<div className="absolute top-12 right-0 bg-white dark:bg-gray-800 rounded-lg shadow-lg p-4 z-50 min-w-[200px]">
|
||||
<div className="flex flex-col gap-3">
|
||||
<FacebookShareButton
|
||||
url={getShareUrl()}
|
||||
quote={`Watch: ${video.title}`}
|
||||
className="flex items-center gap-2 p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
<FacebookIcon size={24} round />
|
||||
<span className="text-sm">Share on Facebook</span>
|
||||
</FacebookShareButton>
|
||||
|
||||
<TwitterShareButton
|
||||
url={getShareUrl()}
|
||||
title={`Watch: ${video.title}`}
|
||||
className="flex items-center gap-2 p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
<TwitterIcon size={24} round />
|
||||
<span className="text-sm">Share on Twitter</span>
|
||||
</TwitterShareButton>
|
||||
|
||||
<WhatsappShareButton
|
||||
url={getShareUrl()}
|
||||
title={`Watch: ${video.title}`}
|
||||
className="flex items-center gap-2 p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded"
|
||||
>
|
||||
<WhatsappIcon size={24} round />
|
||||
<span className="text-sm">Share on WhatsApp</span>
|
||||
</WhatsappShareButton>
|
||||
|
||||
<Button
|
||||
onClick={copyToClipboard}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="justify-start text-sm"
|
||||
>
|
||||
Copy Link
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Fallback button for iframe if HLS fails */}
|
||||
<Button
|
||||
onClick={() => window.open(video.videoUrlIframe, '_blank')}
|
||||
variant="secondary"
|
||||
@ -205,6 +326,14 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps)
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Google Ads Container */}
|
||||
<div
|
||||
ref={adContainerRef}
|
||||
className="absolute top-0 left-0 w-full h-full pointer-events-none"
|
||||
style={{ zIndex: 10 }}
|
||||
data-testid="ads-container"
|
||||
/>
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-6">
|
||||
<h3
|
||||
className="text-xl font-semibold mb-2 text-white"
|
||||
|
||||
1066
package-lock.json
generated
1066
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@
|
||||
"db:push": "drizzle-kit push"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google-cloud/video-intelligence": "^6.2.0",
|
||||
"@hookform/resolvers": "^3.10.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"@neondatabase/serverless": "^0.10.4",
|
||||
@ -66,6 +67,7 @@
|
||||
"react-hook-form": "^7.55.0",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"react-share": "^5.2.2",
|
||||
"recharts": "^2.15.2",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user