180 lines
4.7 KiB
TypeScript
180 lines
4.7 KiB
TypeScript
import { autoUpdate, offset, useFloating } from "@floating-ui/react-dom"
|
|
import { createFileRoute, useNavigate } from "@tanstack/react-router"
|
|
import { useCallback } from "react"
|
|
import { ActionBar } from "~/app/bookmarks/-action-bar"
|
|
import { useLogOut } from "~/auth"
|
|
import { useCollections } from "~/collection/api"
|
|
import { Button } from "~/components/button"
|
|
import { LoadingSpinner } from "~/components/loading-spinner"
|
|
import { SideNav, SideNavItem } from "~/components/side-nav"
|
|
import { useMnemonics } from "~/hooks/use-mnemonics"
|
|
import { CollectionList } from "./-collection-list"
|
|
import { AddCollectionDialog } from "./-dialogs/add-collection-dialog"
|
|
import { DeleteCollectionDialog } from "./-dialogs/delete-collection-dialog"
|
|
import { DialogKind, WindowKind, useCollectionPageStore } from "./-store"
|
|
|
|
export const Route = createFileRoute("/collections/")({
|
|
component: CollectionsPage,
|
|
})
|
|
|
|
function ActiveDialog() {
|
|
const dialog = useCollectionPageStore((state) => state.dialog)
|
|
|
|
switch (dialog.kind) {
|
|
case DialogKind.AddCollection:
|
|
return <AddCollectionDialog />
|
|
case DialogKind.DeleteCollection:
|
|
return <DeleteCollectionDialog collection={dialog.collection} />
|
|
default:
|
|
return null
|
|
}
|
|
}
|
|
|
|
function CollectionsPage() {
|
|
return (
|
|
<main className="w-full flex justify-center">
|
|
<CollectionsPane />
|
|
<CollectionsActionBar className="fixed left-0 right-0 bottom-0" />
|
|
<ActiveDialog />
|
|
</main>
|
|
)
|
|
}
|
|
|
|
function CollectionsPane() {
|
|
return (
|
|
<div className="flex flex-col py-16 container max-w-3xl md:flex-row lg:py-32">
|
|
<_SideNav />
|
|
<div className="flex-1">
|
|
<CollectionsContainer />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function _SideNav() {
|
|
const navigate = useNavigate()
|
|
|
|
useMnemonics(
|
|
{
|
|
b: () => navigate({ to: "/bookmarks" }),
|
|
c: () => navigate({ to: "/collections" }),
|
|
},
|
|
{
|
|
ignore: () => {
|
|
const state = useCollectionPageStore.getState()
|
|
return state.dialog.kind !== DialogKind.None
|
|
},
|
|
},
|
|
)
|
|
|
|
return (
|
|
<SideNav>
|
|
<SideNavItem to="/bookmarks" label="BOOKMARKS" />
|
|
<SideNavItem to="/collections" label="COLLECTIONS" active />
|
|
</SideNav>
|
|
)
|
|
}
|
|
|
|
function CollectionsContainer() {
|
|
const { data: collections, status } = useCollections()
|
|
const handleCollectionListAction = useCollectionPageStore((state) => state.handleCollectionListAction)
|
|
|
|
switch (status) {
|
|
case "success":
|
|
return collections.length === 0 ? (
|
|
<p>You have not created any collections!</p>
|
|
) : (
|
|
<CollectionList collections={collections} onItemAction={handleCollectionListAction} />
|
|
)
|
|
|
|
case "pending":
|
|
return (
|
|
<p>
|
|
Loading <LoadingSpinner />
|
|
</p>
|
|
)
|
|
|
|
case "error":
|
|
return <p>Error loading collections</p>
|
|
}
|
|
}
|
|
|
|
function CollectionsActionBar({ className }: { className?: string }) {
|
|
const activeWindow = useCollectionPageStore((state) => state.activeWindow)
|
|
const { refs, floatingStyles } = useFloating({
|
|
placement: "top",
|
|
whileElementsMounted: autoUpdate,
|
|
middleware: [offset(8)],
|
|
})
|
|
|
|
return (
|
|
<>
|
|
<ActionBar ref={refs.setReference} className={className}>
|
|
<ActionButtons />
|
|
</ActionBar>
|
|
{activeWindow === WindowKind.AppMenu && <AppMenuWindow ref={refs.setFloating} style={floatingStyles} />}
|
|
</>
|
|
)
|
|
}
|
|
|
|
function ActionButtons() {
|
|
const setActiveWindow = useCollectionPageStore((state) => state.setActiveWindow)
|
|
const activeWindow = useCollectionPageStore((state) => state.activeWindow)
|
|
const setActiveDialog = useCollectionPageStore((state) => state.setActiveDialog)
|
|
|
|
useMnemonics(
|
|
{
|
|
a: addCollection,
|
|
},
|
|
{ ignore: useCallback(() => useCollectionPageStore.getState().dialog.kind !== DialogKind.None, []) },
|
|
)
|
|
|
|
function addCollection() {
|
|
setActiveDialog({ kind: DialogKind.AddCollection })
|
|
}
|
|
|
|
function toggleAppMenu() {
|
|
setActiveWindow(activeWindow === WindowKind.AppMenu ? WindowKind.None : WindowKind.AppMenu)
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-row justify-center space-x-4">
|
|
<Button onClick={addCollection}>
|
|
<span className="underline">A</span>DD COLLECTION
|
|
</Button>
|
|
<Button onClick={toggleAppMenu}>⋯</Button>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function AppMenuWindow({ ref, style }: { ref: React.Ref<HTMLDivElement>; style: React.CSSProperties }) {
|
|
return (
|
|
<div ref={ref} style={style} className="border w-full md:w-100">
|
|
<p className="bg-stone-900 dark:bg-stone-200 text-stone-300 dark:text-stone-800 text-center">MENU</p>
|
|
<div className="p-4">
|
|
<ul className="space-y-2">
|
|
<li>
|
|
<LogOutButton />
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function LogOutButton() {
|
|
const logOutMutation = useLogOut()
|
|
const navigate = useNavigate()
|
|
|
|
function logOut() {
|
|
logOutMutation.mutate()
|
|
navigate({ to: "/", replace: true })
|
|
}
|
|
|
|
return (
|
|
<Button disabled={logOutMutation.isPending} onClick={logOut}>
|
|
LOG OUT
|
|
</Button>
|
|
)
|
|
}
|