spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
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