import { Outlet, createFileRoute } from "@tanstack/react-router" import { useState, useId, useRef, useEffect } from "react" import { BadRequestError, ApiErrorCode } from "~/api" import { useCreateBookmark, useDeleteBookmark } from "~/bookmark/api" import { Button } from "~/components/button" import { Dialog, DialogTitle, DialogBody, DialogActionRow } from "~/components/dialog" import { FormField } from "~/components/form-field" import { LoadingSpinner } from "~/components/loading-spinner" import { Message, MessageVariant } from "~/components/message" import { useMnemonics } from "~/hooks/use-mnemonics" import { useBookmarkPageStore, ActiveDialog, LayoutMode } from "./bookmarks/-store" export const Route = createFileRoute("/bookmarks")({ component: RouteComponent, }) function RouteComponent() { const setLayoutMode = useBookmarkPageStore((state) => state.setLayoutMode) useEffect(() => { function mediaQueryListener(this: MediaQueryList) { console.log(this.matches) if (this.matches) { setLayoutMode(LayoutMode.SideBySide) } else { setLayoutMode(LayoutMode.Popup) } } const q = window.matchMedia("(width >= 64rem)") q.addEventListener("change", mediaQueryListener) mediaQueryListener.call(q) return () => { q.removeEventListener("change", mediaQueryListener) } }, [setLayoutMode]) return (
) } function PageDialog() { const dialog = useBookmarkPageStore((state) => state.activeDialog) switch (dialog) { case ActiveDialog.None: return null case ActiveDialog.AddBookmark: return case ActiveDialog.DeleteBookmark: return } } function AddBookmarkDialog() { const [isWebsiteUnreachable, setIsWebsiteUnreachable] = useState(false) const createBookmarkMutation = useCreateBookmark() const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog) const formId = useId() const linkInputRef = useRef(null) useMnemonics( { c: () => { if (linkInputRef.current !== document.activeElement) { cancel() } }, Escape: () => { linkInputRef.current?.blur() }, }, { ignore: () => false }, ) useEffect(() => { setTimeout(() => { if (linkInputRef.current) { linkInputRef.current.focus() } }, 0) }, []) async function onSubmit(event: React.FormEvent) { event.preventDefault() const formData = new FormData(event.currentTarget) const url = formData.get("link") if (url && typeof url === "string") { try { await createBookmarkMutation.mutateAsync({ url, force: isWebsiteUnreachable }) setActiveDialog(ActiveDialog.None) } catch (error) { if (error instanceof BadRequestError && error.code === ApiErrorCode.WebsiteUnreachable) { setIsWebsiteUnreachable(true) } else { setIsWebsiteUnreachable(false) } } } } function cancel() { setActiveDialog(ActiveDialog.None) } function message() { if (createBookmarkMutation.isPending) { return (

Loading

) } if (isWebsiteUnreachable) { return ( The link does not seem to be reachable. Click "SAVE" to save anyways. ) } if (createBookmarkMutation.status === "error") { return ( An error occurred when saving bookmark ) } return null } return ( NEW BOOKMARK {message()}
) } function DeleteBookmarkDialog() { // biome-ignore lint/style/noNonNullAssertion: this cannot be null when delete bookmark dialog is visible const bookmark = useBookmarkPageStore((state) => state.bookmarkToBeDeleted!) const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog) const markBookmarkForDeletion = useBookmarkPageStore((state) => state.markBookmarkForDeletion) const deleteBookmarkMutation = useDeleteBookmark() useMnemonics( { y: proceed, n: cancel, }, { ignore: () => false }, ) async function proceed() { try { await deleteBookmarkMutation.mutateAsync({ bookmark }) setActiveDialog(ActiveDialog.None) markBookmarkForDeletion(null) } catch (error) { console.error(error) } } function cancel() { setActiveDialog(ActiveDialog.None) 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 ( {title()} {body()} ) }