spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1'use client';2import { ClientApiError } from './client-api';3import type { MapResponse, MultiSeriesResponse, RankingResponse, SearchResponse, SimilarResponse } from './types';4import type { ChangesFeedResponse, TrendResponse } from './types-explore';56/**7 * Browser-side fetches used by the interactive widgets of the indicator / region / explore / changes pages.8 * Same-origin `/api/v1/*` (rewritten to the FastAPI service). Small and typed; server components do the9 * first render, these only react to user input.10 */11async function get<T>(path: string, signal?: AbortSignal): Promise<T> {12 const res = await fetch(`/api/v1${path}`, { headers: { accept: 'application/json' }, signal });13 if (!res.ok) throw new ClientApiError(res.status, `API ${res.status}`);14 return (await res.json()) as T;15}1617function qs(query: Record<string, string | number | boolean | null | undefined>): string {18 const p = new URLSearchParams();19 for (const [k, v] of Object.entries(query)) if (v !== undefined && v !== null && v !== '') p.set(k, String(v));20 const s = p.toString();21 return s ? `?${s}` : '';22}2324export const clientExplore = {25 indicatorMap: (slug: string, year: number | null, signal?: AbortSignal) => get<MapResponse>(`/indicators/${encodeURIComponent(slug)}/map${qs({ year })}`, signal),2627 indicatorTrend: (slug: string, group: string, signal?: AbortSignal) => get<TrendResponse>(`/indicators/${encodeURIComponent(slug)}/trend${qs({ group })}`, signal),2829 seriesBundle: (countries: string[], indicator: string, signal?: AbortSignal) =>30 get<MultiSeriesResponse>(`/series${qs({ country: countries.join(','), indicator })}`, signal),3132 ranking: (indicator: string, group: string, limit = 60, signal?: AbortSignal) =>33 get<RankingResponse>(`/rankings/${encodeURIComponent(indicator)}${qs({ group, limit, sparkline: false })}`, signal),3435 changes: (q: { limit?: number; kind?: string | null; indicator?: string | null; topic?: string | null; min_severity?: number | null }, signal?: AbortSignal) =>36 get<ChangesFeedResponse>(`/changes${qs({ limit: 100, ...q })}`, signal),3738 search: (q: string, type?: string, limit = 8, signal?: AbortSignal) => get<SearchResponse>(`/search${qs({ q, type, limit })}`, signal),3940 similar: (id: string, mode: string, limit = 10, signal?: AbortSignal) => get<SimilarResponse>(`/countries/${encodeURIComponent(id)}/similar${qs({ mode, limit })}`, signal),41};42