import { useEffect, useState, useCallback, useRef } from 'react'; import { useParams } from 'react-router-dom'; import { SearchApi } from '../api/client.js'; import EmailRow from '../components/EmailRow.jsx'; import EmailDetail from '../components/EmailDetail.jsx'; import BulkToolbar from '../components/BulkToolbar.jsx'; import { Skeleton, EmptyState } from '../components/ui'; import useSelection from '../hooks/useSelection.js'; import useListKeyboardNav from '../hooks/useListKeyboardNav.js'; const FOLDER_META = { inbox: { icon: '๐ฅ', label: 'Inbox' }, allmail: { icon: '๐ฌ', label: 'All Mail' }, unread: { icon: '๐ต', label: 'Unread' }, starred: { icon: 'โญ', label: 'Starred' }, sent: { icon: '๐ค', label: 'Sent' }, drafts: { icon: 'โ๏ธ', label: 'Drafts' }, archive: { icon: '๐ฆ', label: 'Archive' }, spam: { icon: '๐ซ', label: 'Spam' }, trash: { icon: '๐๏ธ', label: 'Trash' }, unlabeled: { icon: '๐ท๏ธ', label: 'Unlabeled' }, pinned: { icon: '๐', label: 'Pinned' }, readlater: { icon: '๐', label: 'Read Later' }, large: { icon: '๐', label: 'Large Mail' }, old: { icon: '๐ฐ๏ธ', label: 'Old Mail' }, automated: { icon: '๐ค', label: 'Automated' }, noreply: { icon: '๐', label: 'No-Reply' }, shopping: { icon: '๐๏ธ', label: 'Online Shopping' }, gaming: { icon: '๐ฎ', label: 'Gaming' }, finance: { icon: '๐ณ', label: 'Finance & Insurance' }, sales: { icon: '๐ท๏ธ', label: 'Seasonal Sales' }, ridesharing:{ icon: '๐', label: 'Ride Sharing' }, food: { icon: '๐', label: 'Food Delivery' }, social: { icon: '๐ฑ', label: 'Social Notifications' }, wellness: { icon: '๐', label: 'Wellness & Sport' }, }; const PAGE_SIZE = 50; function ListSkeleton({ rows = 6 }) { return (
); } export default function FolderView() { const { slug } = useParams(); const meta = FOLDER_META[slug] ?? { icon: '๐', label: slug }; const [emails, setEmails] = useState([]); const [totalCount, setTotalCount] = useState(null); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [selectedEmail, setSelectedEmail] = useState(null); const { selected, toggle, clear, removeIds, selectedIds } = useSelection(); const focusedId = useListKeyboardNav(emails, (id) => setEmails((prev) => prev.filter((x) => x.id !== id))); // Reset when the folder changes useEffect(() => { setEmails([]); setTotalCount(null); setPage(1); setHasMore(true); setError(null); setSelectedEmail(null); clear(); }, [slug, clear]); // Fetch a page and append results const fetchPage = useCallback((p) => { setLoading(true); SearchApi.folder(slug, p, PAGE_SIZE) .then((r) => { setEmails((prev) => p === 1 ? r.items : [...prev, ...r.items]); setTotalCount(r.totalCount); setHasMore(p < r.totalPages); setPage(p); }) .catch(() => setError('Failed to load emails.')) .finally(() => setLoading(false)); }, [slug]); // Initial load useEffect(() => { fetchPage(1); }, [fetchPage]); // Sentinel div observed to trigger next page const sentinelRef = useRef(null); useEffect(() => { const el = sentinelRef.current; if (!el) return; const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && hasMore && !loading) { fetchPage(page + 1); } }, { rootMargin: '200px' } ); observer.observe(el); return () => observer.disconnect(); }, [hasMore, loading, page, fetchPage]); return (