implement collections page
This commit is contained in:
158
packages/web/src/app/collections/index.tsx
Normal file
158
packages/web/src/app/collections/index.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
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 { 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">
|
||||
<header className="mb-4 md:mb-0 md:mr-16 text-start">
|
||||
<h1 className="font-bold text-start">
|
||||
<span className="invisible md:hidden"> > </span>
|
||||
YOUR COLLECTIONS
|
||||
</h1>
|
||||
</header>
|
||||
<div className="flex-1">
|
||||
<CollectionsContainer />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CollectionsContainer() {
|
||||
const { data: collections, status } = useCollections()
|
||||
|
||||
switch (status) {
|
||||
case "success":
|
||||
return collections.length === 0 ? (
|
||||
<p>You have not created any collections!</p>
|
||||
) : (
|
||||
<CollectionList collections={collections} />
|
||||
)
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user