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

Commit

Permalink
Add query provider
Browse files Browse the repository at this point in the history
  • Loading branch information
acouch committed Jun 25, 2024
1 parent aabf16a commit 3b1b01e
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 7 deletions.
35 changes: 35 additions & 0 deletions frontend/src/app/[locale]/look/QueryProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client"

import React, { use } from "react"
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
import { useSearchParams } from "next/navigation";
import { set } from "lodash";

export const QueryContext = createContext<any>({});

export default function QueryProvider({
children,
}: {
children: React.ReactNode
}) {
const searchParams = useSearchParams() || undefined;
const defaultTerm = searchParams?.get('query');
const [queryTerm, setQueryTerm] = useState(defaultTerm);
console.log("rendering provider:", queryTerm);

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

const contextValue = useMemo(() => ({
queryTerm,
updateQueryTerm
}), [queryTerm, updateQueryTerm]);

return (
<QueryContext.Provider value={contextValue}>
<h3>Query Provider</h3>
{children}
</QueryContext.Provider>
)
}
15 changes: 9 additions & 6 deletions frontend/src/app/[locale]/look/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,35 @@
import { Icon } from "@trussworks/react-uswds";
import { sendGAEvent } from "@next/third-parties/google";
import { useSearchParams, usePathname, useRouter } from 'next/navigation';
import { useState } from "react";
import { QueryContext } from "./QueryProvider";
import { useContext, useState } from "react";

interface SearchBarProps {
query: string;
}

export default function SearchBar({ query }: SearchBarProps) {
const[term, setTerm] = useState<string | null>(null)
let { queryTerm, updateQueryTerm } = useContext(QueryContext);

const searchParams = useSearchParams() || undefined;
const pathname = usePathname() || "";
const router = useRouter();
console.log("queryTerm:", queryTerm, "vs. query:", query);

const handleSubmit = () => {
const params = new URLSearchParams(searchParams);
if (term) {
params.set('query', term);
if (queryTerm) {
params.set('query', queryTerm);
} else {
params.delete('query');
}
sendGAEvent("event", "search", { search_term: term });
sendGAEvent("event", "search", { search_term: queryTerm });
router.replace(`${pathname}?${params.toString()}`);
};

return (
<div className="margin-top-5 margin-bottom-2">
<h1>queryTerm: {queryTerm}</h1>
<label
htmlFor="query"
className="font-sans-lg display-block margin-bottom-2"
Expand All @@ -45,7 +48,7 @@ export default function SearchBar({ query }: SearchBarProps) {
type="search"
name="query"
defaultValue={query}
onChange={(e) => setTerm(e.target?.value)}
onChange={(e) => updateQueryTerm(e.target?.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSubmit();
Expand Down
96 changes: 96 additions & 0 deletions frontend/src/app/[locale]/look/SearchOpportunityStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"use client";

import { useContext, useEffect, useState } from "react";

import { Checkbox } from "@trussworks/react-uswds";
import { useDebouncedCallback } from "use-debounce";
import { sendGAEvent } from "@next/third-parties/google";
import { useSearchParams, usePathname, useRouter } from 'next/navigation';
import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater";
import { QueryContext } from "./QueryProvider";

interface StatusOption {
id: string;
label: string;
value: string;
}

interface SearchOpportunityStatusProps {
selectedStatuses: Set<string>;
}

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" },
];

// Wait a half-second before updating query params
// and submitting the form
const SEARCH_OPPORTUNITY_STATUS_DEBOUNCE_TIME = 500;

const SearchOpportunityStatus: React.FC<SearchOpportunityStatusProps> = ({
selectedStatuses,
}) => {
let { queryTerm } = useContext(QueryContext);

const debouncedUpdate = useDebouncedCallback(
(selectedStatuses: Set<string>) => {
const key = "status";
updateQueryParams(selectedStatuses, key);
formRef?.current?.requestSubmit();
},
SEARCH_OPPORTUNITY_STATUS_DEBOUNCE_TIME,
);

const searchParams = useSearchParams() || undefined;
const pathname = usePathname() || "";
const router = useRouter();
console.log("queryTerm:", queryTerm, "vs. query:", query);

const handleSubmit = (statusValue: string, isChecked: boolean) => {
const params = new URLSearchParams(searchParams);
if (queryTerm) {
params.set('query', queryTerm);
} else {
params.delete('query');
}
sendGAEvent("event", "search", { search_term: queryTerm });
router.replace(`${pathname}?${params.toString()}`);
};

const handleCheck = (statusValue: string, isChecked: boolean) => {
setSelectedStatuses((prevSelectedStatuses) => {
const updatedStatuses = new Set(prevSelectedStatuses);
isChecked
? updatedStatuses.add(statusValue)
: updatedStatuses.delete(statusValue);

debouncedUpdate(updatedStatuses);
return updatedStatuses;
});
};

return (
<>
<h2 className="margin-bottom-1 font-sans-xs">Opportunity status</h2>
<div className="grid-row flex-wrap">
{statusOptions.map((option) => (
<div key={option.id} className="grid-col-6 padding-right-1">
<Checkbox
id={option.id}
name={option.id}
label={option.label}
tile={true}
onChange={(e) => handleCheck(option.value, e.target.checked)}
checked={selectedStatuses.has(option.value)}
/>
</div>
))}
</div>
</>
);
};

export default SearchOpportunityStatus;
13 changes: 12 additions & 1 deletion frontend/src/app/[locale]/look/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getTranslations, unstable_setRequestLocale } from "next-intl/server";
import { Suspense } from 'react';
import Loading from "src/app/[locale]/search/loading";
import SearchResultsList from "./SearchResultList";
import QueryProvider from "./QueryProvider";

export async function generateMetadata() {
const t = await getTranslations({ locale: "en" });
Expand All @@ -33,6 +34,9 @@ export default function Look({
const t = useTranslations("Process");
const query = searchParams?.query || '';

console.log("rendering page");


return (
<>
<PageSEO title={t("page_title")} description={t("meta_description")} />
Expand All @@ -41,10 +45,17 @@ export default function Look({
<SearchCallToAction />
<div className="grid-container">
<div className="search-bar">
<SearchBar query={query}/>
<QueryProvider>
<SearchBar query={query}/>
</QueryProvider>
</div>
<div className="grid-row grid-gap">
<div className="tablet:grid-col-4">
<QueryProvider>
<div>
Term will be here
</div>
</QueryProvider>
</div>
<div className="tablet:grid-col-8">
<Suspense key={query} fallback={<Loading />}>
Expand Down

0 comments on commit 3b1b01e

Please sign in to comment.