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:
parent
5cedc68fd7
commit
22d5328885
43 changed files with 195 additions and 135 deletions
|
|
@ -20,10 +20,10 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { del, get, patch, ApiError } from "../../lib/api.js";
|
||||
import { del, get, patch, ApiError } from "../../lib/api.ts";
|
||||
import { isUnauthorized, useAuth } from "../../lib/auth.tsx";
|
||||
import { canWrite as roleCanWrite, canDelete } from "../../lib/roles.ts";
|
||||
import { feedbackTypeLabel } from "../../data/feedbackTypes.js";
|
||||
import { feedbackTypeLabel } from "../../data/feedbackTypes.ts";
|
||||
|
||||
const STATUSES = ["new", "read", "actioned", "archived", "spam"];
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ function locationOf(row) {
|
|||
function FeedbackCard({ row, onChange, onRemove, canWrite, canRemove }) {
|
||||
const [note, setNote] = useState(row.admin_note ?? "");
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
const [error, setError] = useState<any>(null);
|
||||
const [confirming, setConfirming] = useState(false);
|
||||
|
||||
const noteDirty = note !== (row.admin_note ?? "");
|
||||
|
|
@ -232,11 +232,11 @@ export default function AdminFeedback() {
|
|||
const [query, setQuery] = useState("");
|
||||
const [search, setSearch] = useState(""); // applied, not typed
|
||||
|
||||
const [rows, setRows] = useState([]);
|
||||
const [counts, setCounts] = useState({});
|
||||
const [cursor, setCursor] = useState(null);
|
||||
const [rows, setRows] = useState<any[]>([]);
|
||||
const [counts, setCounts] = useState<any>({});
|
||||
const [cursor, setCursor] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [error, setError] = useState<any>(null);
|
||||
|
||||
// Minimums, not equality — see lib/roles.ts.
|
||||
const canWrite = roleCanWrite(user);
|
||||
|
|
@ -298,7 +298,7 @@ export default function AdminFeedback() {
|
|||
}));
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
const tabs: any[] = [
|
||||
{ id: "all", label: "All" },
|
||||
...STATUSES.map((s) => ({ id: s, label: s, count: counts[s] })),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
the header deliberately drops its tab row here so the same links
|
||||
aren't drawn twice — and a standing info panel on the right.
|
||||
|
||||
The cards come from adminNav.js, the same list the CMS header
|
||||
The cards come from adminNav.ts, the same list the CMS header
|
||||
reads, so a new entity shows up here the moment it's registered.
|
||||
Forms sit in their own block below: they're submissions coming
|
||||
in rather than content going out, and there'll be more of them
|
||||
|
|
@ -22,7 +22,7 @@ import { Link } from "react-router-dom";
|
|||
import { useAuth } from "../../lib/auth.tsx";
|
||||
import { SITE_VERSION } from "../../lib/version.ts";
|
||||
import { ROLE_LABELS, isSuper } from "../../lib/roles.ts";
|
||||
import { CMS_NAV, FORMS_NAV, PANEL_NAV, target } from "./adminNav.js";
|
||||
import { CMS_NAV, FORMS_NAV, PANEL_NAV, target } from "./adminNav.ts";
|
||||
|
||||
/* One card. The title link is stretched over the whole card with
|
||||
`after:absolute`, which makes the card clickable without nesting
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import {
|
|||
matches,
|
||||
navFor,
|
||||
target,
|
||||
} from "./adminNav.js";
|
||||
} from "./adminNav.ts";
|
||||
|
||||
export default function AdminLayout() {
|
||||
const { user, logout } = useAuth();
|
||||
|
|
@ -56,7 +56,7 @@ export default function AdminLayout() {
|
|||
// What the page below has published about itself — a record
|
||||
// name, or null on a list. setDetail is stable so publishing
|
||||
// can't loop.
|
||||
const [detail, setDetail] = useState(null);
|
||||
const [detail, setDetail] = useState<any>(null);
|
||||
const stableSet = useCallback((value) => setDetail(value), []);
|
||||
const titleContext = useMemo(() => ({ setDetail: stableSet }), [stableSet]);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { useState } from "react";
|
|||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
import { useAuth } from "../../lib/auth.tsx";
|
||||
import { ApiError } from "../../lib/api.js";
|
||||
import { ApiError } from "../../lib/api.ts";
|
||||
|
||||
const GOOGLE_ENABLED = false;
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ export default function AdminLogin() {
|
|||
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState(null);
|
||||
const [error, setError] = useState<any>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
const destination = location.state?.from?.pathname ?? "/admin/home";
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
═══════════════════════════════════════════════════════════════ */
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { del, get, patch } from "../../lib/api.js";
|
||||
import { del, get, patch } from "../../lib/api.ts";
|
||||
import { isUnauthorized, useAuth } from "../../lib/auth.tsx";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ROLES, ROLE_LABELS, ROLE_NOTES } from "../../lib/roles.ts";
|
||||
|
|
@ -50,7 +50,7 @@ function uptime(seconds) {
|
|||
return `${m}m`;
|
||||
}
|
||||
|
||||
function Block({ title, note, children }) {
|
||||
function Block({ title, note, children }: any) {
|
||||
return (
|
||||
<section className="mt-8 first:mt-0">
|
||||
<div className="flex items-baseline gap-3">
|
||||
|
|
@ -77,21 +77,21 @@ export default function AdminPanel() {
|
|||
const { user: me } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [data, setData] = useState(null);
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [error, setError] = useState<any>(null);
|
||||
|
||||
// Which row is mid-request, and what went wrong on it. Scoped to
|
||||
// the row so a failure on one account doesn't blank the table.
|
||||
const [busyId, setBusyId] = useState(null);
|
||||
const [rowError, setRowError] = useState(null);
|
||||
const [busyId, setBusyId] = useState<any>(null);
|
||||
const [rowError, setRowError] = useState<any>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
setData(await get("/admin/panel/overview", { ttl: 0 }));
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
if (isUnauthorized(err)) return navigate("/admin/login", { replace: true });
|
||||
setError(err.message || "Couldn't load the panel.");
|
||||
} finally {
|
||||
|
|
@ -121,7 +121,7 @@ export default function AdminPanel() {
|
|||
setRowError(null);
|
||||
try {
|
||||
mergeUser(await work());
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
if (isUnauthorized(err)) return navigate("/admin/login", { replace: true });
|
||||
setRowError({ id, message: err.message || "That didn't work." });
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -43,10 +43,10 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import { get, post, patch, del, ApiError } from "../../lib/api.js";
|
||||
import { get, post, patch, del, ApiError } from "../../lib/api.ts";
|
||||
import { isUnauthorized, useAuth } from "../../lib/auth.tsx";
|
||||
import { useAdminDetail } from "../../lib/adminTitle.tsx";
|
||||
import { ADMIN_ENTITIES, slugify } from "../../lib/adminSchema.js";
|
||||
import { ADMIN_ENTITIES, slugify } from "../../lib/adminSchema.ts";
|
||||
import { atLeast } from "../../lib/roles.ts";
|
||||
import { Field, FieldGrid, Repeater, getPath, setPath } from "../../components/admin/fields.tsx";
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ function friendly(message, singular) {
|
|||
|
||||
export default function EntityEdit() {
|
||||
const { entity: entityKey, id } = useParams();
|
||||
const manifest = ADMIN_ENTITIES[entityKey];
|
||||
const manifest = ADMIN_ENTITIES[entityKey ?? ""];
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
|
||||
|
|
@ -120,17 +120,17 @@ export default function EntityEdit() {
|
|||
// names the field to read instead.
|
||||
const headingPath = slugPaths[slugPaths.length - 1] ?? manifest?.titleFrom;
|
||||
|
||||
const [form, setForm] = useState(null);
|
||||
const [options, setOptions] = useState({});
|
||||
const [errors, setErrors] = useState({});
|
||||
const [message, setMessage] = useState(null);
|
||||
const [form, setForm] = useState<any>(null);
|
||||
const [options, setOptions] = useState<any>({});
|
||||
const [errors, setErrors] = useState<any>({});
|
||||
const [message, setMessage] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [slugTouched, setSlugTouched] = useState(false);
|
||||
|
||||
// The last state the server confirmed. Everything else compares
|
||||
// against this to decide whether there's anything to lose.
|
||||
const baseline = useRef(null);
|
||||
const baseline = useRef<any>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
if (!manifest) return;
|
||||
|
|
|
|||
|
|
@ -15,22 +15,22 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Link, Navigate, useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
|
||||
import { get, ApiError } from "../../lib/api.js";
|
||||
import { get, ApiError } from "../../lib/api.ts";
|
||||
import { isUnauthorized, useAuth } from "../../lib/auth.tsx";
|
||||
import { ADMIN_ENTITIES } from "../../lib/adminSchema.js";
|
||||
import { ADMIN_ENTITIES } from "../../lib/adminSchema.ts";
|
||||
import { atLeast } from "../../lib/roles.ts";
|
||||
|
||||
export default function EntityList() {
|
||||
const { entity: entityKey } = useParams();
|
||||
const manifest = ADMIN_ENTITIES[entityKey];
|
||||
const manifest = ADMIN_ENTITIES[entityKey ?? ""];
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
|
||||
const [params, setParams] = useSearchParams();
|
||||
const [rows, setRows] = useState([]);
|
||||
const [options, setOptions] = useState({});
|
||||
const [rows, setRows] = useState<any[]>([]);
|
||||
const [options, setOptions] = useState<any>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [error, setError] = useState<any>(null);
|
||||
const [query, setQuery] = useState(params.get("q") ?? "");
|
||||
|
||||
// Minimum rank, never equality. POST /api/admin/:entity is gated
|
||||
|
|
@ -79,7 +79,7 @@ export default function EntityList() {
|
|||
setParams(next, { replace: true });
|
||||
}
|
||||
|
||||
function labelFor(filter, value) {
|
||||
function labelFor(filter) {
|
||||
if (filter.optionsFrom) {
|
||||
return (options[filter.optionsFrom] ?? []).map((o) => [o.id, o.label]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@
|
|||
|
||||
import { Navigate, Outlet } from "react-router-dom";
|
||||
import { useAuth } from "../../lib/auth.tsx";
|
||||
import { atLeast } from "../../lib/roles.ts";
|
||||
import { ADMIN_HOME } from "./adminNav.js";
|
||||
import { atLeast, type Role } from "../../lib/roles.ts";
|
||||
import { ADMIN_HOME } from "./adminNav.ts";
|
||||
|
||||
export default function RequireRole({ role = "superadmin" }) {
|
||||
export default function RequireRole({ role = "superadmin" }: { role?: Role }) {
|
||||
const { user, loading } = useAuth();
|
||||
|
||||
// RequireAuth is already showing its own placeholder above this.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/* ═══════════════════════════════════════════════════════════════
|
||||
ADMIN NAVIGATION — src/pages/admin/adminNav.js
|
||||
ADMIN NAVIGATION — src/pages/admin/adminNav.ts
|
||||
|
||||
Lifted out of AdminLayout because two things read it now: the
|
||||
header tabs and the card grid on the home page. Adding an entity
|
||||
|
|
@ -99,7 +99,7 @@ export const PANEL_NAV = {
|
|||
superOnly: true,
|
||||
};
|
||||
|
||||
export const NAV = [...CMS_NAV, FORMS_NAV, PANEL_NAV];
|
||||
export const NAV: any[] = [...CMS_NAV, FORMS_NAV, PANEL_NAV];
|
||||
|
||||
/* What this user may see. Call it with the user from useAuth. */
|
||||
export function navFor(user) {
|
||||
Loading…
Add table
Add a link
Reference in a new issue