SPB Git forge
28commits 1branches 0releases
7.7 MBsize
maindefault branch
10 days agolast push
Python 66.3% TypeScript 22.7% JavaScript 8.6% HTML 1.4% CSS 0.7%
4.5 KB · 94 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { CompanyTable } from '@/components/company/company-table';4import { Pagination, withParams } from '@/components/ui/pagination';5import { Container, PageHeader, Unavailable } from '@/components/ui/section';6import { api, safe } from '@/lib/api';7import { cn } from '@/lib/cn';8import { fmtInt } from '@/lib/format';9import { flat, int, str, type SP } from '@/lib/params';1011export const metadata: Metadata = { title: 'Companies', description: 'Directory of monitored companies with activity, hiring momentum, AI adoption, sensors and event counts.' };12export const revalidate = 120;1314const SORTS = [15  ['activity', 'Activity'],16  ['events', 'Events'],17  ['hiring', 'Hiring'],18  ['recent', 'Most recent event'],19  ['importance', 'Importance'],20  ['name', 'Name'],21] as const;2223export default async function CompaniesPage({ searchParams }: { searchParams: Promise<SP> }) {24  const sp = await searchParams;25  const cur = flat(sp);26  const page = int(sp.page, 1);27  const query = { q: str(sp.q), country: str(sp.country), industry: str(sp.industry), tier: str(sp.tier), public: str(sp.public), has_events: str(sp.has_events), sort: str(sp.sort) ?? 'activity', page, per_page: 50, sparkline: 1 };28  const [data, countries, industries] = await Promise.all([safe(api.companies(query)), safe(api.countries()), safe(api.industries())]);29  const href = (patch: Record<string, string | number | undefined | null>) => withParams('/companies', cur, patch);30  return (31    <Container wide>32      <PageHeader eyebrow="Directory" title="Companies" lede={data ? `${fmtInt(data.total)} monitored companies. Each row links to the profile with its sensors, timeline and historical page versions.` : 'Monitored companies.'} />33      <form method="get" action="/companies" className="mb-4 grid gap-2 sm:grid-cols-[minmax(0,2fr)_repeat(3,minmax(0,1fr))_auto]">34        <input type="search" name="q" defaultValue={str(sp.q)} placeholder="Search name, domain, alias…" className="field" aria-label="Search companies" />35        <select name="country" defaultValue={str(sp.country) ?? ''} className="field" aria-label="Country">36          <option value="">All countries</option>37          {(countries?.items ?? []).map((c) => (38            <option key={c.code} value={c.code}>39              {c.name} ({c.companies})40            </option>41          ))}42        </select>43        <select name="industry" defaultValue={str(sp.industry) ?? ''} className="field" aria-label="Industry">44          <option value="">All industries</option>45          {(industries?.items ?? []).map((i) => (46            <option key={i.slug} value={i.slug}>47              {i.name} ({i.companies})48            </option>49          ))}50        </select>51        <select name="tier" defaultValue={str(sp.tier) ?? ''} className="field" aria-label="Tier">52          <option value="">All tiers</option>53          {[1, 2, 3, 4].map((t) => (54            <option key={t} value={t}>55              Tier {t}56            </option>57          ))}58        </select>59        <input type="hidden" name="sort" value={query.sort} />60        <button type="submit" className="btn btn-primary">61          Filter62        </button>63      </form>64      <div className="mb-3 flex flex-wrap items-center gap-2 text-xs">65        <span className="text-ink-3">Sort</span>66        {SORTS.map(([id, label]) => (67          <Link key={id} href={href({ sort: id === 'activity' ? undefined : id })} className="chip-btn" data-on={query.sort === id}>68            {label}69          </Link>70        ))}71        <Link href={href({ public: str(sp.public) === '1' ? undefined : '1' })} className={cn('chip-btn ml-2')} data-on={str(sp.public) === '1'}>72          Public companies73        </Link>74        <Link href={href({ has_events: str(sp.has_events) === '1' ? undefined : '1' })} className="chip-btn" data-on={str(sp.has_events) === '1'}>75          With events76        </Link>77        {(str(sp.q) || str(sp.country) || str(sp.industry) || str(sp.tier) || str(sp.public) || str(sp.has_events)) && (78          <Link href="/companies" className="text-ink-3 underline-offset-2 hover:underline">79            Clear80          </Link>81        )}82      </div>83      {!data ? (84        <Unavailable what="The directory" />85      ) : (86        <>87          <CompanyTable items={data.items} />88          <Pagination total={data.total} page={data.page} pages={data.pages} perPage={data.per_page} makeHref={(p) => href({ page: p > 1 ? p : undefined })} className="mt-4" />89        </>90      )}91    </Container>92  );93}94