gbclient/app/media/page.tsx

692 lines
25 KiB
TypeScript
Raw Normal View History

"use client";
import React, { useState, useRef, useEffect } from 'react';
import {
Search, Filter, Grid, List, Play, Pause, Volume2, VolumeX,
Eye, Bookmark, Share, Download, Clock, TrendingUp,
Globe, Calendar, Tag, ExternalLink, MoreHorizontal,
ChevronLeft, ChevronRight, Heart, MessageCircle,
Video, Image, Music, FileText, Rss, Archive,
Star, ThumbsUp, Settings, RefreshCw, Maximize,
User, MapPin, Zap, Briefcase, Gamepad2, Palette,
Users, Award, ShoppingBag, Plane, DollarSign,
Newspaper, AlertTriangle, Target, Mic, Camera
} from 'lucide-react';
import { cn } from "@/lib/utils";
// Mock news data - realistic news structure
const mockNews = [
{
id: 1,
headline: "Major Climate Summit Reaches Breakthrough Agreement on Carbon Reduction",
summary: "World leaders agree to unprecedented measures targeting 50% reduction in emissions by 2030, with binding commitments from major economies.",
content: "In a historic development at the Global Climate Summit, representatives from 195 countries have agreed to a comprehensive framework...",
author: "Sarah Chen",
publication: "Global Times",
publishedAt: "2025-01-15T14:30:00Z",
category: "Environment",
subcategory: "Climate Change",
readTime: "8 min read",
mediaType: "article",
imageUrl: "/api/placeholder/800/400",
videoUrl: null,
audioUrl: null,
tags: ["climate", "politics", "international", "environment"],
location: "Geneva, Switzerland",
breaking: true,
featured: true,
viewCount: 45670,
shareCount: 1240,
commentCount: 892,
bookmarkCount: 2130,
sentiment: "positive",
credibilityScore: 0.94
},
{
id: 2,
headline: "Tech Giant Announces Revolutionary AI Chip Architecture",
summary: "New quantum-enhanced processing unit promises 1000x performance improvement for AI workloads, reshaping the semiconductor industry.",
content: "Silicon Valley's leading tech company unveiled its latest innovation today, a groundbreaking AI chip that combines...",
author: "Marcus Rodriguez",
publication: "Tech Weekly",
publishedAt: "2025-01-15T12:45:00Z",
category: "Technology",
subcategory: "AI & Computing",
readTime: "6 min read",
mediaType: "video",
imageUrl: "/api/placeholder/800/400",
videoUrl: "/api/placeholder/video",
audioUrl: null,
videoDuration: "15:32",
tags: ["ai", "technology", "semiconductors", "innovation"],
location: "San Francisco, CA",
breaking: false,
featured: true,
viewCount: 78234,
shareCount: 2156,
commentCount: 445,
bookmarkCount: 3421,
sentiment: "positive",
credibilityScore: 0.91
},
{
id: 3,
headline: "Global Markets React to Federal Reserve Interest Rate Decision",
summary: "Stock markets surge as Fed maintains current rates, signaling confidence in economic recovery and inflation control measures.",
content: "Financial markets around the world responded positively to the Federal Reserve's decision to maintain...",
author: "Elena Vasquez",
publication: "Financial Herald",
publishedAt: "2025-01-15T11:20:00Z",
category: "Business",
subcategory: "Markets",
readTime: "5 min read",
mediaType: "article",
imageUrl: "/api/placeholder/800/400",
videoUrl: null,
audioUrl: null,
tags: ["finance", "fed", "markets", "economy"],
location: "New York, NY",
breaking: false,
featured: false,
viewCount: 34567,
shareCount: 876,
commentCount: 234,
bookmarkCount: 1456,
sentiment: "neutral",
credibilityScore: 0.96
},
{
id: 4,
headline: "Space Mission Discovers Evidence of Ancient Water on Mars",
summary: "NASA's latest rover findings reveal extensive underground water systems, raising new questions about potential for past life.",
content: "The Mars exploration mission has yielded its most significant discovery yet, with clear evidence of...",
author: "Dr. James Mitchell",
publication: "Science Today",
publishedAt: "2025-01-15T09:15:00Z",
category: "Science",
subcategory: "Space",
readTime: "10 min read",
mediaType: "multimedia",
imageUrl: "/api/placeholder/800/400",
videoUrl: "/api/placeholder/video",
audioUrl: "/api/placeholder/audio",
videoDuration: "8:45",
audioDuration: "12:30",
tags: ["space", "mars", "nasa", "discovery"],
location: "Pasadena, CA",
breaking: false,
featured: true,
viewCount: 92341,
shareCount: 4567,
commentCount: 1234,
bookmarkCount: 5678,
sentiment: "positive",
credibilityScore: 0.98
},
{
id: 5,
headline: "Championship Final Breaks Viewership Records Worldwide",
summary: "Historic match draws over 2 billion viewers globally, showcasing the growing international appeal of the sport.",
content: "Last night's championship final has set a new benchmark for global sports viewership...",
author: "Alex Thompson",
publication: "Sports Central",
publishedAt: "2025-01-15T08:30:00Z",
category: "Sports",
subcategory: "Football",
readTime: "4 min read",
mediaType: "video",
imageUrl: "/api/placeholder/800/400",
videoUrl: "/api/placeholder/video",
audioUrl: null,
videoDuration: "22:15",
tags: ["sports", "football", "championship", "records"],
location: "London, UK",
breaking: false,
featured: false,
viewCount: 156789,
shareCount: 8901,
commentCount: 3456,
bookmarkCount: 2345,
sentiment: "positive",
credibilityScore: 0.89
}
];
const categories = [
{ id: 'all', name: 'All News', icon: Newspaper, color: 'text-gray-600' },
{ id: 'breaking', name: 'Breaking', icon: AlertTriangle, color: 'text-red-600' },
{ id: 'politics', name: 'Politics', icon: Users, color: 'text-blue-600' },
{ id: 'technology', name: 'Technology', icon: Zap, color: 'text-purple-600' },
{ id: 'business', name: 'Business', icon: Briefcase, color: 'text-green-600' },
{ id: 'science', name: 'Science', icon: Target, color: 'text-indigo-600' },
{ id: 'sports', name: 'Sports', icon: Award, color: 'text-orange-600' },
{ id: 'entertainment', name: 'Entertainment', icon: Palette, color: 'text-pink-600' },
{ id: 'health', name: 'Health', icon: Heart, color: 'text-emerald-600' },
{ id: 'world', name: 'World', icon: Globe, color: 'text-cyan-600' }
];
const formatDate = (dateString) => {
const date = new Date(dateString);
const now = new Date();
const diffMs = now - date;
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffHours < 1) return 'Just now';
if (diffHours < 24) return `${diffHours}h ago`;
if (diffDays < 7) return `${diffDays}d ago`;
return date.toLocaleDateString();
};
const formatNumber = (num) => {
if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`;
if (num >= 1000) return `${(num / 1000).toFixed(1)}K`;
return num.toString();
};
const MediaPlayer = ({ article, onClose }) => {
const [isPlaying, setIsPlaying] = useState(false);
const [isMuted, setIsMuted] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const videoRef = useRef(null);
const audioRef = useRef(null);
const togglePlay = () => {
if (article.mediaType === 'video' && videoRef.current) {
if (isPlaying) {
videoRef.current.pause();
} else {
videoRef.current.play();
}
setIsPlaying(!isPlaying);
}
};
const toggleMute = () => {
if (videoRef.current) {
videoRef.current.muted = !isMuted;
setIsMuted(!isMuted);
}
};
return (
<div className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center p-4">
<div className="bg-background rounded-xl w-full max-w-4xl max-h-[90vh] overflow-hidden">
<div className="flex items-center justify-between p-4 border-b">
<h2 className="text-lg font-semibold truncate">{article.headline}</h2>
<button
onClick={onClose}
className="p-2 hover:bg-muted rounded-lg transition-colors"
>
×
</button>
</div>
<div className="relative">
{article.mediaType === 'video' && (
<div className="relative">
<video
ref={videoRef}
className="w-full h-auto max-h-[60vh]"
poster={article.imageUrl}
onTimeUpdate={(e) => setCurrentTime(e.target.currentTime)}
onLoadedMetadata={(e) => setDuration(e.target.duration)}
>
<source src={article.videoUrl} type="video/mp4" />
</video>
<div className="absolute bottom-4 left-4 flex items-center gap-2">
<button
onClick={togglePlay}
className="p-2 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
>
{isPlaying ? <Pause className="w-4 h-4" /> : <Play className="w-4 h-4" />}
</button>
<button
onClick={toggleMute}
className="p-2 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
>
{isMuted ? <VolumeX className="w-4 h-4" /> : <Volume2 className="w-4 h-4" />}
</button>
<span className="text-white text-sm bg-black/50 px-2 py-1 rounded">
{article.videoDuration}
</span>
</div>
</div>
)}
{article.mediaType === 'audio' && (
<div className="p-8 text-center">
<div className="w-32 h-32 mx-auto mb-4 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center">
<Music className="w-16 h-16 text-white" />
</div>
<audio
ref={audioRef}
controls
className="w-full max-w-md mx-auto"
src={article.audioUrl}
/>
</div>
)}
</div>
<div className="p-4">
<div className="text-sm text-muted-foreground mb-2">
{article.author} {article.publication} {formatDate(article.publishedAt)}
</div>
<p className="text-sm leading-relaxed">{article.summary}</p>
</div>
</div>
</div>
);
};
const NewsCard = ({ article, layout = 'grid', onPlay, onBookmark, onShare }) => {
const getMediaIcon = () => {
switch (article.mediaType) {
case 'video': return <Video className="w-4 h-4" />;
case 'audio': return <Music className="w-4 h-4" />;
case 'multimedia': return <Camera className="w-4 h-4" />;
default: return <FileText className="w-4 h-4" />;
}
};
const getSentimentColor = () => {
switch (article.sentiment) {
case 'positive': return 'text-green-600';
case 'negative': return 'text-red-600';
default: return 'text-gray-600';
}
};
if (layout === 'list') {
return (
<div className="flex gap-4 p-4 border-b hover:bg-muted/50 transition-colors">
<div className="w-32 h-24 bg-muted rounded-lg overflow-hidden flex-shrink-0">
<img src={article.imageUrl} alt="" className="w-full h-full object-cover" />
{(article.mediaType === 'video' || article.mediaType === 'multimedia') && (
<div className="absolute inset-0 flex items-center justify-center">
<button
onClick={() => onPlay(article)}
className="p-2 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
>
<Play className="w-4 h-4" />
</button>
</div>
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
{article.breaking && (
<span className="bg-red-600 text-white text-xs px-2 py-1 rounded-full font-medium">
BREAKING
</span>
)}
<span className="text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded-full">
{article.category}
</span>
<div className="flex items-center gap-1">
{getMediaIcon()}
<span className="text-xs text-muted-foreground">
{article.videoDuration || article.audioDuration || article.readTime}
</span>
</div>
</div>
<h3 className="font-semibold text-lg mb-2 line-clamp-2 hover:text-blue-600 cursor-pointer">
{article.headline}
</h3>
<p className="text-sm text-muted-foreground mb-2 line-clamp-2">
{article.summary}
</p>
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<span className="font-medium">{article.author}</span>
<span></span>
<span>{article.publication}</span>
<span></span>
<span>{formatDate(article.publishedAt)}</span>
<span></span>
<span className="flex items-center gap-1">
<Eye className="w-3 h-3" />
{formatNumber(article.viewCount)}
</span>
</div>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => onBookmark(article)}
className="p-2 hover:bg-muted rounded-lg transition-colors"
>
<Bookmark className="w-4 h-4" />
</button>
<button
onClick={() => onShare(article)}
className="p-2 hover:bg-muted rounded-lg transition-colors"
>
<Share className="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
);
}
return (
<div className="bg-card border rounded-xl overflow-hidden hover:shadow-lg transition-all duration-300 group">
<div className="relative">
<img
src={article.imageUrl}
alt={article.headline}
className="w-full h-48 object-cover"
/>
{(article.mediaType === 'video' || article.mediaType === 'multimedia') && (
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={() => onPlay(article)}
className="p-4 bg-black/50 text-white rounded-full hover:bg-black/70 transition-colors"
>
<Play className="w-6 h-6" />
</button>
</div>
)}
<div className="absolute top-3 left-3 flex items-center gap-2">
{article.breaking && (
<span className="bg-red-600 text-white text-xs px-2 py-1 rounded-full font-medium">
BREAKING
</span>
)}
<span className="bg-black/70 text-white text-xs px-2 py-1 rounded-full">
{article.category}
</span>
</div>
<div className="absolute top-3 right-3 flex items-center gap-1">
{getMediaIcon()}
<span className="text-white text-xs bg-black/70 px-2 py-1 rounded-full">
{article.videoDuration || article.audioDuration || article.readTime}
</span>
</div>
</div>
<div className="p-4">
<h3 className="font-semibold text-lg mb-2 line-clamp-2 hover:text-blue-600 cursor-pointer">
{article.headline}
</h3>
<p className="text-sm text-muted-foreground mb-3 line-clamp-3">
{article.summary}
</p>
<div className="flex items-center justify-between text-xs text-muted-foreground mb-3">
<div className="flex items-center gap-2">
<span className="font-medium">{article.author}</span>
<span></span>
<span>{formatDate(article.publishedAt)}</span>
</div>
<div className="flex items-center gap-1">
<Eye className="w-3 h-3" />
{formatNumber(article.viewCount)}
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<span className="flex items-center gap-1">
<Heart className="w-3 h-3" />
{formatNumber(article.shareCount)}
</span>
<span className="flex items-center gap-1">
<MessageCircle className="w-3 h-3" />
{formatNumber(article.commentCount)}
</span>
</div>
<div className="flex items-center gap-1">
<button
onClick={() => onBookmark(article)}
className="p-1 hover:bg-muted rounded transition-colors"
>
<Bookmark className="w-4 h-4" />
</button>
<button
onClick={() => onShare(article)}
className="p-1 hover:bg-muted rounded transition-colors"
>
<Share className="w-4 h-4" />
</button>
<button className="p-1 hover:bg-muted rounded transition-colors">
<MoreHorizontal className="w-4 h-4" />
</button>
</div>
</div>
</div>
</div>
);
};
export default function NewsMediaBrowser() {
const [selectedCategory, setSelectedCategory] = useState('all');
const [searchTerm, setSearchTerm] = useState('');
const [layout, setLayout] = useState('grid');
const [sortBy, setSortBy] = useState('latest');
const [articles, setArticles] = useState(mockNews);
const [selectedArticle, setSelectedArticle] = useState(null);
const [showPlayer, setShowPlayer] = useState(false);
const [bookmarkedArticles, setBookmarkedArticles] = useState(new Set());
const filteredArticles = articles.filter(article => {
const matchesCategory = selectedCategory === 'all' ||
article.category.toLowerCase() === selectedCategory ||
(selectedCategory === 'breaking' && article.breaking);
const matchesSearch = searchTerm === '' ||
article.headline.toLowerCase().includes(searchTerm.toLowerCase()) ||
article.summary.toLowerCase().includes(searchTerm.toLowerCase()) ||
article.tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()));
return matchesCategory && matchesSearch;
});
const sortedArticles = [...filteredArticles].sort((a, b) => {
switch (sortBy) {
case 'latest':
return new Date(b.publishedAt) - new Date(a.publishedAt);
case 'popular':
return b.viewCount - a.viewCount;
case 'trending':
return b.shareCount - a.shareCount;
default:
return 0;
}
});
const handlePlay = (article) => {
setSelectedArticle(article);
setShowPlayer(true);
};
const handleBookmark = (article) => {
const newBookmarked = new Set(bookmarkedArticles);
if (newBookmarked.has(article.id)) {
newBookmarked.delete(article.id);
} else {
newBookmarked.add(article.id);
}
setBookmarkedArticles(newBookmarked);
};
const handleShare = (article) => {
navigator.share?.({
title: article.headline,
text: article.summary,
url: window.location.href + '/' + article.id
});
};
return (
<div className="flex flex-col h-screen bg-background">
{/* Header */}
<div className="border-b bg-background/95 backdrop-blur-sm sticky top-0 z-40">
<div className="px-6 py-4">
<div className="flex items-center justify-between mb-4">
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
News Center
</h1>
<div className="flex items-center gap-2">
<button className="flex items-center gap-2 px-3 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
<RefreshCw className="w-4 h-4" />
Refresh
</button>
<button className="p-2 hover:bg-muted rounded-lg transition-colors">
<Settings className="w-4 h-4" />
</button>
</div>
</div>
{/* Search and Filters */}
<div className="flex items-center gap-4 mb-4">
<div className="relative flex-1 max-w-md">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<input
type="text"
placeholder="Search news..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="latest">Latest</option>
<option value="popular">Most Popular</option>
<option value="trending">Trending</option>
</select>
<div className="flex items-center gap-1 bg-muted rounded-lg p-1">
<button
onClick={() => setLayout('grid')}
className={cn(
"p-2 rounded transition-colors",
layout === 'grid' ? "bg-background shadow-sm" : "hover:bg-background/50"
)}
>
<Grid className="w-4 h-4" />
</button>
<button
onClick={() => setLayout('list')}
className={cn(
"p-2 rounded transition-colors",
layout === 'list' ? "bg-background shadow-sm" : "hover:bg-background/50"
)}
>
<List className="w-4 h-4" />
</button>
</div>
</div>
{/* Categories */}
<div className="flex gap-2 overflow-x-auto pb-2">
{categories.map((category) => (
<button
key={category.id}
onClick={() => setSelectedCategory(category.id)}
className={cn(
"flex items-center gap-2 px-4 py-2 rounded-full whitespace-nowrap transition-all",
selectedCategory === category.id
? "bg-blue-600 text-white"
: "bg-muted hover:bg-muted/80"
)}
>
<category.icon className={cn("w-4 h-4", category.color)} />
{category.name}
</button>
))}
</div>
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-auto p-6">
<div className="max-w-7xl mx-auto">
{/* Featured Articles */}
{selectedCategory === 'all' && (
<div className="mb-8">
<h2 className="text-xl font-semibold mb-4">Featured Stories</h2>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{sortedArticles.filter(article => article.featured).slice(0, 2).map((article) => (
<NewsCard
key={article.id}
article={article}
layout="grid"
onPlay={handlePlay}
onBookmark={handleBookmark}
onShare={handleShare}
/>
))}
</div>
</div>
)}
{/* Article Grid/List */}
<div className="mb-4 flex items-center justify-between">
<h2 className="text-xl font-semibold">
{selectedCategory === 'all' ? 'All News' : categories.find(c => c.id === selectedCategory)?.name}
</h2>
<span className="text-sm text-muted-foreground">
{sortedArticles.length} articles
</span>
</div>
{layout === 'grid' ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{sortedArticles.map((article) => (
<NewsCard
key={article.id}
article={article}
layout="grid"
onPlay={handlePlay}
onBookmark={handleBookmark}
onShare={handleShare}
/>
))}
</div>
) : (
<div className="space-y-0 border rounded-xl overflow-hidden">
{sortedArticles.map((article) => (
<NewsCard
key={article.id}
article={article}
layout="list"
onPlay={handlePlay}
onBookmark={handleBookmark}
onShare={handleShare}
/>
))}
</div>
)}
</div>
</div>
{/* Media Player Modal */}
{showPlayer && selectedArticle && (
<MediaPlayer
article={selectedArticle}
onClose={() => setShowPlayer(false)}
/>
)}
</div>
);
}