From 16313b961b92179427507170b506156eaa771218 Mon Sep 17 00:00:00 2001 From: james fitzsimons Date: Sat, 29 Nov 2025 12:45:41 +0000 Subject: [PATCH] Introduce zustand store for notes - Migrate Home to use store actions for creating notes and folders - Remove encrypted flag from NoteCreate interface - Add zustand to frontend dependencies and update package-lock Add Zustand store for notes --- backend/notes.db | Bin 110592 -> 110592 bytes frontend/package-lock.json | 51 +++++++- frontend/package.json | 3 +- frontend/src/api/notes.tsx | 1 - frontend/src/components/sidebar/SideBar.tsx | 125 ++++++++++++++++++++ frontend/src/pages/Home.tsx | 35 ++---- frontend/src/stores/notesStore.ts | 39 ++++++ 7 files changed, 223 insertions(+), 31 deletions(-) create mode 100644 frontend/src/components/sidebar/SideBar.tsx create mode 100644 frontend/src/stores/notesStore.ts diff --git a/backend/notes.db b/backend/notes.db index 168ec63361312b171b17cd3aa26f038390d91247..d72d915c613adcf123d57671eb567bfdec2eb933 100644 GIT binary patch delta 7649 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 delta 7537 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 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9dbd408..f186880 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,7 +19,8 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^7.9.6", - "tailwindcss": "^4.1.17" + "tailwindcss": "^4.1.17", + "zustand": "^5.0.8" }, "devDependencies": { "@catppuccin/tailwindcss": "^1.0.0", @@ -61,6 +62,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -637,6 +639,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -726,6 +729,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", "license": "MIT", + "peer": true, "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } @@ -735,6 +739,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz", "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -1271,6 +1276,7 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.1.0.tgz", "integrity": "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" }, @@ -1635,6 +1641,7 @@ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", "license": "MIT", + "peer": true, "dependencies": { "@lezer/common": "^1.3.0" } @@ -3156,6 +3163,7 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -3580,6 +3588,7 @@ "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3590,6 +3599,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3626,6 +3636,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3733,6 +3744,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -4730,7 +4742,6 @@ "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", "license": "MIT", - "peer": true, "funding": { "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" @@ -4828,7 +4839,6 @@ "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.114.tgz", "integrity": "sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==", "license": "MIT", - "peer": true, "dependencies": { "isomorphic.js": "^0.2.4" }, @@ -6400,6 +6410,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -6421,6 +6432,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -6783,7 +6795,8 @@ "version": "4.1.17", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -7004,6 +7017,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -7104,6 +7118,35 @@ "url": "https://github.com/sponsors/dmonad" } }, + "node_modules/zustand": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index d5ba118..94cc649 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,7 +19,8 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^7.9.6", - "tailwindcss": "^4.1.17" + "tailwindcss": "^4.1.17", + "zustand": "^5.0.8" }, "devDependencies": { "@catppuccin/tailwindcss": "^1.0.0", diff --git a/frontend/src/api/notes.tsx b/frontend/src/api/notes.tsx index 733864e..b273992 100644 --- a/frontend/src/api/notes.tsx +++ b/frontend/src/api/notes.tsx @@ -17,7 +17,6 @@ export interface NoteCreate { title: string; content: string; folder_id: number | null; - encrypted: boolean; } const createNote = async (note: NoteCreate) => { diff --git a/frontend/src/components/sidebar/SideBar.tsx b/frontend/src/components/sidebar/SideBar.tsx new file mode 100644 index 0000000..0fa6118 --- /dev/null +++ b/frontend/src/components/sidebar/SideBar.tsx @@ -0,0 +1,125 @@ +import { useState, useRef, useEffect } from "react"; +import { FolderCreate, FolderTreeResponse, folderApi } from "../../api/folders"; +import { DraggableNote } from "./DraggableNote"; + +export const Sidebar = () => { + const [folderTree, setFolderTree] = useState(null); + const [newFolder, setNewFolder] = useState(false); + const [newFolderText, setNewFolderText] = useState(""); + const newFolderRef = useRef(null); + + useEffect(() => { + if (newFolder && newFolderRef.current) { + newFolderRef.current.focus(); + } + }, [newFolder]); + + useEffect(() => { + loadFolderTree(); + }, []); + + const handleCreateFolder = async () => { + if (!newFolderText.trim()) return; + const newFolderData: FolderCreate = { + name: newFolderText, + parent_id: null, + }; + await folderApi.create(newFolderData); + setNewFolderText(""); + loadFolderTree(); + setNewFolder(false); + }; + + const loadFolderTree = async () => { + const data = await folderApi.tree(); + setFolderTree(data); + }; + + return ( +
e.preventDefault()} + onTouchMove={(e) => e.preventDefault()} + > + + {/* New folder input */} + {newFolder && ( +
+ setNewFolder(false)} + onChange={(e) => setNewFolderText(e.target.value)} + value={newFolderText} + type="text" + placeholder="Folder name..." + className="border border-ctp-mauve rounded-md px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-ctp-mauve bg-ctp-base text-ctp-text placeholder:text-ctp-overlay0" + ref={newFolderRef} + onKeyDown={(e) => { + if (e.key === "Enter") { + handleCreateFolder(); + } + if (e.key === "Escape") { + setNewFolder(false); + } + }} + /> +
+ )} + + {/* Folder tree */} +
+ {folderTree?.folders.map((folder) => ( + + ))} +
+ + {/* Orphaned notes */} + {folderTree?.orphaned_notes && folderTree.orphaned_notes.length > 0 && ( +
+ {/*
+ Unsorted +
*/} + {folderTree.orphaned_notes.map((note) => ( + + ))} +
+ )} +
+ ); +}; + +export const SidebarHeader = () => { + return ( +
+

FastNotes

+
+ + +
+
+ ); +}; diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 41bb9b0..d28ee98 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -41,6 +41,8 @@ import { DroppableFolder } from "../components/sidebar/DroppableFolder"; import { DraggableNote } from "../components/sidebar/DraggableNote"; import CheckIcon from "../assets/fontawesome/svg/circle-check.svg?react"; import SpinnerIcon from "../assets/fontawesome/svg/rotate.svg?react"; +import { useNoteStore } from "../stores/notesStore"; +import { create } from "zustand"; const simpleSandpackConfig: SandpackConfig = { defaultPreset: "react", @@ -58,7 +60,7 @@ const simpleSandpackConfig: SandpackConfig = { }; function Home() { - const [folderTree, setFolderTree] = useState(null); + // const [folderTree, setFolderTree] = useState(null); const [selectedNote, setSelectedNote] = useState(null); const [title, setTitle] = useState(""); const [content, setContent] = useState(""); @@ -68,6 +70,9 @@ function Home() { const [encrypted, setEncrypted] = useState(false); const [updating, setUpdating] = useState(false); + const { folderTree, loadFolderTree, createNote, createFolder, updateNote } = + useNoteStore(); + const pointer = useSensor(PointerSensor, { activationConstraint: { distance: 30, @@ -98,41 +103,21 @@ function Home() { return () => clearTimeout(timer); }, [content, title]); - const loadFolderTree = async () => { - const data = await folderApi.tree(); - setFolderTree(data); - }; const handleCreate = async () => { if (!title.trim()) return; - const newNote: NoteCreate = { - title, - content, - folder_id: selectedFolder, - encrypted, - }; - await notesApi.create(newNote); - setTitle(""); - setContent(""); - loadFolderTree(); + await createNote({ title, content, folder_id: null }); }; const handleCreateFolder = async () => { if (!newFolderText.trim()) return; - const newFolderData: FolderCreate = { - name: newFolderText, - parent_id: null, - }; - await folderApi.create(newFolderData); - setNewFolderText(""); - loadFolderTree(); - setNewFolder(false); + await createFolder({ name: newFolderText, parent_id: null }); }; const handleUpdate = async () => { if (!selectedNote) return; - await notesApi.update(selectedNote.id, { title, content }); - loadFolderTree(); + await updateNote(selectedNote.id, { title, content }); + setTimeout(() => { setUpdating(false); }, 1000); diff --git a/frontend/src/stores/notesStore.ts b/frontend/src/stores/notesStore.ts new file mode 100644 index 0000000..cc8a0e5 --- /dev/null +++ b/frontend/src/stores/notesStore.ts @@ -0,0 +1,39 @@ +import { create } from "zustand"; +import { devtools, persist } from "zustand/middleware"; +import { folderApi, FolderCreate, FolderTreeResponse } from "../api/folders"; +import { Note, NoteCreate, notesApi } from "../api/notes"; + +interface NoteState { + folderTree: FolderTreeResponse | null; + selectedFolder: number | null; + + loadFolderTree: () => Promise; + createNote: (note: NoteCreate) => Promise; + createFolder: (folder: FolderCreate) => Promise; + updateNote: (id: number, note: Partial) => Promise; +} + +export const useNoteStore = create()((set, get) => ({ + folderTree: null, + selectedFolder: null, + + loadFolderTree: async () => { + const data = await folderApi.tree(); + set({ folderTree: data }); + }, + + createNote: async (note: NoteCreate) => { + await notesApi.create(note); + await get().loadFolderTree(); + }, + + createFolder: async (folder: FolderCreate) => { + await folderApi.create(folder); + await get().loadFolderTree(); + }, + + updateNote: async (id: number, note: Partial) => { + await notesApi.update(id, note); + await get().loadFolderTree(); + }, +}));