v1 review pass: fixed some minor bugs
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, useLocation } from "react-router";
|
||||
|
||||
import { API_URL } from "../config/api.ts";
|
||||
import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { useWS } from "../hooks/useWS.ts";
|
||||
import { type Dump } from "../model.ts";
|
||||
import { Avatar } from "../components/Avatar.tsx";
|
||||
import { DumpCard } from "../components/DumpCard.tsx";
|
||||
import { AppHeader } from "../components/AppHeader.tsx";
|
||||
|
||||
import { API_URL } from "../config/api.ts";
|
||||
|
||||
import { deserializeDump, type Dump } from "../model.ts";
|
||||
|
||||
import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { useWS } from "../hooks/useWS.ts";
|
||||
|
||||
type DumpsState =
|
||||
| { status: "loading" }
|
||||
| { status: "error"; error: string }
|
||||
@@ -17,18 +20,29 @@ type DumpsState =
|
||||
type SortMode = "new" | "hot";
|
||||
|
||||
function hotScore(dump: Dump): number {
|
||||
const ageHours = (Date.now() - new Date(dump.createdAt).getTime()) / 3_600_000;
|
||||
const ageHours = (Date.now() - dump.createdAt.getTime()) / 3_600_000;
|
||||
return (dump.voteCount + 1) / Math.pow(ageHours + 2, 1.5);
|
||||
}
|
||||
|
||||
export function Index() {
|
||||
const location = useLocation();
|
||||
const justDeletedId = (location.state as { deletedDumpId?: string } | null)?.deletedDumpId;
|
||||
const justDeletedId = (location.state as { deletedDumpId?: string } | null)
|
||||
?.deletedDumpId;
|
||||
|
||||
const { user } = useAuth();
|
||||
const { onlineUsers, voteCounts, myVotes, recentDumps, deletedDumpIds, castVote, removeVote } = useWS();
|
||||
const {
|
||||
onlineUsers,
|
||||
voteCounts,
|
||||
myVotes,
|
||||
recentDumps,
|
||||
deletedDumpIds,
|
||||
castVote,
|
||||
removeVote,
|
||||
} = useWS();
|
||||
|
||||
const [dumpsState, setDumpsState] = useState<DumpsState>({ status: "loading" });
|
||||
const [dumpsState, setDumpsState] = useState<DumpsState>({
|
||||
status: "loading",
|
||||
});
|
||||
const [sort, setSort] = useState<SortMode>("hot");
|
||||
|
||||
useEffect(() => {
|
||||
@@ -37,9 +51,15 @@ export function Index() {
|
||||
const res = await fetch(`${API_URL}/api/dumps/`);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const body = await res.json();
|
||||
setDumpsState({ status: "loaded", dumps: body.data });
|
||||
setDumpsState({
|
||||
status: "loaded",
|
||||
dumps: body.data.map(deserializeDump),
|
||||
});
|
||||
} catch (err) {
|
||||
setDumpsState({ status: "error", error: err instanceof Error ? err.message : "Failed to load" });
|
||||
setDumpsState({
|
||||
status: "error",
|
||||
error: err instanceof Error ? err.message : "Failed to load",
|
||||
});
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
@@ -54,14 +74,24 @@ export function Index() {
|
||||
const sortedDumps = [...combined].sort(
|
||||
sort === "hot"
|
||||
? (a, b) => hotScore(b) - hotScore(a)
|
||||
: (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||
: (a, b) => b.createdAt.getTime() - a.createdAt.getTime(),
|
||||
);
|
||||
|
||||
const presenceRow = (
|
||||
<div className="index-presence">
|
||||
{onlineUsers.map((u) => (
|
||||
<Link key={u.userId} to={`/users/${u.username}`} title={u.username} className="index-presence-avatar">
|
||||
<Avatar userId={u.userId} username={u.username} hasAvatar={u.hasAvatar} size={32} />
|
||||
<Link
|
||||
key={u.userId}
|
||||
to={`/users/${u.username}`}
|
||||
title={u.username}
|
||||
className="index-presence-avatar"
|
||||
>
|
||||
<Avatar
|
||||
userId={u.userId}
|
||||
username={u.username}
|
||||
hasAvatar={u.hasAvatar}
|
||||
size={32}
|
||||
/>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
@@ -69,19 +99,33 @@ export function Index() {
|
||||
|
||||
const sortButtons = !loading && !error && combined.length > 0 && (
|
||||
<div className="feed-sort">
|
||||
<button className={`feed-sort-btn${sort === "hot" ? " active" : ""}`} onClick={() => setSort("hot")}>Hot</button>
|
||||
<button className={`feed-sort-btn${sort === "new" ? " active" : ""}`} onClick={() => setSort("new")}>New</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`feed-sort-btn${sort === "hot" ? " active" : ""}`}
|
||||
onClick={() => setSort("hot")}
|
||||
>
|
||||
Hot
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`feed-sort-btn${sort === "new" ? " active" : ""}`}
|
||||
onClick={() => setSort("new")}
|
||||
>
|
||||
New
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="index-page">
|
||||
<AppHeader centerSlot={
|
||||
<div className="header-center-slot">
|
||||
{presenceRow}
|
||||
{sortButtons}
|
||||
</div>
|
||||
} />
|
||||
<AppHeader
|
||||
centerSlot={
|
||||
<div className="header-center-slot">
|
||||
{presenceRow}
|
||||
{sortButtons}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Shown only on narrow viewports */}
|
||||
<div className="index-below-header">
|
||||
@@ -98,7 +142,6 @@ export function Index() {
|
||||
|
||||
{!loading && !error && combined.length > 0 && (
|
||||
<>
|
||||
|
||||
<ul className="dump-feed">
|
||||
{sortedDumps.map((dump) => (
|
||||
<DumpCard
|
||||
|
||||
Reference in New Issue
Block a user