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.4 KB · 78 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import { t } from '@/i18n';3import { isNotBuilt } from '@/lib/api';4import { apiAnalytics } from '@/lib/api-analytics';5import { grouped } from '@/lib/format';6import { routes } from '@/lib/site';7import { isTopicId } from '@/lib/topics';8import type { ExtremesResponse } from '@/lib/types-analytics';9import { NotBuiltState } from '@/components/data/empty-state';10import { ExtremeFacetBlock } from '@/components/explorer/extreme-facet';11import { ExtremesControls } from '@/components/explorer/extremes-controls';12import { firstParam, type SP } from '@/components/explorer/options';1314export const revalidate = 900;15const WINDOWS = new Set(['1', '5', '10', '25', 'since1990']);1617function parseState(sp: SP) {18  const w = firstParam(sp, 'window') ?? '10';19  const topic = (firstParam(sp, 'topic') ?? '').toLowerCase();20  return { window: WINDOWS.has(w) ? w : '10', topic: isTopicId(topic) ? topic : null, all: firstParam(sp, 'all') === '1' };21}2223export async function generateMetadata({ searchParams }: { searchParams: Promise<SP> }): Promise<Metadata> {24  const sp = await searchParams;25  return {26    title: t('extremes.metaTitle'),27    description: t('extremes.description'),28    alternates: { canonical: routes.extremes() },29    robots: Object.keys(sp).length ? { index: false, follow: true } : undefined,30    openGraph: { title: `${t('extremes.metaTitle')} — ${t('site.name')}`, description: t('extremes.description'), url: routes.extremes(), type: 'website' },31  };32}3334export default async function ExtremesPage({ searchParams }: { searchParams: Promise<SP> }) {35  const state = parseState(await searchParams);36  let data: ExtremesResponse;37  try {38    data = await apiAnalytics.extremes({ window: state.window, topic: state.topic, min_population: state.all ? 0 : null });39  } catch (e) {40    if (isNotBuilt(e)) return <NotBuiltState />;41    throw e;42  }43  return (44    <>45      <header className="pb-3 pt-6 md:pt-10">46        <h1 className="display text-3xl leading-tight text-ink md:text-4xl">{t('extremes.title')}</h1>47        <p className="mt-2 max-w-prose text-sm text-ink-2 md:text-base">{t('extremes.lede')}</p>48        <p className="tnum mt-1 text-xs text-ink-3">49          {t('extremes.facets', { n: grouped(data.facets.length), y0: data.from_year ?? '', y1: data.to_year ?? '' })}50          {data.filter_note ? ` · ${data.filter_note}` : ` · ${t('extremes.allCountries')}`}51        </p>52      </header>53      <ExtremesControls window={state.window} topic={state.topic} all={state.all} />54      <p className="max-w-prose py-3 text-xs text-ink-3">{t('extremes.semantics')}</p>55      {data.facets.length ? (56        <>57          <nav aria-label={t('extremes.title')} className="-mx-4 px-4 sm:mx-0 sm:px-0">58            <ul className="scrollbar-none flex gap-1.5 overflow-x-auto py-1 sm:flex-wrap">59              {data.facets.map((f) => (60                <li key={f.id} className="shrink-0">61                  <a href={`#${f.id}`} className="inline-flex h-11 items-center whitespace-nowrap rounded-sm border border-rule px-3 text-sm text-ink-2 hover:border-accent hover:text-accent md:h-9 md:px-2.5">62                    {f.title}63                  </a>64                </li>65              ))}66            </ul>67          </nav>68          {data.facets.map((f) => (69            <ExtremeFacetBlock key={f.id} facet={f} />70          ))}71        </>72      ) : (73        <p className="py-10 text-center text-sm text-ink-3">{t('extremes.none')}</p>74      )}75    </>76  );77}78