"use client"; import { useState, useEffect, useCallback } from "react"; import { Article, Folder } from "@/lib/types"; import { useReaderSettings, useTTSSettings } from "@/hooks/useSettings"; import { useTTS } from "@/hooks/useTTS"; import { ArticleList } from "@/components/ArticleList"; import { Reader } from "@/components/Reader"; import { SettingsPanel } from "@/components/SettingsPanel"; import { TTSControls } from "@/components/TTSControls"; import { AddArticle } from "@/components/AddArticle"; import { BookOpen, Star, Archive, Menu, Search, FolderIcon, Settings, BarChart3, X, } from "lucide-react"; import Link from "next/link"; type FilterType = "all" | "favorites" | "archived" | "folder" | "search"; export default function Home() { const [articles, setArticles] = useState([]); const [folders, setFolders] = useState([]); const [selectedArticle, setSelectedArticle] = useState
(null); const [filter, setFilter] = useState("all"); const [selectedFolderId, setSelectedFolderId] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const [isSearching, setIsSearching] = useState(false); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [isLoading, setIsLoading] = useState(true); const [stats, setStats] = useState<{ streak: number; todayCount: number } | null>(null); const [readerSettings, setReaderSettings] = useReaderSettings(); const [ttsSettings, setTTSSettings] = useTTSSettings(); const tts = useTTS({ settings: ttsSettings, text: selectedArticle?.textContent || "", }); // Apply theme (with auto-scheduling) useEffect(() => { if (readerSettings.autoTheme) { const hour = new Date().getHours(); const isDaytime = hour >= 6 && hour < 18; const theme = isDaytime ? readerSettings.dayTheme : readerSettings.nightTheme; document.documentElement.setAttribute("data-theme", theme); } else { document.documentElement.setAttribute("data-theme", readerSettings.theme); } }, [readerSettings]); // Fetch articles const fetchArticles = useCallback(async () => { try { let url = `/api/articles?filter=${filter}`; if (filter === "folder" && selectedFolderId) { url = `/api/articles?folderId=${selectedFolderId}`; } const response = await fetch(url); if (response.ok) { const data = await response.json(); setArticles(data); } } catch (error) { console.error("Failed to fetch articles:", error); } finally { setIsLoading(false); } }, [filter, selectedFolderId]); // Fetch folders const fetchFolders = useCallback(async () => { try { const response = await fetch("/api/folders"); if (response.ok) { setFolders(await response.json()); } } catch (error) { console.error("Failed to fetch folders:", error); } }, []); // Fetch stats const fetchStats = useCallback(async () => { try { const response = await fetch("/api/stats?days=7"); if (response.ok) { const data = await response.json(); const today = new Date().toISOString().split("T")[0]; const todayStats = data.daily.find((d: { date: string }) => d.date === today); setStats({ streak: data.streak, todayCount: todayStats?.articlesRead || 0, }); } } catch (error) { console.error("Failed to fetch stats:", error); } }, []); useEffect(() => { fetchArticles(); fetchFolders(); fetchStats(); }, [fetchArticles, fetchFolders, fetchStats]); // Search const handleSearch = async () => { if (!searchQuery.trim()) { setFilter("all"); return; } setIsSearching(true); try { const response = await fetch(`/api/search?q=${encodeURIComponent(searchQuery)}`); if (response.ok) { const data = await response.json(); setArticles(data); setFilter("search"); } } catch (error) { console.error("Search failed:", error); } finally { setIsSearching(false); } }; // Add article const handleAddArticle = async (url: string) => { const response = await fetch("/api/articles", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url }), }); if (!response.ok) { const data = await response.json(); throw new Error(data.error || "Failed to add article"); } await fetchArticles(); fetchStats(); }; // Toggle favorite const handleToggleFavorite = async (id: string, isFavorite: boolean) => { await fetch(`/api/articles/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ isFavorite }), }); setArticles((prev) => prev.map((a) => (a.id === id ? { ...a, isFavorite } : a)) ); if (selectedArticle?.id === id) { setSelectedArticle((prev) => (prev ? { ...prev, isFavorite } : null)); } }; // Toggle archive const handleToggleArchive = async (id: string, isArchived: boolean) => { await fetch(`/api/articles/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ isArchived }), }); await fetchArticles(); fetchStats(); if (selectedArticle?.id === id) { setSelectedArticle(null); } }; // Delete article const handleDelete = async (id: string) => { await fetch(`/api/articles/${id}`, { method: "DELETE" }); await fetchArticles(); if (selectedArticle?.id === id) { setSelectedArticle(null); } }; // Reading view if (selectedArticle) { return ( <> { tts.stop(); setSelectedArticle(null); }} onToggleFavorite={() => handleToggleFavorite(selectedArticle.id, !selectedArticle.isFavorite) } onToggleArchive={() => { handleToggleArchive(selectedArticle.id, !selectedArticle.isArchived); }} onDelete={() => { handleDelete(selectedArticle.id); }} onOpenSettings={() => setIsSettingsOpen(true)} > setIsSettingsOpen(false)} readerSettings={readerSettings} onReaderSettingsChange={setReaderSettings} ttsSettings={ttsSettings} onTTSSettingsChange={setTTSSettings} availableVoices={tts.voices} /> ); } // List view return (
{/* Sidebar */} {/* Main content */}
{/* Search */}
setSearchQuery(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleSearch()} placeholder="Search articles..." className="w-full pl-10 pr-4 py-2 rounded-lg border border-[var(--border)] bg-[var(--background)] text-sm" /> {searchQuery && ( )}
{articles.length} articles
{isLoading || isSearching ? (
{isSearching ? "Searching..." : "Loading..."}
) : ( )}
setIsSettingsOpen(false)} readerSettings={readerSettings} onReaderSettingsChange={setReaderSettings} ttsSettings={ttsSettings} onTTSSettingsChange={setTTSSettings} availableVoices={tts.voices} />
); }