Convert src/ JavaScript modules to TypeScript

Renames every .js module under src/ to .ts (api, useResource,
adminSchema, navConfig, adminNav and src/data/*) and points imports,
comments and the seed script's module paths at the new names. Their
types now come from inference; no .d.ts files and no shape
interfaces.

tsc stays strict (noImplicitAny off). The errors inference leaves
behind get the lightest fix that clears them: `any` on empty state,
contexts and list defaults, `: any` on components with optional or
spread props, class fields on ApiError, and option shapes on
get/useResource.

Kept as real types, since they belong to modules that were already
TypeScript and later features import them: PageShell's ShellSection
and props, useContent's corrected record types (website/email/
instagram are bare strings) and EventListItem, and TimelineRef's
orgKind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Zaldimmar 2026-09-26 15:39:42 -05:00
parent 5cedc68fd7
commit 22d5328885
43 changed files with 195 additions and 135 deletions

View file

@ -11,10 +11,10 @@
Two things live elsewhere:
the tile grid src/data/mapGrid.js. Where a state sits never
the tile grid src/data/mapGrid.ts. Where a state sits never
changes, so it isn't worth a round trip.
the fetch src/data/organizations.js. One endpoint for
the fetch src/data/organizations.ts. One endpoint for
every kind, so a section listing regions and
a section listing partners read alike.
═══════════════════════════════════════════════════════════════ */
@ -25,12 +25,12 @@ import {
areasSentence,
initialsFor,
useOrganizations,
} from "./organizations.js";
} from "./organizations.ts";
import {
areaForChapter,
buildAreaSlices,
countChaptersByArea,
} from "./mapGrid.js";
} from "./mapGrid.ts";
const byName = (a, b) => a.name.localeCompare(b.name);

View file

@ -3,7 +3,7 @@
One request, filtered per section. The Retreats page has three
bands of events, and all three call this hook — the cache in
api.js keys on the path, so they share a single fetch and each
api.ts keys on the path, so they share a single fetch and each
narrows the result to what it shows.
useEvents({ section: "national" }) one band
@ -33,13 +33,13 @@
import { useMemo } from "react";
import { useResource } from "../lib/useResource.js";
import { useResource } from "../lib/useResource.ts";
/* One shared empty list, so a memo keyed on `sections` doesn't
restart on every render before the data arrives. */
const NO_SECTIONS = [];
const NO_SECTIONS: any[] = [];
export function useEvents({ section, host, status, type } = {}) {
export function useEvents({ section, host, status, type }: any = {}) {
const { data, error, loading } = useResource("/events");
const all = data?.events;
@ -71,9 +71,9 @@ export function useEvents({ section, host, status, type } = {}) {
/* Past and upcoming, split. `status` arrives already resolved — the
explicit value when there is one, otherwise derived from ends_on
— so nothing here needs to know which of the two it got. */
export function splitByStatus(events = []) {
const upcoming = [];
const past = [];
export function splitByStatus(events: any[] = []) {
const upcoming: any[] = [];
const past: any[] = [];
for (const event of events) {
(event.status === "past" ? past : upcoming).push(event);
@ -86,7 +86,7 @@ export function splitByStatus(events = []) {
EVENT_TYPES order rather than whatever order the rows arrived
in. A section with one type has nothing to filter, which is what
lets the chip bar hide itself. */
export function typesPresent(events = [], declared = []) {
export function typesPresent(events: any[] = [], declared: any[] = []) {
const seen = new Set(events.map(e => e.event_type));
return declared.filter(entry => seen.has(entry.id));
}

View file

@ -121,9 +121,9 @@ export function areaForChapter(chapter) {
regionAreas [{ region_id, area_code, share, edge, note }]
regions [{ id, name, color, ... }]
───────────────────────────────────────────────────────────── */
export function buildAreaSlices(regionAreas = [], regions = []) {
export function buildAreaSlices(regionAreas: any[] = [], regions: any[] = []) {
const regionById = Object.fromEntries(regions.map((r) => [r.id, r]));
const byArea = {};
const byArea: Record<string, any[]> = {};
for (const row of regionAreas) {
const area = AREA_BY_CODE[row.area_code];
@ -151,7 +151,7 @@ export function buildAreaSlices(regionAreas = [], regions = []) {
/* ── Chapter counts per tile ───────────────────────────────────
{ WA: 2, MO: 1, CANADA: 1 }
───────────────────────────────────────────────────────────── */
export function countChaptersByArea(chapters = []) {
export function countChaptersByArea(chapters: any[] = []) {
const counts = {};
for (const chapter of chapters) {
const code = areaForChapter(chapter);
@ -165,7 +165,7 @@ export function countChaptersByArea(chapters = []) {
from the row rather than being looked up in a splits table and
branched on whether this region is the primary.
───────────────────────────────────────────────────────────── */
export function areasLabel(regionId, regionAreas = []) {
export function areasLabel(regionId, regionAreas: any[] = []) {
return regionAreas
.filter((row) => row.region_id === regionId && row.area_code !== "CANADA")
.map((row) => (row.note ? `${row.area_code} (${row.note})` : row.area_code))

View file

@ -18,12 +18,12 @@
chapter { region_id, region_name, region_color, meets, started }
partner {}
Each kind is a separate request path, so the cache in api.js
Each kind is a separate request path, so the cache in api.ts
keys them apart and two sections asking for regions share one
fetch.
═══════════════════════════════════════════════════════════════ */
import { useResource } from "../lib/useResource.js";
import { useResource } from "../lib/useResource.ts";
const EMPTY = { organizations: [] };
@ -69,7 +69,7 @@ export function orgPath(org) {
any more.
───────────────────────────────────────────────────────────── */
export function areasSentence(areas = []) {
export function areasSentence(areas: any[] = []) {
return areas
.filter((area) => area.area_code !== "CANADA")
.map((area) => (area.note ? `${area.area_code} (${area.note})` : area.area_code))