import { Link } from 'react-router'; import RouteErrorState from '../../../components/ui/error/RouteErrorState'; import ClientOnly from '../../../components/ClientOnly'; import BreweryCard from '../components/directory/BreweryCard'; import { getBreweries, getBreweryLocations, type SimplifiedBrewery } from '../breweries.server'; import type { Route } from './+types/brewery-directory'; import { lazy, Suspense, useEffect, useRef, useState } from 'react'; import { useFetcher } from 'react-router'; import type { BreweryMapPin } from '../components/directory/BreweryMap'; const PAGE_SIZE = 12; const MAP_PLACEHOLDER_CLASS = 'skeleton h-[28rem] md:h-[34rem] w-full rounded-box'; export const meta = ({}: Route.MetaArgs) => { return [{ title: 'Brewery Directory | The Biergarten App' }]; }; export const loader = async ({ request }: Route.LoaderArgs) => { const url = new URL(request.url); const limit = Number(url.searchParams.get('limit')) || PAGE_SIZE; const offset = Number(url.searchParams.get('offset')) || 0; const [breweries, locations] = await Promise.all([ getBreweries(limit, offset), getBreweryLocations(), ]); return { breweries, locations, limit, offset }; }; const BreweryMap = lazy(() => import('../components/directory/BreweryMap')); const toPins = (breweries: SimplifiedBrewery[]): BreweryMapPin[] => breweries .filter((brewery) => brewery.location?.coordinates) .map((brewery) => ({ id: brewery.breweryPostId, name: brewery.breweryName, latitude: brewery.location!.coordinates!.latitude, longitude: brewery.location!.coordinates!.longitude, location: brewery.location, })); const Breweries = ({ loaderData }: Route.ComponentProps) => { const [allBreweries, setAllBreweries] = useState(loaderData.breweries); const [offset, setOffset] = useState(loaderData.breweries.length); const [hasMore, setHasMore] = useState(loaderData.breweries.length === loaderData.limit); const [loadedPage, setLoadedPage] = useState(undefined); const fetcher = useFetcher(); const sentinelRef = useRef(null); if (fetcher.data && fetcher.data !== loadedPage) { setLoadedPage(fetcher.data); setAllBreweries((prev) => [...prev, ...fetcher.data!.breweries]); setOffset((prev) => prev + fetcher.data!.breweries.length); setHasMore(fetcher.data.breweries.length === PAGE_SIZE); } useEffect(() => { const sentinel = sentinelRef.current; if (!sentinel || !hasMore) return; const observer = new IntersectionObserver((entries) => { if (entries[0]?.isIntersecting && fetcher.state === 'idle') { fetcher.load(`/breweries/directory?limit=${PAGE_SIZE}&offset=${offset}`); } }); observer.observe(sentinel); return () => observer.disconnect(); }, [hasMore, offset, fetcher]); const pins = toPins(loaderData.locations); return (
← Back to Breweries

Full brewery directory

All of our partner breweries, newest first.

}> {() => ( }> )} {allBreweries.length === 0 ? (

No breweries found yet.

) : (
{allBreweries.map((brewery) => ( ))} {fetcher.state !== 'idle' && Array.from({ length: PAGE_SIZE }).map((_, index) => (
))}
)} {hasMore &&
}
); }; export default Breweries; export const ErrorBoundary = ({ error }: Route.ErrorBoundaryProps) => ( );