SPB Git

spb/autoscholar

Public
3.3 KB · 83 lines tsx
1import { listSeries, searchPapers } from "@autoscholar/core";2import { PublicSearchQuery } from "@autoscholar/schemas";3import type { Metadata } from "next";4import { Disclosure } from "@/components/Disclosure";5import { EmptyState } from "@/components/EmptyState";6import { Pagination } from "@/components/Pagination";7import { PaperCard } from "@/components/PaperCard";8import { SearchFilters } from "@/components/SearchFilters";9import { t } from "@/lib/i18n";10import { db } from "@/lib/server/db";1112export const dynamic = "force-dynamic";13export const metadata: Metadata = {14  title: "Research library",15  description: "Searchable library of AutoScholar working papers.",16  alternates: { canonical: "/research" },17};1819export default async function ResearchPage({20  searchParams,21}: {22  searchParams: Promise<Record<string, string | string[] | undefined>>;23}) {24  const sp = await searchParams;25  const raw: Record<string, string | undefined> = {};26  for (const [k, v] of Object.entries(sp)) raw[k] = Array.isArray(v) ? v[0] : v;27  for (const k of Object.keys(raw)) if (raw[k] === "") delete raw[k];28  const parsed = PublicSearchQuery.safeParse(raw);29  const q = parsed.success ? parsed.data : PublicSearchQuery.parse({});30  const d = db();31  const res = await searchPapers(d, q);32  const series = await listSeries(d);33  const hrefFor = (p: number) => {34    const u = new URLSearchParams();35    for (const [k, v] of Object.entries(raw)) if (v && k !== "page") u.set(k, v);36    u.set("page", String(p));37    return `/research?${u.toString()}`;38  };39  const anyFilter = Object.keys(raw).some((k) => !["page", "sort", "pageSize"].includes(k));40  return (41    <div className="space-y-6">42      <header>43        <p className="eyebrow">Library</p>44        <h1 className="serif mt-1 text-3xl font-semibold">Working papers</h1>45        <p className="muted mt-2 max-w-2xl text-sm">46          Search titles, abstracts, keywords and verified metadata. Filter by discipline, methodology, series,47          availability of code and data, and publication status.48        </p>49      </header>50      <SearchFilters q={raw} series={series} />51      {!parsed.success && (52        <p role="alert" className="text-sm" style={{ color: "var(--color-danger-500)" }}>53          Some filters were invalid and have been ignored.54        </p>55      )}56      <div className="flex items-center justify-between text-sm">57        <p className="muted" aria-live="polite">58          {res.total === 0 ? "No results" : `${res.total} ${res.total === 1 ? "paper" : "papers"}`}59          {raw.q ? ` for “${raw.q}”` : ""}60        </p>61        <Pagination page={res.page} pageSize={res.pageSize} total={res.total} hrefFor={hrefFor} />62      </div>63      {res.items.length === 0 ? (64        <EmptyState title={anyFilter ? t("search.empty") : "No working papers yet."}>65          {anyFilter ? <a href="/research">Clear all filters</a> : t("library.empty")}66        </EmptyState>67      ) : (68        <ul className="grid gap-4 md:grid-cols-2">69          {res.items.map((p) => (70            <li key={p.publicId}>71              <PaperCard p={p} />72            </li>73          ))}74        </ul>75      )}76      <div className="flex justify-end">77        <Pagination page={res.page} pageSize={res.pageSize} total={res.total} hrefFor={hrefFor} />78      </div>79      <Disclosure compact />80    </div>81  );82}83
Files · e709d44366a0d3dc4c8b6b32ba43ac31859be02a
Menu

Theme