diff --git a/backend/app/routes/notes.py b/backend/app/routes/notes.py index ba90306..c6e4210 100644 --- a/backend/app/routes/notes.py +++ b/backend/app/routes/notes.py @@ -2,14 +2,14 @@ from datetime import datetime from app.auth import require_auth from app.database import get_session -from app.models import Note, NoteCreate, NoteUpdate, User +from app.models import Note, NoteCreate, NoteRead, NoteUpdate, User from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session, select router = APIRouter(prefix="/notes", tags=["notes"]) -@router.get("/") +@router.get("/", response_model=list[NoteRead]) def list_notes(session: Session = Depends(get_session)): notes = session.exec(select(Note).order_by(Note.updated_at.desc())).all() # pyright: ignore[reportAttributeAccessIssue] return notes diff --git a/frontend/src/api/notes.tsx b/frontend/src/api/notes.tsx index ba2ef9e..a68a29f 100644 --- a/frontend/src/api/notes.tsx +++ b/frontend/src/api/notes.tsx @@ -10,6 +10,7 @@ export type NoteRead = CamelCasedPropertiesDeep< export type NoteCreate = CamelCasedPropertiesDeep< components["schemas"]["NoteCreate"] >; +export type Note = CamelCasedPropertiesDeep; const createNote = async (note: NoteCreate) => { const encryptionKey = useAuthStore.getState().encryptionKey; @@ -31,23 +32,31 @@ const fetchNotes = async () => { const encryptionKey = useAuthStore.getState().encryptionKey; if (!encryptionKey) throw new Error("Not authenticated"); - const { data } = await client.GET(`/api/notes/`); + const { data, error } = await client.GET(`/api/notes/`); + if (error) { + throw new Error(error); + } console.log(data); - const decryptedNotes = await Promise.all( - data.map(async (note: NoteRead) => ({ - ...note, - title: await decryptString(note.title, encryptionKey), - content: await decryptString(note.content, encryptionKey), - tags: await Promise.all( - note.tags.map(async (tag) => ({ - ...tag, - name: await decryptString(tag.name, encryptionKey), - })), - ), - })), - ); - return decryptedNotes; + + if (data) { + const decryptedNotes = await Promise.all( + data.map(async (note) => ({ + ...note, + title: await decryptString(note.title, encryptionKey), + content: await decryptString(note.content, encryptionKey), + tags: note.tags + ? await Promise.all( + note.tags.map(async (tag) => ({ + ...tag, + name: await decryptString(tag.name, encryptionKey), + })), + ) + : [], + })), + ); + return decryptedNotes; + } }; const updateNote = async (id: number, note: Partial) => { @@ -64,9 +73,7 @@ const updateNote = async (id: number, note: Partial) => { if (note.folderId) { encryptedNote.folderId = note.folderId; } - // if (!note.folderId){ - // throw new Error("Folder id missing from note.") - // } + const { data, error } = await client.PATCH(`/api/notes/{note_id}`, { body: encryptedNote, params: { diff --git a/frontend/src/hooks/useFolders.ts b/frontend/src/hooks/useFolders.ts index 1c6afd4..12fe904 100644 --- a/frontend/src/hooks/useFolders.ts +++ b/frontend/src/hooks/useFolders.ts @@ -104,10 +104,14 @@ export const useUpdateFolder = () => { folders: FolderTreeNode[], ): FolderTreeNode[] => { return folders.map((f) => { - if (f.id == folderId) { + if (f.id === folderId) { return { ...f, - ...folder, + ...(folder.name !== undefined && + folder.name !== null && { name: folder.name }), + ...(folder.parentId !== undefined && { + parentId: folder.parentId, + }), }; } return { diff --git a/frontend/src/pages/Home/Home.tsx b/frontend/src/pages/Home/Home.tsx index 898422c..4c884ee 100644 --- a/frontend/src/pages/Home/Home.tsx +++ b/frontend/src/pages/Home/Home.tsx @@ -9,14 +9,16 @@ import { Sidebar } from "./components/sidebar/SideBar"; import { StatusIndicator } from "./components/StatusIndicator"; import { useCreateTag, useTagTree } from "@/hooks/useTags"; import { useFolderTree, useUpdateNote } from "@/hooks/useFolders"; -import { Note } from "@/api/notes"; +import { Note, NoteRead } from "@/api/notes"; import { DecryptedTagNode } from "@/api/encryption"; +// @ts-ignore +import XmarkIcon from "@/assets/fontawesome/svg/xmark.svg?react"; function Home() { const [newFolder] = useState(false); // Local state for editing the current note - const [editingNote, setEditingNote] = useState(null); + const [editingNote, setEditingNote] = useState(null); const [lastSavedNote, setLastSavedNote] = useState<{ id: number; title: string; @@ -85,6 +87,7 @@ function Home() { } try { + if (!editingNote.id) throw new Error("Editing note has no id."); await updateNoteMutation.mutateAsync({ noteId: editingNote.id, note: { @@ -116,51 +119,41 @@ function Home() { return (
{/* Sidebar */} - {showModal && } + {showModal && } - {/*
- setTagName(e.target.value)} - /> - {tags.map((tag) => ( - - ))} -
*/} {/* Main editor area */} -
+
{/**/} - setTitle(e.target.value)} - className="w-full px-4 py-3 text-3xl font-semibold bg-transparentfocus:outline-none focus:border-ctp-mauve transition-colors placeholder:text-ctp-overlay0 text-ctp-text" - /> -
- {editingNote?.tags && - editingNote.tags.map((tag) => ( - - ))} -
+
+ setTitle(e.target.value)} + className="w-full p-4 pb-0 text-3xl font-semibold bg-transparent focus:outline-none border-transparent focus:border-ctp-mauve transition-colors placeholder:text-ctp-overlay0 text-ctp-text" + /> + {/*
+ {editingNote?.tags && + editingNote.tags.map((tag) => ( + + ))} +
*/} - + +
@@ -176,16 +169,28 @@ const Modal = () => { setShowModal(false)} - className="absolute h-screen w-screen flex items-center justify-center bg-ctp-crust/60 z-50" + className="fixed inset-0 h-screen w-screen flex items-center justify-center bg-ctp-crust/70 backdrop-blur-sm z-50" > -
e.stopPropagation()} - className="w-2/3 h-2/3 bg-ctp-base rounded-xl border-ctp-surface2 border p-5" + className="relative w-full max-w-md mx-4 bg-ctp-base rounded-xl border-ctp-surface2 border p-8 shadow-2xl" > + {/**/} -
+
); }; diff --git a/frontend/src/pages/Home/components/sidebar/subcomponents/SideBarHeader.tsx b/frontend/src/pages/Home/components/sidebar/subcomponents/SideBarHeader.tsx index becba2e..59f9d73 100644 --- a/frontend/src/pages/Home/components/sidebar/subcomponents/SideBarHeader.tsx +++ b/frontend/src/pages/Home/components/sidebar/subcomponents/SideBarHeader.tsx @@ -25,29 +25,29 @@ export const SidebarHeader = ({ }; return (
-
+
diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index ef8f8d0..5756a4a 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -26,31 +26,68 @@ export const Login = () => { }; return ( -
- setUsername(e.target.value)} - /> - setPassword(e.target.value)} - /> - {error &&
{error}
} - -
+ +

+ Welcome Back +

+ +
+ setUsername(e.target.value)} + /> +
+ +
+ + setPassword(e.target.value)} + /> +
+ + {error && ( +
+ {error} +
+ )} + +
+ setRemember(e.target.checked)} + className="accent-ctp-mauve cursor-pointer" /> -
Remember me?
+
+ + ); }; diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx index 420b79c..3708c2f 100644 --- a/frontend/src/pages/Register.tsx +++ b/frontend/src/pages/Register.tsx @@ -23,29 +23,63 @@ export const Register = () => { }; return ( -
- setUsername(e.target.value)} - /> - setEmail(e.target.value)} - /> - setPassword(e.target.value)} - /> - {error &&
{error}
} - + +

+ Create Account +

+ +
+ + setUsername(e.target.value)} + /> +
+ +
+ + setEmail(e.target.value)} + /> +
+ +
+ + setPassword(e.target.value)} + /> +
+ + {error && ( +
+ {error} +
+ )} + +
); }; - -// Similar pattern for Register.tsx diff --git a/frontend/src/pages/TipTap.tsx b/frontend/src/pages/TipTap.tsx index d3b66e9..533d6fa 100644 --- a/frontend/src/pages/TipTap.tsx +++ b/frontend/src/pages/TipTap.tsx @@ -79,7 +79,7 @@ export const TiptapEditor = ({ } return ( -
+
{/* Toolbar */} {/*
diff --git a/frontend/src/pages/tiptap.css b/frontend/src/pages/tiptap.css index 3c566f5..d5acfd5 100644 --- a/frontend/src/pages/tiptap.css +++ b/frontend/src/pages/tiptap.css @@ -80,7 +80,7 @@ } .ProseMirror h3 { - @apply text-xl font-semibold text-ctp-teal mt-5 mb-2; + @apply text-xl font-semibold text-ctp-mauve mt-5 mb-2; } .ProseMirror code { @@ -149,11 +149,12 @@ .ProseMirror li[data-checked="true"] > div > p { @apply line-through text-ctp-overlay0; text-decoration-style: wavy; + text-decoration-thickness: 1px; } .ProseMirror u { @apply decoration-ctp-mauve; - text-decoration-style: wavy; + /*text-decoration-style: wavy;*/ } .ProseMirror li::marker { diff --git a/frontend/src/stores/authStore.ts b/frontend/src/stores/authStore.ts index 55abd8b..cadeb4d 100644 --- a/frontend/src/stores/authStore.ts +++ b/frontend/src/stores/authStore.ts @@ -1,6 +1,5 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import { useNoteStore } from "./notesStore"; import { deriveKey, generateMasterKey, diff --git a/frontend/src/stores/uiStore.ts b/frontend/src/stores/uiStore.ts index 6d1873b..fa54506 100644 --- a/frontend/src/stores/uiStore.ts +++ b/frontend/src/stores/uiStore.ts @@ -1,4 +1,4 @@ -import { Note } from "@/api/notes"; +import { Note, NoteRead } from "@/api/notes"; import { create } from "zustand"; import { persist } from "zustand/middleware"; @@ -15,8 +15,8 @@ interface UIState { sideBarView: string; setSideBarView: (view: string) => void; - selectedNote: Note | null; - setSelectedNote: (note: Note | null) => void; + selectedNote: NoteRead | null; + setSelectedNote: (note: NoteRead | null) => void; selectedFolder: number | null; setSelectedFolder: (id: number | null) => void; @@ -43,7 +43,7 @@ export const useUIStore = create()( }, selectedNote: null, - setSelectedNote: (id: Note | null) => { + setSelectedNote: (id: NoteRead | null) => { set({ selectedNote: id }); }, selectedFolder: null, diff --git a/frontend/src/types/api.d.ts b/frontend/src/types/api.d.ts index f382d20..f57363d 100644 --- a/frontend/src/types/api.d.ts +++ b/frontend/src/types/api.d.ts @@ -548,7 +548,7 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": unknown; + "application/json": components["schemas"]["NoteRead"][]; }; }; };