'use client'; import { useEffect, useRef, useState } from 'react'; import Link from 'next/link'; import { formatMinutes } from '@/lib/steam'; interface MapSummary { map_name: string; times_played: number; last_played: string; total_minutes: number; } export default function MapSearch() { const [q, setQ] = useState(''); const [maps, setMaps] = useState([]); const [loading, setLoading] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [hasMore, setHasMore] = useState(true); const offsetRef = useRef(0); const loadingMoreRef = useRef(false); const sentinelRef = useRef(null); async function fetchPage(searchQ: string, offset: number) { const params = new URLSearchParams({ q: searchQ, offset: String(offset) }); const res = await fetch(`/api/maps?${params.toString()}`); return res.json() as Promise<{ maps: MapSummary[]; hasMore: boolean }>; } useEffect(() => { const handle = setTimeout(async () => { setLoading(true); offsetRef.current = 0; const data = await fetchPage(q, 0); setMaps(data.maps); setHasMore(data.hasMore); offsetRef.current = data.maps.length; setLoading(false); }, 250); return () => clearTimeout(handle); }, [q]); async function loadMore() { if (loadingMoreRef.current || !hasMore || loading) return; loadingMoreRef.current = true; setLoadingMore(true); const data = await fetchPage(q, offsetRef.current); setMaps((prev) => [...prev, ...data.maps]); setHasMore(data.hasMore); offsetRef.current += data.maps.length; setLoadingMore(false); loadingMoreRef.current = false; } useEffect(() => { const el = sentinelRef.current; if (!el) return; const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting) loadMore(); }, { rootMargin: '400px' }, ); observer.observe(el); return () => observer.disconnect(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasMore, loading, q]); return (
setQ(e.target.value)} className="w-full bg-base-panel border border-base-border rounded-lg px-4 py-3 text-ink placeholder:text-ink-faint focus:border-accent/50 transition-colors" />
{loading ? (
loading…
) : maps.length === 0 ? (
No maps found.
) : ( maps.map((m) => (
{m.map_name}
{m.times_played}× played · {formatMinutes(m.total_minutes)} total last played {new Date(m.last_played.replace(' ', 'T')).toLocaleString()}
)) )} {!loading && maps.length > 0 && (
{loadingMore ? ( loading more… ) : !hasMore ? ( — end of list — ) : null}
)}
); }