From 9f00c9bb291f1db801c9d46715ca0f9af60e9289 Mon Sep 17 00:00:00 2001 From: Kenneth Date: Wed, 7 May 2025 16:57:09 +0100 Subject: [PATCH] handle bookmark delete loading state --- packages/server/src/bookmark/handlers.ts | 8 +- packages/server/src/database.ts | 17 ++++- packages/web/src/app/bookmarks.tsx | 73 ++++++++++++++----- packages/web/src/app/login.tsx | 2 +- packages/web/src/bookmark/api.ts | 8 +- .../web/src/components/loading-spinner.tsx | 24 ++++++ packages/web/src/index.css | 6 +- 7 files changed, 107 insertions(+), 31 deletions(-) create mode 100644 packages/web/src/components/loading-spinner.tsx diff --git a/packages/server/src/bookmark/handlers.ts b/packages/server/src/bookmark/handlers.ts index 0ddc3fb..b9ed3b0 100644 --- a/packages/server/src/bookmark/handlers.ts +++ b/packages/server/src/bookmark/handlers.ts @@ -18,7 +18,13 @@ async function listUserBookmarks(request: Bun.BunRequest<"/api/bookmarks">, user } const listBookmarksQuery = db.query( - "SELECT id, kind, title, url FROM bookmarks WHERE user_id = $userId ORDER BY id LIMIT $limit OFFSET $skip", + ` +SELECT bookmarks.id, bookmarks.kind, bookmarks.title, bookmarks.url, tags.name as tag FROM bookmarks +LEFT JOIN tags +ON bookmarks.id = tags.bookmark_id +WHERE bookmarks.user_id = $userId +ORDER BY bookmarks.id LIMIT $limit OFFSET $skip +`, ) const results = listBookmarksQuery.all({ diff --git a/packages/server/src/database.ts b/packages/server/src/database.ts index dd49d3f..2983958 100644 --- a/packages/server/src/database.ts +++ b/packages/server/src/database.ts @@ -28,6 +28,12 @@ CREATE TABLE IF NOT EXISTS bookmarks( url TEXT NOT NULL ); +CREATE TABLE IF NOT EXISTS tags( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + bookmark_id TEXT NOT NULL +); + CREATE TABLE IF NOT EXISTS sessions( session_id TEXT NOT NULL, user_id TEXT NOT NULL, @@ -54,23 +60,26 @@ function migrateDatabase() { createMetadataTableQuery.run() const schemaVersionQuery = db.query("SELECT value FROM metadata WHERE key = 'schema_version'") - const setSchemaVersionQuery = db.query("UPDATE metadata SET value = $schemaVersion WHERE key = 'schema_version'") + const setSchemaVersionQuery = db.query("INSERT OR REPLACE INTO metadata VALUES ('schema_version', $schemaVersion)") const row = schemaVersionQuery.get() let currentVersion: number if (row) { currentVersion = (row as { value: number }).value - console.log(`Migrating database from version ${currentVersion} to version ${SCHEMA_VERSION}...`) } else { currentVersion = -1 - console.log("Initializing database...") } if (currentVersion < SCHEMA_VERSION) { + if (currentVersion < 0) { + console.log("Initializing database...") + } else { + console.log(`Migrating database from version ${currentVersion} to version ${SCHEMA_VERSION}...`) + } executeMigrations(migrations.slice(currentVersion + 1, SCHEMA_VERSION + 1)) setSchemaVersionQuery.run({ schemaVersion: SCHEMA_VERSION }) console.log("Database successfully migrated!") - } else { + } else if (currentVersion > SCHEMA_VERSION) { console.error("Rolling back database to a previous version is unsupported. Are you trying to downgrade MARKONE?") } } diff --git a/packages/web/src/app/bookmarks.tsx b/packages/web/src/app/bookmarks.tsx index ba8e9d1..0c5b051 100644 --- a/packages/web/src/app/bookmarks.tsx +++ b/packages/web/src/app/bookmarks.tsx @@ -6,6 +6,7 @@ import { create } from "zustand" import { fetchApi, useAuthenticatedQuery } from "~/api" import { Button } from "~/components/button" import { useDeleteBookmark } from "~/bookmark/api" +import { LoadingSpinner } from "~/components/loading-spinner" import { useMnemonics } from "~/hooks/use-mnemonics" import { Dialog, DialogActionRow, DialogBody, DialogTitle } from "~/components/dialog" @@ -171,8 +172,8 @@ function DeleteBookmarkDialog() { useMnemonics( { - p: proceed, - c: cancel, + y: proceed, + n: cancel, }, { active: true }, ) @@ -192,22 +193,50 @@ function DeleteBookmarkDialog() { markBookmarkForDeletion(null) } + function body() { + switch (deleteBookmarkMutation.status) { + case "pending": + return ( +

+ Deleting +

+ ) + case "idle": + return ( +

+ The bookmark titled{" "} + + "{bookmark.title}" + {" "} + will be deleted. Proceed? +

+ ) + case "error": + return

Failed to delete the bookmark!

+ } + } + + function title() { + switch (deleteBookmarkMutation.status) { + case "pending": + return "PLEASE WAIT" + case "idle": + return "CONFIRM" + case "error": + return "ERROR OCCURRED" + } + } + return ( - CONFIRM - - The bookmark titled{" "} - - "{bookmark.title}" - {" "} - will be deleted. Proceed? - + {title()} + {body()} @@ -215,17 +244,23 @@ function DeleteBookmarkDialog() { } function BookmarkListSection() { - const { - data: bookmarks, - error, - status, - } = useAuthenticatedQuery(["bookmarks"], () => fetchApi("/bookmarks").then(([data]) => data)) + const { data: bookmarks, status } = useAuthenticatedQuery(["bookmarks"], () => + fetchApi("/bookmarks").then(([data]) => data), + ) switch (status) { case "pending": - return

Loading...

+ return ( +

+ Loading  + +

+ ) case "success": - return + if (bookmarks) { + return + } + return null default: return null } diff --git a/packages/web/src/app/login.tsx b/packages/web/src/app/login.tsx index 6ff96d5..91207a2 100644 --- a/packages/web/src/app/login.tsx +++ b/packages/web/src/app/login.tsx @@ -66,7 +66,7 @@ function Page() {
- diff --git a/packages/web/src/bookmark/api.ts b/packages/web/src/bookmark/api.ts index be27670..678c5c7 100644 --- a/packages/web/src/bookmark/api.ts +++ b/packages/web/src/bookmark/api.ts @@ -9,9 +9,11 @@ function useDeleteBookmark() { return useMutation({ mutationFn: ({ bookmark }: { bookmark: Bookmark }) => - fetchApi(`/bookmark/${bookmark.id}`, { - method: "DELETE", - }), + new Promise((resolve) => setTimeout(resolve, 5000)).then(() => + fetchApi(`/bookmark/${bookmark.id}`, { + method: "DELETE", + }), + ), onError: (error) => { if (error instanceof UnauthenticatedError) { navigate({ to: "/login", replace: true }) diff --git a/packages/web/src/components/loading-spinner.tsx b/packages/web/src/components/loading-spinner.tsx new file mode 100644 index 0000000..40f96e5 --- /dev/null +++ b/packages/web/src/components/loading-spinner.tsx @@ -0,0 +1,24 @@ +import { useState, useEffect, useRef } from "react" + +// courtesy of https://github.com/sindresorhus/cli-spinners +const FRAMES = ["-", "\\", "|", "/"] + +function LoadingSpinner() { + const [frame, setFrame] = useState(0) + const timer = useRef>(null) + + useEffect(() => { + timer.current = setInterval(() => { + setFrame((frame) => (frame === 3 ? 0 : frame + 1)) + }, 100) + return () => { + if (timer.current) { + clearInterval(timer.current) + } + } + }, []) + + return {FRAMES[frame]} +} + +export { LoadingSpinner } diff --git a/packages/web/src/index.css b/packages/web/src/index.css index ea731eb..3471cee 100644 --- a/packages/web/src/index.css +++ b/packages/web/src/index.css @@ -12,8 +12,8 @@ body { background: repeating-linear-gradient( -45deg, var(--color-stone-400), - var(--color-stone-400) 4px, - var(--color-stone-300) 4px, - var(--color-stone-300) 12px + var(--color-stone-400) 2px, + var(--color-stone-300) 2px, + var(--color-stone-300) 6px ); }