import { useEffect, useState } from "react"; import { Link, useNavigate, useParams } from "react-router"; import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { API_URL, VALIDATION } from "../config/api.ts"; import type { Dump, RawDump, UpdateDumpRequest } from "../model.ts"; import { deserializeDump, parseAPIResponse } from "../model.ts"; import { useRequiredAuth } from "../hooks/useAuth.ts"; import { formatBytes } from "../utils/format.ts"; import { dumpUrl } from "../utils/urls.ts"; import { PageShell } from "../components/PageShell.tsx"; import { PageError } from "../components/PageError.tsx"; import { friendlyFetchError } from "../utils/apiError.ts"; import { ConfirmModal } from "../components/ConfirmModal.tsx"; import RichContentCard from "../components/RichContentCard.tsx"; import FilePreview from "../components/FilePreview.tsx"; import { TextEditor } from "../components/TextEditor.tsx"; import { FileDropZone } from "../components/FileDropZone.tsx"; type DumpEditState = | { status: "loading" } | { status: "error"; error: string } | { status: "loaded"; dump: Dump }; export function DumpEdit() { const { selectedDump } = useParams(); const navigate = useNavigate(); const { authFetch, token } = useRequiredAuth(); const [state, setState] = useState({ status: "loading" }); const [url, setUrl] = useState(""); const [comment, setComment] = useState(""); const [isPrivate, setIsPrivate] = useState(false); const [newFile, setNewFile] = useState(null); const [confirmDelete, setConfirmDelete] = useState(false); const [refreshing, setRefreshing] = useState(false); useEffect(() => { if (!selectedDump) return; setState({ status: "loading" }); (async () => { try { const res = await fetch(`${API_URL}/api/dumps/${selectedDump}`, { cache: "no-store", headers: token ? { Authorization: `Bearer ${token}` } : {}, }); const apiResponse = parseAPIResponse(await res.json()); if (apiResponse.success) { const dump: Dump = deserializeDump(apiResponse.data); setUrl(dump.url ?? ""); setComment(dump.comment ?? ""); setIsPrivate(dump.isPrivate); setState({ status: "loaded", dump }); } else { setState({ status: "error", error: apiResponse.error.message }); } } catch (err) { setState({ status: "error", error: friendlyFetchError(err) }); } })(); }, [selectedDump, token]); const handleSave = async () => { if ( state.status !== "loaded" || comment.length > VALIDATION.DUMP_COMMENT_MAX ) return; let res: Response; if (state.dump.kind === "file" && newFile) { const formData = new FormData(); formData.append("file", newFile); if (comment.trim()) formData.append("comment", comment.trim()); res = await authFetch(`${API_URL}/api/dumps/${state.dump.id}/file`, { method: "PUT", body: formData, }); } else { const body: UpdateDumpRequest = state.dump.kind === "url" ? { url: url.trim() || undefined, comment: comment.trim() || undefined, isPrivate, } : { comment: comment.trim() || undefined, isPrivate }; res = await authFetch(`${API_URL}/api/dumps/${state.dump.id}`, { method: "PUT", body: JSON.stringify(body), }); } const apiResponse = parseAPIResponse(await res.json()); if (!apiResponse.success) { setState({ status: "error", error: apiResponse.error.message }); return; } const updatedDump: Dump = deserializeDump(apiResponse.data); setState({ status: "loaded", dump: updatedDump }); setNewFile(null); navigate(dumpUrl(updatedDump), { state: { dump: updatedDump } }); }; const handleRefreshMetadata = async () => { if (state.status !== "loaded" || state.dump.kind !== "url") return; setRefreshing(true); try { const res = await authFetch( `${API_URL}/api/dumps/${state.dump.id}/refresh-metadata`, { method: "POST" }, ); const apiResponse = await res.json(); if (apiResponse.success) { const updatedDump: Dump = deserializeDump(apiResponse.data); setState({ status: "loaded", dump: updatedDump }); } } finally { setRefreshing(false); } }; const handleDelete = async () => { if (state.status !== "loaded") return; const res = await authFetch(`${API_URL}/api/dumps/${state.dump.id}`, { method: "DELETE", }); if (!res.ok) { setState({ status: "error", error: `Delete failed (${res.status})` }); return; } navigate("/", { state: { deletedDumpId: state.dump.id } }); }; if (state.status === "loading") { return (

Loading dump…

); } if (state.status === "error") { return ( } /> ); } const { dump } = state; return (

Editing

{dump.title}

{dump.kind === "file" ? : dump.richContent ? : dump.url && ( {dump.url} )} {dump.kind === "url" && ( )}
{ e.preventDefault(); handleSave(); }} > {dump.kind === "url" ? (
setUrl(e.currentTarget.value)} placeholder="https://..." required />
) : (

{dump.fileName} {dump.fileSize != null && ` — ${formatBytes(dump.fileSize)}`}

)}
Cancel
{confirmDelete && ( setConfirmDelete(false)} /> )}
); }