import { createFileRoute, useNavigate } from "@tanstack/react-router" import { useCallback, useEffect, useId, useRef } from "react" import { fetchApi, useAuthenticatedQuery } from "~/api" import { ActionBar } from "~/app/bookmarks/-action-bar.tsx" import { useLogOut } from "~/auth.ts" import { Button } from "~/components/button.tsx" import { LoadingSpinner } from "~/components/loading-spinner" import { useMnemonics } from "~/hooks/use-mnemonics.ts" import { BookmarkList } from "./-bookmark-list" import { ActionBarContentKind, DialogKind, useBookmarkPageStore } from "./-store" export const Route = createFileRoute("/bookmarks/")({ component: RouteComponent, }) function RouteComponent() { return (
) } function BookmarkListPane() { return (

 >  YOUR BOOKMARKS

) } function BookmarkListContainer() { const searchParamsString = new URLSearchParams(Route.useSearch()).toString() const { data: bookmarks, status } = useAuthenticatedQuery( searchParamsString ? ["bookmarks", searchParamsString] : ["bookmarks"], async () => { const res = await fetchApi(searchParamsString ? `/bookmarks?${searchParamsString}` : "/bookmarks") return await res.json() }, ) const handleBookmarkListItemAction = useBookmarkPageStore((state) => state.handleBookmarkListItemAction) switch (status) { case "success": return ( ) case "pending": return (

Loading

) case "error": return

error loading bookmarks

} } function BookmarkListActionBar({ className }: { className?: string }) { const content = useBookmarkPageStore((state) => state.actionBarContent) return ( {(() => { switch (content.kind) { case ActionBarContentKind.Normal: return case ActionBarContentKind.StatusMessage: return

{content.message}

case ActionBarContentKind.SearchBar: return } })()}
) } function SearchBar() { const searchInputId = useId() const inputRef = useRef(null) const setActionBarContent = useBookmarkPageStore((state) => state.setActionBarContent) const searchTimeoutRef = useRef | null>(null) const navigate = Route.useNavigate() const { q } = Route.useSearch() useMnemonics( { Escape: () => { navigate({ search: (prevSearch: Record) => ({ ...prevSearch, tags: undefined, q: undefined, }), }) setActionBarContent({ kind: ActionBarContentKind.Normal }) }, }, { ignore: () => false }, ) useEffect(() => { setInterval(() => { inputRef.current?.focus() }, 0) }, []) useEffect( () => () => { if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current) } }, [], ) function pushSearchQuery(event: React.ChangeEvent) { if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current) } const q = event.currentTarget.value searchTimeoutRef.current = setTimeout(() => { navigate({ search: (prevSearch: Record) => { if (!q) { return { ...prevSearch, tags: undefined, q: undefined } } if (q.startsWith("#")) { const parts = q.split(" ") let searchTermBegin = -1 for (let i = 0; i < parts.length; ++i) { if (!parts[i].startsWith("#")) { searchTermBegin = i break } } const tags = (searchTermBegin >= 0 ? parts.slice(0, searchTermBegin) : parts) .map((tag) => tag.substring(1)) .join(",") const query = searchTermBegin >= 0 ? parts.slice(searchTermBegin).join(" ") : "" if (query) { return { ...prevSearch, tags, q: query } } return { ...prevSearch, tags, q: undefined } } return { ...prevSearch, tags: undefined, q } }, }) }, 500) } function closeSearchBar() { setActionBarContent({ kind: ActionBarContentKind.Normal }) } return (
) } function ActionButtons() { const setActiveDialog = useBookmarkPageStore((state) => state.setActiveDialog) const setActionBarContent = useBookmarkPageStore((state) => state.setActionBarContent) useMnemonics( { a: addBookmark, s: openSearchBar, }, { ignore: useCallback(() => useBookmarkPageStore.getState().dialog.kind !== DialogKind.None, []) }, ) function addBookmark() { setActiveDialog({ kind: DialogKind.AddBookmark }) } function openSearchBar() { setActionBarContent({ kind: ActionBarContentKind.SearchBar }) } return (
) } function LogOutButton() { const logOutMutation = useLogOut() const navigate = useNavigate() function logOut() { logOutMutation.mutate() navigate({ to: "/", replace: true }) } return ( ) } export { BookmarkListActionBar }