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

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