SPB Git forge
3commits 1branches 0releases
417.0 KBsize
maindefault branch
10 days agolast push
TypeScript 66.5% Python 30.9% JavaScript 1.4% CSS 0.7%
4.6 KB · 86 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { SitesMap } from '@/components/launches/sites-map';4import { Container, PageHeader } from '@/components/ui/section';5import { Unavailable } from '@/components/ui/unavailable';6import { api, safe } from '@/lib/api';7import { fmtDate, fmtInt, num } from '@/lib/format';8import { routes, SITE_URL } from '@/lib/site';910export const metadata: Metadata = {11  title: 'Launch sites — every spaceport that put an object in orbit',12  description: 'World map and table of launch sites: total launches, activity over the last 365 days, payloads and most recent launch, derived from SATCAT launch-site codes.',13  alternates: { canonical: routes.launchSites() },14  openGraph: { title: 'Launch sites of the world', description: 'Spaceports ranked by orbital launches, with recent activity and last launch.', url: `${SITE_URL}${routes.launchSites()}` },15  twitter: { card: 'summary_large_image', title: 'Launch sites of the world', description: 'Spaceports ranked by orbital launches, with recent activity and last launch.' },16};1718export default async function LaunchSitesPage() {19  const res = await safe(api.launchSites());20  const sites = res ? [...res.data].sort((a, b) => (num(b.launches) ?? 0) - (num(a.launches) ?? 0)) : null;21  const active365 = sites?.filter((s) => (num(s.launches_last_365d) ?? 0) > 0).length ?? null;2223  return (24    <Container wide>25      <PageHeader26        eyebrow={<>Infrastructure · {sites ? <span className="tnum">{fmtInt(sites.length)} sites · {fmtInt(active365)} active in the last 365 days</span> : 'count unavailable'}</>}27        title="Launch sites"28        lede="Every launch site referenced by a catalogued object, from Baikonur in 1957 to today's commercial pads. Marker area is proportional to the number of orbital launches attributed to the site."29      />3031      {sites === null ? (32        <Unavailable what="Launch sites" />33      ) : (34        <>35          <section className="pb-8" aria-label="Map of launch sites">36            <SitesMap sites={sites} labelTop={5} />37          </section>3839          <section className="pb-10" aria-label="Launch sites table">40            <div className="overflow-x-auto scrollbar-thin">41              <table className="data-table stack">42                <thead>43                  <tr>44                    <th>Site</th>45                    <th>Code</th>46                    <th>Country</th>47                    <th className="num">Launches</th>48                    <th className="num">Last 365 d</th>49                    <th className="num">Payloads</th>50                    <th>First launch</th>51                    <th>Last launch</th>52                  </tr>53                </thead>54                <tbody>55                  {sites.map((s) => {56                    const recent = num(s.launches_last_365d) ?? 0;57                    return (58                      <tr key={s.code}>59                        <td data-label="Site" className="primary">60                          <Link href={routes.launchSite(s.slug)} className="link font-medium">{s.name}</Link>61                          {s.latitude === null && <span className="ml-2 text-2xs text-ink-3">no coordinates</span>}62                        </td>63                        <td data-label="Code" className="mono text-xs text-ink-2">{s.code}</td>64                        <td data-label="Country" className="text-xs">{s.country_code ? <Link href={routes.country(s.country_code)} className="link">{s.country_name ?? s.country_code}</Link> : '—'}</td>65                        <td data-label="Launches" className="num mono text-xs">{fmtInt(s.launches)}</td>66                        <td data-label="Last 365 d" className={`num mono text-xs ${recent ? 'text-active' : 'text-ink-3'}`}>{fmtInt(s.launches_last_365d)}</td>67                        <td data-label="Payloads" className="num mono text-xs">{fmtInt(s.payloads)}</td>68                        <td data-label="First launch" className="mono text-xs">{fmtDate(s.first_launch)}</td>69                        <td data-label="Last launch" className="mono text-xs">{fmtDate(s.last_launch)}</td>70                      </tr>71                    );72                  })}73                </tbody>74              </table>75            </div>76          </section>77        </>78      )}7980      <p className="pb-10 text-2xs text-ink-3">81        Sites and coordinates come from the SATCAT launch-site code table; launches are derived from international designators (see <Link href={routes.methodology()} className="hover:text-accent">methodology</Link>). Sea launches and air launches are attributed to their reference site.82      </p>83    </Container>84  );85}86