Skip to content

Commit

Permalink
Add search on front-end
Browse files Browse the repository at this point in the history
  • Loading branch information
limdingwen committed Jul 31, 2024
1 parent cad1d9c commit 74a0fa5
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 24 deletions.
36 changes: 24 additions & 12 deletions site/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"react": "^18",
"react-dom": "^18",
"react-markdown": "^9.0.1",
"recharts": "^2.12.7"
"recharts": "^2.12.7",
"use-debounce": "^10.0.2"
},
"devDependencies": {
"@cloudflare/next-on-pages": "^1.12.1",
Expand Down
124 changes: 124 additions & 0 deletions site/src/app/components/SearchSpotlight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"use client";

import { Spotlight, SpotlightActionGroupData } from "@mantine/spotlight";
import { useEffect, useState } from "react";
import { useDebounce } from "use-debounce";
import { useRouter } from "next/navigation";
import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";

type ClientFriendlyData = {
href: string;
title: string;
};

async function getResults(query: string): Promise<{
debate: ClientFriendlyData[];
bill: ClientFriendlyData[];
}> {
const url = `${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/search`;
const headers = new Headers({
Authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`,
});
const request = new Request(url, {
headers: headers,
method: "POST",
body: JSON.stringify({ query }),
});
const response = await fetch(request);
return response.json();
}

function convertDataToActions(
router: AppRouterInstance,
data: ClientFriendlyData[],
) {
return data.map((item) => ({
id: item.href,
label: item.title,
onClick: () => router.push(item.href),
}));
}

function convertResultsToActionGroups(
router: AppRouterInstance,
results: {
debate: ClientFriendlyData[];
bill: ClientFriendlyData[];
},
): SpotlightActionGroupData[] {
return [
{
group: "Debates",
actions: convertDataToActions(router, results.debate),
},
{
group: "Bills",
actions: convertDataToActions(router, results.bill),
},
];
}

// Empty action groups make nothingFound not work properly, and empty headers ain't that good anyway
function clearEmptyActionGroups(actionGroups: SpotlightActionGroupData[]) {
return actionGroups.filter((group) => group.actions.length > 0);
}

export default function SearchSpotlight() {
const router = useRouter();

const [query, setQuery] = useState("");
const [debouncedQuery] = useDebounce(query, 500);
const [results, setResults] = useState<{
searchHappened: boolean;
actionGroups: SpotlightActionGroupData[];
}>({
searchHappened: false,
actionGroups: [],
});

useEffect(() => {
let isCanceled = false;
(async () => {
if (debouncedQuery != "") {
const results = await getResults(debouncedQuery);
if (isCanceled) {
return;
}
setResults({
searchHappened: true,
actionGroups: clearEmptyActionGroups(
convertResultsToActionGroups(router, results),
),
});
} else {
setResults({ searchHappened: false, actionGroups: [] });
}
})();
// If the debouncedQuery changes before the request completes, cancel the request's setActions call
return () => {
isCanceled = true;
};
}, [debouncedQuery]);

return (
<Spotlight
query={query}
clearQueryOnClose={false}
onQueryChange={(query) => {
setQuery(query);
}}
actions={results.actionGroups}
shortcut={null}
filter={(_, actions) =>
// Don't filter; search is already done
actions
}
scrollable
highlightQuery
nothingFound={
results.searchHappened ? "No results found" : "Start typing to search"
}
radius="lg"
/>
);
}
40 changes: 29 additions & 11 deletions site/src/app/components/StandardShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,39 @@ import { useDisclosure, useHeadroom } from "@mantine/hooks";
import classes from "./StandardShell.module.css";
import PageFooter from "@/app/components/PageFooter";
import Link from "next/link";
import { spotlight } from "@mantine/spotlight";
import SearchSpotlight from "@/app/components/SearchSpotlight";

type PageLink = {
name: string;
href: string;
};

function generateButtonsFromLinks(links: PageLink[], closeNavbar: () => void) {
return links.map(({ name, href }) => (
<UnstyledButton
key={href}
className={classes.control}
component={Link}
href={href}
onClick={closeNavbar}
>
{name}
</UnstyledButton>
));
return (
<>
<UnstyledButton
className={classes.control}
onClick={() => {
closeNavbar();
spotlight.open();
}}
>
Search
</UnstyledButton>
{links.map(({ name, href }) => (
<UnstyledButton
key={href}
className={classes.control}
component={Link}
href={href}
onClick={closeNavbar}
>
{name}
</UnstyledButton>
))}
</>
);
}

export default function StandardShell({
Expand Down Expand Up @@ -78,6 +93,9 @@ export default function StandardShell({
</AppShellMain>

<PageFooter />

{/* I think this can be anywhere since it's a modal */}
<SearchSpotlight />
</AppShell>
);
}
1 change: 1 addition & 0 deletions site/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "./globals.css";
// Import styles of packages that you've installed.
// All packages except `@mantine/hooks` require styles imports
import "@mantine/core/styles.css";
import "@mantine/spotlight/styles.css";
import { ColorSchemeScript, MantineProvider, Text } from "@mantine/core";
import StandardShell from "@/app/components/StandardShell";

Expand Down

0 comments on commit 74a0fa5

Please sign in to comment.