Refactor Home UI and add StatusIndicator
This commit is contained in:
parent
502d78f244
commit
b596c9f34d
14 changed files with 192 additions and 143 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
// src/App.tsx
|
// src/App.tsx
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
|
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 { Import } from "./pages/Import";
|
||||||
import { Login } from "./pages/Login";
|
import { Login } from "./pages/Login";
|
||||||
import { Register } from "./pages/Register";
|
import { Register } from "./pages/Register";
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,33 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { notesApi } from "../api/notes";
|
import "../../main.css";
|
||||||
import "../main.css";
|
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import "@mdxeditor/editor/style.css";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
// @ts-ignore
|
import { useNoteStore } from "@/stores/notesStore";
|
||||||
import CheckIcon from "../assets/fontawesome/svg/circle-check.svg?react";
|
import { useUIStore } from "@/stores/uiStore";
|
||||||
// @ts-ignore
|
import { Login } from "../Login";
|
||||||
import SpinnerIcon from "../assets/fontawesome/svg/rotate.svg?react";
|
import { TiptapEditor } from "../TipTap";
|
||||||
// @ts-ignore
|
import { Sidebar } from "./components/sidebar/SideBar";
|
||||||
import WarningIcon from "../assets/fontawesome/svg/circle-exclamation.svg?react";
|
import { StatusIndicator } from "./components/StatusIndicator";
|
||||||
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";
|
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const [newFolder, setNewFolder] = useState(false);
|
const [newFolder] = useState(false);
|
||||||
const [lastSavedNote, setLastSavedNote] = useState<{
|
const [lastSavedNote, setLastSavedNote] = useState<{
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const {
|
const { loadFolderTree, updateNote, setContent, selectedNote, setTitle } =
|
||||||
loadFolderTree,
|
useNoteStore();
|
||||||
updateNote,
|
|
||||||
setSelectedNote,
|
|
||||||
setContent,
|
|
||||||
selectedNote,
|
|
||||||
setTitle,
|
|
||||||
} = useNoteStore();
|
|
||||||
|
|
||||||
const { isAuthenticated, encryptionKey } = useAuthStore();
|
const { encryptionKey } = useAuthStore();
|
||||||
|
|
||||||
const { showModal, setShowModal } = useUIStore();
|
const { showModal, setUpdating } = useUIStore();
|
||||||
|
|
||||||
const newFolderRef = useRef<HTMLInputElement>(null);
|
const newFolderRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if (!isAuthenticated) return;
|
if (!encryptionKey) return;
|
||||||
console.log(encryptionKey);
|
|
||||||
loadFolderTree();
|
loadFolderTree();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -51,12 +37,6 @@ function Home() {
|
||||||
}
|
}
|
||||||
}, [newFolder]);
|
}, [newFolder]);
|
||||||
|
|
||||||
const clearSelection = () => {
|
|
||||||
setSelectedNote(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const { updating, setUpdating } = useUIStore();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedNote) return;
|
if (!selectedNote) return;
|
||||||
if (!encryptionKey) return; // Don't try to save without encryption key
|
if (!encryptionKey) return; // Don't try to save without encryption key
|
||||||
|
|
@ -117,7 +97,7 @@ function Home() {
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
{showModal && <Modal />}
|
{showModal && <Modal />}
|
||||||
|
|
||||||
<Sidebar clearSelection={clearSelection} />
|
<Sidebar />
|
||||||
|
|
||||||
{/* Main editor area */}
|
{/* Main editor area */}
|
||||||
<div className="flex flex-col w-full h-screen overflow-hidden">
|
<div className="flex flex-col w-full h-screen overflow-hidden">
|
||||||
|
|
@ -136,31 +116,7 @@ function Home() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status indicator */}
|
<StatusIndicator />
|
||||||
<div
|
|
||||||
className="fixed bottom-2 right-3 bg-ctp-surface0 border border-ctp-surface2 rounded-sm px-2 py-0.5 flex items-center gap-2.5 shadow-lg backdrop-blur-sm"
|
|
||||||
onClick={() => {
|
|
||||||
if (!encryptionKey) {
|
|
||||||
setShowModal(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!encryptionKey ? (
|
|
||||||
<WarningIcon className="h-4 w-4 my-1 [&_.fa-primary]:fill-ctp-yellow [&_.fa-secondary]:fill-ctp-orange" />
|
|
||||||
) : updating ? (
|
|
||||||
<>
|
|
||||||
<SpinnerIcon className="animate-spin h-4 w-4 [&_.fa-primary]:fill-ctp-blue [&_.fa-secondary]:fill-ctp-sapphire" />
|
|
||||||
<span className="text-sm text-ctp-subtext0 font-medium">
|
|
||||||
Saving...
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<CheckIcon className="h-4 w-4 [&_.fa-primary]:fill-ctp-green [&_.fa-secondary]:fill-ctp-teal" />
|
|
||||||
<span className="text-sm text-ctp-subtext0 font-medium">Saved</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
39
frontend/src/pages/Home/components/StatusIndicator.tsx
Normal file
39
frontend/src/pages/Home/components/StatusIndicator.tsx
Normal file
|
|
@ -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 (
|
||||||
|
<div
|
||||||
|
className="fixed bottom-2 right-3 bg-ctp-surface0 border border-ctp-surface2 rounded-sm px-2 py-0.5 flex items-center gap-2.5 shadow-lg backdrop-blur-sm"
|
||||||
|
onClick={() => {
|
||||||
|
if (!encryptionKey) {
|
||||||
|
setShowModal(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!encryptionKey ? (
|
||||||
|
<WarningIcon className="h-4 w-4 my-1 [&_.fa-primary]:fill-ctp-yellow [&_.fa-secondary]:fill-ctp-orange" />
|
||||||
|
) : updating ? (
|
||||||
|
<>
|
||||||
|
<SpinnerIcon className="animate-spin h-4 w-4 [&_.fa-primary]:fill-ctp-blue [&_.fa-secondary]:fill-ctp-sapphire" />
|
||||||
|
<span className="text-sm text-ctp-subtext0 font-medium">
|
||||||
|
Saving...
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<CheckIcon className="h-4 w-4 [&_.fa-primary]:fill-ctp-green [&_.fa-secondary]:fill-ctp-teal" />
|
||||||
|
<span className="text-sm text-ctp-subtext0 font-medium">Saved</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import React, { useState, useRef, useEffect, SetStateAction } from "react";
|
import React, { useState, useRef, useEffect, SetStateAction } from "react";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import FolderPlusIcon from "../../assets/fontawesome/svg/folder-plus.svg?react";
|
import FolderIcon from "@/assets/fontawesome/svg/folder.svg?react";
|
||||||
// @ts-ignore
|
import { DraggableNote } from "./subcomponents/DraggableNote";
|
||||||
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 {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
DragEndEvent,
|
DragEndEvent,
|
||||||
|
|
@ -17,12 +14,13 @@ import {
|
||||||
useSensors,
|
useSensors,
|
||||||
} from "@dnd-kit/core";
|
} from "@dnd-kit/core";
|
||||||
|
|
||||||
import { RecursiveFolder } from "./RecursiveFolder";
|
import { FolderTree } from "./subcomponents/FolderTree.tsx";
|
||||||
import { useAuthStore } from "../../stores/authStore";
|
import { SidebarHeader } from "./subcomponents/SideBarHeader.tsx";
|
||||||
import { useUIStore } from "../../stores/uiStore";
|
import { useAuthStore } from "@/stores/authStore.ts";
|
||||||
import { NoteRead } from "../../api/folders";
|
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 [newFolder, setNewFolder] = useState(false);
|
||||||
const [newFolderText, setNewFolderText] = useState("");
|
const [newFolderText, setNewFolderText] = useState("");
|
||||||
const [activeItem, setActiveItem] = useState<{
|
const [activeItem, setActiveItem] = useState<{
|
||||||
|
|
@ -39,7 +37,7 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => {
|
||||||
createFolder,
|
createFolder,
|
||||||
} = useNoteStore();
|
} = useNoteStore();
|
||||||
|
|
||||||
const { isAuthenticated } = useAuthStore();
|
const { encryptionKey } = useAuthStore();
|
||||||
|
|
||||||
const { setSideBarResize, sideBarResize } = useUIStore();
|
const { setSideBarResize, sideBarResize } = useUIStore();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -49,9 +47,9 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => {
|
||||||
}, [newFolder]);
|
}, [newFolder]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if (!isAuthenticated) return;
|
if (!encryptionKey) return;
|
||||||
loadFolderTree();
|
loadFolderTree();
|
||||||
}, []);
|
}, [encryptionKey]);
|
||||||
|
|
||||||
const handleCreateFolder = async () => {
|
const handleCreateFolder = async () => {
|
||||||
if (!newFolderText.trim()) return;
|
if (!newFolderText.trim()) return;
|
||||||
|
|
@ -170,10 +168,7 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => {
|
||||||
className="flex flex-col min-h-full"
|
className="flex flex-col min-h-full"
|
||||||
style={{ width: `${sideBarResize}px` }}
|
style={{ width: `${sideBarResize}px` }}
|
||||||
>
|
>
|
||||||
<SidebarHeader
|
<SidebarHeader setNewFolder={setNewFolder} />
|
||||||
clearSelection={clearSelection}
|
|
||||||
setNewFolder={setNewFolder}
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
className="bg-ctp-mantle min-h-full border-r border-ctp-surface2 w-full p-4 overflow-y-auto sm:block hidden flex-col gap-3"
|
className="bg-ctp-mantle min-h-full border-r border-ctp-surface2 w-full p-4 overflow-y-auto sm:block hidden flex-col gap-3"
|
||||||
onDragOver={(e) => e.preventDefault()}
|
onDragOver={(e) => e.preventDefault()}
|
||||||
|
|
@ -205,7 +200,7 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => {
|
||||||
{/* Folder tree */}
|
{/* Folder tree */}
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
{folderTree?.folders.map((folder) => (
|
{folderTree?.folders.map((folder) => (
|
||||||
<RecursiveFolder key={folder.id} folder={folder} depth={0} />
|
<FolderTree key={folder.id} folder={folder} depth={0} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -238,38 +233,3 @@ export const Sidebar = ({ clearSelection }: { clearSelection: () => void }) => {
|
||||||
</DndContext>
|
</DndContext>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidebarHeader = ({
|
|
||||||
clearSelection,
|
|
||||||
setNewFolder,
|
|
||||||
}: {
|
|
||||||
clearSelection: () => void;
|
|
||||||
setNewFolder: React.Dispatch<SetStateAction<boolean>>;
|
|
||||||
}) => {
|
|
||||||
const { createNote, selectedFolder } = useNoteStore();
|
|
||||||
const handleCreate = async () => {
|
|
||||||
await createNote({
|
|
||||||
title: "Untitled",
|
|
||||||
content: "",
|
|
||||||
folder_id: selectedFolder,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-center w-full gap-2 bg-ctp-mantle border-b border-ctp-surface0 p-1">
|
|
||||||
<button
|
|
||||||
onClick={() => setNewFolder(true)}
|
|
||||||
className="hover:bg-ctp-mauve group transition-colors rounded-sm p-2"
|
|
||||||
title="New folder"
|
|
||||||
>
|
|
||||||
<FolderPlusIcon className="w-4 h-4 group-hover:fill-ctp-base transition-colors fill-ctp-mauve" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={handleCreate}
|
|
||||||
className="hover:bg-ctp-mauve group transition-colors rounded-sm p-2 fill-ctp-mauve hover:fill-ctp-base"
|
|
||||||
title="New note"
|
|
||||||
>
|
|
||||||
<FileCirclePlusIcon className="w-4 h-4 text-ctp-mauve group-hover:text-ctp-base transition-colors" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import React from "react";
|
|
||||||
import { useDraggable } from "@dnd-kit/core";
|
import { useDraggable } from "@dnd-kit/core";
|
||||||
import { NoteRead } from "../../api/folders";
|
import { useContextMenu } from "@/contexts/ContextMenuContext";
|
||||||
import { useNoteStore } from "../../stores/notesStore";
|
import { useNoteStore } from "@/stores/notesStore";
|
||||||
import { useContextMenu } from "../../contexts/ContextMenuContext";
|
import { NoteRead } from "@/api/folders";
|
||||||
|
|
||||||
export const DraggableNote = ({ note }: { note: NoteRead }) => {
|
export const DraggableNote = ({ note }: { note: NoteRead }) => {
|
||||||
const { selectedNote, setSelectedNote } = useNoteStore();
|
const { selectedNote, setSelectedNote } = useNoteStore();
|
||||||
|
|
@ -35,7 +34,7 @@ export const DraggableNote = ({ note }: { note: NoteRead }) => {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
key={note.id}
|
key={note.id}
|
||||||
onClick={(e) => {
|
onClick={() => {
|
||||||
setSelectedNote(note);
|
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 ${
|
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"
|
: "hover:bg-ctp-surface1"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>
|
<span className="truncate">
|
||||||
{selectedNote?.id == note.id ? selectedNote.title : note.title}
|
{selectedNote?.id == note.id ? selectedNote.title : note.title}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useDroppable, useDraggable } from "@dnd-kit/core";
|
import { useDroppable, useDraggable } from "@dnd-kit/core";
|
||||||
import { Folder } from "../../api/folders";
|
|
||||||
import { useContextMenu } from "../../contexts/ContextMenuContext";
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import CaretRightIcon from "../../assets/fontawesome/svg/caret-right.svg?react";
|
import CaretRightIcon from "@/assets/fontawesome/svg/caret-right.svg?react";
|
||||||
// @ts-ignore
|
// @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 = ({
|
export const DroppableFolder = ({
|
||||||
folder,
|
folder,
|
||||||
|
|
@ -59,15 +59,15 @@ export const DroppableFolder = ({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
openContextMenu(e.clientX, e.clientY, "folder", folder);
|
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}
|
{...listeners}
|
||||||
{...attributes}
|
{...attributes}
|
||||||
>
|
>
|
||||||
<CaretRightIcon
|
<CaretRightIcon
|
||||||
className={`w-4 h-4 mr-1 transition-all duration-200 ease-in-out ${collapse ? "rotate-90" : ""} fill-ctp-mauve`}
|
className={`w-4 h-4 min-h-4 min-w-4 mr-1 transition-all duration-200 ease-in-out ${collapse ? "rotate-90" : ""} fill-ctp-mauve`}
|
||||||
/>
|
/>
|
||||||
<FolderIcon className="w-4 h-4 fill-ctp-mauve mr-1" />
|
<FolderIcon className="w-4 h-4 min-h-4 min-w-4 fill-ctp-mauve mr-1" />
|
||||||
{folder.name}
|
<span className="truncate">{folder.name}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -1,18 +1,15 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { FolderTreeNode, NoteRead } from "../../api/folders";
|
|
||||||
import { DraggableNote } from "./DraggableNote";
|
import { DraggableNote } from "./DraggableNote";
|
||||||
import { DroppableFolder } from "./DroppableFolder";
|
import { DroppableFolder } from "./DroppableFolder";
|
||||||
|
import { FolderTreeNode } from "../../../../../api/folders";
|
||||||
|
|
||||||
interface RecursiveFolderProps {
|
interface FolderTreeProps {
|
||||||
folder: FolderTreeNode;
|
folder: FolderTreeNode;
|
||||||
depth?: number;
|
depth?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecursiveFolder = ({
|
export const FolderTree = ({ folder, depth = 0 }: FolderTreeProps) => {
|
||||||
folder,
|
|
||||||
depth = 0,
|
|
||||||
}: RecursiveFolderProps) => {
|
|
||||||
const [collapse, setCollapse] = useState(false);
|
const [collapse, setCollapse] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -42,11 +39,7 @@ export const RecursiveFolder = ({
|
||||||
|
|
||||||
{/* Child Folders */}
|
{/* Child Folders */}
|
||||||
{folder.children.map((child) => (
|
{folder.children.map((child) => (
|
||||||
<RecursiveFolder
|
<FolderTree key={child.id} folder={child} depth={depth + 1} />
|
||||||
key={child.id}
|
|
||||||
folder={child}
|
|
||||||
depth={depth + 1}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
@ -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<SetStateAction<boolean>>;
|
||||||
|
}) => {
|
||||||
|
const { createNote, selectedFolder } = useNoteStore();
|
||||||
|
const handleCreate = async () => {
|
||||||
|
await createNote({
|
||||||
|
title: "Untitled",
|
||||||
|
content: "",
|
||||||
|
folder_id: selectedFolder,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center w-full gap-2 bg-ctp-mantle border-b border-ctp-surface0 p-1">
|
||||||
|
<button
|
||||||
|
onClick={() => setNewFolder(true)}
|
||||||
|
className="hover:bg-ctp-mauve group transition-colors rounded-sm p-2"
|
||||||
|
title="New folder"
|
||||||
|
>
|
||||||
|
<FolderPlusIcon className="w-4 h-4 group-hover:fill-ctp-base transition-colors fill-ctp-mauve" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleCreate}
|
||||||
|
className="hover:bg-ctp-mauve group transition-colors rounded-sm p-2 fill-ctp-mauve hover:fill-ctp-base"
|
||||||
|
title="New note"
|
||||||
|
>
|
||||||
|
<FileCirclePlusIcon className="w-4 h-4 text-ctp-mauve group-hover:text-ctp-base transition-colors" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -21,7 +21,7 @@ export const Login = () => {
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
navigate("/");
|
navigate("/");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message);
|
setError((err as Error).message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* @tailwind */
|
||||||
@reference "../main.css";
|
@reference "../main.css";
|
||||||
|
|
||||||
/* Custom Scrollbar */
|
/* Custom Scrollbar */
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,9 @@ const updateFolder = (
|
||||||
if (folder.children) {
|
if (folder.children) {
|
||||||
return {
|
return {
|
||||||
...folder,
|
...folder,
|
||||||
children: folder.children.map((f) => updateFolder(id, f, newFolder)),
|
children: folder.children.map((folder) =>
|
||||||
|
updateFolder(id, folder, newFolder),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return folder;
|
return folder;
|
||||||
|
|
|
||||||
37
frontend/tsconfig.json
Normal file
37
frontend/tsconfig.json
Normal file
|
|
@ -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" }]
|
||||||
|
}
|
||||||
11
frontend/tsconfig.node.json
Normal file
11
frontend/tsconfig.node.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.mts"]
|
||||||
|
}
|
||||||
|
|
@ -2,9 +2,21 @@ import { defineConfig } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import svgr from "vite-plugin-svgr";
|
import svgr from "vite-plugin-svgr";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tailwindcss(), react(), svgr()],
|
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: {
|
server: {
|
||||||
port: 5173,
|
port: 5173,
|
||||||
proxy: {
|
proxy: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue