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 (

{meta.icon} {meta.label}

{totalCount !== null && ( {totalCount.toLocaleString()} email{totalCount !== 1 ? 's' : ''} )}
{error &&
{error}
} { if (action === 'trash' || action === 'archive') { setEmails((prev) => prev.filter((x) => !ids.includes(x.id))); removeIds(ids); } }} />
{loading && emails.length === 0 && !error && } {!loading && !error && emails.length === 0 && ( )} {emails.length > 0 && ( {emails.map((e) => ( setSelectedEmail(email)} onRemove={(id) => { setEmails((prev) => prev.filter((x) => x.id !== id)); setSelectedEmail((cur) => (cur?.id === id ? null : cur)); }} /> ))}
)} {/* Sentinel โ€” triggers next page load when scrolled into view */}
{loading && emails.length > 0 &&
Loadingโ€ฆ
} {!hasMore && emails.length > 0 && (
โ€” {emails.length.toLocaleString()} emails โ€”
)}
{selectedEmail && ( )}
); }