v3: code quality pass, various bug fixes

This commit is contained in:
khannurien
2026-03-23 07:47:49 +00:00
parent d94a319d96
commit fbbbb43258
44 changed files with 1060 additions and 698 deletions

View File

@@ -7,13 +7,14 @@ import {
} from "react";
import { Link, useParams } from "react-router";
import { API_URL } from "../config/api.ts";
import { API_URL, DEFAULT_PAGE_SIZE } from "../config/api.ts";
import { friendlyFetchError } from "../utils/apiError.ts";
import type { Dump, PaginatedData, PublicUser, RawDump } from "../model.ts";
import { deserializeDump, deserializePublicUser } from "../model.ts";
import { deserializeDump, deserializePublicUser, hydrateDump } from "../model.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { useWS } from "../hooks/useWS.ts";
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
import { usePositionAwareSync } from "../hooks/usePositionAwareSync.ts";
import { useInfiniteScroll } from "../hooks/useInfiniteScroll.ts";
import { useFeedCache } from "../hooks/useFeedCache.ts";
import { Avatar } from "../components/Avatar.tsx";
@@ -22,10 +23,6 @@ import { DumpCreateModal } from "../components/DumpCreateModal.tsx";
import { PageShell } from "../components/PageShell.tsx";
import { PageError } from "../components/PageError.tsx";
const PAGE_SIZE = 20;
const hydrateDump = (raw: Dump): Dump =>
deserializeDump(raw as unknown as RawDump);
type State =
| { status: "loading" }
| { status: "error"; error: string }
@@ -41,7 +38,7 @@ type State =
export function UserDumps() {
const { username } = useParams();
const { user: me, token } = useAuth();
const { voteCounts, myVotes, castVote, removeVote } = useWS();
const { voteCounts, myVotes, lastDumpEvent, castVote, removeVote } = useWS();
const { cached, saveState } = useFeedCache<Dump>(
`feed:user-dumps-full:${username ?? ""}`,
hydrateDump,
@@ -56,19 +53,27 @@ export function UserDumps() {
const setDumps = useCallback((fn: (prev: Dump[]) => Dump[]) => {
setState((s) => s.status !== "loaded" ? s : { ...s, dumps: fn(s.dumps) });
}, []);
const addFilter = useCallback((dump: Dump): boolean => {
if (!profileUserId) return false;
if (dump.userId !== profileUserId) return false;
return isOwnProfile || !dump.isPrivate;
}, [profileUserId, isOwnProfile]);
useDumpListSync(setDumps, addFilter);
const dumpItems = state.status === "loaded" ? state.dumps : [];
usePositionAwareSync(
dumpItems,
setDumps,
lastDumpEvent,
(d) => d.isPrivate,
(d) => !d.isPrivate && d.userId === profileUserId,
);
useDumpListSync(setDumps, {
ownerId: profileUserId ?? undefined,
isOwner: isOwnProfile,
skipReinsert: true,
});
useEffect(() => {
if (!username) return;
setState({ status: "loading" });
const controller = new AbortController();
if (cached) {
fetch(`${API_URL}/api/users/${username}`)
fetch(`${API_URL}/api/users/${username}`, { signal: controller.signal })
.then((r) => r.json())
.then((body) => {
if (!body.success) throw new Error("User not found");
@@ -81,23 +86,21 @@ export function UserDumps() {
loadingMore: false,
});
})
.catch((err) =>
setState({
status: "error",
error: friendlyFetchError(err),
})
);
return;
.catch((err) => {
if (err.name === "AbortError") return;
setState({ status: "error", error: friendlyFetchError(err) });
});
return () => controller.abort();
}
const authHeaders: HeadersInit = token
? { Authorization: `Bearer ${token}` }
: {};
Promise.all([
fetch(`${API_URL}/api/users/${username}`),
fetch(`${API_URL}/api/users/${username}`, { signal: controller.signal }),
fetch(
`${API_URL}/api/users/${username}/dumps?page=1&limit=${PAGE_SIZE}`,
{ headers: authHeaders },
`${API_URL}/api/users/${username}/dumps?page=1&limit=${DEFAULT_PAGE_SIZE}`,
{ headers: authHeaders, signal: controller.signal },
),
])
.then(([userRes, dumpsRes]) =>
@@ -117,12 +120,11 @@ export function UserDumps() {
loadingMore: false,
});
})
.catch((err) =>
setState({
status: "error",
error: friendlyFetchError(err),
})
);
.catch((err) => {
if (err.name === "AbortError") return;
setState({ status: "error", error: friendlyFetchError(err) });
});
return () => controller.abort();
}, [username]);
const loadMore = useCallback(() => {
@@ -133,7 +135,7 @@ export function UserDumps() {
const nextPage = state.page + 1;
setState((s) => s.status === "loaded" ? { ...s, loadingMore: true } : s);
fetch(
`${API_URL}/api/users/${username}/dumps?page=${nextPage}&limit=${PAGE_SIZE}`,
`${API_URL}/api/users/${username}/dumps?page=${nextPage}&limit=${DEFAULT_PAGE_SIZE}`,
{ headers: token ? { Authorization: `Bearer ${token}` } : {} },
)
.then((r) => r.json())