Website: Next.js app for accounts, linking, who's online, device sign-in and the console

apps/web: Next.js 16 (App Router), Tailwind 4 and shadcn/ui, light and dark from the system. Pages render on the
server and call the API with Eden Treaty (typed by @t5de/api), forwarding the visitor's cookie; forms use server
actions; sign-up, sign-in and sign-out use Better Auth's React client. /api is rewritten to the API so its cookies
are first-party. Who's online follows the API's event stream (Next's gzip is off: it held the stream back).
Standalone output with a Dockerfile; deploys to Vercel as is.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-29 05:02:00 -07:00
1 parent 7afada1c63
commit 64c552330b
57 files changed
+3834

No files matched your search

+68
View File
@@ -0,0 +1,68 @@
"use client";
// Who's online in T5DE, kept live: the page renders the list, then this follows the API's event stream.
import Link from "next/link";
import { useEffect, useState } from "react";
import { Initial, Outfit, Tag } from "@/components/status";
import { Since } from "@/components/time";
import type { OnlineEntry } from "@/lib/api";
export function OnlineList({ initial, moderator = false }: { initial: OnlineEntry[]; moderator?: boolean }) {
const [online, setOnline] = useState(initial);
const [live, setLive] = useState(false);
useEffect(() => {
const events = new EventSource("/api/v1/online/events");
events.addEventListener("online", (e) => {
setOnline((JSON.parse(e.data) as { online: OnlineEntry[] }).online);
setLive(true);
});
events.addEventListener("error", () => setLive(false));
return () => events.close();
}, []);
return (
<div className="space-y-4">
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<span
className={live ? "size-2 rounded-full bg-emerald-500" : "size-2 rounded-full bg-muted-foreground/40"}
aria-hidden
/>
{live ? "Live" : "Connecting…"}
<span className="ml-auto tabular-nums">
{online.length} {online.length === 1 ? "account" : "accounts"}
</span>
</div>
{online.length === 0 ? (
<p className="py-6 text-sm text-muted-foreground">Nobody is showing that they&apos;re online right now.</p>
) : (
<ul className="divide-y">
{online.map((o) => (
<li key={o.imvuId} className="flex gap-4 py-4 first:pt-0 last:pb-0">
<Initial name={o.username} />
<div className="min-w-0 flex-1 space-y-3">
<div className="flex flex-wrap items-center gap-2">
<span className="font-medium">{o.username}</span>
{o.badge && <Tag tone="strong">T5DE</Tag>}
<Tag tone="online" dot>
online since <Since iso={o.since} />
</Tag>
{o.hidden && <Tag>hidden: moderators only</Tag>}
{moderator && o.adult && <Tag tone="adult">adult</Tag>}
{moderator && o.personId && (
<Link
href={`/console/people/${o.personId}`}
className="text-xs text-muted-foreground underline underline-offset-4 hover:text-foreground"
>
person
</Link>
)}
</div>
<Outfit items={o.outfit} />
</div>
</li>
))}
</ul>
)}
</div>
);
}