SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
3.8 KB · 85 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { t } from '@/i18n';4import { isNotBuilt } from '@/lib/api';5import { apiExplore } from '@/lib/api-explore';6import { formatDate } from '@/lib/format';7import { routes } from '@/lib/site';8import type { ChangesFeedResponse } from '@/lib/types-explore';9import { ChangesFeed } from '@/components/changes/changes-feed';10import { NotBuiltState } from '@/components/data/empty-state';11import { Section } from '@/components/data/section';12import { PageHeader } from '@/components/explore/page-header';13import { Movers } from '@/components/home/movers';14import { apiAnalytics } from '@/lib/api-analytics';15import { safe } from '@/lib/api';1617export const revalidate = 900;1819export const metadata: Metadata = {20  title: t('changes.title'),21  description: t('changes.sub'),22  alternates: { canonical: routes.changes() },23};2425type SP = Record<string, string | string[] | undefined>;2627export default async function ChangesPage({ searchParams }: { searchParams: Promise<SP> }) {28  const sp = await searchParams;29  const countryRaw = Array.isArray(sp.country) ? sp.country[0] : sp.country;30  const country = countryRaw && /^[a-z0-9-]+$/i.test(countryRaw) ? countryRaw : undefined;31  let data: ChangesFeedResponse | null = null;32  try {33    data = await apiExplore.changes({ limit: 100, country });34  } catch (e) {35    if (!isNotBuilt(e)) throw e;36  }37  const winRaw = Array.isArray(sp.window) ? sp.window[0] : sp.window;38  const win: 5 | 10 = winRaw === '10' ? 10 : 5;39  const movers = await safe(apiAnalytics.movers({ window: win, limit: 12 }));40  return (41    <>42      <PageHeader title={t('changes.title')} lede={t('changes.sub')} meta={data?.meta.built_at ? t('site.footer.refreshed', { date: formatDate(data.meta.built_at) }) : undefined} />43      {country ? (44        <p className="mb-3 text-sm text-ink-2">45          {t('changes.filteredCountry', { country })}{' '}46          <Link href={routes.changes()} className="text-accent hover:underline">47            {t('changes.clearCountry')}48          </Link>49        </p>50      ) : null}51      {!data ? (52        <NotBuiltState />53      ) : (54        <>55          <nav aria-label={t('changes.windows.nav')} className="mb-3 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-ink-2">56            <span>{t('changes.windows.nav')}</span>57            <span className="inline-flex min-h-[36px] items-center rounded-sm bg-ink px-2 text-xs text-paper">{t('home.movers.window.1')}</span>58            <Link href={`${routes.changes()}?window=5#windows`} className="inline-flex min-h-[36px] items-center text-accent hover:underline">59              {t('home.movers.window.5')} ↓60            </Link>61            <Link href={`${routes.changes()}?window=10#windows`} className="inline-flex min-h-[36px] items-center text-accent hover:underline">62              {t('home.movers.window.10')} ↓63            </Link>64          </nav>65          <details className="mb-2 border-y border-rule py-3 text-sm">66            <summary className="flex min-h-[44px] cursor-pointer list-none items-center gap-2 font-medium text-ink-2 hover:text-ink">67              <span aria-hidden>›</span> {t('changes.methodology')}68            </summary>69            <p className="mt-2 max-w-prose leading-relaxed text-ink-2">{t('changes.how')}</p>70            <p className="mt-2 text-xs">71              <Link href={`${routes.methodology()}#changes`} className="text-accent hover:underline">72                {t('site.footer.methodology')} →73              </Link>74            </p>75          </details>76          <ChangesFeed initial={data.items} kinds={data.kinds ?? []} limit={100} />77          <Section id="windows" title={t('changes.windows.title')} subtitle={t('changes.windows.sub')}>78            <Movers key={win} initial={movers} initialWindow={win} limit={12} compact />79          </Section>80        </>81      )}82    </>83  );84}85