spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { EXAMPLE_QUERIES, GroupedResults, Shortcuts } from '@/components/search/results';4import { SearchForm } from '@/components/search/search-form';5import { Container, PageHeader } from '@/components/ui/section';6import { Unavailable } from '@/components/ui/unavailable';7import { api, safe } from '@/lib/api';8import { fmtInt } from '@/lib/format';9import { routes, SITE_URL } from '@/lib/site';1011export const dynamic = 'force-dynamic';1213type SP = Promise<Record<string, string | string[] | undefined>>;1415function readQ(sp: Record<string, string | string[] | undefined>): string {16 const raw = Array.isArray(sp.q) ? sp.q[0] : sp.q;17 return (raw ?? '').trim().slice(0, 120);18}1920export async function generateMetadata({ searchParams }: { searchParams: SP }): Promise<Metadata> {21 const q = readQ(await searchParams);22 const title = q ? `Search results for “${q}”` : 'Search satellites, operators, constellations and launches';23 return {24 title,25 description: q ? `SatelliteIndex search results for “${q}”: satellites, constellations, operators, countries, launches and launch sites.` : 'Search the SatelliteIndex catalogue by satellite name, NORAD or COSPAR identifier, operator, constellation, country or launch.',26 alternates: { canonical: q ? routes.search(q) : '/search' },27 robots: q ? { index: false, follow: true } : { index: true, follow: true },28 openGraph: { title, url: `${SITE_URL}${routes.search(q || undefined)}`, type: 'website' },29 };30}3132export default async function SearchPage({ searchParams }: { searchParams: SP }) {33 const q = readQ(await searchParams);34 const res = q ? await safe(api.search(q, 50)) : null;35 const payload = res?.data ?? null;36 const failed = Boolean(q) && res === null;3738 return (39 <Container>40 <PageHeader eyebrow="Search" title={q ? <>Results for <span className="text-accent">“{q}”</span></> : 'Search the index'} lede={q ? undefined : 'Satellites by name, NORAD or COSPAR identifier; constellations, operators, countries, launches and launch sites. Press ⌘K anywhere for the quick search.'} />41 <SearchForm q={q} className="max-w-2xl" autoFocus={!q} />4243 <div className="py-8 md:py-10">44 {!q ? (45 <div>46 <p className="eyebrow mb-3">Try</p>47 <ul className="flex flex-wrap gap-2">48 {EXAMPLE_QUERIES.map((ex) => (49 <li key={ex}>50 <Link href={routes.search(ex)} className="inline-flex min-h-[40px] items-center rounded-full border border-rule bg-plane-2 px-3 text-sm text-ink-2 hover:border-rule-strong hover:text-ink">51 {ex}52 </Link>53 </li>54 ))}55 </ul>56 <p className="mt-6 max-w-xl text-sm leading-relaxed text-ink-3">57 Launch vehicles are not indexed yet — a query such as “Falcon 9” only matches objects whose catalogue name contains those words. Browse the{' '}58 <Link href={routes.launches()} className="link">launch log</Link> or <Link href={routes.satellites()} className="link">filter the catalogue</Link> instead.59 </p>60 </div>61 ) : failed ? (62 <Unavailable what="Search" />63 ) : payload ? (64 <>65 <Shortcuts items={payload.shortcuts} />66 {payload.results.length === 0 ? (67 <div className="rounded-lg border border-dashed border-rule-strong px-5 py-10 text-center">68 <p className="text-base text-ink">No results for “{q}”.</p>69 <p className="mx-auto mt-2 max-w-md text-sm leading-relaxed text-ink-3">70 The index covers satellite names, NORAD and COSPAR identifiers, constellations, operators, countries, launches and launch sites. Launch vehicles (e.g. “Falcon 9”) are not indexed yet.71 </p>72 <div className="mt-5 flex flex-wrap justify-center gap-2">73 <Link href={routes.satellites(`q=${encodeURIComponent(q)}`)} className="inline-flex min-h-[44px] items-center rounded-md border border-rule-strong px-4 text-sm text-ink hover:bg-plane-2">Filter the catalogue for “{q}”</Link>74 <Link href={routes.explore()} className="inline-flex min-h-[44px] items-center rounded-md border border-rule px-4 text-sm text-ink-2 hover:bg-plane-2">Open the live globe</Link>75 </div>76 </div>77 ) : (78 <>79 <p className="tnum mb-6 text-xs text-ink-3">80 {fmtInt(payload.results.length)} result{payload.results.length === 1 ? '' : 's'}81 {payload.results.length >= 50 && ' (top 50 by relevance)'}82 </p>83 <GroupedResults results={payload.results} />84 </>85 )}86 </>87 ) : null}88 </div>89 </Container>90 );91}92