2026-10-05 14:22:28 -04:00
|
|
|
/**
|
|
|
|
|
* The ten most recently solved problems — LeetCode's "Recent AC" list. Each
|
|
|
|
|
* row is dated by the problem's first pass (an ET calendar date: the Worker
|
|
|
|
|
* logs days, not times), read relative to today in ET with the full date on
|
|
|
|
|
* hover. A row links to the problem's docs page when one exists: the
|
|
|
|
|
* sync-generated short links (`redirects.ts`) are exactly the pages built.
|
|
|
|
|
*/
|
2026-10-13 09:16:30 -04:00
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
|
|
|
|
|
import { useStats, utcDate } from "../../lib/stats";
|
|
|
|
|
import redirects from "../../redirects";
|
|
|
|
|
import { StatsPending } from "./shared";
|
2026-10-05 14:22:28 -04:00
|
|
|
|
|
|
|
|
const PAGES = new Map(redirects.map((r) => [r.from, r.to]));
|
|
|
|
|
|
|
|
|
|
const ET_TODAY = new Intl.DateTimeFormat("en-CA", { timeZone: "America/New_York" });
|
|
|
|
|
const RELATIVE = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
|
|
|
|
const FULL = new Intl.DateTimeFormat("en-US", { dateStyle: "medium", timeZone: "UTC" });
|
|
|
|
|
const DAY_MS = 86_400_000;
|
|
|
|
|
|
|
|
|
|
export default function RecentSolves() {
|
|
|
|
|
const load = useStats();
|
|
|
|
|
|
|
|
|
|
if (load.phase !== "ready") {
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Recently solved</CardTitle>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<StatsPending load={load} className="h-80 w-full" />
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const today = utcDate(ET_TODAY.format(new Date())).getTime();
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Recently solved</CardTitle>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
{load.stats.recent.length === 0 ? (
|
|
|
|
|
<p className="my-0! text-muted-foreground text-sm">Nothing solved yet.</p>
|
|
|
|
|
) : (
|
|
|
|
|
<ol className="my-0! flex flex-col">
|
|
|
|
|
{load.stats.recent.map((p) => {
|
|
|
|
|
const date = utcDate(p.date);
|
|
|
|
|
const page = PAGES.get(`/${p.lc}`);
|
|
|
|
|
const name = (
|
|
|
|
|
<>
|
|
|
|
|
<span className="text-muted-foreground tabular-nums">{p.lc}.</span> {p.title}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
return (
|
|
|
|
|
<li
|
|
|
|
|
key={p.lc}
|
|
|
|
|
className="flex items-center justify-between gap-4 rounded-lg px-4 py-3 text-sm odd:bg-muted"
|
|
|
|
|
>
|
|
|
|
|
{page ? (
|
|
|
|
|
<a href={page} className="truncate font-normal! text-foreground no-underline! hover:underline!">
|
|
|
|
|
{name}
|
|
|
|
|
</a>
|
|
|
|
|
) : (
|
|
|
|
|
<span className="truncate">{name}</span>
|
|
|
|
|
)}
|
|
|
|
|
<time dateTime={p.date} title={FULL.format(date)} className="shrink-0 text-muted-foreground">
|
|
|
|
|
{RELATIVE.format(Math.round((date.getTime() - today) / DAY_MS), "day")}
|
|
|
|
|
</time>
|
|
|
|
|
</li>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</ol>
|
|
|
|
|
)}
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
|
|
|
|
}
|