import { useEffect, useState, useCallback, useRef } from 'react'; import { useSearchParams } from 'react-router-dom'; import { SearchApi } from '../api/client.js'; import EmailRow from '../components/EmailRow.jsx'; import BulkToolbar from '../components/BulkToolbar.jsx'; import useSelection from '../hooks/useSelection.js'; import useListKeyboardNav from '../hooks/useListKeyboardNav.js'; import useSavedSearches from '../hooks/useSavedSearches.js'; const PAGE_SIZE = 50; export default function SearchResults() { const [searchParams] = useSearchParams(); const q = searchParams.get('q') ?? ''; 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 { selected, toggle, clear, removeIds, selectedIds } = useSelection(); const focusedId = useListKeyboardNav(emails, (id) => setEmails((prev) => prev.filter((x) => x.id !== id))); const { searches: savedSearches, add: addSavedSearch } = useSavedSearches(); const isSaved = savedSearches.some((s) => s.query === q); const handleSave = () => { const label = window.prompt('Name this saved search:', q); if (label && label.trim()) addSavedSearch(label.trim(), q); }; useEffect(() => { setEmails([]); setTotalCount(null); setPage(1); setHasMore(true); setError(null); clear(); }, [q, clear]); const fetchPage = useCallback((p) => { if (!q.trim()) return; setLoading(true); SearchApi.query(q, 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('Search failed. Please try again.')) .finally(() => setLoading(false)); }, [q]); useEffect(() => { fetchPage(1); }, [fetchPage]); 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 (