mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 21:22:43 +00:00
feat: refactored docs structure
This commit is contained in:
1 parent
d388b3000e
commit
9de25fecff
212 files changed
+641
-4169
No files matched your search
@@ -1,90 +0,0 @@
|
||||
/**
|
||||
* The progress page's data: GET /api/stats from the SRS Worker, fetched in the
|
||||
* browser so `blume build` never depends on the API being up. API_BASE is the
|
||||
* deployed Worker — do not inline it anywhere else.
|
||||
*
|
||||
* Four islands read this (SolvedRing, SubmissionHeatmap, RecentSolves,
|
||||
* TopicBubbles). Each hydrates on its own, but they share this module, so the
|
||||
* one in-flight request is shared too: the page asks the Worker once.
|
||||
*
|
||||
* Invariants: a malformed body is an error state, never a throw; the first
|
||||
* frame is always the loading state, so server and client render identically.
|
||||
*/
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const API_BASE = "https://srs-api.prdlk.workers.dev";
|
||||
|
||||
// ── /api/stats response shape (the fields these islands read) ────
|
||||
|
||||
export const DIFFICULTIES = ["easy", "medium", "hard"] as const;
|
||||
|
||||
export type Difficulty = (typeof DIFFICULTIES)[number];
|
||||
|
||||
export interface Stats {
|
||||
generated: string;
|
||||
/** Per difficulty, catalog size and how many entered the ladder. */
|
||||
difficulty: { difficulty: Difficulty; total: number; done: number }[];
|
||||
/** Problems whose most recent attempt failed — the sweep day's miss log. */
|
||||
attempting: number;
|
||||
topics: { issue: number; name: string; total: number; done: number; taught: boolean }[];
|
||||
/** The ten most recently solved, newest first, dated by first pass (ET). */
|
||||
recent: { lc: number; title: string; difficulty: Difficulty; date: string }[];
|
||||
/**
|
||||
* The 365 days ending today (ET), zeros included — always the current
|
||||
* calendar year to date. Per difficulty: every logged attempt, and every
|
||||
* first solve (the first-pass date `recent` uses).
|
||||
*/
|
||||
year: Day[];
|
||||
}
|
||||
|
||||
export interface Day {
|
||||
date: string;
|
||||
passes: number;
|
||||
attempts: Record<Difficulty, number>;
|
||||
solved: Record<Difficulty, number>;
|
||||
}
|
||||
|
||||
function isStats(value: unknown): value is Stats {
|
||||
if (typeof value !== "object" || value === null) return false;
|
||||
const v = value as Record<string, unknown>;
|
||||
return (
|
||||
Array.isArray(v.difficulty) &&
|
||||
typeof v.attempting === "number" &&
|
||||
Array.isArray(v.topics) &&
|
||||
Array.isArray(v.recent) &&
|
||||
Array.isArray(v.year)
|
||||
);
|
||||
}
|
||||
|
||||
// ── the shared request ───────────────────────────────────────────
|
||||
|
||||
export type Load = { phase: "loading" } | { phase: "error" } | { phase: "ready"; stats: Stats };
|
||||
|
||||
let request: Promise<Stats | null> | undefined;
|
||||
|
||||
/** The page's stats, fetched once however many islands ask. */
|
||||
export function useStats(): Load {
|
||||
const [load, setLoad] = useState<Load>({ phase: "loading" });
|
||||
useEffect(() => {
|
||||
if (!request) {
|
||||
request = fetch(`${API_BASE}/api/stats`)
|
||||
.then((res) => (res.ok ? res.json() : null))
|
||||
.then((body: unknown) => (isStats(body) ? body : null))
|
||||
.catch(() => null);
|
||||
}
|
||||
let live = true;
|
||||
request.then((stats) => {
|
||||
if (live) setLoad(stats ? { phase: "ready", stats } : { phase: "error" });
|
||||
});
|
||||
return () => {
|
||||
live = false;
|
||||
};
|
||||
}, []);
|
||||
return load;
|
||||
}
|
||||
|
||||
/** "YYYY-MM-DD" → a UTC-midnight Date, so calendar math never drifts with the reader's timezone. */
|
||||
export function utcDate(iso: string): Date {
|
||||
const [y, m, d] = iso.split("-").map(Number);
|
||||
return new Date(Date.UTC(y!, m! - 1, d!));
|
||||
}
|
||||
Reference in new issue
Block a user