A /watch link is opened by whoever it was sent to — signed out, often on a phone on mobile data — and it booted through main.tsx: every app provider, the app's i18n (every locale of every area), the flag sprites, ~392 KB gzip before the player could start. It now has its own HTML entry, watch.html → watchMain.tsx, which imports WatchPage and what it renders; the server hands every /watch/ path that page (the OG block injected as before), and vite dev does the same. Measured on the build: /watch JS 290 → 87 KB gzip, CSS 104 → 19 KB; the app's own bytes unchanged. Two leaks had to go for that: lib/format imported the app's i18n for one "just now" string (the relative-time formatters now take `t`, like the file's other t-formatters), and the boundary's crash copy came from the same i18n (CrashBoundary takes its copy; ErrorBoundary is the app's wrapper, the watch entry gives it the page's own strings). The page's popover tree went too: it renders no popover. Held by: a contract test walking the entry's runtime imports, naming the path to anything only the app may load (red on a planted i18n import in lib/format), and holding watch.html's head equal to index.html's; a DB-lane test that every /watch/ path gets the player's page with the link's OG tags and app paths get the app (red on the old routing). Screenshots at 375 px, EN and HU, video / audio / password / gone / wrong password / unlocked: identical to the old entry's, bar the video's buffered bar.
36 lines
1.3 KiB
TypeScript
36 lines
1.3 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import { formatDateTime, formatDateTimeSec, relativeFromMs } from "../../lib/format";
|
|
|
|
// docs/DESIGN.md "Copy": a relative time ("3d ago") says how recent something is in a list, and the
|
|
// exact moment is one hover away — so the pair travels together here instead of every list
|
|
// remembering (or forgetting) its own `title`. `seconds` is the log form (the audit). An unreadable
|
|
// or missing moment renders `empty` ("—" in a table cell) or nothing.
|
|
export function RelativeTime({
|
|
at,
|
|
seconds = false,
|
|
empty = "",
|
|
className,
|
|
}: {
|
|
/** An ISO timestamp, or epoch millis (a client-side notification). */
|
|
at: string | number | null | undefined;
|
|
seconds?: boolean;
|
|
empty?: string;
|
|
className?: string;
|
|
}) {
|
|
const { t, i18n } = useTranslation();
|
|
const lang = i18n.language;
|
|
const ms = typeof at === "number" ? at : at ? new Date(at).getTime() : NaN;
|
|
const text = Number.isFinite(ms) ? relativeFromMs(ms, lang, t) : "";
|
|
if (!text) return empty ? <span className={className}>{empty}</span> : null;
|
|
const iso = new Date(ms).toISOString();
|
|
return (
|
|
<time
|
|
dateTime={iso}
|
|
title={seconds ? formatDateTimeSec(iso, lang) : formatDateTime(iso, lang)}
|
|
className={className}
|
|
>
|
|
{text}
|
|
</time>
|
|
);
|
|
}
|