-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch '1.x' into feature/SUM-49--favoritesPage
- Loading branch information
Showing
25 changed files
with
1,928 additions
and
1,029 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,178 +1,156 @@ | ||
"use client"; | ||
|
||
import algoliasearch from "algoliasearch/lite"; | ||
import {useHits, useSearchBox} from "react-instantsearch"; | ||
import {useHits, useSearchBox, usePagination} from "react-instantsearch"; | ||
import {InstantSearchNext} from "react-instantsearch-nextjs"; | ||
import Link from "@components/elements/link"; | ||
import {H2} from "@components/elements/headers"; | ||
import Image from "next/image"; | ||
import {useRef} from "react"; | ||
import {useEffect, useMemo, useRef} from "react"; | ||
import Button from "@components/elements/button"; | ||
import {UseSearchBoxProps} from "react-instantsearch"; | ||
import {useRouter, useSearchParams} from "next/navigation"; | ||
import {UseHitsProps} from "react-instantsearch-core/dist/es/connectors/useHits"; | ||
import {Hit as HitType} from "instantsearch.js"; | ||
import {IndexUiState} from "instantsearch.js/es/types/ui-state"; | ||
import {MagnifyingGlassIcon} from "@heroicons/react/20/solid"; | ||
import DefaultResult, {AlgoliaHit} from "@components/algolia-results/default"; | ||
|
||
type Props = { | ||
appId: string | ||
searchIndex: string | ||
searchApiKey: string | ||
initialUiState?: IndexUiState | ||
} | ||
|
||
const AlgoliaSearch = ({appId, searchIndex, searchApiKey}: Props) => { | ||
const searchClient = algoliasearch(appId, searchApiKey); | ||
const searchParams = useSearchParams(); | ||
const AlgoliaSearch = ({appId, searchIndex, searchApiKey, initialUiState = {}}: Props) => { | ||
const searchClient = useMemo(() => algoliasearch(appId, searchApiKey), [appId, searchApiKey]) | ||
|
||
return ( | ||
<div> | ||
<InstantSearchNext | ||
indexName={searchIndex} | ||
searchClient={searchClient} | ||
initialUiState={{ | ||
[searchIndex]: {query: searchParams.get("q") || ""}, | ||
}} | ||
initialUiState={{[searchIndex]: initialUiState}} | ||
future={{preserveSharedStateOnUnmount: true}} | ||
> | ||
<div className="space-y-10"> | ||
<SearchBox/> | ||
<HitList/> | ||
</div> | ||
<SearchForm/> | ||
</InstantSearchNext> | ||
</div> | ||
) | ||
} | ||
|
||
const HitList = (props: UseHitsProps) => { | ||
const {hits} = useHits(props); | ||
if (hits.length === 0) { | ||
return ( | ||
<p>No results for your search. Please try another search.</p> | ||
) | ||
} | ||
const SearchForm = () => { | ||
|
||
return ( | ||
<ul className="list-unstyled"> | ||
{hits.map(hit => | ||
<li key={hit.objectID} className="border-b border-gray-300 last:border-0"> | ||
<Hit hit={hit as unknown as AlgoliaHit}/> | ||
</li> | ||
)} | ||
</ul> | ||
) | ||
} | ||
const router = useRouter() | ||
const searchParams = useSearchParams(); | ||
|
||
type AlgoliaHit = { | ||
url: string | ||
title: string | ||
summary?: string | ||
photo?: string | ||
updated?: number | ||
} | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
const {query, refine} = useSearchBox({}); | ||
|
||
useEffect(() => { | ||
const params = new URLSearchParams(searchParams.toString()); | ||
params.delete("q") | ||
|
||
const Hit = ({hit}: { hit: AlgoliaHit }) => { | ||
const hitUrl = new URL(hit.url); | ||
// Keyword search. | ||
if (query) params.set("q", query) | ||
router.replace(`?${params.toString()}`, {scroll: false}) | ||
}, [router, searchParams, query]); | ||
|
||
return ( | ||
<article className="@container flex justify-between gap-20 py-12"> | ||
<div> | ||
<H2 className="text-m2"> | ||
<Link href={hit.url.replace(hitUrl.origin, "")}> | ||
{hit.title} | ||
</Link> | ||
</H2> | ||
<p>{hit.summary}</p> | ||
|
||
{hit.updated && | ||
<div className="text-2xl"> | ||
Last Updated: {new Date(hit.updated * 1000).toLocaleDateString("en-us", { | ||
month: "long", | ||
day: "numeric", | ||
year: "numeric" | ||
})} | ||
</div> | ||
} | ||
</div> | ||
|
||
{hit.photo && | ||
<div className="hidden @6xl:block relative shrink-0 aspect-1 h-[150px] w-[150px]"> | ||
<Image | ||
className="object-cover" | ||
src={hit.photo.replace(hitUrl.origin, `${process.env.NEXT_PUBLIC_DRUPAL_BASE_URL}`)} | ||
alt="" | ||
fill | ||
<div> | ||
<form role="search" aria-labelledby="page-title" onSubmit={(e) => e.preventDefault()}> | ||
<div className="max-w-6xl mx-auto mb-20 flex gap-5 items-center"> | ||
<label className="sr-only" htmlFor="search-input"> | ||
Keywords Search | ||
</label> | ||
<input | ||
id="search-input" | ||
className="flex-grow border-0 border-b border-black-30 text-m2" | ||
ref={inputRef} | ||
autoComplete="on" | ||
autoCapitalize="off" | ||
spellCheck={false} | ||
maxLength={512} | ||
type="search" | ||
placeholder="Search" | ||
defaultValue={query} | ||
/> | ||
|
||
<button | ||
type="submit" | ||
onClick={() => refine(inputRef.current?.value || "")} | ||
> | ||
<span className="sr-only">Submit Search</span> | ||
<MagnifyingGlassIcon width={40} className="bg-cardinal-red text-white rounded-full p-3 block"/> | ||
</button> | ||
|
||
<Button | ||
className="my-16" | ||
centered | ||
buttonElem | ||
onClick={() => { | ||
refine("") | ||
if (inputRef.current) inputRef.current.value = "" | ||
}} | ||
> | ||
Reset | ||
</Button> | ||
</div> | ||
} | ||
</article> | ||
</form> | ||
<HitList/> | ||
</div> | ||
) | ||
} | ||
|
||
const HitList = () => { | ||
const {hits} = useHits<HitType<AlgoliaHit>>({}); | ||
const {currentRefinement: currentPage, pages, nbPages, nbHits, refine: goToPage} = usePagination({padding: 2}) | ||
|
||
const SearchBox = (props?: UseSearchBoxProps) => { | ||
const router = useRouter(); | ||
const {query, refine} = useSearchBox(props); | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
|
||
if (query) { | ||
router.replace(`?q=${query}`, {scroll: false}) | ||
if (hits.length === 0) { | ||
return ( | ||
<p>No results for your search. Please try another search.</p> | ||
) | ||
} | ||
|
||
return ( | ||
<form | ||
className="flex flex-col gap-10" | ||
action="" | ||
role="search" | ||
noValidate | ||
onSubmit={(e) => { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
inputRef.current?.blur(); | ||
refine(inputRef.current?.value || ""); | ||
}} | ||
onReset={(event) => { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
refine(""); | ||
|
||
if (inputRef.current) { | ||
inputRef.current.value = ""; | ||
inputRef.current.focus(); | ||
} | ||
}} | ||
> | ||
<div className="flex flex-col"> | ||
<label className="font-bold" htmlFor="search-input"> | ||
Keywords<span className="sr-only"> Search</span> | ||
</label> | ||
<input | ||
id="search-input" | ||
className="rounded-full hocus:shadow-2xl max-w-xl h-20 text-m1" | ||
ref={inputRef} | ||
autoComplete="on" | ||
autoCorrect="on" | ||
autoCapitalize="off" | ||
spellCheck={false} | ||
maxLength={512} | ||
type="search" | ||
required | ||
defaultValue={query} | ||
autoFocus | ||
/> | ||
</div> | ||
<div className="flex gap-10"> | ||
<Button type="submit"> | ||
Submit | ||
</Button> | ||
<Button | ||
secondary | ||
type="reset" | ||
className={query.length === 0 ? "hidden" : undefined} | ||
> | ||
Reset | ||
</Button> | ||
</div> | ||
<div className="sr-only" aria-live="polite" aria-atomic>Showing results for {query}</div> | ||
</form> | ||
); | ||
<div> | ||
<div aria-live="polite">{nbHits} {nbHits > 1 ? "Results" : "Result"}</div> | ||
|
||
<ul className="list-unstyled"> | ||
{hits.map(hit => | ||
<li key={hit.objectID} className="border-b border-gray-300 last:border-0"> | ||
<DefaultResult hit={hit}/> | ||
</li> | ||
)} | ||
</ul> | ||
|
||
{pages.length > 1 && | ||
<nav aria-label="Search results pager"> | ||
<ul className="list-unstyled flex justify-between"> | ||
{pages[0] > 0 && | ||
<li> | ||
<button onClick={() => goToPage(0)}> | ||
First | ||
</button> | ||
</li> | ||
} | ||
|
||
{pages.map(pageNum => | ||
<li key={`page-${pageNum}`} aria-current={currentPage === pageNum}> | ||
<button onClick={() => goToPage(pageNum)}> | ||
{pageNum + 1} | ||
</button> | ||
</li> | ||
)} | ||
|
||
{pages[pages.length - 1] !== nbPages && | ||
<li> | ||
<button onClick={() => goToPage(nbPages - 1)}> | ||
Last | ||
</button> | ||
</li> | ||
} | ||
</ul> | ||
</nav> | ||
} | ||
</div> | ||
) | ||
} | ||
|
||
export default AlgoliaSearch; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.