From b6ba3a1338c558c955ad7e7dfec7ffb3989e7d97 Mon Sep 17 00:00:00 2001 From: Brandon Tabaska Date: Wed, 7 Aug 2024 13:09:02 -0400 Subject: [PATCH 1/6] initial commit --- frontend/src/app/[locale]/search/page.tsx | 12 ++-- frontend/src/components/search/SearchBar.tsx | 13 ++-- .../components/search/SearchCallToAction.tsx | 11 +-- .../SearchFilterToggleAll.tsx | 7 +- .../search/SearchOpportunityStatus.tsx | 18 +++-- .../components/search/SearchResultsHeader.tsx | 6 +- .../search/SearchResultsListFetch.tsx | 19 ++--- .../search/SearchResultsListItem.tsx | 23 ++++--- .../src/components/search/SearchSortBy.tsx | 30 ++++---- frontend/src/i18n/messages/en/index.ts | 69 +++++++++++++++++++ 10 files changed, 148 insertions(+), 60 deletions(-) diff --git a/frontend/src/app/[locale]/search/page.tsx b/frontend/src/app/[locale]/search/page.tsx index c91209ae3..d8e15d44f 100644 --- a/frontend/src/app/[locale]/search/page.tsx +++ b/frontend/src/app/[locale]/search/page.tsx @@ -49,7 +49,7 @@ interface searchParamsTypes { function Search({ searchParams }: { searchParams: searchParamsTypes }) { unstable_setRequestLocale("en"); - const t = useTranslations("Process"); + const t = useTranslations("Search"); const convertedSearchParams = convertSearchParamsToProperTypes(searchParams); const { agency, @@ -71,7 +71,7 @@ function Search({ searchParams }: { searchParams: searchParamsTypes }) { return ( <> - + @@ -85,25 +85,25 @@ function Search({ searchParams }: { searchParams: searchParamsTypes }) { diff --git a/frontend/src/components/search/SearchBar.tsx b/frontend/src/components/search/SearchBar.tsx index 331c2819a..6da973957 100644 --- a/frontend/src/components/search/SearchBar.tsx +++ b/frontend/src/components/search/SearchBar.tsx @@ -3,6 +3,7 @@ import { Icon } from "@trussworks/react-uswds"; import { QueryContext } from "src/app/[locale]/search/QueryProvider"; import { useContext } from "react"; import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater"; +import { useTranslations } from "next-intl"; interface SearchBarProps { query: string | null | undefined; @@ -16,16 +17,18 @@ export default function SearchBar({ query }: SearchBarProps) { updateQueryParams("", "query", queryTerm, false); }; + const t = useTranslations("Search"); + return (
@@ -36,7 +39,7 @@ const SearchFilterToggleAll: React.FC = ({ }} disabled={isNoneSelected} > - Clear All + {t("filterToggleAll.clear")}
diff --git a/frontend/src/components/search/SearchOpportunityStatus.tsx b/frontend/src/components/search/SearchOpportunityStatus.tsx index 752647b9d..55240405d 100644 --- a/frontend/src/components/search/SearchOpportunityStatus.tsx +++ b/frontend/src/components/search/SearchOpportunityStatus.tsx @@ -3,6 +3,7 @@ import { Checkbox } from "@trussworks/react-uswds"; import { QueryContext } from "src/app/[locale]/search/QueryProvider"; import { useContext } from "react"; import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater"; +import { useTranslations } from "next-intl" interface StatusOption { id: string; @@ -14,12 +15,6 @@ interface SearchOpportunityStatusProps { query: Set; } -const statusOptions: StatusOption[] = [ - { id: "status-forecasted", label: "Forecasted", value: "forecasted" }, - { id: "status-posted", label: "Posted", value: "posted" }, - { id: "status-closed", label: "Closed", value: "closed" }, - { id: "status-archived", label: "Archived", value: "archived" }, -]; export default function SearchOpportunityStatus({ query, @@ -33,9 +28,18 @@ export default function SearchOpportunityStatus({ updateQueryParams(updated, "status", queryTerm); }; + const t = useTranslations("Search") + + const statusOptions: StatusOption[] = [ + { id: "status-forecasted", label: t("opportunityStatus.label.forecasted"), value: "forecasted" }, + { id: "status-posted", label: t("opportunityStatus.label.posted"), value: "posted" }, + { id: "status-closed", label: t("opportunityStatus.label.closed"), value: "closed" }, + { id: "status-archived", label: t("opportunityStatus.label.archived"), value: "archived" }, + ]; + return ( <> -

Opportunity status

+

{t("opportunityStatus.title")}

{statusOptions.map((option) => { return ( diff --git a/frontend/src/components/search/SearchResultsHeader.tsx b/frontend/src/components/search/SearchResultsHeader.tsx index 72296d0bf..e29be2c77 100644 --- a/frontend/src/components/search/SearchResultsHeader.tsx +++ b/frontend/src/components/search/SearchResultsHeader.tsx @@ -2,6 +2,7 @@ import SearchSortyBy from "./SearchSortBy"; import { QueryContext } from "src/app/[locale]/search/QueryProvider"; import { useContext } from "react"; +import { useTranslations} from "next-intl" export default function SearchResultsHeader({ sortby, @@ -23,10 +24,13 @@ export default function SearchResultsHeader({ "tablet-lg:margin-bottom-0", ]; if (loading) gridRowClasses.push("opacity-50"); + + const t = useTranslations("Search") + return (

- {total && <>{total} Opportunities} + {t("resultsHeader.message", {count: total})}

; @@ -21,12 +23,11 @@ export default async function SearchResultsListFetch({ if (searchResults.data.length === 0) { return (
-

Your search did not return any results.

+

{t("resultsListFetch.title")}

    -
  • {"Check any terms you've entered for typos"}
  • -
  • Try different keywords
  • -
  • {"Make sure you've selected the right statuses"}
  • -
  • Try resetting filters or selecting fewer options
  • + {t.rich("resultsListFetch.body", { + li: (chunks) =>
  • {chunks}
  • , + })}
); @@ -35,13 +36,7 @@ export default async function SearchResultsListFetch({ return (
    {/* TODO #1485: show proper USWDS error */} - {maxPaginationError && ( -

    - { - "You''re trying to access opportunity results that are beyond the last page of data." - } -

    - )} + {maxPaginationError &&

    {t("resultsListFetch.paginationError")}

    } {searchResults.data.map((opportunity) => (
  • diff --git a/frontend/src/components/search/SearchResultsListItem.tsx b/frontend/src/components/search/SearchResultsListItem.tsx index caa71630d..a7930c151 100644 --- a/frontend/src/components/search/SearchResultsListItem.tsx +++ b/frontend/src/components/search/SearchResultsListItem.tsx @@ -2,6 +2,7 @@ import { AgencyNamyLookup } from "src/utils/search/generateAgencyNameLookup"; import { formatDate } from "src/utils/dateUtil"; import { Opportunity } from "src/types/search/searchResponseTypes"; +import { useTranslations } from "next-intl"; interface SearchResultsListItemProps { opportunity: Opportunity; @@ -19,6 +20,8 @@ export default function SearchResultsListItem({ ? "https://grants.gov" : "https://test.grants.gov"; + const t = useTranslations("Search") + const metadataBorderClasses = ` display-block tablet:display-inline-block @@ -56,7 +59,7 @@ export default function SearchResultsListItem({
    {opportunity.opportunity_status === "archived" && ( - Archived:{" "} + {t("resultsListItem.status.archived")} {opportunity?.summary?.archive_date ? formatDate(opportunity?.summary?.archive_date) : "--"} @@ -66,7 +69,7 @@ export default function SearchResultsListItem({ opportunity?.opportunity_status === "closed") && opportunity?.summary?.close_date && ( - Closed:{" "} + {t("resultsListItem.status.closed")} {opportunity?.summary?.close_date ? formatDate(opportunity?.summary?.close_date) : "--"} @@ -75,7 +78,7 @@ export default function SearchResultsListItem({ {opportunity?.opportunity_status === "posted" && ( - Closing:{" "} + {t("resultsListItem.status.posted")} {opportunity?.summary?.close_date ? formatDate(opportunity?.summary?.close_date) @@ -87,12 +90,12 @@ export default function SearchResultsListItem({ {opportunity?.opportunity_status === "forecasted" && ( - Forecasted + {t("resultsListItem.status.forecasted")} )} - Posted:{" "} + {t("resultsListItem.summary.posted")} {opportunity?.summary?.post_date ? formatDate(opportunity?.summary?.post_date) : "--"} @@ -100,7 +103,7 @@ export default function SearchResultsListItem({
    - Agency:{" "} + {t("resultsListItem.summary.agency")} {opportunity?.summary?.agency_name && opportunity?.summary?.agency_code && agencyNameLookup @@ -109,7 +112,7 @@ export default function SearchResultsListItem({ : "--"} - Opportunity Number:{" "} + {t("resultsListItem.opportunity_number")} {opportunity?.opportunity_number}
    @@ -120,8 +123,8 @@ export default function SearchResultsListItem({ {/* TODO: Better way to format as a dollar amounts */} - Award Ceiling:{" "} + > + {t("resultsListItem.award_ceiling")} ${opportunity?.summary?.award_ceiling?.toLocaleString() || "--"} @@ -129,7 +132,7 @@ export default function SearchResultsListItem({ - Floor: $ + {t("resultsListItem.floor")} {opportunity?.summary?.award_floor?.toLocaleString() || "--"}
diff --git a/frontend/src/components/search/SearchSortBy.tsx b/frontend/src/components/search/SearchSortBy.tsx index da13acd66..3f18ce635 100644 --- a/frontend/src/components/search/SearchSortBy.tsx +++ b/frontend/src/components/search/SearchSortBy.tsx @@ -3,24 +3,14 @@ import { Select } from "@trussworks/react-uswds"; import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater"; import { QueryContext } from "src/app/[locale]/search/QueryProvider"; import { useContext } from "react"; +import { useTranslations } from "next-intl" type SortOption = { label: string; value: string; }; -const SORT_OPTIONS: SortOption[] = [ - { label: "Posted Date (newest)", value: "postedDateDesc" }, - { label: "Posted Date (oldest)", value: "postedDateAsc" }, - { label: "Close Date (newest)", value: "closeDateDesc" }, - { label: "Close Date (oldest)", value: "closeDateAsc" }, - { label: "Opportunity Title (A to Z)", value: "opportunityTitleAsc" }, - { label: "Opportunity Title (Z to A)", value: "opportunityTitleDesc" }, - { label: "Agency (A to Z)", value: "agencyAsc" }, - { label: "Agency (Z to A)", value: "agencyDesc" }, - { label: "Opportunity Number (descending)", value: "opportunityNumberDesc" }, - { label: "Opportunity Number (ascending)", value: "opportunityNumberAsc" }, -]; + interface SearchSortByProps { queryTerm: string | null | undefined; @@ -35,6 +25,20 @@ export default function SearchSortBy({ }: SearchSortByProps) { const { updateQueryParams } = useSearchParamUpdater(); const { updateTotalResults } = useContext(QueryContext); + const t = useTranslations("Search") + + const SORT_OPTIONS: SortOption[] = [ + { label: t("sortBy.options.posted_date_desc"), value: "postedDateDesc" }, + { label: t("sortBy.options.posted_date_asc"), value: "postedDateAsc" }, + { label: t("sortBy.options.close_date_desc"), value: "closeDateDesc" }, + { label: t("sortBy.options.close_date_asc"), value: "closeDateAsc" }, + { label: t("sortBy.options.opportunity_title_asc"), value: "opportunityTitleAsc" }, + { label: t("sortBy.options.opportunity_title_desc"), value: "opportunityTitleDesc" }, + { label: t("sortBy.options.agency_asc"), value: "agencyAsc" }, + { label: t("sortBy.options.agency_desc"), value: "agencyDesc" }, + { label: t("sortBy.options.opportunity_number_desc"), value: "opportunityNumberDesc" }, + { label: t("sortBy.options.opportunity_number_asc"), value: "opportunityNumberAsc" }, + ]; const handleChange = (event: React.ChangeEvent) => { const newValue = event.target.value; @@ -45,7 +49,7 @@ export default function SearchSortBy({ return (