midrashim/src/components/Feed/UserFeed.tsx
2025-05-05 10:46:51 -05:00

237 lines
7.2 KiB
TypeScript

import type { Book, Checkout, HoldRequest, Repository, User } from '@/payload-types'
import { getPayload, PaginatedDocs } from 'payload'
import config from '@/payload.config'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../ui/card'
import Image from 'next/image'
import clsx from 'clsx'
import Link from 'next/link'
import { LoginForm } from '../login-form'
type Props = {
user?: User
repos: PaginatedDocs<Repository> | null
}
const UserFeed = async (props: Props) => {
const { user, repos } = props
const isLoggedIn = !!user
if (!isLoggedIn)
return (
<div className="flex w-full max-w-sm flex-col gap-6 mx-auto my-6">
<LoginForm />
</div>
)
const payloadConfig = await config
const payload = await getPayload({ config: payloadConfig })
const borrowRequests = (await payload.find({
collection: 'holdRequests',
limit: 10,
depth: 3,
select: {
copy: true,
dateRequested: true,
repository: true,
book: true,
},
where: {
userRequested: {
equals: user?.id,
},
isCheckedOut: {
not_equals: true,
},
isRejected: {
not_equals: true,
},
},
})) as PaginatedDocs<HoldRequest>
const currentlyHeldBooks = (await payload.find({
collection: 'holdRequests',
limit: 10,
depth: 3,
select: {
copy: true,
dateRequested: true,
repository: true,
book: true,
},
where: {
'repository.owner': {
equals: user?.id,
},
isHolding: {
equals: true,
},
},
})) as PaginatedDocs<HoldRequest>
let loanedOutBooks: PaginatedDocs<Checkout> | null = null
if (user.id)
loanedOutBooks = (await payload.find({
collection: 'checkouts',
limit: 10,
depth: 2,
select: {
book: true,
dateDue: true,
loanerReturnedDate: true,
isReturned: true,
ownerVerifiedReturnedDate: true,
},
where: {
and: [
{
'copy.repository.owner': {
equals: user.id,
},
},
{
isReturned: {
not_equals: true,
},
},
],
},
})) as PaginatedDocs<Checkout>
const totalHoldNotifications =
repos?.docs
.flatMap((r) => r.holdRequests?.docs)
.filter((r) => {
const repo = r as HoldRequest
return !repo.isRejected && !repo.isCheckedOut
}).length || 0
const outBoundLoanCount = loanedOutBooks?.totalDocs || 0
const currentlyHoldingCount = currentlyHeldBooks?.totalDocs || 0
const stats = [
{
name: 'Hold Request',
iconSrc: '/images/mail.svg',
value: totalHoldNotifications,
href: '/manage',
ctaText: 'Answer Requests',
shouldAnimate: totalHoldNotifications > 0,
},
{
name: 'Loaned Out',
iconSrc: '/images/book-loan.svg',
value: outBoundLoanCount,
href: '/manage',
ctaText: 'Handle Returns',
shouldAnimate: false,
},
{
name: 'Currently Holding',
iconSrc: '/images/book-shelf.svg',
value: currentlyHoldingCount,
href: '/manage',
ctaText: 'Loan Out',
shouldAnimate: false,
},
]
return (
<section>
<div className="my-6">
<h3 className="text-lg font-semibold text-foreground">Loan Activity</h3>
<dl className="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
{stats.map((s) => {
return (
<div
key={s.name}
className={clsx(
'relative overflow-hidden rounded-lg shadow-sm border border-accent px-4 pt-5 pb-12 sm:px-6 sm:pt-6',
s.shouldAnimate && 'animate-pulse',
)}
>
<dt>
<div className="absolute rounded-md bg-emerald-500 p-3">
<Image
src={s.iconSrc}
alt={s.name}
width={30}
height={30}
className="dark:invert"
/>
</div>
<p className="ml-16 truncate text-sm font-medium text-muted-foreground">
{s.name}
</p>
</dt>
<dd className="ml-16 flex items-baseline pb-6 sm:pb-7">
<p className="text-2xl font-semibold text-foreground">{s.value}</p>
<div className="absolute inset-x-0 bottom-0 px-4 py-4 sm:px-6">
<div className="text-sm">
<a
href={s.href}
className="font-medium text-emerald-800 hover:text-emerald-300"
>
<span>{s.ctaText}</span>
</a>
</div>
</div>
</dd>
</div>
)
})}
</dl>
</div>
<div className="my-6">
<h2 className="text-lg font-semibold text-foreground">Borrow Activity</h2>
<div className="my-3">
<h3 className="text-base font-semibold text-muted-foreground mb-4">Your Holds</h3>
<ul className="grid grid-cols-1 gap-y-6 sm:grid-cols-3 md:grid-cols-4 last-child-adjustment">
{borrowRequests.docs?.map((h) => {
const book = h.book as Book
const repository = h.repository as Repository
const formatedDateRequested = h.dateRequested
? new Date(h.dateRequested).toDateString()
: ''
return (
<li className="col-span-1 auto-rows-fr inline-block" key={book.isbn}>
<Link className="block hover:scale-105 transition-all" href={`/books/${book.id}`}>
<Card className="w-48 sm:w-42 mx-auto pt-4 pb-2">
<CardHeader className="-mb-4">
<CardTitle className="text-overflow-ellipsis line-clamp-2">
{book.title}
</CardTitle>
<CardDescription>{repository.abbreviation}</CardDescription>
</CardHeader>
<CardContent>
<Image
alt="book cover"
className="mx-auto h-[170px]"
width={120}
height={180}
src={
book.isbn
? `https://covers.openlibrary.org/b/isbn/${book.isbn}-M.jpg`
: '/images/book-48.svg'
}
/>
</CardContent>
<CardFooter className="text-muted-foreground text-sm block -mt-5">
<span className="block w-full text-xs">Requested On</span>
<span className="block w-full">{formatedDateRequested}</span>
</CardFooter>
</Card>
</Link>
</li>
)
})}
</ul>
</div>
</div>
</section>
)
}
export default UserFeed