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 bd738a9..61fd716 100644 --- a/src/components/PeopleTiles.tsx +++ b/src/components/PeopleTiles.tsx @@ -164,7 +164,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, })), @@ -198,8 +198,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; @@ -304,10 +304,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 c56f092..95296f8 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]) => (