- Internal links use next/link instead of <a> (no full page reload). - Remove dead code: RegisterForm's unreachable Yoco checkout branch, the unused AttendeeForms on the registration success page, unused Geist fonts, and assorted unused variables and catch bindings. - Escape quotes in JSX text; replace @ts-ignore in apiFetch with a cast. - Keep the intentional raw <img> elements (data URLs, runtime API host) with a documented lint exception; the event editor uses ApiImage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
42 lines
1.8 KiB
TypeScript
42 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { resolveToApiOrigin } from "@/lib/api";
|
|
|
|
type Props = {
|
|
src?: string | null;
|
|
alt?: string;
|
|
className?: string;
|
|
style?: React.CSSProperties;
|
|
};
|
|
|
|
export function ApiImage({ src, alt = "", className, style }: Props) {
|
|
const hasSrc = !!(src || "").trim();
|
|
const [resolved, setResolved] = React.useState<string | null>(null);
|
|
|
|
React.useEffect(() => {
|
|
const raw = (src || "").trim();
|
|
if (!raw) { setResolved(null); return; }
|
|
// Compute in the browser so that localhost origins are swapped to the actual device hostname.
|
|
// This has to stay in an effect (not computed during render) because the server-rendered pass
|
|
// has no `window`, so resolving synchronously would make the client's first render disagree
|
|
// with the server-rendered HTML (a hydration mismatch) — the round trip here is a browser-vs-
|
|
// server naming difference, not a network fetch.
|
|
const url = resolveToApiOrigin(raw);
|
|
setResolved(url);
|
|
}, [src]);
|
|
|
|
// No image was ever provided for this item — render nothing, same as before.
|
|
if (!hasSrc) return null;
|
|
|
|
// A src was provided but the browser-resolved URL isn't ready yet — reserve the same box the
|
|
// real <img> will occupy so the surrounding layout (e.g. a card) doesn't collapse to zero height
|
|
// and then jump once resolved — same class of flash as an unauthenticated nav link showing too early.
|
|
if (!resolved) return <div className={`${className || ""} bg-gray-100 animate-pulse`} style={style} aria-hidden="true" />;
|
|
|
|
// A plain <img>: the API host is only known at runtime and the images have no fixed size,
|
|
// so next/image's optimizer (which needs both up front) doesn't fit here.
|
|
// eslint-disable-next-line @next/next/no-img-element
|
|
return <img src={resolved} alt={alt} className={className} style={style} />;
|
|
}
|