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.8 KB · 106 lines tsx
Raw Blame History
1import Link from 'next/link';2import { TypeBadge } from '@/components/ui/badges';3import { Section } from '@/components/ui/section';4import { Unavailable } from '@/components/ui/unavailable';5import { fmtAgo, fmtDate, fmtInt } from '@/lib/format';6import { EVENT_TYPE_LABELS, routes } from '@/lib/site';7import type { HomePayload } from '@/lib/types';89export function LatestLaunches({ items }: { items: HomePayload['latest_launches'] }) {10  return (11    <Section eyebrow="Launches" title="Latest launches" action={{ href: routes.launches(), label: 'Launch log' }} className="py-0">12      {items.length === 0 ? (13        <Unavailable what="Latest launches" />14      ) : (15        <ul className="divide-y divide-rule border-t border-rule">16          {items.map((l) => (17            <li key={l.id}>18              <Link href={routes.launch(l.cospar_launch_id)} className="group grid min-h-[56px] grid-cols-[5.5rem_minmax(0,1fr)_auto] items-center gap-x-3 py-2.5">19                <span className="mono text-xs text-ink-3">{fmtDate(l.launch_date)}</span>20                <span className="min-w-0">21                  <span className="block truncate text-[15px] text-ink group-hover:text-accent">{l.primary_name ?? l.cospar_launch_id}</span>22                  <span className="block truncate text-2xs text-ink-3">23                    <span className="mono">{l.cospar_launch_id}</span>24                    {l.site_name && <> · {l.site_name}</>}25                    {l.site_country && <> · {l.site_country}</>}26                  </span>27                </span>28                <span className="tnum text-right text-sm text-ink">29                  {fmtInt(l.payload_count)} <span className="text-2xs text-ink-3">payload{(Number(l.payload_count) || 0) === 1 ? '' : 's'}</span>30                </span>31              </Link>32            </li>33          ))}34        </ul>35      )}36    </Section>37  );38}3940export function RecentEvents({ items }: { items: HomePayload['events'] }) {41  return (42    <Section eyebrow="Events" title="Recent orbital events" action={{ href: routes.events(), label: 'Event feed' }} className="py-0">43      {items.length === 0 ? (44        <Unavailable what="Events" />45      ) : (46        <ul className="divide-y divide-rule border-t border-rule">47          {items.slice(0, 8).map((e) => {48            const primary = e.entities?.[0];49            const href = primary?.type === 'launch' && primary.slug ? routes.launch(primary.slug) : primary?.type === 'satellite' && primary.slug ? routes.satellite(primary.slug) : routes.events(`type=${e.type}`);50            return (51              <li key={e.id}>52                <Link href={href} className="group flex min-h-[56px] items-start gap-3 py-2.5">53                  <span className="mt-0.5 inline-flex shrink-0 rounded border border-rule px-1.5 py-0.5 text-2xs text-ink-2">{EVENT_TYPE_LABELS[e.type] ?? e.type}</span>54                  <span className="min-w-0 flex-1">55                    <span className="block truncate text-[15px] text-ink group-hover:text-accent">{e.title}</span>56                    {e.summary && <span className="block truncate text-2xs text-ink-3">{e.summary}</span>}57                  </span>58                  <span className="mono shrink-0 pt-0.5 text-2xs text-ink-3">{fmtAgo(e.event_time)}</span>59                </Link>60              </li>61            );62          })}63        </ul>64      )}65    </Section>66  );67}6869export function RecentReentries({ items }: { items: HomePayload['reentries'] }) {70  return (71    <Section eyebrow="Reentries" title="Recent reentries" action={{ href: routes.reentries(), label: 'All reentries' }} className="py-0">72      {items.length === 0 ? (73        <Unavailable what="Reentries" />74      ) : (75        <table className="data-table stack">76          <thead>77            <tr>78              <th>Object</th>79              <th>NORAD</th>80              <th>Type</th>81              <th>Decayed</th>82              <th>Country</th>83            </tr>84          </thead>85          <tbody>86            {items.map((r) => (87              <tr key={r.id}>88                <td className="primary">89                  <Link href={routes.satellite(r.slug)} className="link">{r.name}</Link>90                </td>91                <td data-label="NORAD" className="mono text-xs text-ink-2">{r.norad_id ?? '—'}</td>92                <td data-label="Type">93                  <TypeBadge type={r.object_type} />94                </td>95                <td data-label="Decayed" className="mono text-xs text-ink-2">{fmtDate(r.decay_date)}</td>96                <td data-label="Country" className="text-sm text-ink-2">{r.country_name ?? r.country_code ?? '—'}</td>97              </tr>98            ))}99          </tbody>100        </table>101      )}102      <p className="mt-3 text-2xs text-ink-3">Decay dates come from the public catalogue; SatelliteIndex does not predict reentry locations.</p>103    </Section>104  );105}106