2 コミット 1d4e984417 ... 9c2b2173af

作者 SHA1 メッセージ 日付
  gpt-engineer-app[bot] 9c2b2173af Implémenter mode Découverte 2 週間 前
  gpt-engineer-app[bot] ed908e7b9b Changes 2 週間 前

+ 23 - 12
src/components/CategoryFilter.tsx

@@ -49,7 +49,8 @@ interface CategoryFilterProps {
   dateFilter?: 'today' | 'yesterday' | null;
   onDateFilterChange?: (filter: 'today' | 'yesterday' | null) => void;
   showFollowedOnly?: boolean;
-  onShowFollowedOnlyChange?: (showFollowedOnly: boolean) => void;
+  showDiscoveryMode?: boolean;
+  onViewModeChange?: (mode: 'followed' | 'discovery' | 'all') => void;
   showReadArticles?: boolean;
   onShowReadArticlesChange?: (showReadArticles: boolean) => void;
   onTogglePin?: (articleId: string) => void;
@@ -76,7 +77,8 @@ const CategoryFilter = ({
   dateFilter,
   onDateFilterChange,
   showFollowedOnly,
-  onShowFollowedOnlyChange,
+  showDiscoveryMode,
+  onViewModeChange,
   showReadArticles,
   onShowReadArticlesChange,
   onTogglePin,
@@ -133,12 +135,12 @@ const CategoryFilter = ({
         })}
       </div>
       
-      {(onDateFilterChange || (user && onShowFollowedOnlyChange) || user) && (
+      {(onDateFilterChange || (user && onViewModeChange) || user) && (
         <div className="pt-4 border-t space-y-3">
           <div className="flex flex-wrap items-start gap-4">
             
             {/* Section 1: Filtres d'affichage */}
-            {user && onShowFollowedOnlyChange && (
+            {user && onViewModeChange && (
               <div className="flex flex-col gap-2 min-w-fit">
                 <div className="flex items-center gap-2 mb-1">
                   <Heart className="h-4 w-4 text-muted-foreground" />
@@ -146,21 +148,30 @@ const CategoryFilter = ({
                 </div>
                 <div className="flex flex-wrap gap-2">
                   <Button
-                    variant={!showFollowedOnly ? "default" : "outline"}
+                    variant={showFollowedOnly ? "default" : "outline"}
                     size="sm"
                     className="justify-start gap-2 whitespace-nowrap"
-                    onClick={() => onShowFollowedOnlyChange(false)}
+                    onClick={() => onViewModeChange('followed')}
                   >
-                    Tous les flux
+                    <Heart className="h-3 w-3" />
+                    Mes flux
                   </Button>
                   <Button
-                    variant={showFollowedOnly ? "default" : "outline"}
+                    variant={showDiscoveryMode ? "default" : "outline"}
                     size="sm"
                     className="justify-start gap-2 whitespace-nowrap"
-                    onClick={() => onShowFollowedOnlyChange(true)}
+                    onClick={() => onViewModeChange('discovery')}
                   >
-                    <Heart className="h-3 w-3" />
-                    Flux suivis uniquement
+                    <Rss className="h-3 w-3" />
+                    Découverte
+                  </Button>
+                  <Button
+                    variant={!showFollowedOnly && !showDiscoveryMode ? "default" : "outline"}
+                    size="sm"
+                    className="justify-start gap-2 whitespace-nowrap"
+                    onClick={() => onViewModeChange('all')}
+                  >
+                    Tous les flux
                   </Button>
                 </div>
               </div>
@@ -188,7 +199,7 @@ const CategoryFilter = ({
             )}
 
             {/* Section 2: Filtres de date - Only show for followed feeds */}
-            {onDateFilterChange && showFollowedOnly && (
+            {onDateFilterChange && showFollowedOnly && !showDiscoveryMode && (
               <div className="flex flex-col gap-2 min-w-fit">
                 <div className="flex items-center gap-2 mb-1">
                   <Calendar className="h-4 w-4 text-muted-foreground" />

+ 9 - 2
src/components/NewsCard.tsx

@@ -13,6 +13,7 @@ interface NewsCardProps {
   onDelete: (id: string) => void;
   onOpenArticle: (article: NewsItem) => void;
   onSourceClick?: (feedId: string, feedName: string) => void;
+  isDiscoveryMode?: boolean;
 }
 const NewsCard = ({
   news,
@@ -20,7 +21,8 @@ const NewsCard = ({
   onMarkAsRead,
   onDelete,
   onOpenArticle,
-  onSourceClick
+  onSourceClick,
+  isDiscoveryMode
 }: NewsCardProps) => {
   const {
     user
@@ -73,11 +75,16 @@ const NewsCard = ({
     onOpenArticle(news);
     // Don't automatically mark as read on card click - user can use the "Mark as read" button
   };
-  return <Card className={cn("group hover:shadow-lg transition-all duration-300 border-l-4 cursor-pointer", news.isPinned && "border-l-yellow-500", news.isRead && "opacity-75", !news.isRead && "border-l-primary")}>
+  return <Card className={cn("group hover:shadow-lg transition-all duration-300 border-l-4 cursor-pointer", news.isPinned && "border-l-yellow-500", isDiscoveryMode && "border-l-purple-500", news.isRead && "opacity-75", !news.isRead && !isDiscoveryMode && "border-l-primary")}>
       <CardHeader className="space-y-3">
         <div className="flex items-start justify-between gap-4">
           <div className="flex-1 space-y-2" onClick={handleCardClick}>
             <div className="flex items-center gap-2">
+              {isDiscoveryMode && (
+                <Badge variant="outline" className="bg-purple-500/10 text-purple-700 border-purple-200">
+                  🔍 Nouveau flux
+                </Badge>
+              )}
             </div>
             
             <h3 className={cn("flex items-center gap-2 font-semibold leading-tight group-hover:text-primary transition-colors", news.isRead && "text-muted-foreground")}>

+ 67 - 1
src/hooks/useRealArticles.tsx

@@ -5,7 +5,7 @@ import { useAuth } from './useAuth';
 import { NewsItem } from '@/types/news';
 import { toast } from 'sonner';
 
-export function useRealArticles(dateFilter?: 'today' | 'yesterday' | null, showFollowedOnly?: boolean, showReadArticles?: boolean) {
+export function useRealArticles(dateFilter?: 'today' | 'yesterday' | null, showFollowedOnly?: boolean, showReadArticles?: boolean, showDiscoveryMode?: boolean) {
   const [articles, setArticles] = useState<NewsItem[]>([]);
   const [loading, setLoading] = useState(true);
   const { user } = useAuth();
@@ -155,6 +155,72 @@ export function useRealArticles(dateFilter?: 'today' | 'yesterday' | null, showF
           })) || [];
 
         setArticles(transformedArticles.slice(0, 100));
+      } else if (showDiscoveryMode && user) {
+        // ======= MODE DÉCOUVERTE =======
+        console.log('🔍 Discovery mode active');
+        
+        // 1. Récupérer tous les feed_id que l'utilisateur a déjà interagi avec
+        const { data: knownFeeds } = await supabase
+          .from('user_feeds')
+          .select('feed_id')
+          .eq('user_id', user.id);
+        
+        const knownFeedIds = knownFeeds?.map(f => f.feed_id) || [];
+        console.log('📚 Known feeds to exclude:', knownFeedIds);
+        
+        // 2. Récupérer uniquement les articles des flux inconnus + actifs
+        let discoveryQuery = supabase
+          .from('articles')
+          .select(`
+            *,
+            feeds!inner(name, category, status),
+            user_articles(is_read, is_pinned)
+          `)
+          .eq('feeds.status', 'active');
+        
+        // Exclure les flux connus
+        if (knownFeedIds.length > 0) {
+          discoveryQuery = discoveryQuery.not('feed_id', 'in', `(${knownFeedIds.join(',')})`);
+        }
+        
+        // Appliquer filtre "lus/non lus" si l'utilisateur a cliqué sur certains articles découverte
+        if (!showReadArticles && user) {
+          discoveryQuery = discoveryQuery.or('user_articles.is.null,user_articles.is_read.eq.false', { referencedTable: 'user_articles' });
+        }
+        
+        discoveryQuery = discoveryQuery
+          .order('published_at', { ascending: false })
+          .limit(100);
+        
+        const { data: discoveryArticles, error: discoveryError } = await discoveryQuery;
+        
+        if (discoveryError) {
+          console.error('❌ Error fetching discovery articles:', discoveryError);
+          toast.error('Erreur lors du chargement des articles en découverte');
+          return;
+        }
+        
+        console.log('✅ Discovery articles loaded:', discoveryArticles?.length || 0);
+        
+        // Formater les articles
+        const formattedArticles = (discoveryArticles || []).map(article => ({
+          id: article.id,
+          title: article.title,
+          description: article.description || '',
+          content: article.content || '',
+          publishedAt: article.published_at,
+          source: article.feeds.name,
+          category: article.feeds.category as 'rss' | 'youtube' | 'steam' | 'actualites',
+          url: article.url || '',
+          imageUrl: article.image_url,
+          isPinned: false,
+          isRead: article.user_articles?.[0]?.is_read || false,
+          feedId: article.feed_id,
+          readTime: article.read_time || 5,
+          isDiscovery: true
+        }));
+        
+        setArticles(formattedArticles);
       } else {
         // For users wanting all articles or visitors - show all articles from all feeds
         console.log('👤 Loading all articles (visitor or showFollowedOnly=false)');

+ 24 - 10
src/pages/Index.tsx

@@ -23,14 +23,26 @@ const Index = () => {
   const navigate = useNavigate();
   const [dateFilter, setDateFilter] = useState<'today' | 'yesterday' | null>(null);
   const [showFollowedOnly, setShowFollowedOnly] = useState(!!user);
+  const [showDiscoveryMode, setShowDiscoveryMode] = useState(false);
   const [showReadArticles, setShowReadArticles] = useState(false);
 
-  // Reset date filter when switching to "All articles" mode
-  const handleShowFollowedOnlyChange = (value: boolean) => {
-    setShowFollowedOnly(value);
-    if (!value) {
-      // When switching to "All articles", reset date filter
-      setDateFilter(null);
+  // Handle view mode changes (followed, discovery, all)
+  const handleViewModeChange = (mode: 'followed' | 'discovery' | 'all') => {
+    switch (mode) {
+      case 'followed':
+        setShowFollowedOnly(true);
+        setShowDiscoveryMode(false);
+        break;
+      case 'discovery':
+        setShowFollowedOnly(false);
+        setShowDiscoveryMode(true);
+        setDateFilter(null);
+        break;
+      case 'all':
+        setShowFollowedOnly(false);
+        setShowDiscoveryMode(false);
+        setDateFilter(null);
+        break;
     }
   };
   const {
@@ -40,7 +52,7 @@ const Index = () => {
     markAsRead,
     deleteArticle,
     refetch
-  } = useRealArticles(dateFilter, showFollowedOnly, showReadArticles);
+  } = useRealArticles(dateFilter, showFollowedOnly, showReadArticles, showDiscoveryMode);
   const isMobile = useIsMobile();
   const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
   const [searchQuery, setSearchQuery] = useState('');
@@ -158,7 +170,8 @@ const Index = () => {
                 dateFilter={dateFilter}
                 onDateFilterChange={setDateFilter}
                 showFollowedOnly={showFollowedOnly}
-                onShowFollowedOnlyChange={handleShowFollowedOnlyChange}
+                showDiscoveryMode={showDiscoveryMode}
+                onViewModeChange={handleViewModeChange}
                 showReadArticles={showReadArticles}
                 onShowReadArticlesChange={setShowReadArticles}
                 onTogglePin={togglePin}
@@ -220,7 +233,8 @@ const Index = () => {
                           dateFilter={dateFilter}
                           onDateFilterChange={setDateFilter}
                           showFollowedOnly={showFollowedOnly}
-                          onShowFollowedOnlyChange={handleShowFollowedOnlyChange}
+                          showDiscoveryMode={showDiscoveryMode}
+                          onViewModeChange={handleViewModeChange}
                           showReadArticles={showReadArticles}
                           onShowReadArticlesChange={setShowReadArticles}
                           onTogglePin={togglePin}
@@ -299,7 +313,7 @@ const Index = () => {
                     </Button>
                   </div>}
               </div> : <div className="space-y-4">
-                {regularArticles.map(item => <NewsCard key={item.id} news={item} onTogglePin={togglePin} onMarkAsRead={markAsRead} onDelete={deleteArticle} onOpenArticle={handleOpenArticle} onSourceClick={handleSourceClick} />)}
+                {regularArticles.map(item => <NewsCard key={item.id} news={item} onTogglePin={togglePin} onMarkAsRead={markAsRead} onDelete={deleteArticle} onOpenArticle={handleOpenArticle} onSourceClick={handleSourceClick} isDiscoveryMode={showDiscoveryMode} />)}
               </div>}
           </div>
         </div>

+ 1 - 0
src/types/news.ts

@@ -13,6 +13,7 @@ export interface NewsItem {
   url?: string;
   imageUrl?: string;
   feedId?: string;
+  isDiscovery?: boolean;
 }
 
 export interface NewsCategory {