spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1'use client';2import type { RankingResponse } from './types';3import type { CompareMode, CompareResponse, IndicatorsResponse, RankHistoryResponse } from './types-compare';45/** Browser-side fetches for the compare / rankings pages (same-origin `/api/v1`, rewritten to the API). */6export class ClientCompareError extends Error {7 constructor(8 readonly status: number,9 message: string,10 ) {11 super(message);12 this.name = 'ClientCompareError';13 }14}1516async function get<T>(path: string, signal?: AbortSignal): Promise<T> {17 const res = await fetch(`/api/v1${path}`, { headers: { accept: 'application/json' }, signal });18 if (!res.ok) throw new ClientCompareError(res.status, `API ${res.status}`);19 return (await res.json()) as T;20}2122function qs(obj: Record<string, string | number | boolean | null | undefined>): string {23 const p = new URLSearchParams();24 for (const [k, v] of Object.entries(obj)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v));25 const s = p.toString();26 return s ? `?${s}` : '';27}2829export const clientCompare = {30 compare: (countries: string[], indicators: string[], q: { from?: number | null; to?: number | null; mode?: CompareMode } = {}, signal?: AbortSignal) =>31 get<CompareResponse>(`/compare${qs({ countries: countries.join(','), indicators: indicators.join(','), from: q.from, to: q.to, mode: q.mode ?? 'absolute' })}`, signal),3233 indicators: (signal?: AbortSignal) => get<IndicatorsResponse>('/indicators', signal),3435 rankingTop: (slug: string, limit = 3, signal?: AbortSignal) => get<RankingResponse>(`/rankings/${encodeURIComponent(slug)}${qs({ limit, sparkline: false })}`, signal),3637 rankHistory: (slug: string, countries: string[], signal?: AbortSignal) => get<RankHistoryResponse>(`/rankings/${encodeURIComponent(slug)}/history${qs({ countries: countries.join(',') })}`, signal),38};39