From 47f0e180e3fb2c283baeeceb9dbdc00c70c5e3f4 Mon Sep 17 00:00:00 2001 From: kmck133 Date: Tue, 26 May 2026 18:23:08 +1200 Subject: [PATCH 1/4] Allowed renaming of resources in edit view --- backend/src/routes/api/files.js | 23 ++++ .../resources/ManageResourcesPage.jsx | 126 +++++++++++++++--- 2 files changed, 128 insertions(+), 21 deletions(-) diff --git a/backend/src/routes/api/files.js b/backend/src/routes/api/files.js index b6481674..8dc095f0 100644 --- a/backend/src/routes/api/files.js +++ b/backend/src/routes/api/files.js @@ -145,6 +145,29 @@ router.post("/upload", upload.array("files"), async (req, res) => { } }); +/** + * @route PATCH /api/files/:fileId + * @desc Update the name of a stored file + */ +router.patch("/:fileId", async (req, res) => { + try { + const { fileId } = req.params; + const { name } = req.body; + if (!name || !name.trim()) { + return res.status(400).json({ error: "name is required" }); + } + const meta = await StoredFile.findById(fileId); + if (!meta) return res.status(404).json({ error: "File not found" }); + meta.name = name.trim(); + await meta.save(); + const file = meta.toObject(); + delete file.gridFsId; + return res.json(file); + } catch (err) { + return res.status(500).json({ error: err.message }); + } +}); + /** * @route DELETE /api/files/:fileId * @desc Delete a stored file and its GridFS data diff --git a/frontend/src/features/resources/ManageResourcesPage.jsx b/frontend/src/features/resources/ManageResourcesPage.jsx index 892c43ff..da899161 100644 --- a/frontend/src/features/resources/ManageResourcesPage.jsx +++ b/frontend/src/features/resources/ManageResourcesPage.jsx @@ -11,6 +11,8 @@ import { UsersIcon, PlusIcon, XIcon, + PencilIcon, + CheckIcon, } from "lucide-react"; import AddGroup from "./components/AddGroup"; import StateConditionalMenu from "../../components/StateVariables/StateConditionalMenu"; @@ -47,6 +49,8 @@ export default function ManageResourcesPage() { // Groups (each with files) const [groups, setGroups] = useState([]); const [selectedFile, setSelectedFile] = useState(null); + const [renamingFileId, setRenamingFileId] = useState(null); + const [renameInput, setRenameInput] = useState(""); // Load groups and files useEffect(() => { @@ -133,6 +137,50 @@ export default function ManageResourcesPage() { } } + async function renameFile(fileId, newName) { + try { + const user = getAuth().currentUser; + if (!user) { + toast.error("You must be logged in."); + return; + } + const idToken = await user.getIdToken(); + const { data } = await axios.patch( + `/api/files/${fileId}`, + { name: newName }, + { headers: { Authorization: `Bearer ${idToken}` } } + ); + + setGroups((prev) => + prev.map((g) => ({ + ...g, + files: (g.files || []).map((f) => + f.id === fileId ? { ...f, name: data.name } : f + ), + })) + ); + + if (selectedFile?.id === fileId) { + setSelectedFile((prev) => ({ ...prev, name: data.name })); + } + + toast.success("Renamed"); + } catch (err) { + console.error(err); + toast.error(err?.response?.data?.error || "Rename failed"); + } + } + + async function handleRenameSubmit() { + const trimmed = renameInput.trim(); + if (!trimmed) { setRenamingFileId(null); return; } + const current = groups.flatMap((g) => g.files).find((f) => f.id === renamingFileId); + if (current && trimmed !== current.name) { + await renameFile(renamingFileId, trimmed); + } + setRenamingFileId(null); + } + async function removeFile(fileId) { try { const user = getAuth().currentUser; @@ -301,27 +349,63 @@ export default function ManageResourcesPage() { {group.files.map((f) => (
  • -
    - - setSelectedFile({ - ...f, - groupId: group.id, - groupName: group.name, - }) - } - > - {f.name} - - -
    + {renamingFileId === f.id ? ( +
    + setRenameInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") handleRenameSubmit(); + if (e.key === "Escape") setRenamingFileId(null); + }} + /> + + +
    + ) : ( +
    + + setSelectedFile({ + ...f, + groupId: group.id, + groupName: group.name, + }) + } + > + {f.name} + + + +
    + )}
  • ))} From 10b3fbde491463790597f8e0c528eb1d2a7cad5b Mon Sep 17 00:00:00 2001 From: kmck133 Date: Tue, 26 May 2026 18:36:48 +1200 Subject: [PATCH 2/4] Fixed resourse page code format --- .../resources/ManageResourcesPage.jsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/frontend/src/features/resources/ManageResourcesPage.jsx b/frontend/src/features/resources/ManageResourcesPage.jsx index da899161..0b6e70e8 100644 --- a/frontend/src/features/resources/ManageResourcesPage.jsx +++ b/frontend/src/features/resources/ManageResourcesPage.jsx @@ -173,8 +173,13 @@ export default function ManageResourcesPage() { async function handleRenameSubmit() { const trimmed = renameInput.trim(); - if (!trimmed) { setRenamingFileId(null); return; } - const current = groups.flatMap((g) => g.files).find((f) => f.id === renamingFileId); + if (!trimmed) { + setRenamingFileId(null); + return; + } + const current = groups + .flatMap((g) => g.files) + .find((f) => f.id === renamingFileId); if (current && trimmed !== current.name) { await renameFile(renamingFileId, trimmed); } @@ -355,10 +360,14 @@ export default function ManageResourcesPage() { autoFocus className="input input-bordered input-xs flex-1 min-w-0" value={renameInput} - onChange={(e) => setRenameInput(e.target.value)} + onChange={(e) => + setRenameInput(e.target.value) + } onKeyDown={(e) => { - if (e.key === "Enter") handleRenameSubmit(); - if (e.key === "Escape") setRenamingFileId(null); + if (e.key === "Enter") + handleRenameSubmit(); + if (e.key === "Escape") + setRenamingFileId(null); }} /> ) : ( -
    +
    setSelectedFile({ ...f, From bab4def5b27328216f29a90d33336339e2e088a2 Mon Sep 17 00:00:00 2001 From: kmck133 Date: Wed, 3 Jun 2026 12:35:32 +1200 Subject: [PATCH 4/4] Prettier + lint --- backend/src/routes/api/files.js | 4 ++- .../resources/ManageResourcesPage.jsx | 27 +++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/backend/src/routes/api/files.js b/backend/src/routes/api/files.js index 928c6931..defd3f5f 100644 --- a/backend/src/routes/api/files.js +++ b/backend/src/routes/api/files.js @@ -157,7 +157,9 @@ router.patch("/:fileId", async (req, res) => { return res.status(400).json({ error: "Name is required" }); } if (name.trim().length > 255) { - return res.status(400).json({ error: "Name must be 255 characters or fewer" }); + return res + .status(400) + .json({ error: "Name must be 255 characters or fewer" }); } const meta = await StoredFile.findById(fileId); if (!meta) return res.status(404).json({ error: "File not found" }); diff --git a/frontend/src/features/resources/ManageResourcesPage.jsx b/frontend/src/features/resources/ManageResourcesPage.jsx index 97176f8e..08b0b932 100644 --- a/frontend/src/features/resources/ManageResourcesPage.jsx +++ b/frontend/src/features/resources/ManageResourcesPage.jsx @@ -387,11 +387,24 @@ export default function ManageResourcesPage() {
    ) : ( -
    +
    setSelectedFile({ ...f, @@ -561,10 +574,14 @@ function Preview({ file }) { return (