import { useState } from 'react'; import { useSwipeable } from 'react-swipeable'; import { type Article, articlesApi } from '../api/articles'; interface ArticleCardProps { article: Article; onClick: (article: Article) => void; onToggleRead: (id: string, is_read: boolean) => void; onToggleFavorite: (id: string) => void; } export function ArticleCard({ article, onClick, onToggleRead, onToggleFavorite }: ArticleCardProps) { const [swipeOffset, setSwipeOffset] = useState(0); const [isSwiping, setIsSwiping] = useState(false); const [isSummarizing, setIsSummarizing] = useState(false); const handlers = useSwipeable({ onSwiping: (event) => { const offset = event.deltaX; // Limit swipe to 80px if (Math.abs(offset) < 100) { setSwipeOffset(offset); setIsSwiping(true); } }, onSwipedRight: () => { if (swipeOffset > 50) { onToggleRead(article.id, !article.is_read); } resetSwipe(); }, onSwipedLeft: () => { if (swipeOffset < -50) { onToggleFavorite(article.id); } resetSwipe(); }, onSwiped: () => resetSwipe(), trackMouse: true, preventScrollOnSwipe: true, delta: 10, }); const resetSwipe = () => { setSwipeOffset(0); setIsSwiping(false); }; const handleSummarize = async (e: React.MouseEvent) => { e.stopPropagation(); if (article.ai_summary || isSummarizing) return; setIsSummarizing(true); try { await articlesApi.summarize(article.id); // The article will be updated via WebSocket broadcast } catch (error) { console.error('Failed to summarize:', error); alert('Erreur lors de la génération du résumé. Vérifiez votre clé API OpenRouter.'); } finally { setIsSummarizing(false); } }; return (
{/* Action Layers behind the card */}
{/* Left side swipe (Mark as Read) */}
10 ? 'opacity-100' : 'opacity-0'}`}>
{article.is_read ? 'Marquer non lu' : 'Marquer lu'}
{/* Right side swipe (Favorite) */}
Favori
{/* Main Interactive Card */}
!isSwiping && Math.abs(swipeOffset) < 5 && onClick(article)} > {/* Unread Ribbon - Polished Wrapping Effect */} {!article.is_read && (
{/* The Ribbon Itself */}
Nouveau
{/* Darker Folds for "Wrapping" Effect (Simulated behind the card) */}
)} {!article.is_read && ( /* External fold bits to cover the corner if needed for perfection */ <>
)} {/* Image Placeholder with Gradient Overlay */}
{/* Visual placeholder or real image */}
{/* Meta on top of image */}
{article.feed_title || 'Journal'}
{/* Content */}

{article.title}

{article.ai_summary ? (
✨

{article.ai_summary}

) : article.summary && (

{article.summary.replace(/<[^>]*>?/gm, '').substring(0, 160)}...

)}
{new Date(article.published_at || article.created_at).toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' })}
); }