server/assets/js/react/pages/Search.tsx

100 lines
2.8 KiB
TypeScript
Raw Normal View History

2023-12-04 13:42:32 +00:00
import * as React from "react"
2023-12-06 02:46:27 +00:00
import type { SearchParams } from "../types/SearchParams"
2023-12-04 13:42:32 +00:00
import { Container } from "../components/Container"
2023-12-06 02:46:27 +00:00
import { FadeIn } from "../components/FadeIn"
2023-12-04 13:42:32 +00:00
import { FallbackMessage } from "../components/FallbackMessage"
import { Loading } from "../components/Loading"
2023-12-04 20:35:01 +00:00
import { SearchResult } from "../components/SearchResult"
2023-12-07 14:58:40 +00:00
import { SortModal } from "../components/SortModal"
import { SortScroll } from "../components/SortScroll"
2023-12-04 23:04:30 +00:00
import { defaultSearchParams } from "../types/SearchParams"
2023-12-06 02:46:27 +00:00
import { useCoachesInfiniteQuery } from "../utils/queries"
function SearchResults({ searchParams }: { searchParams: SearchParams }) {
const { isLoading, isError, data, fetchNextPage, hasNextPage, isFetching } =
useCoachesInfiniteQuery(searchParams)
const resultsRef = React.useRef<HTMLDivElement | null>(null)
const handleScroll = React.useCallback(() => {
if (!resultsRef.current) {
return
}
const resultRect = resultsRef.current.getBoundingClientRect()
if (!isFetching && hasNextPage && resultRect.bottom < 800) {
fetchNextPage()
}
}, [isFetching, hasNextPage, resultsRef])
2023-12-04 13:42:32 +00:00
2023-12-06 02:46:27 +00:00
React.useEffect(() => {
window.addEventListener("scroll", handleScroll, { passive: true })
return () => {
window.removeEventListener("scroll", handleScroll)
}
}, [isFetching, hasNextPage, resultsRef])
2023-12-04 13:42:32 +00:00
2023-12-04 20:35:01 +00:00
if (isLoading) {
2023-12-04 23:16:50 +00:00
return <Loading className="mt-40" loading />
2023-12-04 20:35:01 +00:00
}
if (isError) {
return (
<FallbackMessage
2023-12-04 23:16:50 +00:00
className="mt-40"
title="Blunder!"
body="Our tech team is working to restore the checkmate."
2023-12-04 20:35:01 +00:00
/>
)
}
2023-12-04 13:42:32 +00:00
2023-12-04 20:35:01 +00:00
return (
2023-12-06 02:46:27 +00:00
<>
<div
ref={resultsRef}
className="mt-10 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
{data?.pages.map((group, index) => (
<React.Fragment key={index}>
{group.map((coach) => (
<FadeIn
key={`${coach.site}-${coach.username}`}
2023-12-07 14:44:59 +00:00
className="flex flex-col"
2023-12-06 02:46:27 +00:00
>
2023-12-07 14:44:59 +00:00
<SearchResult coach={coach} />
2023-12-06 02:46:27 +00:00
</FadeIn>
))}
</React.Fragment>
))}
</div>
{hasNextPage ? <Loading className="pt-20" loading /> : null}
</>
2023-12-04 20:35:01 +00:00
)
2023-12-04 13:42:32 +00:00
}
export function Search() {
2023-12-04 20:35:01 +00:00
const [searchParams, setSearchParams] = React.useState(defaultSearchParams)
2023-12-04 23:04:30 +00:00
const [modalOpen, setModalOpen] = React.useState(false)
2023-12-04 13:42:32 +00:00
return (
<Container className="pt-8">
2023-12-07 14:58:40 +00:00
<SortScroll
2023-12-04 20:35:01 +00:00
params={searchParams}
onSelect={setSearchParams}
2023-12-04 23:04:30 +00:00
onModal={() => setModalOpen(true)}
/>
2023-12-07 14:58:40 +00:00
<SortModal
2023-12-04 23:04:30 +00:00
open={modalOpen}
defaultValues={searchParams}
onClose={() => setModalOpen(false)}
onSubmit={(q) => {
setSearchParams(q)
setModalOpen(false)
}}
2023-12-04 20:35:01 +00:00
/>
2023-12-06 02:46:27 +00:00
<SearchResults searchParams={searchParams} />
2023-12-04 13:42:32 +00:00
</Container>
)
}