Type src/ against API and database shapes, fixing implicit anys
Adds .d.ts declarations beside the untyped JS modules imported from TS (api.js, navConfig.js, adminSchema.js, adminNav.js, src/data/*), with shapes taken from the server routes and migrations. get/post/ patch/del now return unknown unless the caller names the response. Fixes found along the way: - website/email/instagram are bare strings from splitLinks, not Link objects; OrganizationDetail's website and email pills rendered with no href or label and now link correctly. - The chapter map falls back to FALLBACK_COLOR for a region with no colour instead of painting its tiles black. Adds defineSection() so each page manifest entry's props are checked against its own Component. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5286cae9ca
commit
2428f4412a
39 changed files with 1318 additions and 343 deletions
|
|
@ -1,5 +1,13 @@
|
|||
import { useId, useState } from "react";
|
||||
import {
|
||||
useId,
|
||||
useState,
|
||||
type AnchorHTMLAttributes,
|
||||
type ButtonHTMLAttributes,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import ArrowLink from "../../components/ArrowLink.tsx";
|
||||
import type { OrgKind } from "../../lib/hrefs.ts";
|
||||
import type { OrganizationListItem } from "../../lib/useContent.ts";
|
||||
import {
|
||||
areasSentence,
|
||||
initialsFor,
|
||||
|
|
@ -47,7 +55,14 @@ const FALLBACK_COLOR = "#4a6b72";
|
|||
it — a link nested in a button is invalid, and a screen reader
|
||||
announces the whole row as one confused control.
|
||||
───────────────────────────────────────────────────────────── */
|
||||
function RowButton({ as: As = "button", color, children, ...rest }) {
|
||||
type RowButtonProps = {
|
||||
as?: "a" | "button";
|
||||
color: string;
|
||||
children: ReactNode;
|
||||
} & AnchorHTMLAttributes<HTMLAnchorElement> &
|
||||
ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
function RowButton({ as: As = "button", color, children, ...rest }: RowButtonProps) {
|
||||
return (
|
||||
<As
|
||||
className="shrink-0 whitespace-nowrap py-2 px-4 rounded-xl font-700 text-sm transition-transform duration-200 hover:scale-105"
|
||||
|
|
@ -59,7 +74,13 @@ function RowButton({ as: As = "button", color, children, ...rest }) {
|
|||
);
|
||||
}
|
||||
|
||||
function OrgMark({ org, color }) {
|
||||
function OrgMark({
|
||||
org,
|
||||
color,
|
||||
}: {
|
||||
org: Pick<OrganizationListItem, "logo" | "color" | "name">;
|
||||
color: string;
|
||||
}) {
|
||||
const [failed, setFailed] = useState(false);
|
||||
|
||||
if (org.logo && !failed) {
|
||||
|
|
@ -94,7 +115,9 @@ function OrgMark({ org, color }) {
|
|||
);
|
||||
}
|
||||
|
||||
function OrgRow({ org, pageLabel, siteLabel }) {
|
||||
type RowLabels = { pageLabel: string; siteLabel: string };
|
||||
|
||||
function OrgRow({ org, pageLabel, siteLabel }: RowLabels & { org: OrganizationListItem }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const panelId = useId();
|
||||
|
||||
|
|
@ -245,7 +268,12 @@ function OrgRow({ org, pageLabel, siteLabel }) {
|
|||
);
|
||||
}
|
||||
|
||||
function Block({ title, orgs, pageLabel, siteLabel }) {
|
||||
function Block({
|
||||
title,
|
||||
orgs,
|
||||
pageLabel,
|
||||
siteLabel,
|
||||
}: RowLabels & { title?: string; orgs: OrganizationListItem[] }) {
|
||||
if (orgs.length === 0) return null;
|
||||
|
||||
return (
|
||||
|
|
@ -262,7 +290,30 @@ function Block({ title, orgs, pageLabel, siteLabel }) {
|
|||
);
|
||||
}
|
||||
|
||||
const byName = (a, b) => a.name.localeCompare(b.name);
|
||||
const byName = (a: OrganizationListItem, b: OrganizationListItem) =>
|
||||
a.name.localeCompare(b.name);
|
||||
|
||||
/* One heading's worth of the list. Labels override the list's own. */
|
||||
type OrgGroup = {
|
||||
key: string;
|
||||
title: string;
|
||||
pageLabel?: string;
|
||||
siteLabel?: string;
|
||||
};
|
||||
|
||||
type OrgListVerticalProps = {
|
||||
kind?: OrgKind;
|
||||
title?: string;
|
||||
groups?: OrgGroup[];
|
||||
/** Which group key an organization files under. */
|
||||
groupBy?: (org: OrganizationListItem) => string | null | undefined;
|
||||
accent?: string;
|
||||
pageLabel?: string;
|
||||
siteLabel?: string;
|
||||
/** Null keeps the API's order. */
|
||||
sort?: ((a: OrganizationListItem, b: OrganizationListItem) => number) | null;
|
||||
empty?: string;
|
||||
};
|
||||
|
||||
export default function OrgListVertical({
|
||||
kind,
|
||||
|
|
@ -274,10 +325,10 @@ export default function OrgListVertical({
|
|||
siteLabel = "Visit site",
|
||||
sort = byName,
|
||||
empty = "· Nothing to show here just yet ·",
|
||||
}) {
|
||||
}: OrgListVerticalProps) {
|
||||
const { organizations, loading, error } = useOrganizations(kind);
|
||||
|
||||
const shell = children => (
|
||||
const shell = (children: ReactNode) => (
|
||||
<div className="mx-auto px-8 md:px-12 max-w-6xl">{children}</div>
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue