diff --git a/client/src/components/social-share.tsx b/client/src/components/social-share.tsx new file mode 100644 index 0000000..7331942 --- /dev/null +++ b/client/src/components/social-share.tsx @@ -0,0 +1,154 @@ +import { useState } from "react"; +import { Copy, Check, Share2 } from "lucide-react"; +import { + SiX, + SiFacebook, + SiLinkedin, + SiWhatsapp, + SiTelegram, + SiReddit +} from "react-icons/si"; +import { Button } from "@/components/ui/button"; +import { type Video } from "@shared/schema"; + +interface SocialShareProps { + video: Video; + className?: string; +} + +export function SocialShare({ video, className = "" }: SocialShareProps) { + const [copied, setCopied] = useState(false); + const [shareOpen, setShareOpen] = useState(false); + + // Generate shareable URL (in production, this would be your actual domain) + const shareUrl = `${window.location.origin}/?video=${video.id}`; + const shareText = `Check out this video: ${video.title}`; + const shareTextEncoded = encodeURIComponent(shareText); + const shareUrlEncoded = encodeURIComponent(shareUrl); + + const handleCopyLink = async () => { + try { + await navigator.clipboard.writeText(shareUrl); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy link:', err); + } + }; + + const socialPlatforms = [ + { + name: "X", + icon: SiX, + url: `https://twitter.com/intent/tweet?text=${shareTextEncoded}&url=${shareUrlEncoded}`, + color: "hover:bg-gray-900 hover:text-white", + }, + { + name: "Facebook", + icon: SiFacebook, + url: `https://www.facebook.com/sharer/sharer.php?u=${shareUrlEncoded}`, + color: "hover:bg-blue-600 hover:text-white", + }, + { + name: "LinkedIn", + icon: SiLinkedin, + url: `https://www.linkedin.com/sharing/share-offsite/?url=${shareUrlEncoded}`, + color: "hover:bg-blue-700 hover:text-white", + }, + { + name: "WhatsApp", + icon: SiWhatsapp, + url: `https://wa.me/?text=${shareTextEncoded}%20${shareUrlEncoded}`, + color: "hover:bg-green-500 hover:text-white", + }, + { + name: "Telegram", + icon: SiTelegram, + url: `https://t.me/share/url?url=${shareUrlEncoded}&text=${shareTextEncoded}`, + color: "hover:bg-blue-400 hover:text-white", + }, + { + name: "Reddit", + icon: SiReddit, + url: `https://reddit.com/submit?url=${shareUrlEncoded}&title=${shareTextEncoded}`, + color: "hover:bg-orange-500 hover:text-white", + }, + ]; + + const handlePlatformShare = (platform: typeof socialPlatforms[0]) => { + window.open(platform.url, '_blank', 'width=600,height=400'); + }; + + return ( +
+ + + {shareOpen && ( + <> + {/* Backdrop */} +
setShareOpen(false)} + /> + + {/* Share Menu */} +
+

Share this video

+ + {/* Copy Link */} +
+ +
+ + {/* Social Platforms */} +
+ {socialPlatforms.map((platform) => { + const IconComponent = platform.icon; + return ( + + ); + })} +
+
+ + )} +
+ ); +} \ No newline at end of file diff --git a/client/src/components/video-card.tsx b/client/src/components/video-card.tsx index 73d9866..c15c3a9 100644 --- a/client/src/components/video-card.tsx +++ b/client/src/components/video-card.tsx @@ -1,5 +1,6 @@ -import { Play } from "lucide-react"; +import { Play, Share2 } from "lucide-react"; import { type Video } from "@shared/schema"; +import { Button } from "@/components/ui/button"; interface VideoCardProps { video: Video; @@ -40,6 +41,30 @@ function formatDate(date: Date | string): string { } export default function VideoCard({ video, onClick }: VideoCardProps) { + const handleShare = async (e: React.MouseEvent) => { + e.stopPropagation(); // Prevent video modal from opening + + const shareUrl = `${window.location.origin}/?video=${video.id}`; + const shareData = { + title: video.title, + text: `Check out this video: ${video.title}`, + url: shareUrl, + }; + + try { + if (navigator.share) { + // Use native share API if available (mobile devices) + await navigator.share(shareData); + } else { + // Fallback: copy to clipboard + await navigator.clipboard.writeText(shareUrl); + alert('Video link copied to clipboard!'); + } + } catch (err) { + console.error('Error sharing:', err); + } + }; + return (
+ + {/* Share button */} +
+ +
diff --git a/client/src/components/video-modal.tsx b/client/src/components/video-modal.tsx index e27a24f..6922ba3 100644 --- a/client/src/components/video-modal.tsx +++ b/client/src/components/video-modal.tsx @@ -1,9 +1,10 @@ import { useEffect, useRef } from "react"; -import { X } from "lucide-react"; +import { X, Share2, Copy, Check } 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 { SocialShare } from "@/components/social-share"; interface VideoModalProps { video: Video | null; @@ -212,16 +213,19 @@ export default function VideoModal({ video, isOpen, onClose }: VideoModalProps) > {video.title} -
- - {formatViews(video.views)} - - - {formatDate(video.createdAt)} - - - {formatDuration(video.duration)} - +
+
+ + {formatViews(video.views)} + + + {formatDate(video.createdAt)} + + + {formatDuration(video.duration)} + +
+
{video.description && (

diff --git a/client/src/pages/home.tsx b/client/src/pages/home.tsx index af9c65e..60d58e5 100644 --- a/client/src/pages/home.tsx +++ b/client/src/pages/home.tsx @@ -3,6 +3,7 @@ import { useQuery } from "@tanstack/react-query"; import { type Video } from "@shared/schema"; import SearchHeader from "@/components/search-header"; import VideoGrid from "@/components/video-grid"; +import VideoModal from "@/components/video-modal"; interface VideosResponse { videos: Video[]; @@ -15,6 +16,23 @@ export default function Home() { const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); const [offset, setOffset] = useState(0); const [allVideos, setAllVideos] = useState([]); + const [selectedVideo, setSelectedVideo] = useState

+ {/* Shared video modal - handles deep links */} + {selectedVideo && isVideoModalOpen && ( + + )} + {/* Footer */}