feat: requestHold feature
This commit is contained in:
parent
54d9794d41
commit
06573d9044
@ -4,18 +4,31 @@ import { Book, Genre, Repository } from '@/payload-types'
|
||||
|
||||
import { Combobox, ComboboxLabel, ComboboxOption, ComboboxDescription } from '@/components/combobox'
|
||||
import { Field, Label } from '@/components/fieldset'
|
||||
import { PaginatedDocs } from 'payload'
|
||||
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'
|
||||
|
||||
type DropDownProps = {
|
||||
currentRepository: Repository
|
||||
repositories: Repository[]
|
||||
isDisabled: boolean
|
||||
isRequesting: boolean
|
||||
onClickRequest: () => void
|
||||
onChange: (repo: Repository | null) => void
|
||||
}
|
||||
function RepoDropdown({ currentRepository, repositories }: DropDownProps) {
|
||||
function RepoDropdown({
|
||||
currentRepository,
|
||||
repositories,
|
||||
isRequesting,
|
||||
isDisabled,
|
||||
onClickRequest,
|
||||
onChange,
|
||||
}: DropDownProps) {
|
||||
return (
|
||||
<Field className="">
|
||||
<Label htmlFor="repositories">From Repository</Label>
|
||||
@ -25,7 +38,8 @@ function RepoDropdown({ currentRepository, repositories }: DropDownProps) {
|
||||
options={repositories}
|
||||
displayValue={(repo) => repo?.name}
|
||||
defaultValue={currentRepository}
|
||||
className="w"
|
||||
className=""
|
||||
onChange={(repo) => onChange(repo)}
|
||||
>
|
||||
{(repo) => (
|
||||
<ComboboxOption value={repo}>
|
||||
@ -34,8 +48,12 @@ function RepoDropdown({ currentRepository, repositories }: DropDownProps) {
|
||||
</ComboboxOption>
|
||||
)}
|
||||
</Combobox>
|
||||
<Button className="hover:scale-105 bg-emerald-500 text-foreground hover:text-black">
|
||||
<Loader2 className="animate-spin" />
|
||||
<Button
|
||||
disabled={isDisabled}
|
||||
onClick={onClickRequest}
|
||||
className="hover:scale-105 bg-emerald-500 text-foreground hover:text-black cursor-pointer"
|
||||
>
|
||||
{isRequesting && <Loader2 className="animate-spin" />}
|
||||
<span>Request Copy</span>
|
||||
</Button>
|
||||
</div>
|
||||
@ -53,6 +71,29 @@ export default function BookByIdPageClient(props: Props) {
|
||||
|
||||
const repos = repositories.docs
|
||||
|
||||
const [isRequestingCopy, setIsRequestingCopy] = useState(false)
|
||||
const [selectedRepository, setSelectedRepository] = useState<Repository | null>(
|
||||
repos.length ? repos[0] : null,
|
||||
)
|
||||
|
||||
const isRequestDisabled = useMemo(() => {
|
||||
return isRequestingCopy
|
||||
}, [isRequestingCopy])
|
||||
|
||||
const onClickRequest = async () => {
|
||||
if (isRequestingCopy || !selectedRepository || !book) return
|
||||
|
||||
setIsRequestingCopy(true)
|
||||
const response = await requestHold({
|
||||
repositoryId: selectedRepository.id,
|
||||
bookId: book.id,
|
||||
})
|
||||
|
||||
console.log(response)
|
||||
|
||||
setIsRequestingCopy(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto px-4 py-16">
|
||||
{/* Book */}
|
||||
@ -94,7 +135,14 @@ export default function BookByIdPageClient(props: Props) {
|
||||
|
||||
<p className="my-6 text-accent-foreground">{book.summary}</p>
|
||||
|
||||
<RepoDropdown currentRepository={repos[0]} repositories={repos} />
|
||||
<RepoDropdown
|
||||
currentRepository={repos[0]}
|
||||
repositories={repos}
|
||||
isRequesting={isRequestingCopy}
|
||||
isDisabled={isRequestDisabled}
|
||||
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>
|
||||
|
@ -38,12 +38,14 @@ export default async function HomePage() {
|
||||
<div className="py-24 sm:py-32">
|
||||
<div className="mx-auto max-w-7xl px-6 lg:px-8">
|
||||
<div className="mx-auto max-w-2xl lg:mx-0">
|
||||
<p className="text-base/7 font-semibold text-indigo-600">Get the help you need</p>
|
||||
<p className="text-base/7 font-semibold text-foreground">
|
||||
Engage In Our Community Resources
|
||||
</p>
|
||||
|
||||
<h2 className="mt-2 text-5xl font-semibold tracking-tight text-foreground sm:text-7xl">
|
||||
<TextShimmer
|
||||
duration={1.2}
|
||||
className="[--base-color:var(--color-indigo-600)] [--base-gradient-color:var(--color-blue-200)] dark:[--base-color:var(--color-blue-700)] dark:[--base-gradient-color:var(--color-blue-400)]"
|
||||
duration={2.2}
|
||||
className="[--base-color:var(--color-emerald-700)] [--base-gradient-color:var(--color-white)] dark:[--base-color:var(--color-emerald-600)] dark:[--base-gradient-color:var(--color-white)]"
|
||||
>
|
||||
Welcome
|
||||
</TextShimmer>
|
||||
|
24
src/app/(frontend)/serverCalls/requestHold.ts
Normal file
24
src/app/(frontend)/serverCalls/requestHold.ts
Normal file
@ -0,0 +1,24 @@
|
||||
'use server'
|
||||
|
||||
import { getPayload } from "payload"
|
||||
import configPromise from '@payload-config'
|
||||
|
||||
type Props = {
|
||||
bookId: number,
|
||||
repositoryId: number,
|
||||
}
|
||||
const requestHold = async (props: Props) => {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
|
||||
const requestHoldResponse = await payload.create({
|
||||
collection: 'holdRequests',
|
||||
data: {
|
||||
repository: props.repositoryId,
|
||||
book: props.bookId,
|
||||
},
|
||||
})
|
||||
|
||||
return requestHoldResponse
|
||||
}
|
||||
|
||||
export default requestHold
|
34
src/collections/Checkouts/Checkouts.ts
Normal file
34
src/collections/Checkouts/Checkouts.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { CollectionConfig } from "payload";
|
||||
|
||||
const Checkouts: CollectionConfig = {
|
||||
slug: 'checkouts',
|
||||
fields: [
|
||||
{
|
||||
name: 'fromHold',
|
||||
type: 'relationship',
|
||||
relationTo: 'holdRequests',
|
||||
hasMany: false,
|
||||
},
|
||||
{
|
||||
name: 'user',
|
||||
type: 'relationship',
|
||||
relationTo: 'users',
|
||||
hasMany: false,
|
||||
},
|
||||
{
|
||||
name: 'copy',
|
||||
type: 'relationship',
|
||||
relationTo: 'copies',
|
||||
hasMany: false,
|
||||
},
|
||||
{
|
||||
name: 'book',
|
||||
type: 'join',
|
||||
collection: 'copies',
|
||||
on: 'book',
|
||||
hasMany: false,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default Checkouts
|
79
src/collections/Checkouts/HoldRequests.ts
Normal file
79
src/collections/Checkouts/HoldRequests.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { CollectionConfig } from "payload";
|
||||
|
||||
const HoldRequests: CollectionConfig = {
|
||||
slug: 'holdRequests',
|
||||
fields: [
|
||||
{
|
||||
name: 'copy',
|
||||
type: 'relationship',
|
||||
relationTo: 'copies',
|
||||
filterOptions: ({ data }) => {
|
||||
return {
|
||||
book: {
|
||||
equals: data.book
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'book',
|
||||
type: 'relationship',
|
||||
relationTo: 'books',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'repository',
|
||||
type: 'relationship',
|
||||
relationTo: 'repositories',
|
||||
},
|
||||
{
|
||||
name: 'userRequested',
|
||||
type: 'relationship',
|
||||
relationTo: 'users',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'dateRequested',
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
name: 'isHolding',
|
||||
type: 'checkbox',
|
||||
hooks: {
|
||||
beforeValidate: [({ data, originalDoc }) => {
|
||||
if (data?.isHolding && !data.copy) return originalDoc
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'holdingUntilDate',
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
name: 'isCheckedOut',
|
||||
type: 'checkbox',
|
||||
hooks:
|
||||
{
|
||||
beforeValidate: [({ data, originalDoc }) => {
|
||||
if (data?.isCheckedOut && !data.copy) return originalDoc
|
||||
|
||||
if (originalDoc.isCheckedOut && !data?.isCheckedOut) return originalDoc
|
||||
}],
|
||||
afterChange: [({ value, data, req }) => {
|
||||
if (value) {
|
||||
req.payload.create({
|
||||
collection: 'checkouts',
|
||||
data: {
|
||||
user: data?.userRequested,
|
||||
copy: data?.copy,
|
||||
}
|
||||
})
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
export default HoldRequests
|
@ -73,6 +73,26 @@ export const Copies: CollectionConfig = {
|
||||
{
|
||||
name: 'notes',
|
||||
type: 'richText'
|
||||
},
|
||||
{
|
||||
name: 'holdRequests',
|
||||
type: 'join',
|
||||
collection: 'holdRequests',
|
||||
on: 'copy',
|
||||
where: {
|
||||
or: [
|
||||
{
|
||||
isCheckedOut: {
|
||||
equals: false
|
||||
}
|
||||
},
|
||||
{
|
||||
isCheckedOut: {
|
||||
equals: null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
hooks: {
|
||||
|
@ -30,6 +30,27 @@ export const Repositories: CollectionConfig = {
|
||||
{
|
||||
name: 'dateOpenToPublic',
|
||||
type: 'date'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'holdRequests',
|
||||
type: 'join',
|
||||
collection: 'holdRequests',
|
||||
on: 'repository',
|
||||
defaultSort: 'dateRequested',
|
||||
where: {
|
||||
or: [
|
||||
{
|
||||
isCheckedOut: {
|
||||
equals: false
|
||||
}
|
||||
},
|
||||
{
|
||||
isCheckedOut: {
|
||||
equals: null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -73,6 +73,8 @@ export interface Config {
|
||||
authors: Author;
|
||||
repositories: Repository;
|
||||
copies: Copy;
|
||||
holdRequests: HoldRequest;
|
||||
checkouts: Checkout;
|
||||
genre: Genre;
|
||||
pages: Page;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
@ -86,6 +88,15 @@ export interface Config {
|
||||
authors: {
|
||||
books: 'books';
|
||||
};
|
||||
repositories: {
|
||||
holdRequests: 'holdRequests';
|
||||
};
|
||||
copies: {
|
||||
holdRequests: 'holdRequests';
|
||||
};
|
||||
checkouts: {
|
||||
book: 'copies';
|
||||
};
|
||||
};
|
||||
collectionsSelect: {
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
@ -94,6 +105,8 @@ export interface Config {
|
||||
authors: AuthorsSelect<false> | AuthorsSelect<true>;
|
||||
repositories: RepositoriesSelect<false> | RepositoriesSelect<true>;
|
||||
copies: CopiesSelect<false> | CopiesSelect<true>;
|
||||
holdRequests: HoldRequestsSelect<false> | HoldRequestsSelect<true>;
|
||||
checkouts: CheckoutsSelect<false> | CheckoutsSelect<true>;
|
||||
genre: GenreSelect<false> | GenreSelect<true>;
|
||||
pages: PagesSelect<false> | PagesSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
@ -265,6 +278,11 @@ export interface Copy {
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
holdRequests?: {
|
||||
docs?: (number | HoldRequest)[];
|
||||
hasNextPage?: boolean;
|
||||
totalDocs?: number;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@ -281,6 +299,45 @@ export interface Repository {
|
||||
abbreviation: string;
|
||||
owner: (number | User)[];
|
||||
dateOpenToPublic?: string | null;
|
||||
holdRequests?: {
|
||||
docs?: (number | HoldRequest)[];
|
||||
hasNextPage?: boolean;
|
||||
totalDocs?: number;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "holdRequests".
|
||||
*/
|
||||
export interface HoldRequest {
|
||||
id: number;
|
||||
copy?: (number | null) | Copy;
|
||||
book: number | Book;
|
||||
repository?: (number | null) | Repository;
|
||||
userRequested: number | User;
|
||||
dateRequested?: string | null;
|
||||
isHolding?: boolean | null;
|
||||
holdingUntilDate?: string | null;
|
||||
isCheckedOut?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "checkouts".
|
||||
*/
|
||||
export interface Checkout {
|
||||
id: number;
|
||||
fromHold?: (number | null) | HoldRequest;
|
||||
user?: (number | null) | User;
|
||||
copy?: (number | null) | Copy;
|
||||
book?: {
|
||||
docs?: (number | Copy)[];
|
||||
hasNextPage?: boolean;
|
||||
totalDocs?: number;
|
||||
};
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@ -337,6 +394,14 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'copies';
|
||||
value: number | Copy;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'holdRequests';
|
||||
value: number | HoldRequest;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'checkouts';
|
||||
value: number | Checkout;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'genre';
|
||||
value: number | Genre;
|
||||
@ -463,6 +528,7 @@ export interface RepositoriesSelect<T extends boolean = true> {
|
||||
abbreviation?: T;
|
||||
owner?: T;
|
||||
dateOpenToPublic?: T;
|
||||
holdRequests?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
@ -476,6 +542,35 @@ export interface CopiesSelect<T extends boolean = true> {
|
||||
book?: T;
|
||||
repository?: T;
|
||||
notes?: T;
|
||||
holdRequests?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "holdRequests_select".
|
||||
*/
|
||||
export interface HoldRequestsSelect<T extends boolean = true> {
|
||||
copy?: T;
|
||||
book?: T;
|
||||
repository?: T;
|
||||
userRequested?: T;
|
||||
dateRequested?: T;
|
||||
isHolding?: T;
|
||||
holdingUntilDate?: T;
|
||||
isCheckedOut?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "checkouts_select".
|
||||
*/
|
||||
export interface CheckoutsSelect<T extends boolean = true> {
|
||||
fromHold?: T;
|
||||
user?: T;
|
||||
copy?: T;
|
||||
book?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ import { Copies } from './collections/Copies/Copies'
|
||||
import { Genre } from './collections/Books/Genre'
|
||||
import { Header } from './globals/header/config'
|
||||
import { Pages } from './collections/Pages/Pages'
|
||||
import HoldRequests from './collections/Checkouts/HoldRequests'
|
||||
import Checkouts from './collections/Checkouts/Checkouts'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
@ -35,7 +37,7 @@ export default buildConfig({
|
||||
},
|
||||
},
|
||||
globals: [Header],
|
||||
collections: [Users, Media, Books, Authors, Repositories, Copies, Genre, Pages],
|
||||
collections: [Users, Media, Books, Authors, Repositories, Copies, HoldRequests, Checkouts, Genre, Pages],
|
||||
editor: lexicalEditor(),
|
||||
secret: process.env.PAYLOAD_SECRET || '',
|
||||
typescript: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user