Implements one-click social sharing feature via `<SocialShare>` component and URL parameters for video sharing. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aa92e7e2-ec62-4c92-b21b-02ef78a664c2 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/aa92e7e2-ec62-4c92-b21b-02ef78a664c2/Xt4Awd6
127 lines
4.3 KiB
TypeScript
127 lines
4.3 KiB
TypeScript
import { Play, Share2 } from "lucide-react";
|
|
import { type Video } from "@shared/schema";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
interface VideoCardProps {
|
|
video: Video;
|
|
onClick: (video: Video) => void;
|
|
}
|
|
|
|
function formatDuration(seconds: number): string {
|
|
const minutes = Math.floor(seconds / 60);
|
|
const remainingSeconds = seconds % 60;
|
|
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
function formatViews(views: number): string {
|
|
if (views >= 1000000) {
|
|
return `${(views / 1000000).toFixed(1)}M views`;
|
|
} else if (views >= 1000) {
|
|
return `${(views / 1000).toFixed(1)}K views`;
|
|
}
|
|
return `${views} views`;
|
|
}
|
|
|
|
function formatDate(date: Date | string): string {
|
|
const now = new Date();
|
|
const createdDate = typeof date === 'string' ? new Date(date) : date;
|
|
|
|
if (!createdDate || isNaN(createdDate.getTime())) {
|
|
return "Unknown";
|
|
}
|
|
|
|
const diffTime = Math.abs(now.getTime() - createdDate.getTime());
|
|
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
|
|
|
|
if (diffDays === 0) return "Today";
|
|
if (diffDays === 1) return "1 day ago";
|
|
if (diffDays < 7) return `${diffDays} days ago`;
|
|
if (diffDays < 30) return `${Math.floor(diffDays / 7)} week${Math.floor(diffDays / 7) > 1 ? 's' : ''} ago`;
|
|
return `${Math.floor(diffDays / 30)} month${Math.floor(diffDays / 30) > 1 ? 's' : ''} ago`;
|
|
}
|
|
|
|
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 (
|
|
<div
|
|
className="group cursor-pointer"
|
|
onClick={() => onClick(video)}
|
|
data-testid={`card-video-${video.id}`}
|
|
>
|
|
<div className="relative bg-bunny-gray rounded-xl overflow-hidden mb-4 aspect-video">
|
|
<img
|
|
src={video.thumbnailUrl}
|
|
alt={video.title}
|
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
|
data-testid={`img-thumbnail-${video.id}`}
|
|
/>
|
|
|
|
<div className="absolute inset-0 bg-black/20 group-hover:bg-black/40 transition-colors duration-300 flex items-center justify-center">
|
|
<div className="w-16 h-16 bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center group-hover:bg-white/30 transition-colors duration-300">
|
|
<Play className="text-white text-xl ml-1" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="absolute bottom-3 right-3 bg-black/80 px-2 py-1 rounded text-xs font-medium">
|
|
<span data-testid={`text-duration-${video.id}`}>
|
|
{formatDuration(video.duration)}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Share button */}
|
|
<div className="absolute top-3 right-3 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
onClick={handleShare}
|
|
className="bg-black/80 hover:bg-black/90 text-white border-0 h-8 w-8 p-0"
|
|
data-testid={`button-share-${video.id}`}
|
|
>
|
|
<Share2 className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<h3
|
|
className="font-semibold line-clamp-2 group-hover:text-bunny-blue transition-colors text-bunny-light"
|
|
data-testid={`text-title-${video.id}`}
|
|
>
|
|
{video.title}
|
|
</h3>
|
|
<div className="flex items-center space-x-3 text-sm text-bunny-muted">
|
|
<span data-testid={`text-views-${video.id}`}>
|
|
{formatViews(video.views)}
|
|
</span>
|
|
<span data-testid={`text-date-${video.id}`}>
|
|
{formatDate(video.createdAt)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|