spb/vquant Public MIT
VibeQuant — AI-powered institutional-grade financial intelligence platform.
TypeScript 84.3%
Python 11.7%
JavaScript 1.6%
CSS 1.5%
HTML 0.7%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: client/src/components/explore/news-view.tsx6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 * Website: https://www.spboucher.ai10 * Demo: https://www.vquant.ai11 * License: MIT (see LICENSE)12 *13 * Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";18import { Badge } from "@/components/ui/badge";19import { Newspaper, ExternalLink, Calendar, TrendingUp } from "lucide-react";2021interface NewsArticle {22 title: string;23 text: string;24 url: string;25 site: string;26 publishedDate: string;27 symbol: string;28 image?: string;29 [key: string]: any;30}3132interface NewsViewProps {33 news?: NewsArticle[];34}3536export function NewsView({ news }: NewsViewProps) {37 const formatDate = (dateString: string) => {38 const date = new Date(dateString);39 const now = new Date();40 const diffInHours = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60));4142 if (diffInHours < 1) return "Il y a moins d'une heure";43 if (diffInHours < 24) return `Il y a ${diffInHours} heure${diffInHours > 1 ? 's' : ''}`;44 if (diffInHours < 48) return "Hier";4546 return date.toLocaleDateString('fr-FR', {47 year: 'numeric',48 month: 'long',49 day: 'numeric',50 hour: '2-digit',51 minute: '2-digit'52 });53 };5455 if (!news || news.length === 0) {56 return (57 <Card>58 <CardHeader>59 <CardTitle className="flex items-center gap-2">60 <Newspaper className="w-5 h-5" />61 Actualités Financières62 </CardTitle>63 </CardHeader>64 <CardContent>65 <p className="text-muted-foreground">Aucune actualité disponible</p>66 </CardContent>67 </Card>68 );69 }7071 return (72 <div className="space-y-6">73 <div className="flex items-center gap-3 mb-4">74 <div className="p-2 rounded-lg bg-primary/10">75 <Newspaper className="w-6 h-6 text-primary" />76 </div>77 <div>78 <h2 className="text-2xl font-bold">Actualités & Analyses</h2>79 <p className="text-muted-foreground">80 {news.length} article{news.length > 1 ? 's' : ''} récent{news.length > 1 ? 's' : ''}81 </p>82 </div>83 </div>8485 <div className="grid grid-cols-1 gap-4">86 {news.map((article, idx) => (87 <Card88 key={idx}89 className="hover:shadow-lg transition-all duration-300 hover:border-primary/50 cursor-pointer"90 onClick={() => window.open(article.url, '_blank')}91 >92 <CardContent className="pt-6">93 <div className="flex gap-4">94 {/* Image */}95 {article.image && (96 <div className="flex-shrink-0 w-48 h-32 rounded-lg overflow-hidden bg-muted">97 <img98 src={article.image}99 alt={article.title}100 className="w-full h-full object-cover"101 onError={(e) => {102 (e.target as HTMLImageElement).style.display = 'none';103 }}104 />105 </div>106 )}107108 {/* Content */}109 <div className="flex-1 min-w-0">110 <div className="flex items-start justify-between gap-4 mb-2">111 <h3 className="text-xl font-bold line-clamp-2 hover:text-primary transition-colors">112 {article.title}113 </h3>114 <ExternalLink className="w-5 h-5 flex-shrink-0 text-muted-foreground" />115 </div>116117 <p className="text-muted-foreground line-clamp-3 mb-3">118 {article.text}119 </p>120121 <div className="flex items-center gap-3 flex-wrap">122 <Badge variant="outline" className="gap-1">123 <Calendar className="w-3 h-3" />124 {formatDate(article.publishedDate)}125 </Badge>126127 <Badge variant="secondary">128 {article.site}129 </Badge>130131 {article.symbol && (132 <Badge variant="default">133 {article.symbol}134 </Badge>135 )}136 </div>137 </div>138 </div>139 </CardContent>140 </Card>141 ))}142 </div>143144 {/* Load More Button */}145 {news.length >= 10 && (146 <Card className="border-dashed">147 <CardContent className="flex items-center justify-center py-8">148 <button className="text-primary hover:underline font-semibold flex items-center gap-2">149 <TrendingUp className="w-4 h-4" />150 Charger plus d'actualités151 </button>152 </CardContent>153 </Card>154 )}155 </div>156 );157}158