pr_01m47d24b0e6n91zwymwxg0vpx/apps/web/app/routes/explore.tsx

51 lines1,941 bytesCodeBlame
1import { Search } from "lucide-react";
2import { Form } from "react-router";
3
4import type { Route } from "./+types/explore";
5import { RepoList } from "../components/repo-list";
6import { notACredential } from "../components/ui";
7import { repos } from "../lib/services.server";
8import { getViewer } from "../lib/session.server";
9
10export function meta({ loaderData }: Route.MetaArgs) {
11 return [
12 { title: loaderData?.query ? `${loaderData.query} · Search · g1t` : "Explore · g1t" },
13 ];
14}
15
16/** Serves both /explore and /search?q=. */
17export async function loader({ request, context }: Route.LoaderArgs) {
18 const query = new URL(request.url).searchParams.get("q")?.trim() ?? "";
19 return { query, repos: await repos.list(getViewer(context), { query }) };
20}
21
22export default function Explore({ loaderData }: Route.ComponentProps) {
23 const { query, repos } = loaderData;
24 return (
25 <main className="mx-auto max-w-4xl px-4 py-10">
26 <h1 className="text-2xl font-semibold tracking-tight">
27 {query ? "Search" : "Explore"}
28 </h1>
29 <p className="mt-1 text-muted">
30 {query
31 ? `${repos.length} ${repos.length === 1 ? "repository matches" : "repositories match"} "${query}".`
32 : "Public repositories on g1t."}
33 </p>
34 <Form action="/search" role="search" className="relative mt-6">
35 <Search
36 size={16}
37 className="pointer-events-none absolute top-1/2 left-3.5 -translate-y-1/2 text-faint"
38 />
39 <input
40 name="q"
41 {...notACredential()}
42 defaultValue={query}
43 placeholder="Search by name or description"
44 aria-label="Search repositories"
45 className="w-full rounded-lg border border-line bg-surface py-2.5 pr-4 pl-10 outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim"
46 />
47 </Form>
48 <RepoList repos={repos} />
49 </main>
50 );
51}