Skip to content
Aggarly OS
  • Getting Started
    • Introduction
  • Foundations
    • Colors
    • Spacing
    • Radius
    • Typography
    • Shadows
    • Z-Index
    • Gradients
    • Tokens
    • RTL & Direction
    • Theming
  • Layout
    • Containers
    • Grid System
    • Layout Utilities
  • Components
    • Alerts
    • Avatars
    • Buttons
    • Button Group
    • Badges
    • Inputs
    • Select
    • Checkbox
    • Counters-timers
    • Radio
    • Switch
    • Stepper
    • Select-pro
    • Cards
    • Modals
    • Dropdown
    • Popover
    • Tooltip
    • Accordion
    • Breadcrumb
    • Tabs
    • Navbar
    • List Group
    • Toasts
    • Skeleton
    • Pagination
    • Progress
    • Table
    • Icons
    • Form
    • Ribbons
    • Spinner
    • Offcanvas
    • Feedback
    • Grid
    • Data UI
  • Data UI
    • Overview
  • Platform
    • Reference DataCore
  • Widgets
    • KPI
    • Analytics
    • Behavior
  • Brand
    • Logo
    • Brand Usage
Platform/Reference Data

Reference Data Platform

These selects demonstrate production-ready integration with the backend-authoritative Reference Data Platform via the Next.js BFF /api/ proxy. Use serverSearch=falsefor static lists (countries/timezones/languages) to fetch once and filter client-side.

BFF Proxy Pattern

Browser talks to Next.js only; Next.js proxies to Django. This matches your CoreIdentity/BFF posture.

See code below (route handler).
Show code
BFF proxy route (TS)
// Browser calls Next.js (same origin). Next proxies to Django.
// Catch-all BFF proxy route (this repo):
//   src/app/api/[...path]/route.ts
//
// Key projects:
// - allowlisted backend prefixes only (e.g. /api/v1/)
// - never forwards client Authorization/cookies
// - injects Authorization: Bearer <access_token> from httpOnly cookie
// - refreshes via Identity /token (grant_type=refresh_token) and retries once

// Example: browser fetch
//   fetch("/api/v1/meta/bootstrap")

Countries

ISO 3166-1. Persist the country code (alpha-2).

Fetch once via BFF proxy; filter locally.
Show code
Countries select (JSX)
import { ApiSelect } from "@/design-system/components/ApiSelect";

export function CountriesSelect() {
  return (
    <ApiSelect
      label="Country"
      placeholder="Select country"
      url="/api/v1/meta/countries/?locale=en"
      serverSearch={false} // fetch once, filter client-side
      queryParam={undefined}
      transform={(data) => {
        const list = Array.isArray(data)
          ? data
          : (data as any)?.countries ?? (data as any)?.items ?? [];
        return (list ?? []).map((c: any) => ({
          value: c.code,
          label: c.name,
          keywords: [c.alpha3, c.numeric],
          meta: c,
        }));
      }}
    />
  );
}

Cities — country-scoped autocomplete (server-driven)

Cascades off the selected country. Seeded into Postgres from GeoNames cities15000 (population >= 15k) via `seed_reference_cities`; queries never leave your infra (zero external APIs at runtime). Persist the chosen city name; CityApiSelect falls back to free-text for long-tail towns.

Open the dropdown to see a country's cities ranked by population, or type to search.

City data © GeoNames, licensed under CC BY 4.0.

Show code
Country + city cascade (JSX)
import { useState } from "react";
import { CityApiSelect, CountryApiSelect } from "@/components/location/LocationSelects.client";

// Cities are country-scoped and server-driven. The city list cascades off
// the selected country and is seeded into Postgres via:
//   python manage.py seed_reference_cities   (GeoNames cities15000)
// Best practice:
// - persist the country code (alpha-2) + the chosen city name
// - CityApiSelect falls back to free-text for long-tail / custom cities
export function LocationFields() {
  const [country, setCountry] = useState("");
  const [city, setCity] = useState("");
  return (
    <>
      <CountryApiSelect
        name="country"
        locale="en"
        value={country}
        onValueChange={(next) => { setCountry(next); setCity(""); }}
      />
      <CityApiSelect
        name="city"
        locale="en"
        country={country}
        value={city}
        onValueChange={setCity}
      />
    </>
  );
}

Timezones

IANA timezones. Use virtualization for smoother performance.

Timezone lists benefit from virtualization + search.
Show code
Timezones select (JSX)
import { ApiSelect } from "@/design-system/components/ApiSelect";

export function TimezoneSelect() {
  return (
    <ApiSelect
      label="Timezone"
      placeholder="Select timezone"
      url="/api/v1/meta/timezones/?locale=en"
      serverSearch={false}
      queryParam={undefined}
      virtualization={{ threshold: 200 }}
      transform={(data) => {
        const list = Array.isArray(data)
          ? data
          : (data as any)?.timezones ?? (data as any)?.items ?? [];
        return (list ?? []).map((tz: any) => ({
          value: tz.id,
          label: tz.label ?? tz.id,
          keywords: [tz.offset, tz.id],
          meta: tz,
        }));
      }}
    />
  );
}

Languages

ISO 639-1 with dir (rtl/ltr). Store canonical language code.

Store language code (ISO 639-1).
Show code
Languages select (JSX)
// See preview component for mapping + client-side filtering.

Phone Input (E.164) — Country dropdown + backend validation

Backend is authoritative. Persist only E.164. Treat phone validation as PII; avoid caching responses.

—
Enter a phone number to validate.Validation is backend-authoritative (E.164).
Show code
Phone validation pattern (TS)
// Phone recommended pattern:
// 1) GET /api/v1/meta/phone-countries/ (or bootstrap) for dropdown
// 2) POST /api/v1/meta/phone/validate/ on blur/submit
// 3) Persist only E.164 returned by backend

const res = await fetch("/api/v1/meta/phone/validate/", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ input: "0501234567", region: "SA", strict_region: true }),
});
const json = await res.json();
// json.e164 is the canonical value to store.