From b596c9f34d21de6bb07e3b16ea7891dc0204112b Mon Sep 17 00:00:00 2001 From: james fitzsimons Date: Thu, 11 Dec 2025 22:20:23 +0000 Subject: [PATCH] Refactor Home UI and add StatusIndicator --- frontend/src/App.tsx | 2 +- frontend/src/pages/{ => Home}/Home.tsx | 76 ++++--------------- .../pages/Home/components/StatusIndicator.tsx | 39 ++++++++++ .../Home}/components/sidebar/SideBar.tsx | 70 ++++------------- .../sidebar/subcomponents}/DraggableNote.tsx | 11 ++- .../subcomponents}/DroppableFolder.tsx | 16 ++-- .../sidebar/subcomponents/FolderTree.tsx} | 15 +--- .../sidebar/subcomponents/SideBarHeader.tsx | 39 ++++++++++ frontend/src/pages/Login.tsx | 2 +- frontend/src/pages/tiptap.css | 1 + frontend/src/stores/notesStore.ts | 4 +- frontend/tsconfig.json | 37 +++++++++ frontend/tsconfig.node.json | 11 +++ frontend/vite.config.mts | 12 +++ 14 files changed, 192 insertions(+), 143 deletions(-) rename frontend/src/pages/{ => Home}/Home.tsx (59%) create mode 100644 frontend/src/pages/Home/components/StatusIndicator.tsx rename frontend/src/{ => pages/Home}/components/sidebar/SideBar.tsx (75%) rename frontend/src/{components/sidebar => pages/Home/components/sidebar/subcomponents}/DraggableNote.tsx (84%) rename frontend/src/{components/sidebar => pages/Home/components/sidebar/subcomponents}/DroppableFolder.tsx (72%) rename frontend/src/{components/sidebar/RecursiveFolder.tsx => pages/Home/components/sidebar/subcomponents/FolderTree.tsx} (80%) create mode 100644 frontend/src/pages/Home/components/sidebar/subcomponents/SideBarHeader.tsx create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6511fe9..60ba030 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,7 +1,7 @@ // src/App.tsx import { useEffect } from "react"; import { BrowserRouter, Routes, Route, Link } from "react-router-dom"; -import Home from "./pages/Home"; // existing home page +import Home from "./pages/Home/Home.tsx"; // existing home page import { Import } from "./pages/Import"; import { Login } from "./pages/Login"; import { Register } from "./pages/Register"; diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home/Home.tsx similarity index 59% rename from frontend/src/pages/Home.tsx rename to frontend/src/pages/Home/Home.tsx index 5fae3cf..4dc8cfa 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home/Home.tsx @@ -1,47 +1,33 @@ import { useEffect, useRef, useState } from "react"; -import { notesApi } from "../api/notes"; -import "../main.css"; +import "../../main.css"; import { motion } from "framer-motion"; -import "@mdxeditor/editor/style.css"; -// @ts-ignore -import CheckIcon from "../assets/fontawesome/svg/circle-check.svg?react"; -// @ts-ignore -import SpinnerIcon from "../assets/fontawesome/svg/rotate.svg?react"; -// @ts-ignore -import WarningIcon from "../assets/fontawesome/svg/circle-exclamation.svg?react"; -import { useNoteStore } from "../stores/notesStore"; -import { Sidebar } from "../components/sidebar/SideBar"; -import { useUIStore } from "../stores/uiStore"; -import { TiptapEditor } from "./TipTap"; -import { useAuthStore } from "../stores/authStore"; -import { Login } from "./Login"; +import { useAuthStore } from "@/stores/authStore"; +import { useNoteStore } from "@/stores/notesStore"; +import { useUIStore } from "@/stores/uiStore"; +import { Login } from "../Login"; +import { TiptapEditor } from "../TipTap"; +import { Sidebar } from "./components/sidebar/SideBar"; +import { StatusIndicator } from "./components/StatusIndicator"; function Home() { - const [newFolder, setNewFolder] = useState(false); + const [newFolder] = useState(false); const [lastSavedNote, setLastSavedNote] = useState<{ id: number; title: string; content: string; } | null>(null); - const { - loadFolderTree, - updateNote, - setSelectedNote, - setContent, - selectedNote, - setTitle, - } = useNoteStore(); + const { loadFolderTree, updateNote, setContent, selectedNote, setTitle } = + useNoteStore(); - const { isAuthenticated, encryptionKey } = useAuthStore(); + const { encryptionKey } = useAuthStore(); - const { showModal, setShowModal } = useUIStore(); + const { showModal, setUpdating } = useUIStore(); const newFolderRef = useRef(null); useEffect(() => { - // if (!isAuthenticated) return; - console.log(encryptionKey); + if (!encryptionKey) return; loadFolderTree(); }, []); @@ -51,12 +37,6 @@ function Home() { } }, [newFolder]); - const clearSelection = () => { - setSelectedNote(null); - }; - - const { updating, setUpdating } = useUIStore(); - useEffect(() => { if (!selectedNote) return; if (!encryptionKey) return; // Don't try to save without encryption key @@ -117,7 +97,7 @@ function Home() { {/* Sidebar */} {showModal && } - + {/* Main editor area */}
@@ -136,31 +116,7 @@ function Home() { />
- {/* Status indicator */} -
{ - if (!encryptionKey) { - setShowModal(true); - } - }} - > - {!encryptionKey ? ( - - ) : updating ? ( - <> - - - Saving... - - - ) : ( - <> - - Saved - - )} -
+ ); } diff --git a/frontend/src/pages/Home/components/StatusIndicator.tsx b/frontend/src/pages/Home/components/StatusIndicator.tsx new file mode 100644 index 0000000..ca7dd2b --- /dev/null +++ b/frontend/src/pages/Home/components/StatusIndicator.tsx @@ -0,0 +1,39 @@ +import { useAuthStore } from "../../../stores/authStore"; +import { useUIStore } from "../../../stores/uiStore"; +// @ts-ignore +import CheckIcon from "../../../assets/fontawesome/svg/circle-check.svg?react"; +// @ts-ignore +import SpinnerIcon from "../../../assets/fontawesome/svg/rotate.svg?react"; +// @ts-ignore +import WarningIcon from "../../../assets/fontawesome/svg/circle-exclamation.svg?react"; + +export const StatusIndicator = () => { + const { encryptionKey } = useAuthStore(); + const { updating, setShowModal } = useUIStore(); + return ( +
{ + if (!encryptionKey) { + setShowModal(true); + } + }} + > + {!encryptionKey ? ( + + ) : updating ? ( + <> + + + Saving... + + + ) : ( + <> + + Saved + + )} +
+ ); +}; diff --git a/frontend/src/components/sidebar/SideBar.tsx b/frontend/src/pages/Home/components/sidebar/SideBar.tsx similarity index 75% rename from frontend/src/components/sidebar/SideBar.tsx rename to frontend/src/pages/Home/components/sidebar/SideBar.tsx index d2b3c93..9e8578a 100644 --- a/frontend/src/components/sidebar/SideBar.tsx +++ b/frontend/src/pages/Home/components/sidebar/SideBar.tsx @@ -1,12 +1,9 @@ import React, { useState, useRef, useEffect, SetStateAction } from "react"; + // @ts-ignore -import FolderPlusIcon from "../../assets/fontawesome/svg/folder-plus.svg?react"; -// @ts-ignore -import FileCirclePlusIcon from "../../assets/fontawesome/svg/file-circle-plus.svg?react"; -// @ts-ignore -import FolderIcon from "../../assets/fontawesome/svg/folder.svg?react"; -import { DraggableNote } from "./DraggableNote"; -import { useNoteStore } from "../../stores/notesStore"; +import FolderIcon from "@/assets/fontawesome/svg/folder.svg?react"; +import { DraggableNote } from "./subcomponents/DraggableNote"; + import { DndContext, DragEndEvent, @@ -17,12 +14,13 @@ import { useSensors, } from "@dnd-kit/core"; -import { RecursiveFolder } from "./RecursiveFolder"; -import { useAuthStore } from "../../stores/authStore"; -import { useUIStore } from "../../stores/uiStore"; -import { NoteRead } from "../../api/folders"; +import { FolderTree } from "./subcomponents/FolderTree.tsx"; +import { SidebarHeader } from "./subcomponents/SideBarHeader.tsx"; +import { useAuthStore } from "@/stores/authStore.ts"; +import { useNoteStore } from "@/stores/notesStore.ts"; +import { useUIStore } from "@/stores/uiStore.ts"; -export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => { +export const Sidebar = () => { const [newFolder, setNewFolder] = useState(false); const [newFolderText, setNewFolderText] = useState(""); const [activeItem, setActiveItem] = useState<{ @@ -39,7 +37,7 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => { createFolder, } = useNoteStore(); - const { isAuthenticated } = useAuthStore(); + const { encryptionKey } = useAuthStore(); const { setSideBarResize, sideBarResize } = useUIStore(); useEffect(() => { @@ -49,9 +47,9 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => { }, [newFolder]); useEffect(() => { - // if (!isAuthenticated) return; + if (!encryptionKey) return; loadFolderTree(); - }, []); + }, [encryptionKey]); const handleCreateFolder = async () => { if (!newFolderText.trim()) return; @@ -170,10 +168,7 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => { className="flex flex-col min-h-full" style={{ width: `${sideBarResize}px` }} > - +
e.preventDefault()} @@ -205,7 +200,7 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => { {/* Folder tree */}
{folderTree?.folders.map((folder) => ( - + ))}
@@ -238,38 +233,3 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => { ); }; - -export const SidebarHeader = ({ - clearSelection, - setNewFolder, -}: { - clearSelection: () => void; - setNewFolder: React.Dispatch>; -}) => { - const { createNote, selectedFolder } = useNoteStore(); - const handleCreate = async () => { - await createNote({ - title: "Untitled", - content: "", - folder_id: selectedFolder, - }); - }; - return ( -
- - -
- ); -}; diff --git a/frontend/src/components/sidebar/DraggableNote.tsx b/frontend/src/pages/Home/components/sidebar/subcomponents/DraggableNote.tsx similarity index 84% rename from frontend/src/components/sidebar/DraggableNote.tsx rename to frontend/src/pages/Home/components/sidebar/subcomponents/DraggableNote.tsx index 34f17d0..392c486 100644 --- a/frontend/src/components/sidebar/DraggableNote.tsx +++ b/frontend/src/pages/Home/components/sidebar/subcomponents/DraggableNote.tsx @@ -1,8 +1,7 @@ -import React from "react"; import { useDraggable } from "@dnd-kit/core"; -import { NoteRead } from "../../api/folders"; -import { useNoteStore } from "../../stores/notesStore"; -import { useContextMenu } from "../../contexts/ContextMenuContext"; +import { useContextMenu } from "@/contexts/ContextMenuContext"; +import { useNoteStore } from "@/stores/notesStore"; +import { NoteRead } from "@/api/folders"; export const DraggableNote = ({ note }: { note: NoteRead }) => { const { selectedNote, setSelectedNote } = useNoteStore(); @@ -35,7 +34,7 @@ export const DraggableNote = ({ note }: { note: NoteRead }) => { >
{ + onClick={() => { setSelectedNote(note); }} className={` rounded-sm px-2 mb-0.5 select-none cursor-pointer font-light transition-all duration-150 flex items-center gap-1 ${ @@ -44,7 +43,7 @@ export const DraggableNote = ({ note }: { note: NoteRead }) => { : "hover:bg-ctp-surface1" }`} > - + {selectedNote?.id == note.id ? selectedNote.title : note.title}
diff --git a/frontend/src/components/sidebar/DroppableFolder.tsx b/frontend/src/pages/Home/components/sidebar/subcomponents/DroppableFolder.tsx similarity index 72% rename from frontend/src/components/sidebar/DroppableFolder.tsx rename to frontend/src/pages/Home/components/sidebar/subcomponents/DroppableFolder.tsx index 8f994f0..c73b302 100644 --- a/frontend/src/components/sidebar/DroppableFolder.tsx +++ b/frontend/src/pages/Home/components/sidebar/subcomponents/DroppableFolder.tsx @@ -1,11 +1,11 @@ import React from "react"; import { useDroppable, useDraggable } from "@dnd-kit/core"; -import { Folder } from "../../api/folders"; -import { useContextMenu } from "../../contexts/ContextMenuContext"; // @ts-ignore -import CaretRightIcon from "../../assets/fontawesome/svg/caret-right.svg?react"; +import CaretRightIcon from "@/assets/fontawesome/svg/caret-right.svg?react"; // @ts-ignore -import FolderIcon from "../../assets/fontawesome/svg/folder.svg?react"; +import FolderIcon from "@/assets/fontawesome/svg/folder.svg?react"; +import { Folder } from "@/api/folders"; +import { useContextMenu } from "@/contexts/ContextMenuContext"; export const DroppableFolder = ({ folder, @@ -59,15 +59,15 @@ export const DroppableFolder = ({ e.stopPropagation(); openContextMenu(e.clientX, e.clientY, "folder", folder); }} - className={`font-semibold mb-1 flex items-center gap-1 pr-1 py-1 rounded cursor-pointer select-none`} + className={`font-semibold mb-1 flex items-center gap-1 pr-1 py-1 rounded cursor-pointer select-none min-w-0`} {...listeners} {...attributes} > - - {folder.name} + + {folder.name}
); diff --git a/frontend/src/components/sidebar/RecursiveFolder.tsx b/frontend/src/pages/Home/components/sidebar/subcomponents/FolderTree.tsx similarity index 80% rename from frontend/src/components/sidebar/RecursiveFolder.tsx rename to frontend/src/pages/Home/components/sidebar/subcomponents/FolderTree.tsx index 935271b..ed04fba 100644 --- a/frontend/src/components/sidebar/RecursiveFolder.tsx +++ b/frontend/src/pages/Home/components/sidebar/subcomponents/FolderTree.tsx @@ -1,18 +1,15 @@ import { useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; -import { FolderTreeNode, NoteRead } from "../../api/folders"; import { DraggableNote } from "./DraggableNote"; import { DroppableFolder } from "./DroppableFolder"; +import { FolderTreeNode } from "../../../../../api/folders"; -interface RecursiveFolderProps { +interface FolderTreeProps { folder: FolderTreeNode; depth?: number; } -export const RecursiveFolder = ({ - folder, - depth = 0, -}: RecursiveFolderProps) => { +export const FolderTree = ({ folder, depth = 0 }: FolderTreeProps) => { const [collapse, setCollapse] = useState(false); return ( @@ -42,11 +39,7 @@ export const RecursiveFolder = ({ {/* Child Folders */} {folder.children.map((child) => ( - + ))} diff --git a/frontend/src/pages/Home/components/sidebar/subcomponents/SideBarHeader.tsx b/frontend/src/pages/Home/components/sidebar/subcomponents/SideBarHeader.tsx new file mode 100644 index 0000000..2af39d4 --- /dev/null +++ b/frontend/src/pages/Home/components/sidebar/subcomponents/SideBarHeader.tsx @@ -0,0 +1,39 @@ +import { SetStateAction } from "react"; +// @ts-ignore +import FolderPlusIcon from "@assets/fontawesome/svg/folder-plus.svg?react"; +// @ts-ignore +import FileCirclePlusIcon from "@assets/fontawesome/svg/file-circle-plus.svg?react"; +import { useNoteStore } from "@/stores/notesStore"; + +export const SidebarHeader = ({ + setNewFolder, +}: { + setNewFolder: React.Dispatch>; +}) => { + const { createNote, selectedFolder } = useNoteStore(); + const handleCreate = async () => { + await createNote({ + title: "Untitled", + content: "", + folder_id: selectedFolder, + }); + }; + return ( +
+ + +
+ ); +}; diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 9373cfd..ef8f8d0 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -21,7 +21,7 @@ export const Login = () => { setShowModal(false); navigate("/"); } catch (err) { - setError(err.message); + setError((err as Error).message); } }; diff --git a/frontend/src/pages/tiptap.css b/frontend/src/pages/tiptap.css index 621d221..3c566f5 100644 --- a/frontend/src/pages/tiptap.css +++ b/frontend/src/pages/tiptap.css @@ -1,3 +1,4 @@ +/* @tailwind */ @reference "../main.css"; /* Custom Scrollbar */ diff --git a/frontend/src/stores/notesStore.ts b/frontend/src/stores/notesStore.ts index 3ae29a6..038971c 100644 --- a/frontend/src/stores/notesStore.ts +++ b/frontend/src/stores/notesStore.ts @@ -43,7 +43,9 @@ const updateFolder = ( if (folder.children) { return { ...folder, - children: folder.children.map((f) => updateFolder(id, f, newFolder)), + children: folder.children.map((folder) => + updateFolder(id, folder, newFolder), + ), }; } return folder; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..24d0346 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + /* Path aliases */ + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + "@components/*": ["./src/components/*"], + "@pages/*": ["./src/pages/*"], + "@stores/*": ["./src/stores/*"], + "@api/*": ["./src/api/*"], + "@contexts/*": ["./src/contexts/*"], + "@assets/*": ["./src/assets/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..18e69ee --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.mts"] +} diff --git a/frontend/vite.config.mts b/frontend/vite.config.mts index ff1e9be..32f7445 100644 --- a/frontend/vite.config.mts +++ b/frontend/vite.config.mts @@ -2,9 +2,21 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; import svgr from "vite-plugin-svgr"; +import path from "path"; export default defineConfig({ plugins: [tailwindcss(), react(), svgr()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + "@components": path.resolve(__dirname, "./src/components"), + "@pages": path.resolve(__dirname, "./src/pages"), + "@stores": path.resolve(__dirname, "./src/stores"), + "@api": path.resolve(__dirname, "./src/api"), + "@contexts": path.resolve(__dirname, "./src/contexts"), + "@assets": path.resolve(__dirname, "./src/assets"), + }, + }, server: { port: 5173, proxy: {