import React, { useEffect, useState, useCallback } from "react"; import { getFeed } from "../../api/client"; import Card from "../../components/common/Card"; import { Loader2, Clock, Bookmark, MessageSquare, Highlighter, } from "lucide-react"; import { useStore } from "@nanostores/react"; import { $user } from "../../store/auth"; import type { AnnotationItem } from "../../types"; import { Tabs, EmptyState, Button } from "../../components/ui"; import LayoutToggle from "../../components/ui/LayoutToggle"; import { $feedLayout } from "../../store/feedLayout"; import { clsx } from "clsx"; interface FeedProps { initialType?: string; motivation?: string; showTabs?: boolean; emptyMessage?: string; } function FeedContent({ type, motivation, emptyMessage, layout, }: { type: string; motivation?: string; emptyMessage: string; layout: "list" | "mosaic"; }) { const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [hasMore, setHasMore] = useState(false); const [offset, setOffset] = useState(0); const LIMIT = 50; useEffect(() => { let cancelled = false; getFeed({ type, motivation, limit: LIMIT, offset: 0 }) .then((data) => { if (cancelled) return; const fetched = data?.items || []; setItems(fetched); setHasMore(fetched.length >= LIMIT); setOffset(fetched.length); setLoading(false); }) .catch((e) => { if (cancelled) return; console.error(e); setItems([]); setHasMore(false); setLoading(false); }); return () => { cancelled = true; }; }, [type, motivation]); const loadMore = useCallback(async () => { setLoadingMore(true); try { const data = await getFeed({ type, motivation, limit: LIMIT, offset }); const fetched = data?.items || []; setItems((prev) => [...prev, ...fetched]); setHasMore(fetched.length >= LIMIT); setOffset((prev) => prev + fetched.length); } catch (e) { console.error(e); } finally { setLoadingMore(false); } }, [type, motivation, offset]); const handleDelete = (uri: string) => { setItems((prev) => prev.filter((i) => i.uri !== uri)); }; if (loading) { return (

Loading feed...

); } if (items.length === 0) { return ( } title="Nothing here yet" message={emptyMessage} /> ); } const loadMoreButton = hasMore && (
); if (layout === "mosaic") { return ( <>
{items.map((item) => (
))}
{loadMoreButton} ); } return ( <>
{items.map((item) => ( ))}
{loadMoreButton} ); } export default function Feed({ initialType = "all", motivation, showTabs = true, emptyMessage = "No items found.", }: FeedProps) { const user = useStore($user); const layout = useStore($feedLayout); const [activeTab, setActiveTab] = useState(initialType); const [activeFilter, setActiveFilter] = useState( motivation, ); const handleTabChange = (id: string) => { if (id === activeTab) return; setActiveTab(id); window.scrollTo({ top: 0, behavior: "smooth" }); }; const handleFilterChange = (id: string) => { const next = id === "all" ? undefined : id; if (next === activeFilter) return; setActiveFilter(next); window.scrollTo({ top: 0, behavior: "smooth" }); }; const tabs = [ { id: "all", label: "Recent" }, { id: "popular", label: "Popular" }, { id: "shelved", label: "Shelved" }, { id: "margin", label: "Margin" }, { id: "semble", label: "Semble" }, ]; const filters = [ { id: "all", label: "All", icon: null }, { id: "commenting", label: "Annotations", icon: MessageSquare }, { id: "highlighting", label: "Highlights", icon: Highlighter }, { id: "bookmarking", label: "Bookmarks", icon: Bookmark }, ]; return (
{!user && (

Welcome to Margin

Annotate, highlight, and bookmark anything on the web.

)} {showTabs && (
{filters.map((f) => { const isActive = f.id === "all" ? !activeFilter : activeFilter === f.id; return ( ); })}
)}
); }