196 lines
6.1 KiB
TypeScript
196 lines
6.1 KiB
TypeScript
'use client'
|
|
|
|
import { Book, Genre, HoldRequest, Repository } from '@/payload-types'
|
|
|
|
import { Combobox, ComboboxLabel, ComboboxOption, ComboboxDescription } from '@/components/combobox'
|
|
import { Field, Label } from '@/components/fieldset'
|
|
import type { PaginatedDocs } from 'payload'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Badge } from '@/components/ui/badge'
|
|
|
|
import { Loader2 } from 'lucide-react'
|
|
import { RichText } from '@/components/RichText'
|
|
import { useMemo, useState } from 'react'
|
|
import requestHold from '../../serverCalls/requestHold'
|
|
import { useGlobal } from '@/providers/GlobalProvider'
|
|
import Link from 'next/link'
|
|
|
|
type DropDownProps = {
|
|
currentRepository: Repository
|
|
repositories: Repository[]
|
|
isDisabled: boolean
|
|
doesHoldExist: boolean
|
|
isRequesting: boolean
|
|
onClickRequest: () => void
|
|
onChange: (repo: Repository | null) => void
|
|
}
|
|
function RepoDropdown({
|
|
currentRepository,
|
|
repositories,
|
|
isRequesting,
|
|
doesHoldExist,
|
|
isDisabled,
|
|
onClickRequest,
|
|
onChange,
|
|
}: DropDownProps) {
|
|
return (
|
|
<Field className="">
|
|
{doesHoldExist ? (
|
|
<span className="font-medium text-emerald-800 dark:text-emerald-300 ">
|
|
You have requested a hold for this book
|
|
</span>
|
|
) : (
|
|
<>
|
|
<Label htmlFor="repositories">From Repository</Label>
|
|
<div className="flex gap-2">
|
|
<Combobox
|
|
name="repositories"
|
|
options={repositories}
|
|
disabled={isDisabled || doesHoldExist}
|
|
displayValue={(repo) => repo?.name}
|
|
defaultValue={currentRepository}
|
|
className=""
|
|
onChange={(repo) => onChange(repo)}
|
|
>
|
|
{(repo) => (
|
|
<ComboboxOption value={repo}>
|
|
<ComboboxLabel>{repo.abbreviation}</ComboboxLabel>
|
|
<ComboboxDescription>{repo.name}</ComboboxDescription>
|
|
</ComboboxOption>
|
|
)}
|
|
</Combobox>
|
|
<Button
|
|
disabled={isDisabled}
|
|
onClick={onClickRequest}
|
|
className="hover:scale-105 bg-emerald-500 text-foreground hover:text-background cursor-pointer disabled:bg-muted-foreground/80"
|
|
>
|
|
{isRequesting && <Loader2 className="animate-spin" />}
|
|
<span>Request Hold</span>
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</Field>
|
|
)
|
|
}
|
|
|
|
type Props = {
|
|
book: Book
|
|
repositories: PaginatedDocs<Repository>
|
|
existingHolds: PaginatedDocs<HoldRequest> | null
|
|
}
|
|
|
|
export default function BookByIdPageClient(props: Props) {
|
|
const { book, repositories, existingHolds } = props
|
|
const repos = repositories.docs
|
|
|
|
const { user } = useGlobal()
|
|
|
|
console.log(user)
|
|
|
|
const [isRequestingCopy, setIsRequestingCopy] = useState(false)
|
|
const [selectedRepository, setSelectedRepository] = useState<Repository | null>(
|
|
repos.length ? repos[0] : null,
|
|
)
|
|
|
|
const [doesHoldExist, setDoesHoldExist] = useState(!!existingHolds?.totalDocs)
|
|
|
|
const isRequestDisabled = useMemo(() => {
|
|
return isRequestingCopy
|
|
}, [isRequestingCopy])
|
|
|
|
const onClickRequest = async () => {
|
|
if (isRequestingCopy || !selectedRepository || !book || doesHoldExist) return
|
|
|
|
setIsRequestingCopy(true)
|
|
const newHoldResponse = await requestHold({
|
|
repositoryId: selectedRepository.id,
|
|
bookId: book.id,
|
|
})
|
|
|
|
if (newHoldResponse?.id) setDoesHoldExist(true)
|
|
setIsRequestingCopy(false)
|
|
}
|
|
|
|
return (
|
|
<div className="mx-auto px-4 py-16">
|
|
<div className="md:grid md:grid-cols-7 md:gap-8">
|
|
{/* Book Cover */}
|
|
<div className="md:col-span-3">
|
|
<img
|
|
alt="book cover art"
|
|
src={
|
|
book.isbn
|
|
? `https://covers.openlibrary.org/b/isbn/${book.isbn}-L.jpg`
|
|
: '/images/book-48.svg'
|
|
}
|
|
className="w-full rounded-lg bg-gray-100 object-cover"
|
|
/>
|
|
</div>
|
|
|
|
{/* Book details */}
|
|
<div className="mt-14 sm:mt-16 md:col-span-4 md:mt-0">
|
|
<div className="mt-4">
|
|
<h1 className="text-2xl font-bold tracking-tight text-foreground sm:text-3xl">
|
|
{book.title}
|
|
</h1>
|
|
|
|
<h2 id="information-heading" className="sr-only">
|
|
Book information
|
|
</h2>
|
|
<p className="mt-2 text-sm text-muted-foreground">
|
|
{book.publication && (
|
|
<span>
|
|
Published:
|
|
<time dateTime={book.publication}>{book.publication}</time>
|
|
</span>
|
|
)}
|
|
</p>
|
|
</div>
|
|
|
|
<p className="my-6 text-accent-foreground">{book.summary}</p>
|
|
|
|
{!user ? (
|
|
<Link
|
|
href={`/login?redirectUrl=/books/${book.id}`}
|
|
className="underline dark:text-amber-200 text-amber-700 text-lg font-medium"
|
|
>
|
|
Sign in to request a hold
|
|
</Link>
|
|
) : (
|
|
<RepoDropdown
|
|
currentRepository={repos[0]}
|
|
repositories={repos}
|
|
isRequesting={isRequestingCopy}
|
|
doesHoldExist={doesHoldExist}
|
|
isDisabled={isRequestDisabled || !user}
|
|
onClickRequest={onClickRequest}
|
|
onChange={(repo) => setSelectedRepository(repo)}
|
|
/>
|
|
)}
|
|
|
|
<div className="mt-10 border-t border-gray-200 pt-10 ">
|
|
<h3 className="text-sm font-medium text-foreground">Genres</h3>
|
|
<div className="mt-4">
|
|
<ul role="list" className="text-foreground flex gap-2 flex-wrap">
|
|
{(book.genre as Genre[])?.map((g) => (
|
|
<Badge key={g.id} className="inline-block cursor-pointer">
|
|
{g.name}
|
|
</Badge>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
{book.description && (
|
|
<div className="mt-10 border-t border-gray-200 pt-10">
|
|
<h3 className="text-sm font-medium text-muted-foreground">Description</h3>
|
|
<RichText data={book.description} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|