diff --git a/web/eslint.config.js b/web/eslint.config.js index 34e22c6..5e428f1 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -1,10 +1,12 @@ +// @ts-check import js from "@eslint/js"; -import * as tsParser from "@typescript-eslint/parser"; import solid from "eslint-plugin-solid/configs/typescript"; -import globals from "globals"; +import tseslint from "typescript-eslint"; -export default [js.configs.recommended, { - files: ["**/*.{ts,tsx}"], - ...solid, - languageOptions: { parser: tsParser, globals: globals.browser }, -}]; +export default tseslint.config( + { ignores: ["dist/", "node_modules/", "coverage/"] }, + js.configs.recommended, + tseslint.configs.recommended, + solid, + { rules: { "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }] } }, +); diff --git a/web/package.json b/web/package.json index 77c7c99..348a86c 100644 --- a/web/package.json +++ b/web/package.json @@ -38,6 +38,7 @@ "globals": "^16.5.0", "jsdom": "^27.4.0", "typescript": "~5.9.3", + "typescript-eslint": "^8.50.1", "vite": "npm:rolldown-vite@7.2.5", "vite-plugin-solid": "^2.11.10", "vitest": "^4.0.16" diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 85128ae..62a4251 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -84,6 +84,9 @@ importers: typescript: specifier: ~5.9.3 version: 5.9.3 + typescript-eslint: + specifier: ^8.50.1 + version: 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) vite: specifier: npm:rolldown-vite@7.2.5 version: rolldown-vite@7.2.5(@types/node@24.10.4)(jiti@2.6.1) @@ -587,6 +590,14 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@typescript-eslint/eslint-plugin@8.50.1': + resolution: {integrity: sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.50.1 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser@8.50.1': resolution: {integrity: sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -610,6 +621,13 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/type-utils@8.50.1': + resolution: {integrity: sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/types@8.50.1': resolution: {integrity: sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1028,6 +1046,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -1592,6 +1614,13 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + typescript-eslint@8.50.1: + resolution: {integrity: sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -2222,6 +2251,22 @@ snapshots: '@types/unist@3.0.3': {} + '@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.50.1 + '@typescript-eslint/type-utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.50.1 + eslint: 9.39.2(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.2.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.50.1 @@ -2252,6 +2297,18 @@ snapshots: dependencies: typescript: 5.9.3 + '@typescript-eslint/type-utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.50.1 + '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.2(jiti@2.6.1) + ts-api-utils: 2.2.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@8.50.1': {} '@typescript-eslint/typescript-estree@8.50.1(typescript@5.9.3)': @@ -2688,6 +2745,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -3288,6 +3347,17 @@ snapshots: dependencies: prelude-ls: 1.2.1 + typescript-eslint@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.50.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.2(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} undici-types@7.16.0: {} diff --git a/web/src/App.tsx b/web/src/App.tsx index f4707c0..2834791 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -4,11 +4,13 @@ import { Show } from "solid-js"; import { AppLayout } from "./components/layout/AppLayout"; import { authStore } from "./lib/store"; import DeckNew from "./pages/DeckNew"; +import DeckView from "./pages/DeckView"; import Home from "./pages/Home"; import Import from "./pages/Import"; import Landing from "./pages/Landing"; import Login from "./pages/Login"; import NoteNew from "./pages/NoteNew"; +import NotFound from "./pages/NotFound"; const ProtectedRoute: Component<{ component: Component }> = (props) => { return ( @@ -24,16 +26,12 @@ const App: Component = () => { return ( - - {/* Protected Routes */} } /> } /> } /> + } /> } /> - - { - /* TODO: Catch-all or 404 */ - } + } /> ); }; diff --git a/web/src/components/CardEditor.tsx b/web/src/components/CardEditor.tsx index 8eebf80..376c792 100644 --- a/web/src/components/CardEditor.tsx +++ b/web/src/components/CardEditor.tsx @@ -1,4 +1,4 @@ -import { createSignal, Show } from "solid-js"; +import { createEffect, createSignal, Show } from "solid-js"; import { Button } from "./ui/Button"; type CardEditorProps = { @@ -10,9 +10,15 @@ type CardEditorProps = { }; export function CardEditor(props: CardEditorProps) { - const [front, setFront] = createSignal(props.front || ""); - const [back, setBack] = createSignal(props.back || ""); - const [mediaUrl, setMediaUrl] = createSignal(props.mediaUrl || ""); + const [front, setFront] = createSignal(""); + const [back, setBack] = createSignal(""); + const [mediaUrl, setMediaUrl] = createSignal(""); + + createEffect(() => { + if (props.front) setFront(props.front); + if (props.back) setBack(props.back); + if (props.mediaUrl) setMediaUrl(props.mediaUrl); + }); const handleSubmit = (e: Event) => { e.preventDefault(); diff --git a/web/src/components/DeckEditor.test.tsx b/web/src/components/DeckEditor.test.tsx index 189a7d5..b1004ad 100644 --- a/web/src/components/DeckEditor.test.tsx +++ b/web/src/components/DeckEditor.test.tsx @@ -3,7 +3,10 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { api } from "../lib/api"; import { DeckEditor } from "./DeckEditor"; -vi.mock("../lib/api", () => ({ api: { post: vi.fn() } })); +vi.mock( + "../lib/api", + () => ({ api: { post: vi.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({}) }) } }), +); describe("DeckEditor", () => { afterEach(cleanup); diff --git a/web/src/components/DeckEditor.tsx b/web/src/components/DeckEditor.tsx index b39aa9a..8b2b434 100644 --- a/web/src/components/DeckEditor.tsx +++ b/web/src/components/DeckEditor.tsx @@ -1,17 +1,17 @@ import { createSignal, For, Show } from "solid-js"; import { api } from "../lib/api"; -import type { Visibility } from "../lib/store"; +import type { Card, CreateDeckPayload, Visibility } from "../lib/store"; import { toast } from "../lib/toast"; import { CardEditor } from "./CardEditor"; import { Button } from "./ui/Button"; -export function DeckEditor(props: { onSave?: (data: any) => void }) { +export function DeckEditor(props: { onSave?: (deck: CreateDeckPayload) => void }) { const [title, setTitle] = createSignal(""); const [description, setDescription] = createSignal(""); const [visibilityType, setVisibilityType] = createSignal("Private"); const [sharedWith, setSharedWith] = createSignal(""); - const [cards, setCards] = createSignal([]); + const [cards, setCards] = createSignal([]); const [showCardEditor, setShowCardEditor] = createSignal(false); const handleSubmit = async (e: Event) => { @@ -32,14 +32,18 @@ export function DeckEditor(props: { onSave?: (data: any) => void }) { } try { - const _res = await api.post("/decks", payload); - toast.success("Deck created!"); + const res = await api.post("/decks", payload); + if (res.ok) { + toast.success("Deck created!"); + } else { + toast.error("Failed to create deck"); + } } catch { - toast.error("Failed to create deck"); + toast.error("Network error creating deck"); } }; - const addCard = (cardData: any) => { + const addCard = (cardData: Card) => { setCards([...cards(), cardData]); setShowCardEditor(false); }; diff --git a/web/src/components/NoteEditor.tsx b/web/src/components/NoteEditor.tsx index f714ab3..dd133d1 100644 --- a/web/src/components/NoteEditor.tsx +++ b/web/src/components/NoteEditor.tsx @@ -1,56 +1,63 @@ +/* eslint-disable solid/no-innerhtml */ import rehypeExternalLinks from "rehype-external-links"; import rehypeSanitize from "rehype-sanitize"; import rehypeStringify from "rehype-stringify"; import remarkParse from "remark-parse"; import remarkRehype from "remark-rehype"; -import { createEffect, createSignal } from "solid-js"; +import { createEffect, createSignal, Show } from "solid-js"; import { unified } from "unified"; import { api } from "../lib/api"; import { toast } from "../lib/toast"; import { Button } from "./ui/Button"; -interface NoteEditorProps { - noteId?: string; // If editing existing - initialTitle?: string; - initialContent?: string; -} +type NoteEditorProps = { noteId?: string; initialTitle?: string; initialContent?: string }; export function NoteEditor(props: NoteEditorProps) { const [title, setTitle] = createSignal(props.initialTitle || ""); const [content, setContent] = createSignal(props.initialContent || ""); const [preview, setPreview] = createSignal(""); - const [tags, setTags] = createSignal(""); // Comma sep - const [visibility, setVisibility] = createSignal("Private"); + const [tags, setTags] = createSignal(""); + const [visibilityType, setVisibilityType] = createSignal("Private"); + const [sharedWith, setSharedWith] = createSignal(""); - const processor = unified().use(remarkParse) // .use(remarkWikiLink) // Would need a plugin for wikilinks -> links - .use(remarkRehype).use(rehypeSanitize) // Safety first - .use(rehypeExternalLinks, { target: "_blank", rel: ["nofollow"] }).use(rehypeStringify); + const processor = unified().use(remarkParse).use(remarkRehype).use(rehypeSanitize).use(rehypeExternalLinks, { + target: "_blank", + rel: ["nofollow"], + }).use(rehypeStringify); - createEffect(async () => { - try { - const file = await processor.process(content()); - setPreview(String(file)); - } catch (e) { - console.error(e); - } + createEffect(() => { + processor.process(content()).then((file) => setPreview(String(file))).catch((e) => console.error(e)); }); const handleSubmit = async (e: Event) => { e.preventDefault(); try { + let visibility; + if (visibilityType() === "SharedWith") { + visibility = { type: "SharedWith", content: sharedWith().split(",").map(s => s.trim()).filter(s => s) }; + } else { + visibility = { type: visibilityType() }; + } + const payload = { title: title(), body: content(), tags: tags().split(",").map(t => t.trim()).filter(t => t), - visibility: { type: visibility() as "Private" | "Public" }, + visibility, }; - await api.post("/notes", payload); - toast.success("Note saved!"); - if (!props.noteId) { - setTitle(""); - setContent(""); - setTags(""); + const res = await api.post("/notes", payload); + if (res.ok) { + toast.success("Note saved!"); + if (!props.noteId) { + setTitle(""); + setContent(""); + setTags(""); + setVisibilityType("Private"); + setSharedWith(""); + } + } else { + toast.error("Failed to save note"); } } catch (e) { console.error(e); @@ -97,21 +104,36 @@ export function NoteEditor(props: NoteEditorProps) {
+ +
+ + setSharedWith(e.target.value)} + class="w-full bg-gray-800 border-gray-700 text-white rounded p-2" + placeholder="did:plc:..., did:plc:..." /> +
+
+

Preview

+ {}
diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 8b737e7..54c6e19 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -26,5 +26,5 @@ export async function apiFetch(path: string, options: RequestInit = {}) { export const api = { get: (path: string) => apiFetch(path, { method: "GET" }), - post: (path: string, body: any) => apiFetch(path, { method: "POST", body: JSON.stringify(body) }), + post: (path: string, body: unknown) => apiFetch(path, { method: "POST", body: JSON.stringify(body) }), }; diff --git a/web/src/lib/store.ts b/web/src/lib/store.ts index 7e77434..9f55f45 100644 --- a/web/src/lib/store.ts +++ b/web/src/lib/store.ts @@ -46,6 +46,8 @@ export type Visibility = { type: "Private" } | { type: "Unlisted" } | { type: "P content: string[]; }; +export type Card = { id?: string; front: string; back: string; mediaUrl?: string }; + export type Deck = { id: string; owner_did: string; @@ -56,3 +58,11 @@ export type Deck = { published_at?: string; fork_of?: string; }; + +export type CreateDeckPayload = { + title: string; + description: string; + tags: string[]; + visibility: Visibility; + cards: Card[]; +}; diff --git a/web/src/pages/DeckNew.tsx b/web/src/pages/DeckNew.tsx index ee830c4..5edd03f 100644 --- a/web/src/pages/DeckNew.tsx +++ b/web/src/pages/DeckNew.tsx @@ -2,12 +2,13 @@ import { useNavigate } from "@solidjs/router"; import type { Component } from "solid-js"; import { DeckEditor } from "../components/DeckEditor"; import { api } from "../lib/api"; +import type { Card, CreateDeckPayload } from "../lib/store"; import { toast } from "../lib/toast"; const DeckNew: Component = () => { const navigate = useNavigate(); - const handleSave = async (data: any) => { + const handleSave = async (data: CreateDeckPayload) => { try { const { cards, ...deckPayload } = data; const res = await api.post("/decks", deckPayload); @@ -17,7 +18,7 @@ const DeckNew: Component = () => { if (cards && cards.length > 0) { await Promise.all( - cards.map((c: any) => + cards.map((c: Card) => api.post("/cards", { deck_id: deck.id, front: c.front, back: c.back, media_url: c.mediaUrl }) ), ); diff --git a/web/src/pages/DeckView.test.tsx b/web/src/pages/DeckView.test.tsx new file mode 100644 index 0000000..5583414 --- /dev/null +++ b/web/src/pages/DeckView.test.tsx @@ -0,0 +1,67 @@ +import { cleanup, render, screen, waitFor } from "@solidjs/testing-library"; +import { JSX } from "solid-js"; +import { afterEach, describe, expect, it, vi } from "vitest"; +import { api } from "../lib/api"; +import DeckView from "./DeckView"; + +vi.mock("../lib/api", () => ({ api: { get: vi.fn() } })); + +vi.mock( + "@solidjs/router", + () => ({ + useParams: () => ({ id: "123" }), + A: (props: { href: string; children: JSX.Element }) => {props.children}, + }), +); + +describe("DeckView", () => { + afterEach(cleanup); + + it("renders deck details and cards", async () => { + const deck = { + id: "123", + title: "Test Deck", + description: "A test deck", + tags: ["test"], + visibility: { type: "Public" }, + owner_did: "did:test", + }; + + const cards = [{ id: "c1", front: "Front 1", back: "Back 1" }, { id: "c2", front: "Front 2", back: "Back 2" }]; + + vi.mocked(api.get).mockImplementation( + ((path: string) => { + if (path === "/decks/123") { + return Promise.resolve({ ok: true, json: () => Promise.resolve(deck) }); + } + if (path === "/decks/123/cards") { + return Promise.resolve({ ok: true, json: () => Promise.resolve(cards) }); + } + return Promise.reject(new Error(`Unexpected path: ${path}`)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + }) as any, + ); + + render(() => ); + + await waitFor(() => expect(screen.getByText("Test Deck")).toBeInTheDocument()); + expect(screen.getByText("A test deck")).toBeInTheDocument(); + expect(screen.getByText("#test")).toBeInTheDocument(); + expect(screen.getByText("Front 1")).toBeInTheDocument(); + expect(screen.getByText("Front 2")).toBeInTheDocument(); + expect(screen.getByText("Back 1")).toBeInTheDocument(); + }); + + it("renders not found state when deck returns error", async () => { + vi.mocked(api.get).mockImplementation( + (() => { + return Promise.resolve({ ok: false }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + }) as any, + ); + + render(() => ); + + await waitFor(() => expect(screen.getByText(/Deck not found/i)).toBeInTheDocument()); + }); +}); diff --git a/web/src/pages/DeckView.tsx b/web/src/pages/DeckView.tsx new file mode 100644 index 0000000..e4c09df --- /dev/null +++ b/web/src/pages/DeckView.tsx @@ -0,0 +1,124 @@ +import { A, useParams } from "@solidjs/router"; +import type { Component } from "solid-js"; +import { createResource, For, Show } from "solid-js"; +import { api } from "../lib/api"; +import type { Visibility } from "../lib/store"; + +type Deck = { + id: string; + title: string; + description: string; + tags: string[]; + visibility: Visibility; + owner_did: string; +}; + +type Card = { id: string; front: string; back?: string }; + +const fetchDeck = async (id: string): Promise => { + const res = await api.get(`/decks/${id}`); + if (!res.ok) return null; + return res.json(); +}; + +const fetchCards = async (id: string): Promise => { + const res = await api.get(`/decks/${id}/cards`); + if (!res.ok) return []; + return res.json(); +}; + +const DeckView: Component = () => { + const params = useParams(); + + const [deck] = createResource(() => params.id, fetchDeck); + const [cards] = createResource(() => params.id, fetchCards); + + return ( +
+ +
Loading deck...
+
+ + +
+ Deck not found or you don't have access. +
+
+ + +
+
+

{deck()?.title}

+ + + {deck()?.visibility.type} + + +
+ +

{deck()?.description}

+ +
+ + {(tag) => ( + #{tag} + )} + +
+ +
+ {/* Placeholder for Study Action */} + + + Back to Library + +
+
+ +
+

+ Cards ({cards()?.length || 0}) +

+ + +
Loading cards...
+
+ +
+ + {(card, i) => ( +
+
+ CARD {i() + 1} +
+
+
+
Front
+
{card.front}
+
+
+
Back
+
{card.back || Empty}
+
+
+
+ )} +
+ + +
+ No cards in this deck. +
+
+
+
+
+
+ ); +}; + +export default DeckView; diff --git a/web/src/pages/NotFound.tsx b/web/src/pages/NotFound.tsx new file mode 100644 index 0000000..04429f4 --- /dev/null +++ b/web/src/pages/NotFound.tsx @@ -0,0 +1,21 @@ +import { A } from "@solidjs/router"; +import type { Component } from "solid-js"; + +const NotFound: Component = () => { + return ( +
+

404

+

Page not found

+

+ The page you are looking for might have been removed, had its name changed, or is temporarily unavailable. +

+ + Go to Library + +
+ ); +}; + +export default NotFound; diff --git a/web/src/pages/NoteNew.tsx b/web/src/pages/NoteNew.tsx index 5299344..a9d6ad4 100644 --- a/web/src/pages/NoteNew.tsx +++ b/web/src/pages/NoteNew.tsx @@ -1,4 +1,3 @@ -import { useNavigate } from "@solidjs/router"; import { NoteEditor } from "../components/NoteEditor"; const NoteNew = () => {