From 5cedc68fd72cad24286153d24ed83e0df15905fd Mon Sep 17 00:00:00 2001 From: Zaldimmar Date: Sat, 26 Sep 2026 15:32:28 -0500 Subject: [PATCH 1/2] Revert the implicit-any typing of src/ Reverts 2428f44. The .d.ts files beside the JS modules and the annotations threaded through src/ to satisfy noImplicitAny go; tsconfig turns noImplicitAny off instead. The JS modules become TypeScript in the next commit, so their types come from inference. Kept from that commit, since they're runtime fixes rather than types: - OrganizationDetail's website and email pills get an href and label (they arrive as bare strings, not link objects). - The chapter map falls back to FALLBACK_COLOR for a region with no colour instead of painting its tiles black. Co-Authored-By: Claude Opus 5.5 --- src/components/ArrowLink.tsx | 10 +- src/components/Layout.tsx | 2 +- src/components/PageShell.tsx | 22 +- src/components/PeopleTiles.tsx | 15 +- src/components/admin/fields.tsx | 101 ++------ src/data/bannerConfig.d.ts | 10 - src/data/chapters.d.ts | 47 ---- src/data/eventData.d.ts | 29 --- src/data/feedbackTypes.d.ts | 7 - src/data/mapGrid.d.ts | 51 ---- src/data/organizations.d.ts | 21 -- src/lib/adminSchema.d.ts | 123 --------- src/lib/adminTitle.tsx | 6 +- src/lib/api.d.ts | 26 -- src/lib/auth.tsx | 35 +-- src/lib/sections.tsx | 58 +---- src/lib/timeline.ts | 4 - src/lib/useContent.ts | 46 +--- src/lib/useRecord.ts | 2 +- src/navConfig.d.ts | 20 -- src/pages/Community.tsx | 14 +- src/pages/EventDetail.tsx | 4 +- src/pages/Retreats.tsx | 28 +- src/pages/admin/AdminFeedback.tsx | 75 ++---- src/pages/admin/AdminHome.tsx | 19 +- src/pages/admin/AdminLayout.tsx | 4 +- src/pages/admin/AdminLogin.tsx | 6 +- src/pages/admin/AdminPanel.tsx | 95 ++----- src/pages/admin/EntityEdit.tsx | 85 +++--- src/pages/admin/EntityList.tsx | 35 +-- src/pages/admin/RequireRole.tsx | 4 +- src/pages/admin/adminNav.d.ts | 45 ---- src/pages/sections/EventList-Cards.tsx | 88 ++----- src/pages/sections/FeedbackForm.tsx | 48 +--- src/pages/sections/OrgList-Card.tsx | 58 +---- src/pages/sections/OrgList-Map.tsx | 331 ++++++++---------------- src/pages/sections/OrgList-Vertical.tsx | 67 +---- tsconfig.json | 1 + 38 files changed, 340 insertions(+), 1302 deletions(-) delete mode 100644 src/data/bannerConfig.d.ts delete mode 100644 src/data/chapters.d.ts delete mode 100644 src/data/eventData.d.ts delete mode 100644 src/data/feedbackTypes.d.ts delete mode 100644 src/data/mapGrid.d.ts delete mode 100644 src/data/organizations.d.ts delete mode 100644 src/lib/adminSchema.d.ts delete mode 100644 src/lib/api.d.ts delete mode 100644 src/navConfig.d.ts delete mode 100644 src/pages/admin/adminNav.d.ts diff --git a/src/components/ArrowLink.tsx b/src/components/ArrowLink.tsx index 056fd49..b2f9af0 100644 --- a/src/components/ArrowLink.tsx +++ b/src/components/ArrowLink.tsx @@ -4,15 +4,7 @@ import { Link } from "react-router-dom"; ARROW LINK ═══════════════════════════════════════════════════════════════ */ -type ArrowLinkProps = { - to: string; - label: string; - color: string; - /** Tailwind size classes for the circle. */ - size?: string; -}; - -export default function ArrowLink({ to, label, color, size = "h-9 w-9" }: ArrowLinkProps) { +export default function ArrowLink({ to, label, color, size = "h-9 w-9" }) { return ( document.querySelector(s.hash)) - .filter((el): el is Element => el !== null); + .filter(Boolean); if (targets.length === 0) return; const observer = new IntersectionObserver( diff --git a/src/components/PageShell.tsx b/src/components/PageShell.tsx index 496f926..63ce434 100644 --- a/src/components/PageShell.tsx +++ b/src/components/PageShell.tsx @@ -29,27 +29,9 @@ /> ═══════════════════════════════════════════════════════════════ */ -import type { ReactNode } from "react"; - const TEAL = "#138ba0"; -export type ShellSection = { - id: string; - title: string; - blurb?: string; - accent: string; - background: string; - actions?: ReactNode; - content: ReactNode; -}; - -type PageShellProps = { - title: ReactNode; - intro?: ReactNode; - sections: ShellSection[]; -}; - -export function Section({ section }: { section: ShellSection }) { +export function Section({ section }) { const { id, title, @@ -88,7 +70,7 @@ export function Section({ section }: { section: ShellSection }) { ); } -export default function PageShell({ title, intro, sections }: PageShellProps) { +export default function PageShell({ title, intro, sections }) { return ( <> {/* Page header */} diff --git a/src/components/PeopleTiles.tsx b/src/components/PeopleTiles.tsx index 62dcf4f..9ef1043 100644 --- a/src/components/PeopleTiles.tsx +++ b/src/components/PeopleTiles.tsx @@ -174,7 +174,7 @@ export default function PeopleTiles({ Promise.all( specs.map((spec) => - get(`/teams/${spec.id}/people`, { ttl }).then((data) => ({ + get(`/teams/${spec.id}/people`, { ttl }).then((data: TeamResponse) => ({ spec, data, })), @@ -208,8 +208,8 @@ export default function PeopleTiles({ let live = true; setFailed(false); - get<{ people: Person[] }>(`/people?ids=${encodeURIComponent(slugKey)}`, { ttl }) - .then((data) => { + get(`/people?ids=${encodeURIComponent(slugKey)}`, { ttl }) + .then((data: { people: Person[] }) => { if (!live) return; const byId: Record = {}; for (const person of data.people) byId[String(person.id)] = person; @@ -314,10 +314,11 @@ function resolveAll( } const { peopleslug, ...overrides } = entry; - const defined: Partial = Object.fromEntries( - Object.entries(overrides).filter(([, value]) => value !== undefined), - ); - resolved.push({ ...base, ...defined }); + const merged: Person = { ...base }; + for (const [key, value] of Object.entries(overrides)) { + if (value !== undefined) (merged as Record)[key] = value; + } + resolved.push(merged); } return resolved; diff --git a/src/components/admin/fields.tsx b/src/components/admin/fields.tsx index d3a761b..aba024c 100644 --- a/src/components/admin/fields.tsx +++ b/src/components/admin/fields.tsx @@ -32,16 +32,7 @@ able to read and copy. ═══════════════════════════════════════════════════════════════ */ -import { useRef, useState, type ChangeEvent, type ReactNode } from "react"; - -import type { FieldErrors } from "../../lib/api.js"; -import type { - AdminFieldSpec, - AdminOption, - AdminOptions, - AdminRow, - CollectionSpec, -} from "../../lib/adminSchema.js"; +import { useRef, useState } from "react"; const input = "w-full rounded-lg border border-[#4a6b72]/25 bg-white px-3 py-2 text-sm text-[#26454c] " + @@ -65,56 +56,37 @@ const inputLocked = /* ── Dotted paths ────────────────────────────────────────────── */ -export function getPath(object: unknown, path: string | null | undefined): unknown { +export function getPath(object, path) { // An entity with no slug has no heading path either, and a missing // path should read as "no value" rather than throwing on .split. if (!path) return undefined; - return path - .split(".") - .reduce((value, key) => (value == null ? undefined : (value as AdminRow)[key]), object); + return path.split(".").reduce((value, key) => value?.[key], object); } -export function setPath(object: AdminRow | null | undefined, path: string, value: unknown): AdminRow { +export function setPath(object, path, value) { const [head, ...rest] = path.split("."); if (rest.length === 0) return { ...object, [head]: value }; - const inner = (object?.[head] ?? {}) as AdminRow; - return { ...object, [head]: setPath(inner, rest.join("."), value) }; + return { ...object, [head]: setPath(object?.[head] ?? {}, rest.join("."), value) }; } /* ── Field ───────────────────────────────────────────────────── */ -/* [value, label, the option row it came from]. Manifest options - have no row, which is what filterBy's `!raw` lets through. */ -type Choice = [id: string, label: string, raw?: AdminOption]; - -type FieldProps = { - field: AdminFieldSpec; - /* Whatever the row holds at field.path; shown as text. */ - value: unknown; - row?: AdminRow; - options?: AdminOptions | null; - error?: string; - onChange: (value: string | number) => void; -}; - -export function Field({ field, value, row, options, error, onChange }: FieldProps) { +export function Field({ field, value, row, options, error, onChange }) { const id = `f-${field.path.replace(/\./g, "-")}`; const widget = field.widget ?? "text"; const locked = Boolean(field.readOnly); - const text = value == null ? "" : String(value); - let list: Choice[] = []; + let list = null; let orphaned = false; if (widget === "select") { list = field.optionsFrom - ? (options?.[field.optionsFrom] ?? []).map((o): Choice => [o.id, o.label, o]) - : (field.options ?? []).map((o): Choice => - typeof o === "string" ? [o, o] : [o[0], o[1]], + ? (options?.[field.optionsFrom] ?? []).map((o) => [o.id, o.label, o]) + : (field.options ?? []).map((o) => + Array.isArray(o) ? [o[0], o[1]] : [o, o], ); - const { filterBy } = field; - if (filterBy && row) { - list = list.filter(([, , raw]) => !raw || filterBy(raw, row)); + if (field.filterBy && row) { + list = list.filter(([, , raw]) => !raw || field.filterBy(raw, row)); } // A stored value with no matching option renders as the blank @@ -130,9 +102,8 @@ export function Field({ field, value, row, options, error, onChange }: FieldProp const common = { id, className: `${input} ${error ? inputError : ""}`, - value: text, - onChange: (e: ChangeEvent) => - onChange(e.target.value), + value: value ?? "", + onChange: (e) => onChange(e.target.value), }; return ( @@ -174,7 +145,7 @@ export function Field({ field, value, row, options, error, onChange }: FieldProp }`} > - {orphaned && } + {orphaned && } {list.map(([id2, label]) => (