feat: approve hold and check out from hold
This commit is contained in:
parent
27c29949da
commit
c5e07e7c82
@ -14,9 +14,9 @@ const eslintConfig = [
|
||||
{
|
||||
rules: {
|
||||
'@next/next/no-img-element': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-empty-object-type': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { HoldRequest } from "@/payload-types";
|
||||
import { CollectionConfig } from "payload";
|
||||
|
||||
const HoldRequests: CollectionConfig = {
|
||||
@ -56,8 +55,8 @@ const HoldRequests: CollectionConfig = {
|
||||
hooks:
|
||||
{
|
||||
beforeValidate: [({ data, originalDoc }) => {
|
||||
if (data?.isCheckedOut && !data.copy) return originalDoc
|
||||
|
||||
if (originalDoc.isCheckedOut) return originalDoc
|
||||
if (data?.isCheckedOut && !originalDoc.copy) return originalDoc
|
||||
if (originalDoc.isCheckedOut && !data?.isCheckedOut) return originalDoc
|
||||
}],
|
||||
afterChange: [({ value, data, req }) => {
|
||||
@ -69,6 +68,8 @@ const HoldRequests: CollectionConfig = {
|
||||
copy: data?.copy,
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
@ -26,6 +26,8 @@ import { z } from 'zod'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { Button } from '../ui/button'
|
||||
import { Select } from '@headlessui/react'
|
||||
import approveHoldRequest from '@/serverActions/ApproveHoldRequests'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
const formSchema = z
|
||||
.object({
|
||||
@ -90,18 +92,21 @@ const ApproveHoldRequestModal = (props: Props) => {
|
||||
setAvailableCopies(availableCopies)
|
||||
})
|
||||
.catch((err) => {
|
||||
availableCopies
|
||||
console.error(err)
|
||||
})
|
||||
.finally(() => {})
|
||||
}, [availableCopies, setAvailableCopies, isOpen])
|
||||
}, [availableCopies, setAvailableCopies, isOpen, book.id, repository.id])
|
||||
|
||||
const onSubmit = (values: z.infer<typeof formSchema>) => {
|
||||
const acceptHoldPayload = {
|
||||
const onSubmit = async (values: z.infer<typeof formSchema>) => {
|
||||
const updateRequest = await approveHoldRequest({
|
||||
holdRequestId: hold.id,
|
||||
copyId: parseInt(values.copyId, 10),
|
||||
untilDate: values.untilDate,
|
||||
}
|
||||
console.log(acceptHoldPayload)
|
||||
})
|
||||
|
||||
if (!updateRequest) toast('There was an issue approving that request.')
|
||||
else toast('Approved')
|
||||
|
||||
onOpenChange()
|
||||
}
|
||||
|
||||
@ -162,7 +167,7 @@ const ApproveHoldRequestModal = (props: Props) => {
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="mt-4 inline-flex items-center justify-center self-end rounded-lg bg-black px-4 py-2 text-sm font-medium text-zinc-50 dark:bg-white dark:text-zinc-900"
|
||||
className="mt-4 cursor-pointer inline-flex items-center justify-center self-end rounded-lg bg-black px-4 py-2 text-sm font-medium text-zinc-50 dark:bg-white dark:text-zinc-900"
|
||||
type="submit"
|
||||
>
|
||||
Approve Hold
|
||||
|
||||
89
src/components/Manage/CheckoutFromHoldModal.tsx
Normal file
89
src/components/Manage/CheckoutFromHoldModal.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogDescription,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogClose,
|
||||
} from '@/components/ui/dialog'
|
||||
import { Book, HoldRequest, User } from '@/payload-types'
|
||||
import { Variants, Transition } from 'motion/react'
|
||||
import { Button } from '../ui/button'
|
||||
import { toast } from 'sonner'
|
||||
import checkoutFromHoldRequest from '@/serverActions/CheckoutFromHoldRequests'
|
||||
|
||||
type Props = {
|
||||
holdRequest: HoldRequest
|
||||
isOpen: boolean
|
||||
onOpenChange: () => void
|
||||
}
|
||||
const CheckoutFromHoldModal = (props: Props) => {
|
||||
const { holdRequest, isOpen, onOpenChange } = props
|
||||
|
||||
const customVariants: Variants = {
|
||||
initial: {
|
||||
scale: 0.9,
|
||||
filter: 'blur(10px)',
|
||||
y: '100%',
|
||||
},
|
||||
animate: {
|
||||
scale: 1,
|
||||
filter: 'blur(0px)',
|
||||
y: 0,
|
||||
},
|
||||
}
|
||||
|
||||
const customTransition: Transition = {
|
||||
type: 'spring',
|
||||
bounce: 0,
|
||||
duration: 0.4,
|
||||
}
|
||||
|
||||
const hold = holdRequest as HoldRequest
|
||||
const book = hold.book as Book
|
||||
const userRequested = hold.userRequested as User
|
||||
|
||||
const onSubmit = async () => {
|
||||
const updateRequest = await checkoutFromHoldRequest({
|
||||
holdRequestId: hold.id,
|
||||
})
|
||||
|
||||
if (!updateRequest) toast('There was an issue checking out that hold request.')
|
||||
else toast('Checked out')
|
||||
|
||||
onOpenChange()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
variants={customVariants}
|
||||
transition={customTransition}
|
||||
>
|
||||
<DialogContent className="w-full max-w-md bg-white p-6 dark:bg-zinc-900">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-zinc-900 dark:text-white">
|
||||
Checkout held for for {`${userRequested.firstName} ${userRequested.lastName}`}?
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-zinc-600 dark:text-zinc-400">
|
||||
{book.title}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Button
|
||||
className="mt-4 cursor-pointer inline-flex items-center justify-center self-end rounded-lg bg-black px-4 py-2 text-sm font-medium text-zinc-50 dark:bg-white dark:text-zinc-900"
|
||||
type="button"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
Checkout Held Copy
|
||||
</Button>
|
||||
<DialogClose />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default CheckoutFromHoldModal
|
||||
@ -3,8 +3,8 @@ import { Author, Book, HoldRequest, Repository, User } from '@/payload-types'
|
||||
import { Button } from '../ui/button'
|
||||
import Image from 'next/image'
|
||||
import ApproveHoldRequestModal from './ApproveHoldRequestModal'
|
||||
import { DialogTrigger } from '../ui/dialog'
|
||||
import { useState } from 'react'
|
||||
import CheckoutFromHoldModal from './CheckoutFromHoldModal'
|
||||
|
||||
type Props = {
|
||||
repos: PaginatedDocs<Repository> | null
|
||||
@ -24,7 +24,11 @@ const HoldRequestNotifications = (props: Props) => {
|
||||
const book = hold.book as Book
|
||||
const authors = book.authors as Author[]
|
||||
const dateRequested = hold.dateRequested ? new Date(hold.dateRequested) : new Date()
|
||||
const holdingUntilDate = hold.holdingUntilDate
|
||||
? new Date(hold.holdingUntilDate)
|
||||
: new Date()
|
||||
const userRequested = hold.userRequested as User
|
||||
const userName = `${userRequested.firstName} ${userRequested.lastName}`
|
||||
|
||||
return (
|
||||
<li key={hold.id} className="col-span-1 rounded-lg shadow-sm border border-accent">
|
||||
@ -37,10 +41,10 @@ const HoldRequestNotifications = (props: Props) => {
|
||||
{authors.map((a) => a.lf).join(' | ')}
|
||||
</p>
|
||||
{hold.isHolding ? (
|
||||
<span>
|
||||
<span className="mr-0.5 text-xs">Hold Until</span>
|
||||
<span className="text-wrap">
|
||||
<span className="mr-0.5 text-xs">{userName} Until</span>
|
||||
<time className="inline-flex shrink-0 items-center rounded-full bg-background/20 px-1.5 py-0.5 text-xs font-medium text-emerald-600 ring-1 ring-emerald-600/20 ring-inset">
|
||||
{hold.holdingUntilDate}
|
||||
{holdingUntilDate.toLocaleDateString()}
|
||||
</time>
|
||||
</span>
|
||||
) : (
|
||||
@ -48,9 +52,7 @@ const HoldRequestNotifications = (props: Props) => {
|
||||
<time className="inline-flex shrink-0 items-center rounded-full bg-background/20 px-1.5 py-0.5 text-xs font-medium text-amber-500 ring-1 ring-amber-600/20 ring-inset">
|
||||
{dateRequested.toLocaleDateString()}
|
||||
</time>
|
||||
<span className="text-xs">
|
||||
{`${userRequested.firstName} ${userRequested.lastName}`}
|
||||
</span>
|
||||
<span className="text-xs">{userName}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@ -75,14 +77,22 @@ const HoldRequestNotifications = (props: Props) => {
|
||||
onClick={() => setOpenedModalId(hold.id)}
|
||||
>
|
||||
<Image width={24} height={24} src="/images/approve.svg" alt="approve hold" />
|
||||
<span>Approve</span>
|
||||
<span>{hold.isHolding ? 'Checkout' : 'Approve'}</span>
|
||||
</Button>
|
||||
|
||||
<ApproveHoldRequestModal
|
||||
{hold.isHolding ? (
|
||||
<CheckoutFromHoldModal
|
||||
isOpen={openedModalId === hold.id}
|
||||
onOpenChange={setOpenedModalId}
|
||||
onOpenChange={() => setOpenedModalId(null)}
|
||||
holdRequest={hold}
|
||||
/>
|
||||
) : (
|
||||
<ApproveHoldRequestModal
|
||||
isOpen={openedModalId === hold.id}
|
||||
onOpenChange={() => setOpenedModalId(null)}
|
||||
holdRequest={hold}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { useEffect, useLayoutEffect } from 'react';
|
||||
import { useEffect, useLayoutEffect } from 'react'
|
||||
|
||||
function isMac(): boolean | undefined {
|
||||
return testPlatform(/^Mac/);
|
||||
return testPlatform(/^Mac/)
|
||||
}
|
||||
|
||||
function isIPhone(): boolean | undefined {
|
||||
return testPlatform(/^iPhone/);
|
||||
return testPlatform(/^iPhone/)
|
||||
}
|
||||
|
||||
function isIPad(): boolean | undefined {
|
||||
@ -13,60 +13,57 @@ function isIPad(): boolean | undefined {
|
||||
testPlatform(/^iPad/) ||
|
||||
// iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
|
||||
(isMac() && navigator.maxTouchPoints > 1)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
function isIOS(): boolean | undefined {
|
||||
return isIPhone() || isIPad();
|
||||
return isIPhone() || isIPad()
|
||||
}
|
||||
|
||||
function testPlatform(re: RegExp): boolean | undefined {
|
||||
return typeof window !== 'undefined' && window.navigator != null
|
||||
? re.test(window.navigator.platform)
|
||||
: undefined;
|
||||
: undefined
|
||||
}
|
||||
|
||||
const KEYBOARD_BUFFER = 24;
|
||||
const KEYBOARD_BUFFER = 24
|
||||
|
||||
export const useIsomorphicLayoutEffect =
|
||||
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect
|
||||
|
||||
interface PreventScrollOptions {
|
||||
/** Whether the scroll lock is disabled. */
|
||||
isDisabled?: boolean;
|
||||
focusCallback?: () => void;
|
||||
isDisabled?: boolean
|
||||
focusCallback?: () => void
|
||||
}
|
||||
|
||||
function chain(...callbacks: any[]): (...args: any[]) => void {
|
||||
return (...args: any[]) => {
|
||||
for (let callback of callbacks) {
|
||||
for (const callback of callbacks) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(...args);
|
||||
callback(...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const visualViewport = typeof document !== 'undefined' && window.visualViewport;
|
||||
const visualViewport = typeof document !== 'undefined' && window.visualViewport
|
||||
|
||||
export function isScrollable(node: Element): boolean {
|
||||
let style = window.getComputedStyle(node);
|
||||
return /(auto|scroll)/.test(
|
||||
style.overflow + style.overflowX + style.overflowY
|
||||
);
|
||||
const style = window.getComputedStyle(node)
|
||||
return /(auto|scroll)/.test(style.overflow + style.overflowX + style.overflowY)
|
||||
}
|
||||
|
||||
export function getScrollParent(node: Element): Element {
|
||||
if (isScrollable(node)) {
|
||||
node = node.parentElement as HTMLElement;
|
||||
node = node.parentElement as HTMLElement
|
||||
}
|
||||
|
||||
while (node && !isScrollable(node)) {
|
||||
node = node.parentElement as HTMLElement;
|
||||
node = node.parentElement as HTMLElement
|
||||
}
|
||||
|
||||
return node || document.scrollingElement || document.documentElement;
|
||||
return node || document.scrollingElement || document.documentElement
|
||||
}
|
||||
|
||||
// HTML input types that do not cause the software keyboard to appear.
|
||||
@ -80,11 +77,11 @@ const nonTextInputTypes = new Set([
|
||||
'button',
|
||||
'submit',
|
||||
'reset',
|
||||
]);
|
||||
])
|
||||
|
||||
// The number of active usePreventScroll calls. Used to determine whether to revert back to the original page style/scroll position
|
||||
let preventScrollCount = 0;
|
||||
let restore: () => void;
|
||||
let preventScrollCount = 0
|
||||
let restore: () => void
|
||||
|
||||
/**
|
||||
* Prevents scrolling on the document body on mount, and
|
||||
@ -92,27 +89,27 @@ let restore: () => void;
|
||||
* shift due to the scrollbars disappearing.
|
||||
*/
|
||||
export function usePreventScroll(options: PreventScrollOptions = {}) {
|
||||
let { isDisabled } = options;
|
||||
const { isDisabled } = options
|
||||
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
if (isDisabled) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
preventScrollCount++;
|
||||
preventScrollCount++
|
||||
if (preventScrollCount === 1) {
|
||||
if (isIOS()) {
|
||||
restore = preventScrollMobileSafari();
|
||||
restore = preventScrollMobileSafari()
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
preventScrollCount--;
|
||||
preventScrollCount--
|
||||
if (preventScrollCount === 0) {
|
||||
restore?.();
|
||||
restore?.()
|
||||
}
|
||||
};
|
||||
}, [isDisabled]);
|
||||
}
|
||||
}, [isDisabled])
|
||||
}
|
||||
|
||||
// Mobile Safari is a whole different beast. Even with overflow: hidden,
|
||||
@ -142,79 +139,72 @@ export function usePreventScroll(options: PreventScrollOptions = {}) {
|
||||
// 6. As a last resort, handle window scroll events, and scroll back to the top. This can happen when attempting
|
||||
// to navigate to an input with the next/previous buttons that's outside a modal.
|
||||
function preventScrollMobileSafari() {
|
||||
let scrollable: Element;
|
||||
let lastY = 0;
|
||||
let onTouchStart = (e: TouchEvent) => {
|
||||
let scrollable: Element
|
||||
let lastY = 0
|
||||
const onTouchStart = (e: TouchEvent) => {
|
||||
// Store the nearest scrollable parent element from the element that the user touched.
|
||||
scrollable = getScrollParent(e.target as Element);
|
||||
if (
|
||||
scrollable === document.documentElement &&
|
||||
scrollable === document.body
|
||||
) {
|
||||
return;
|
||||
scrollable = getScrollParent(e.target as Element)
|
||||
if (scrollable === document.documentElement && scrollable === document.body) {
|
||||
return
|
||||
}
|
||||
|
||||
lastY = e.changedTouches[0].pageY;
|
||||
};
|
||||
lastY = e.changedTouches[0].pageY
|
||||
}
|
||||
|
||||
let onTouchMove = (e: TouchEvent) => {
|
||||
const onTouchMove = (e: TouchEvent) => {
|
||||
// Prevent scrolling the window.
|
||||
if (
|
||||
!scrollable ||
|
||||
scrollable === document.documentElement ||
|
||||
scrollable === document.body
|
||||
) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
if (!scrollable || scrollable === document.documentElement || scrollable === document.body) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
// Prevent scrolling up when at the top and scrolling down when at the bottom
|
||||
// of a nested scrollable area, otherwise mobile Safari will start scrolling
|
||||
// the window instead. Unfortunately, this disables bounce scrolling when at
|
||||
// the top but it's the best we can do.
|
||||
let y = e.changedTouches[0].pageY;
|
||||
let scrollTop = scrollable.scrollTop;
|
||||
let bottom = scrollable.scrollHeight - scrollable.clientHeight;
|
||||
const y = e.changedTouches[0].pageY
|
||||
const scrollTop = scrollable.scrollTop
|
||||
const bottom = scrollable.scrollHeight - scrollable.clientHeight
|
||||
|
||||
if (bottom === 0) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if ((scrollTop <= 0 && y > lastY) || (scrollTop >= bottom && y < lastY)) {
|
||||
e.preventDefault();
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
lastY = y;
|
||||
};
|
||||
lastY = y
|
||||
}
|
||||
|
||||
let onTouchEnd = (e: TouchEvent) => {
|
||||
let target = e.target as HTMLElement;
|
||||
const onTouchEnd = (e: TouchEvent) => {
|
||||
const target = e.target as HTMLElement
|
||||
|
||||
// Apply this change if we're not already focused on the target element
|
||||
if (isInput(target) && target !== document.activeElement) {
|
||||
e.preventDefault();
|
||||
e.preventDefault()
|
||||
|
||||
// Apply a transform to trick Safari into thinking the input is at the top of the page
|
||||
// so it doesn't try to scroll it into view. When tapping on an input, this needs to
|
||||
// be done before the "focus" event, so we have to focus the element ourselves.
|
||||
target.style.transform = 'translateY(-2000px)';
|
||||
target.focus();
|
||||
target.style.transform = 'translateY(-2000px)'
|
||||
target.focus()
|
||||
requestAnimationFrame(() => {
|
||||
target.style.transform = '';
|
||||
});
|
||||
target.style.transform = ''
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let onFocus = (e: FocusEvent) => {
|
||||
let target = e.target as HTMLElement;
|
||||
const onFocus = (e: FocusEvent) => {
|
||||
const target = e.target as HTMLElement
|
||||
if (isInput(target)) {
|
||||
// Transform also needs to be applied in the focus event in cases where focus moves
|
||||
// other than tapping on an input directly, e.g. the next/previous buttons in the
|
||||
// software keyboard. In these cases, it seems applying the transform in the focus event
|
||||
// is good enough, whereas when tapping an input, it must be done before the focus event. 🤷♂️
|
||||
target.style.transform = 'translateY(-2000px)';
|
||||
target.style.transform = 'translateY(-2000px)'
|
||||
requestAnimationFrame(() => {
|
||||
target.style.transform = '';
|
||||
target.style.transform = ''
|
||||
|
||||
// This will have prevented the browser from scrolling the focused element into view,
|
||||
// so we need to do this ourselves in a way that doesn't cause the whole page to scroll.
|
||||
@ -223,48 +213,44 @@ function preventScrollMobileSafari() {
|
||||
// If the keyboard is already visible, do this after one additional frame
|
||||
// to wait for the transform to be removed.
|
||||
requestAnimationFrame(() => {
|
||||
scrollIntoView(target);
|
||||
});
|
||||
scrollIntoView(target)
|
||||
})
|
||||
} else {
|
||||
// Otherwise, wait for the visual viewport to resize before scrolling so we can
|
||||
// measure the correct position to scroll to.
|
||||
visualViewport.addEventListener(
|
||||
'resize',
|
||||
() => scrollIntoView(target),
|
||||
{ once: true }
|
||||
);
|
||||
visualViewport.addEventListener('resize', () => scrollIntoView(target), { once: true })
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let onWindowScroll = () => {
|
||||
const onWindowScroll = () => {
|
||||
// Last resort. If the window scrolled, scroll it back to the top.
|
||||
// It should always be at the top because the body will have a negative margin (see below).
|
||||
window.scrollTo(0, 0);
|
||||
};
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
|
||||
// Record the original scroll position so we can restore it.
|
||||
// Then apply a negative margin to the body to offset it by the scroll position. This will
|
||||
// enable us to scroll the window to the top, which is required for the rest of this to work.
|
||||
let scrollX = window.pageXOffset;
|
||||
let scrollY = window.pageYOffset;
|
||||
const scrollX = window.pageXOffset
|
||||
const scrollY = window.pageYOffset
|
||||
|
||||
let restoreStyles = chain(
|
||||
const restoreStyles = chain(
|
||||
setStyle(
|
||||
document.documentElement,
|
||||
'paddingRight',
|
||||
`${window.innerWidth - document.documentElement.clientWidth}px`
|
||||
)
|
||||
`${window.innerWidth - document.documentElement.clientWidth}px`,
|
||||
),
|
||||
// setStyle(document.documentElement, 'overflow', 'hidden'),
|
||||
// setStyle(document.body, 'marginTop', `-${scrollY}px`),
|
||||
);
|
||||
)
|
||||
|
||||
// Scroll to the top. The negative margin on the body will make this appear the same.
|
||||
window.scrollTo(0, 0);
|
||||
window.scrollTo(0, 0)
|
||||
|
||||
let removeEvents = chain(
|
||||
const removeEvents = chain(
|
||||
addEvent(document, 'touchstart', onTouchStart, {
|
||||
passive: false,
|
||||
capture: true,
|
||||
@ -278,33 +264,29 @@ function preventScrollMobileSafari() {
|
||||
capture: true,
|
||||
}),
|
||||
addEvent(document, 'focus', onFocus, true),
|
||||
addEvent(window, 'scroll', onWindowScroll)
|
||||
);
|
||||
addEvent(window, 'scroll', onWindowScroll),
|
||||
)
|
||||
|
||||
return () => {
|
||||
// Restore styles and scroll the page back to where it was.
|
||||
restoreStyles();
|
||||
removeEvents();
|
||||
window.scrollTo(scrollX, scrollY);
|
||||
};
|
||||
restoreStyles()
|
||||
removeEvents()
|
||||
window.scrollTo(scrollX, scrollY)
|
||||
}
|
||||
}
|
||||
|
||||
// Sets a CSS property on an element, and returns a function to revert it to the previous value.
|
||||
function setStyle(
|
||||
element: HTMLElement,
|
||||
style: keyof React.CSSProperties,
|
||||
value: string
|
||||
) {
|
||||
function setStyle(element: HTMLElement, style: keyof React.CSSProperties, value: string) {
|
||||
// https://github.com/microsoft/TypeScript/issues/17827#issuecomment-391663310
|
||||
// @ts-ignore
|
||||
let cur = element.style[style];
|
||||
const cur = element.style[style]
|
||||
// @ts-ignore
|
||||
element.style[style] = value;
|
||||
element.style[style] = value
|
||||
|
||||
return () => {
|
||||
// @ts-ignore
|
||||
element.style[style] = cur;
|
||||
};
|
||||
element.style[style] = cur
|
||||
}
|
||||
}
|
||||
|
||||
// Adds an event listener to an element, and returns a function to remove it.
|
||||
@ -312,49 +294,47 @@ function addEvent<K extends keyof GlobalEventHandlersEventMap>(
|
||||
target: EventTarget,
|
||||
event: K,
|
||||
handler: (this: Document, ev: GlobalEventHandlersEventMap[K]) => any,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
options?: boolean | AddEventListenerOptions,
|
||||
) {
|
||||
// @ts-ignore
|
||||
target.addEventListener(event, handler, options);
|
||||
target.addEventListener(event, handler, options)
|
||||
|
||||
return () => {
|
||||
// @ts-ignore
|
||||
target.removeEventListener(event, handler, options);
|
||||
};
|
||||
target.removeEventListener(event, handler, options)
|
||||
}
|
||||
}
|
||||
|
||||
function scrollIntoView(target: Element) {
|
||||
let root = document.scrollingElement || document.documentElement;
|
||||
const root = document.scrollingElement || document.documentElement
|
||||
while (target && target !== root) {
|
||||
// Find the parent scrollable element and adjust the scroll position if the target is not already in view.
|
||||
let scrollable = getScrollParent(target);
|
||||
const scrollable = getScrollParent(target)
|
||||
if (
|
||||
scrollable !== document.documentElement &&
|
||||
scrollable !== document.body &&
|
||||
scrollable !== target
|
||||
) {
|
||||
let scrollableTop = scrollable.getBoundingClientRect().top;
|
||||
let targetTop = target.getBoundingClientRect().top;
|
||||
let targetBottom = target.getBoundingClientRect().bottom;
|
||||
const scrollableTop = scrollable.getBoundingClientRect().top
|
||||
const targetTop = target.getBoundingClientRect().top
|
||||
const targetBottom = target.getBoundingClientRect().bottom
|
||||
// Buffer is needed for some edge cases
|
||||
const keyboardHeight =
|
||||
scrollable.getBoundingClientRect().bottom + KEYBOARD_BUFFER;
|
||||
const keyboardHeight = scrollable.getBoundingClientRect().bottom + KEYBOARD_BUFFER
|
||||
|
||||
if (targetBottom > keyboardHeight) {
|
||||
scrollable.scrollTop += targetTop - scrollableTop;
|
||||
scrollable.scrollTop += targetTop - scrollableTop
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
target = scrollable.parentElement;
|
||||
target = scrollable.parentElement
|
||||
}
|
||||
}
|
||||
|
||||
export function isInput(target: Element) {
|
||||
return (
|
||||
(target instanceof HTMLInputElement &&
|
||||
!nonTextInputTypes.has(target.type)) ||
|
||||
(target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type)) ||
|
||||
target instanceof HTMLTextAreaElement ||
|
||||
(target instanceof HTMLElement && target.isContentEditable)
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
34
src/serverActions/ApproveHoldRequests.ts
Normal file
34
src/serverActions/ApproveHoldRequests.ts
Normal file
@ -0,0 +1,34 @@
|
||||
'use server'
|
||||
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@/payload.config'
|
||||
import { HoldRequest } from '@/payload-types'
|
||||
|
||||
type Props = {
|
||||
holdRequestId: number,
|
||||
copyId: number,
|
||||
untilDate: string,
|
||||
}
|
||||
export const approveHoldRequest = async (props: Props): Promise<HoldRequest | null> => {
|
||||
const { holdRequestId, copyId, untilDate } = props
|
||||
|
||||
const payloadConfig = await config
|
||||
const payload = await getPayload({ config: payloadConfig })
|
||||
|
||||
try {
|
||||
const updatedHold = await payload.update({
|
||||
collection: 'holdRequests',
|
||||
id: holdRequestId,
|
||||
data: {
|
||||
copy: copyId,
|
||||
holdingUntilDate: untilDate,
|
||||
isHolding: true,
|
||||
}
|
||||
})
|
||||
return updatedHold
|
||||
} catch (_) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export default approveHoldRequest
|
||||
30
src/serverActions/CheckoutFromHoldRequests.ts
Normal file
30
src/serverActions/CheckoutFromHoldRequests.ts
Normal file
@ -0,0 +1,30 @@
|
||||
'use server'
|
||||
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@/payload.config'
|
||||
import { HoldRequest } from '@/payload-types'
|
||||
|
||||
type Props = {
|
||||
holdRequestId: number
|
||||
}
|
||||
export const checkoutFromHoldRequest = async (props: Props): Promise<HoldRequest | null> => {
|
||||
const { holdRequestId } = props
|
||||
|
||||
const payloadConfig = await config
|
||||
const payload = await getPayload({ config: payloadConfig })
|
||||
|
||||
try {
|
||||
const updatedHold = await payload.update({
|
||||
collection: 'holdRequests',
|
||||
id: holdRequestId,
|
||||
data: {
|
||||
isCheckedOut: true,
|
||||
}
|
||||
})
|
||||
return updatedHold
|
||||
} catch (_) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export default checkoutFromHoldRequest
|
||||
Loading…
x
Reference in New Issue
Block a user