Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Add suspense boundary around search page results #101

Merged
merged 37 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1c379e6
Add look for temp search page stand-in
acouch Jun 24, 2024
9b527a7
Add sorting and layout update
acouch Jun 25, 2024
6685ed3
Add query provider
acouch Jun 25, 2024
91632c4
Adds status to look search page
acouch Jun 25, 2024
0dc625e
Clean up status WIP
acouch Jun 26, 2024
c966fe7
Readding set items
acouch Jun 26, 2024
43500f9
Further Clean up status WIP
acouch Jun 26, 2024
fabc772
Finish logic for status update
acouch Jun 26, 2024
4f2db10
Re-add debounce for status
acouch Jun 26, 2024
61315a2
Add funding filter and update search filters
acouch Jun 29, 2024
85d8682
Update to new search page
acouch Jun 30, 2024
440ddbf
Add search filters without separate component
acouch Jul 1, 2024
d04de0c
Add agency to page and remove unnecessary components
acouch Jul 1, 2024
769c85f
Add pager
acouch Jul 2, 2024
504a11d
Move look to search
acouch Jul 3, 2024
8b0193a
Move look to search
acouch Jul 3, 2024
a3dbc28
Update page.tsx
acouch Jul 3, 2024
5f8e662
Update page.tsx
acouch Jul 3, 2024
419113d
Update SearchOpportunityAPI.ts
acouch Jul 3, 2024
2736162
Format fixes
acouch Jul 3, 2024
c5000b3
Update page.tsx
acouch Jul 5, 2024
70ead97
Add search result header into suspense
acouch Jul 5, 2024
72be27f
Lint fix
acouch Jul 5, 2024
f9ee121
Update tests
acouch Jul 5, 2024
b24e8a3
Update tests
acouch Jul 5, 2024
6243b10
Fix e2e tests
acouch Jul 5, 2024
28ebc9a
Update format
acouch Jul 5, 2024
5fa441f
Lint fix
acouch Jul 5, 2024
3a62b6f
Remove duplicate folder
acouch Jul 23, 2024
956e0fa
Update useSearchParamUpdater.ts to include comments
acouch Jul 23, 2024
924d447
Update Footer.tsx re-add maxh for logo
acouch Jul 23, 2024
8a076cb
Increase timeout for playrwight
acouch Jul 23, 2024
d1a4cec
Break up slow tests
acouch Jul 26, 2024
6d51e0a
Add review suggestions
acouch Jul 30, 2024
57f9c81
Update SearchFilterAccordion.test.tsx
acouch Jul 30, 2024
711c24c
Update SearchResultsHeader.tsx
acouch Aug 6, 2024
78da679
Update SearchResultsHeader.tsx
acouch Aug 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions frontend/src/app/[locale]/search/QueryProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client";
import { createContext, useCallback, useMemo, useState } from "react";
import { useSearchParams } from "next/navigation";

interface QueryContextParams {
queryTerm: string | null | undefined;
updateQueryTerm: (term: string) => void;
totalPages: string | null | undefined;
updateTotalPages: (page: string) => void;
totalResults: string;
updateTotalResults: (total: string) => void;
}

export const QueryContext = createContext({} as QueryContextParams);

export default function QueryProvider({
children,
}: {
children: React.ReactNode;
}) {
const searchParams = useSearchParams() || undefined;
const defaultTerm = searchParams?.get("query");
const [queryTerm, setQueryTerm] = useState(defaultTerm);
const [totalPages, setTotalPages] = useState("na");
const [totalResults, setTotalResults] = useState("");

const updateQueryTerm = useCallback((term: string) => {
setQueryTerm(term);
}, []);

const updateTotalResults = useCallback((total: string) => {
setTotalResults(total);
}, []);

const updateTotalPages = useCallback((page: string) => {
setTotalPages(page);
}, []);

const contextValue = useMemo(
() => ({
queryTerm,
updateQueryTerm,
totalPages,
updateTotalPages,
totalResults,
updateTotalResults,
}),
[
queryTerm,
updateQueryTerm,
totalPages,
updateTotalPages,
totalResults,
updateTotalResults,
],
);

return (
<QueryContext.Provider value={contextValue}>
{children}
</QueryContext.Provider>
);
}
122 changes: 0 additions & 122 deletions frontend/src/app/[locale]/search/SearchForm.tsx

This file was deleted.

20 changes: 0 additions & 20 deletions frontend/src/app/[locale]/search/actions.ts

This file was deleted.

119 changes: 70 additions & 49 deletions frontend/src/app/[locale]/search/error.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
"use client"; // Error components must be Client Components

import {
PaginationInfo,
SearchAPIResponse,
} from "src/types/search/searchResponseTypes";

import BetaAlert from "src/components/BetaAlert";
import Breadcrumbs from "src/components/Breadcrumbs";
import PageSEO from "src/components/PageSEO";
import { QueryParamData } from "src/services/search/searchfetcher/SearchFetcher";
import QueryProvider from "src/app/[locale]/search/QueryProvider";
import SearchBar from "src/components/search/SearchBar";
import SearchCallToAction from "src/components/search/SearchCallToAction";
import { SearchForm } from "src/app/[locale]/search/SearchForm";
import SearchFilterAccordion from "src/components/search/SearchFilterAccordion/SearchFilterAccordion";
import SearchOpportunityStatus from "src/components/search/SearchOpportunityStatus";
import SearchResultsHeader from "src/components/search/SearchResultsHeader";
import {
agencyOptions,
categoryOptions,
eligibilityOptions,
fundingOptions,
} from "src/components/search/SearchFilterAccordion/SearchFilterOptions";
import { SEARCH_CRUMBS } from "src/constants/breadcrumbs";
import { QueryParamData } from "src/services/search/searchfetcher/SearchFetcher";
import { useEffect } from "react";
import SearchErrorAlert from "src/components/search/error/SearchErrorAlert";

interface ErrorProps {
// Next's error boundary also includes a reset function as a prop for retries,
Expand All @@ -29,8 +37,8 @@ export default function Error({ error }: ErrorProps) {
// Parse it here.

let parsedErrorData;
const pagination_info = getErrorPaginationInfo();
let convertedSearchParams;

if (!isValidJSON(error.message)) {
// the error likely is just a string with a non-specific Server Component error when running the built app
// "An error occurred in the Server Components render. The specific message is omitted in production builds..."
Expand All @@ -46,11 +54,15 @@ export default function Error({ error }: ErrorProps) {
parsedErrorData.searchInputs,
);
}

const initialSearchResults: SearchAPIResponse = getErrorInitialSearchResults(
pagination_info,
parsedErrorData,
);
const {
agency,
category,
eligibility,
fundingInstrument,
query,
sortby,
status,
} = convertedSearchParams;

useEffect(() => {
console.error(error);
Expand All @@ -62,46 +74,55 @@ export default function Error({ error }: ErrorProps) {
title="Search Funding Opportunities"
description="Try out our experimental search page."
/>
<BetaAlert />
<Breadcrumbs breadcrumbList={SEARCH_CRUMBS} />
<SearchCallToAction />
<SearchForm
initialSearchResults={initialSearchResults}
requestURLQueryParams={convertedSearchParams}
/>
<QueryProvider>
<div className="grid-container">
<div className="search-bar">
<SearchBar query={query} />
</div>
<div className="grid-row grid-gap">
<div className="tablet:grid-col-4">
<SearchOpportunityStatus query={status} />
<SearchFilterAccordion
filterOptions={fundingOptions}
title="Funding instrument"
queryParamKey="fundingInstrument"
query={fundingInstrument}
/>
<SearchFilterAccordion
filterOptions={eligibilityOptions}
title="Eligibility"
queryParamKey="eligibility"
query={eligibility}
/>
<SearchFilterAccordion
filterOptions={agencyOptions}
title="Agency"
queryParamKey="agency"
query={agency}
/>
<SearchFilterAccordion
filterOptions={categoryOptions}
title="Category"
queryParamKey="category"
query={category}
/>
</div>
<div className="tablet:grid-col-8">
<SearchResultsHeader sortby={sortby} />
<div className="usa-prose">
<SearchErrorAlert />
</div>
</div>
</div>
</div>
</QueryProvider>
</>
);
}

/*
* Generate empty response data to render the full page on an error
* which otherwise may not have any data.
*/
function getErrorInitialSearchResults(
pagination_info: PaginationInfo,
parsedError: ParsedError,
) {
return {
errors: parsedError ? [{ ...parsedError }] : [{}],
data: [],
pagination_info,
status_code: parsedError?.status || -1,
message: parsedError?.message || "Unable to parse thrown error",
};
}

// There will be no pagination shown on an error
// so the values here just need to be valid for the page to
// load without error
function getErrorPaginationInfo() {
return {
order_by: "opportunity_id",
page_offset: 0,
page_size: 25,
sort_direction: "ascending",
total_pages: 1,
total_records: 0,
};
}

function convertSearchInputArraysToSets(
searchInputs: QueryParamData,
): QueryParamData {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/[locale]/search/loading.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import Spinner from "../../../components/Spinner";
import Spinner from "src/components/Spinner";

export default function Loading() {
// TODO (Issue #1937): Use translation utility for strings in this file
Expand Down
Loading
Loading