handle bookmark delete loading state

This commit is contained in:
2025-05-07 16:57:09 +01:00
parent e87a6586b6
commit 9f00c9bb29
7 changed files with 107 additions and 31 deletions

View File

@@ -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({

View File

@@ -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?")
}
}

View File

@@ -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 (
<p>
Deleting <LoadingSpinner />
</p>
)
case "idle":
return (
<p>
The bookmark titled{" "}
<strong>
<em>"{bookmark.title}"</em>
</strong>{" "}
will be deleted. Proceed?
</p>
)
case "error":
return <p className="text-red-500">Failed to delete the bookmark!</p>
}
}
function title() {
switch (deleteBookmarkMutation.status) {
case "pending":
return "PLEASE WAIT"
case "idle":
return "CONFIRM"
case "error":
return "ERROR OCCURRED"
}
}
return (
<Dialog>
<DialogTitle>CONFIRM</DialogTitle>
<DialogBody>
The bookmark titled{" "}
<strong>
<em>"{bookmark.title}"</em>
</strong>{" "}
will be deleted. Proceed?
</DialogBody>
<DialogTitle>{title()}</DialogTitle>
<DialogBody>{body()}</DialogBody>
<DialogActionRow>
<Button disabled={deleteBookmarkMutation.isPending} onClick={proceed}>
<span className="underline">P</span>roceed
{deleteBookmarkMutation.isError ? "Retry" : "Proceed"} (y)
</Button>
<Button disabled={deleteBookmarkMutation.isPending} onClick={cancel}>
<span className="underline">C</span>ancel
Cancel (n)
</Button>
</DialogActionRow>
</Dialog>
@@ -215,17 +244,23 @@ function DeleteBookmarkDialog() {
}
function BookmarkListSection() {
const {
data: bookmarks,
error,
status,
} = useAuthenticatedQuery(["bookmarks"], () => fetchApi<LinkBookmark[]>("/bookmarks").then(([data]) => data))
const { data: bookmarks, status } = useAuthenticatedQuery(["bookmarks"], () =>
fetchApi<LinkBookmark[]>("/bookmarks").then(([data]) => data),
)
switch (status) {
case "pending":
return <p>Loading...</p>
return (
<p className="container max-w-2xl">
Loading&nbsp;
<LoadingSpinner />
</p>
)
case "success":
return <BookmarkList bookmarks={bookmarks} />
if (bookmarks) {
return <BookmarkList bookmarks={bookmarks} />
}
return null
default:
return null
}

View File

@@ -66,7 +66,7 @@ function Page() {
<form onSubmit={submitLoginForm}>
<FormField type="text" name="username" label="USERNAME" className="mb-2" />
<FormField type="password" name="password" label="PASSWORD" className="mb-8" />
<Button className="w-full py-2" type="submit">
<Button className="w-full py-2 md:py-2" type="submit">
Log in
</Button>
</form>

View File

@@ -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 })

View File

@@ -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<ReturnType<typeof setInterval>>(null)
useEffect(() => {
timer.current = setInterval(() => {
setFrame((frame) => (frame === 3 ? 0 : frame + 1))
}, 100)
return () => {
if (timer.current) {
clearInterval(timer.current)
}
}
}, [])
return <span className="whitespace-pre">{FRAMES[frame]}</span>
}
export { LoadingSpinner }

View File

@@ -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
);
}