vibe coded v1
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link, useParams } from "react-router";
|
||||
import { Link, useLocation, useParams } from "react-router";
|
||||
|
||||
import { API_URL } from "../config/api.ts";
|
||||
|
||||
import type { Dump } from "../model.ts";
|
||||
import type { Dump, PublicUser } from "../model.ts";
|
||||
|
||||
import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { relativeTime } from "../utils/relativeTime.ts";
|
||||
import { useWS } from "../hooks/useWS.ts";
|
||||
import { Avatar } from "../components/Avatar.tsx";
|
||||
import RichContentCard from "../components/RichContentCard.tsx";
|
||||
import FilePreview from "../components/FilePreview.tsx";
|
||||
import { VoteButton } from "../components/VoteButton.tsx";
|
||||
import { PageShell } from "../components/PageShell.tsx";
|
||||
|
||||
type DumpState =
|
||||
| { status: "loading" }
|
||||
@@ -14,26 +21,44 @@ type DumpState =
|
||||
|
||||
export function Dump() {
|
||||
const { selectedDump } = useParams();
|
||||
const location = useLocation();
|
||||
const preloaded = (location.state as { dump?: Dump } | null)?.dump ?? null;
|
||||
|
||||
const [dumpState, setDumpState] = useState<DumpState>({ status: "loading" });
|
||||
const [dumpState, setDumpState] = useState<DumpState>(
|
||||
preloaded ? { status: "loaded", dump: preloaded } : { status: "loading" },
|
||||
);
|
||||
const [op, setOp] = useState<PublicUser | null>(null);
|
||||
|
||||
const { user } = useAuth();
|
||||
const { voteCounts, myVotes, castVote, removeVote } = useWS();
|
||||
|
||||
// Fetch dump data
|
||||
useEffect(() => {
|
||||
if (!selectedDump) return;
|
||||
|
||||
if (preloaded) {
|
||||
fetch(`${API_URL}/api/users/by-id/${preloaded.userId}`)
|
||||
.then((r) => r.json())
|
||||
.then((r) => r.success && setOp(r.data))
|
||||
.catch(() => {});
|
||||
return;
|
||||
}
|
||||
|
||||
setDumpState({ status: "loading" });
|
||||
setOp(null);
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/dumps/${selectedDump}`);
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
const res = await fetch(`${API_URL}/api/dumps/${selectedDump}`, { cache: "no-store" });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
|
||||
const apiResponse = await res.json();
|
||||
setDumpState({ status: "loaded", dump: apiResponse.data });
|
||||
const dump: Dump = apiResponse.data;
|
||||
setDumpState({ status: "loaded", dump });
|
||||
|
||||
fetch(`${API_URL}/api/users/by-id/${dump.userId}`)
|
||||
.then((r) => r.json())
|
||||
.then((r) => r.success && setOp(r.data))
|
||||
.catch(() => {});
|
||||
} catch (err) {
|
||||
setDumpState({
|
||||
status: "error",
|
||||
@@ -44,49 +69,82 @@ export function Dump() {
|
||||
}, [selectedDump]);
|
||||
|
||||
if (dumpState.status === "loading") {
|
||||
return <div className="loading">Loading dump...</div>;
|
||||
return <PageShell><p className="page-loading">Loading dump…</p></PageShell>;
|
||||
}
|
||||
|
||||
if (dumpState.status === "error") {
|
||||
return (
|
||||
<div className="error-container">
|
||||
<h2>Error</h2>
|
||||
<p>{dumpState.error}</p>
|
||||
<button type="button" onClick={() => globalThis.location.reload()}>
|
||||
Retry
|
||||
</button>
|
||||
<p>
|
||||
<PageShell>
|
||||
<div className="page-error">
|
||||
<h2>Error</h2>
|
||||
<p>{dumpState.error}</p>
|
||||
<button type="button" onClick={() => globalThis.location.reload()}>Retry</button>
|
||||
<Link to="/">← Back to all dumps</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
|
||||
const { dump } = dumpState;
|
||||
const canEdit = !!user &&
|
||||
(dump.userId === user.id || user.isAdmin === true);
|
||||
const canEdit = !!user && (dump.userId === user.id || user.isAdmin === true);
|
||||
|
||||
return (
|
||||
<div className="dump-container">
|
||||
<div className="dump-meta">
|
||||
<h1>{dump.title}</h1>
|
||||
{dump.description && (
|
||||
<p className="dump-description">{dump.description}</p>
|
||||
)}
|
||||
</div>
|
||||
<PageShell>
|
||||
<div className="dump-detail">
|
||||
{/* Post header */}
|
||||
<div className="dump-post-header">
|
||||
<div className="dump-header-block">
|
||||
<VoteButton
|
||||
dumpId={dump.id}
|
||||
count={voteCounts[dump.id] ?? dump.voteCount}
|
||||
voted={myVotes.has(dump.id)}
|
||||
disabled={!user}
|
||||
onCast={castVote}
|
||||
onRemove={removeVote}
|
||||
/>
|
||||
<div className="dump-header-info">
|
||||
<h1 className="dump-title">{dump.title}</h1>
|
||||
<div className="dump-op">
|
||||
<Avatar
|
||||
userId={dump.userId}
|
||||
username={op?.username ?? "?"}
|
||||
hasAvatar={!!op?.avatarMime}
|
||||
size={22}
|
||||
/>
|
||||
{op
|
||||
? <Link to={`/users/${op.username}`} className="dump-op-link">{op.username}</Link>
|
||||
: <span className="dump-op-link">…</span>}
|
||||
<time className="dump-card-date" dateTime={dump.createdAt} title={new Date(dump.createdAt).toLocaleString()}>
|
||||
{relativeTime(dump.createdAt)}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="dump-grid">
|
||||
~
|
||||
</div>
|
||||
{dump.comment && (
|
||||
<blockquote className="dump-comment">{dump.comment}</blockquote>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="dump-actions">
|
||||
{canEdit && (
|
||||
<Link to={`/dumps/${dump.id}/edit`}>
|
||||
Edit dump
|
||||
</Link>
|
||||
)}
|
||||
<Link to="/">← Back to all dumps</Link>
|
||||
{/* Main content */}
|
||||
<div className="dump-rich-content">
|
||||
{dump.kind === "file"
|
||||
? <FilePreview dump={dump} />
|
||||
: dump.richContent
|
||||
? <RichContentCard richContent={dump.richContent} />
|
||||
: (
|
||||
<a href={dump.url} target="_blank" rel="noopener noreferrer" className="dump-url-link">
|
||||
{dump.url}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="dump-actions">
|
||||
{canEdit && <Link to={`/dumps/${dump.id}/edit`}>Edit</Link>}
|
||||
<Link to="/">← Back to all dumps</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user