Compare commits
No commits in common. "b16a677e653b73b0ff5b4c068dae728815262681" and "461df9d7bab8d3c25c98e4d1c40195ebc1ac6361" have entirely different histories.
b16a677e65
...
461df9d7ba
@ -1,15 +1,10 @@
|
|||||||
DATABASE_URI=postgres://postgres:<password>@127.0.0.1:5432/your-database-name
|
DATABASE_URI=postgres://postgres:<password>@127.0.0.1:5432/your-database-name
|
||||||
PAYLOAD_SECRET=YOUR_SECRET_HERE
|
PAYLOAD_SECRET=YOUR_SECRET_HERE
|
||||||
|
|
||||||
PORT=9889
|
DOMIAN_NAME=
|
||||||
|
|
||||||
DOMAIN_NAME=localhost:3000
|
|
||||||
SERVER_URL=http://localhost:3000
|
|
||||||
|
|
||||||
SMTP_HOST=
|
SMTP_HOST=
|
||||||
SMTP_USER=
|
SMTP_USER=
|
||||||
SMTP_PASS=
|
SMTP_PASS=
|
||||||
SMTP_PORT=587
|
SMTP_PORT=587
|
||||||
PASSWORD_RESET_EXPIRATION_IN_MINUTES=
|
PASSWORD_RESET_EXPIRATION_IN_MINUTES=
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ COPY . .
|
|||||||
# Next.js collects completely anonymous telemetry data about general usage.
|
# Next.js collects completely anonymous telemetry data about general usage.
|
||||||
# Learn more here: https://nextjs.org/telemetry
|
# Learn more here: https://nextjs.org/telemetry
|
||||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
if [ -f yarn.lock ]; then yarn run build; \
|
if [ -f yarn.lock ]; then yarn run build; \
|
||||||
|
|||||||
@ -1,16 +1,43 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
payload:
|
payload:
|
||||||
container_name: "midrashim"
|
|
||||||
image: node:18-alpine
|
image: node:18-alpine
|
||||||
ports:
|
ports:
|
||||||
- '${PORT}:${PORT}'
|
- '3000:3000'
|
||||||
volumes:
|
volumes:
|
||||||
- .:/home/node/app
|
- .:/home/node/app
|
||||||
- node_modules:/home/node/app/node_modules
|
- node_modules:/home/node/app/node_modules
|
||||||
working_dir: /home/node/app/
|
working_dir: /home/node/app/
|
||||||
command: sh -c "npm install && npm run build && npm start"
|
command: sh -c "corepack enable && corepack prepare pnpm@latest --activate && pnpm install && pnpm dev"
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
# - postgres
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|
||||||
|
# Ensure your DATABASE_URI uses 'mongo' as the hostname ie. mongodb://mongo/my-db-name
|
||||||
|
mongo:
|
||||||
|
image: mongo:latest
|
||||||
|
ports:
|
||||||
|
- '27017:27017'
|
||||||
|
command:
|
||||||
|
- --storageEngine=wiredTiger
|
||||||
|
volumes:
|
||||||
|
- data:/data/db
|
||||||
|
logging:
|
||||||
|
driver: none
|
||||||
|
|
||||||
|
# Uncomment the following to use postgres
|
||||||
|
# postgres:
|
||||||
|
# restart: always
|
||||||
|
# image: postgres:latest
|
||||||
|
# volumes:
|
||||||
|
# - pgdata:/var/lib/postgresql/data
|
||||||
|
# ports:
|
||||||
|
# - "5432:5432"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
data:
|
data:
|
||||||
|
# pgdata:
|
||||||
node_modules:
|
node_modules:
|
||||||
|
|||||||
@ -11,10 +11,6 @@ const nextConfig = {
|
|||||||
source: '/search',
|
source: '/search',
|
||||||
destination: '/books',
|
destination: '/books',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
source: '/profile',
|
|
||||||
destination: '/manage',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
13
package-lock.json
generated
13
package-lock.json
generated
@ -12,7 +12,6 @@
|
|||||||
"@headlessui/react": "^2.2.1",
|
"@headlessui/react": "^2.2.1",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@hookform/resolvers": "^5.0.1",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@next/env": "^15.3.1",
|
|
||||||
"@payloadcms/db-postgres": "3.31.0",
|
"@payloadcms/db-postgres": "3.31.0",
|
||||||
"@payloadcms/next": "3.31.0",
|
"@payloadcms/next": "3.31.0",
|
||||||
"@payloadcms/payload-cloud": "3.31.0",
|
"@payloadcms/payload-cloud": "3.31.0",
|
||||||
@ -3338,9 +3337,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "15.3.1",
|
"version": "15.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.3.tgz",
|
||||||
"integrity": "sha512-cwK27QdzrMblHSn9DZRV+DQscHXRuJv6MydlJRpFSqJWZrTYMLzKDeyueJNN9MGd8NNiUKzDQADAf+dMLXX7YQ==",
|
"integrity": "sha512-a26KnbW9DFEUsSxAxKBORR/uD9THoYoKbkpFywMN/AFvboTt94b8+g/07T8J6ACsdLag8/PDU60ov4rPxRAixw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
@ -12070,12 +12069,6 @@
|
|||||||
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next/node_modules/@next/env": {
|
|
||||||
"version": "15.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.3.tgz",
|
|
||||||
"integrity": "sha512-a26KnbW9DFEUsSxAxKBORR/uD9THoYoKbkpFywMN/AFvboTt94b8+g/07T8J6ACsdLag8/PDU60ov4rPxRAixw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/next/node_modules/postcss": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
"version": "8.4.31",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
|
|||||||
@ -19,7 +19,6 @@
|
|||||||
"@headlessui/react": "^2.2.1",
|
"@headlessui/react": "^2.2.1",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@hookform/resolvers": "^5.0.1",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@next/env": "^15.3.1",
|
|
||||||
"@payloadcms/db-postgres": "3.31.0",
|
"@payloadcms/db-postgres": "3.31.0",
|
||||||
"@payloadcms/next": "3.31.0",
|
"@payloadcms/next": "3.31.0",
|
||||||
"@payloadcms/payload-cloud": "3.31.0",
|
"@payloadcms/payload-cloud": "3.31.0",
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
import SiteNavigation from '@/components/SiteNavigation'
|
import SiteNavigation from '@/components/SiteNavigation'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { getPayload } from 'payload'
|
|
||||||
import config from '@/payload.config'
|
|
||||||
import { Header } from '@/payload-types'
|
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
description: 'House of Study for Temple Beth El',
|
description: 'House of Study for Temple Beth El',
|
||||||
@ -12,19 +9,5 @@ export const metadata = {
|
|||||||
export default async function RootLayout(props: { children: React.ReactNode }) {
|
export default async function RootLayout(props: { children: React.ReactNode }) {
|
||||||
const { children } = props
|
const { children } = props
|
||||||
|
|
||||||
const payloadConfig = await config
|
return <SiteNavigation>{children}</SiteNavigation>
|
||||||
const payload = await getPayload({ config: payloadConfig })
|
|
||||||
|
|
||||||
const headerGlobals = (await payload.findGlobal({
|
|
||||||
slug: 'header',
|
|
||||||
})) as Header
|
|
||||||
|
|
||||||
const navItems =
|
|
||||||
headerGlobals.headerLinks?.map((l) => ({
|
|
||||||
label: l.label || '',
|
|
||||||
href: l.href || '',
|
|
||||||
newTab: l.newTab || false,
|
|
||||||
})) || []
|
|
||||||
|
|
||||||
return <SiteNavigation navItems={navItems}>{children}</SiteNavigation>
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import Manage from '@/components/Manage/Manage'
|
|
||||||
import { Checkout, Repository, User } from '@/payload-types'
|
|
||||||
import { PaginatedDocs } from 'payload'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
repos: PaginatedDocs<Repository> | null
|
|
||||||
borrows: PaginatedDocs<Checkout> | null
|
|
||||||
loans: PaginatedDocs<Checkout> | null
|
|
||||||
user: User | null
|
|
||||||
}
|
|
||||||
const ManagePageClient = (props: Props) => {
|
|
||||||
const { repos, borrows, loans, user } = props
|
|
||||||
return <Manage repos={repos} borrows={borrows} user={user} loans={loans} />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ManagePageClient
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
import { headers as getHeaders } from 'next/headers.js'
|
|
||||||
import { getPayload, PaginatedDocs } from 'payload'
|
|
||||||
import { Checkout, Repository } from '@/payload-types'
|
|
||||||
import config from '@/payload.config'
|
|
||||||
import ManagePageClient from './page.client'
|
|
||||||
import { PageBreadCrumb, Route } from '@/components/PageBreadCrumb'
|
|
||||||
|
|
||||||
const breadcrumRoutes: Route[] = [
|
|
||||||
{
|
|
||||||
label: 'Home',
|
|
||||||
href: '/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Manage',
|
|
||||||
href: '/manage',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const ManagePage = async () => {
|
|
||||||
const headers = await getHeaders()
|
|
||||||
const payloadConfig = await config
|
|
||||||
const payload = await getPayload({ config: payloadConfig })
|
|
||||||
const { user } = await payload.auth({ headers })
|
|
||||||
|
|
||||||
let userRepos: PaginatedDocs<Repository> | null = null
|
|
||||||
if (user?.id)
|
|
||||||
userRepos = (await payload.find({
|
|
||||||
collection: 'repositories',
|
|
||||||
depth: 3,
|
|
||||||
limit: 10,
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
abbreviation: true,
|
|
||||||
image: true,
|
|
||||||
description: true,
|
|
||||||
dateOpenToPublic: true,
|
|
||||||
holdRequests: true,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
'owner.id': {
|
|
||||||
equals: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
joins: {
|
|
||||||
holdRequests: {
|
|
||||||
limit: 100,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})) as PaginatedDocs<Repository>
|
|
||||||
|
|
||||||
let userBorrows: PaginatedDocs<Checkout> | null = null
|
|
||||||
if (user?.id)
|
|
||||||
userBorrows = await payload.find({
|
|
||||||
collection: 'checkouts',
|
|
||||||
depth: 3,
|
|
||||||
limit: 10,
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
copy: true,
|
|
||||||
dateDue: true,
|
|
||||||
ownerVerifiedReturnedDate: true,
|
|
||||||
loaneeReturnedDate: true,
|
|
||||||
},
|
|
||||||
sort: 'dateDue',
|
|
||||||
where: {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
isReturned: {
|
|
||||||
not_equals: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'user.id': {
|
|
||||||
equals: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
let userLoans: PaginatedDocs<Checkout> | null = null
|
|
||||||
if (user?.id)
|
|
||||||
userLoans = await payload.find({
|
|
||||||
collection: 'checkouts',
|
|
||||||
depth: 2,
|
|
||||||
limit: 10,
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
copy: true,
|
|
||||||
dateDue: true,
|
|
||||||
ownerVerifiedReturnedDate: true,
|
|
||||||
loaneeReturnedDate: true,
|
|
||||||
},
|
|
||||||
sort: 'dateDue',
|
|
||||||
where: {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
isReturned: {
|
|
||||||
not_equals: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'copy.repository.owner.id': {
|
|
||||||
equals: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageBreadCrumb routes={breadcrumRoutes} />
|
|
||||||
<ManagePageClient repos={userRepos} borrows={userBorrows} user={user} loans={userLoans} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ManagePage
|
|
||||||
@ -59,9 +59,9 @@ export default async function HomePage() {
|
|||||||
},
|
},
|
||||||
})) as PaginatedDocs<Repository>
|
})) as PaginatedDocs<Repository>
|
||||||
|
|
||||||
let userBorrows: PaginatedDocs<Checkout> | null = null
|
let userCheckouts: PaginatedDocs<Checkout> | null = null
|
||||||
if (user?.id)
|
if (user?.id)
|
||||||
userBorrows = await payload.find({
|
userCheckouts = await payload.find({
|
||||||
collection: 'checkouts',
|
collection: 'checkouts',
|
||||||
depth: 3,
|
depth: 3,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
@ -69,8 +69,6 @@ export default async function HomePage() {
|
|||||||
id: true,
|
id: true,
|
||||||
copy: true,
|
copy: true,
|
||||||
dateDue: true,
|
dateDue: true,
|
||||||
ownerVerifiedReturnedDate: true,
|
|
||||||
loaneeReturnedDate: true,
|
|
||||||
},
|
},
|
||||||
sort: 'dateDue',
|
sort: 'dateDue',
|
||||||
where: {
|
where: {
|
||||||
@ -89,36 +87,6 @@ export default async function HomePage() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
let userLoans: PaginatedDocs<Checkout> | null = null
|
|
||||||
if (user?.id)
|
|
||||||
userLoans = await payload.find({
|
|
||||||
collection: 'checkouts',
|
|
||||||
depth: 2,
|
|
||||||
limit: 10,
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
copy: true,
|
|
||||||
dateDue: true,
|
|
||||||
ownerVerifiedReturnedDate: true,
|
|
||||||
loaneeReturnedDate: true,
|
|
||||||
},
|
|
||||||
sort: 'dateDue',
|
|
||||||
where: {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
isReturned: {
|
|
||||||
not_equals: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'copy.repository.owner.id': {
|
|
||||||
equals: user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="home">
|
<div className="home">
|
||||||
<HomeHero user={user} />
|
<HomeHero user={user} />
|
||||||
@ -140,7 +108,7 @@ export default async function HomePage() {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="manage">
|
<TabsContent value="manage">
|
||||||
<Manage repos={userRepos} user={user} borrows={userBorrows} loans={userLoans} />
|
<Manage repos={userRepos} user={user} checkouts={userCheckouts} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -42,7 +42,7 @@ const Checkouts: CollectionConfig = {
|
|||||||
type: 'date',
|
type: 'date',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'loaneeReturnedDate',
|
name: 'loanerReturnedDate',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { authenticated } from "@/access/authenticated";
|
|
||||||
import { Copy } from "@/payload-types";
|
import { Copy } from "@/payload-types";
|
||||||
import { CollectionBeforeValidateHook, CollectionConfig } from "payload";
|
import { CollectionBeforeValidateHook, CollectionConfig } from "payload";
|
||||||
|
|
||||||
@ -39,22 +38,12 @@ export const Copies: CollectionConfig = {
|
|||||||
limits: [10, 20],
|
limits: [10, 20],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
access: {
|
|
||||||
read: () => true,
|
|
||||||
update: authenticated,
|
|
||||||
create: authenticated,
|
|
||||||
delete: authenticated,
|
|
||||||
},
|
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'label',
|
name: 'label',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'isOpenToPublic',
|
|
||||||
type: 'checkbox',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'condition',
|
name: 'condition',
|
||||||
label: 'Condition out of 5',
|
label: 'Condition out of 5',
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import { admin } from '@/access/admin'
|
import { admin } from '@/access/admin'
|
||||||
import type { CollectionConfig } from 'payload'
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
import '../envConfig'
|
const expirationInMinutes = parseInt(process.env.PASSWORD_RESET_EXPIRATION_IN_MINUTES || '30')
|
||||||
|
const domain = process.env.DOMAIN_NAME || 'localhost:3000'
|
||||||
const expirationInMinutes = parseInt(process.env.PASSWORD_RESET_EXPIRATION_IN_MINUTES || '30', 10)
|
|
||||||
const domain = process.env.DOMAIN_NAME || ''
|
|
||||||
const serverUrl = process.env.SERVER_URL || ''
|
|
||||||
|
|
||||||
export const Users: CollectionConfig = {
|
export const Users: CollectionConfig = {
|
||||||
slug: 'users',
|
slug: 'users',
|
||||||
@ -16,13 +13,31 @@ export const Users: CollectionConfig = {
|
|||||||
admin: admin
|
admin: admin
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
|
// verify: {
|
||||||
|
// generateEmailSubject: () => {
|
||||||
|
// return `Verify Account for ${domain}`
|
||||||
|
// },
|
||||||
|
// generateEmailHTML: ({ req, token, user }) => {
|
||||||
|
// const url = `https://${domain}/verify?token=${token}`
|
||||||
|
// return `
|
||||||
|
// <!doctype html>
|
||||||
|
// <html>
|
||||||
|
// <body>
|
||||||
|
// <h1>Verify Account for ${domain}</h1>
|
||||||
|
// <p>Hey ${user.email}, verify your email by clicking here: ${url}</p>
|
||||||
|
// <p>If you have not recently been signed up for ${domain} then please ignore this email.</p>
|
||||||
|
// </body>
|
||||||
|
// </html>
|
||||||
|
// `
|
||||||
|
// },
|
||||||
|
// },
|
||||||
forgotPassword: {
|
forgotPassword: {
|
||||||
|
expiration: (60000 * expirationInMinutes),
|
||||||
generateEmailSubject: () => {
|
generateEmailSubject: () => {
|
||||||
console.log({ domain, serverUrl, expirationInMinutes })
|
|
||||||
return `Reset password request for ${domain}`
|
return `Reset password request for ${domain}`
|
||||||
},
|
},
|
||||||
generateEmailHTML: (props) => {
|
generateEmailHTML: (props) => {
|
||||||
const resetPasswordURL = `${serverUrl}/forgotPassword?token=${props?.token}`
|
const resetPasswordURL = `https://${domain}/forgotPassword?token=${props?.token}`
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@ -43,6 +58,7 @@ export const Users: CollectionConfig = {
|
|||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
// Email added by default
|
// Email added by default
|
||||||
|
// Add more fields as needed
|
||||||
{
|
{
|
||||||
name: 'role',
|
name: 'role',
|
||||||
type: 'select',
|
type: 'select',
|
||||||
|
|||||||
@ -25,7 +25,7 @@ const UserFeed = async (props: Props) => {
|
|||||||
const payloadConfig = await config
|
const payloadConfig = await config
|
||||||
const payload = await getPayload({ config: payloadConfig })
|
const payload = await getPayload({ config: payloadConfig })
|
||||||
|
|
||||||
const borrowRequests = (await payload.find({
|
const holdRequests = (await payload.find({
|
||||||
collection: 'holdRequests',
|
collection: 'holdRequests',
|
||||||
limit: 10,
|
limit: 10,
|
||||||
depth: 3,
|
depth: 3,
|
||||||
@ -97,13 +97,7 @@ const UserFeed = async (props: Props) => {
|
|||||||
},
|
},
|
||||||
})) as PaginatedDocs<Checkout>
|
})) as PaginatedDocs<Checkout>
|
||||||
|
|
||||||
const totalHoldNotifications =
|
const totalHoldNotifications = repos?.docs.flatMap((r) => r.holdRequests?.docs).length || 0
|
||||||
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 outBoundLoanCount = loanedOutBooks?.totalDocs || 0
|
||||||
const currentlyHoldingCount = currentlyHeldBooks?.totalDocs || 0
|
const currentlyHoldingCount = currentlyHeldBooks?.totalDocs || 0
|
||||||
|
|
||||||
@ -112,7 +106,7 @@ const UserFeed = async (props: Props) => {
|
|||||||
name: 'Hold Request',
|
name: 'Hold Request',
|
||||||
iconSrc: '/images/mail.svg',
|
iconSrc: '/images/mail.svg',
|
||||||
value: totalHoldNotifications,
|
value: totalHoldNotifications,
|
||||||
href: '/manage',
|
href: '#',
|
||||||
ctaText: 'Answer Requests',
|
ctaText: 'Answer Requests',
|
||||||
shouldAnimate: totalHoldNotifications > 0,
|
shouldAnimate: totalHoldNotifications > 0,
|
||||||
},
|
},
|
||||||
@ -120,7 +114,7 @@ const UserFeed = async (props: Props) => {
|
|||||||
name: 'Loaned Out',
|
name: 'Loaned Out',
|
||||||
iconSrc: '/images/book-loan.svg',
|
iconSrc: '/images/book-loan.svg',
|
||||||
value: outBoundLoanCount,
|
value: outBoundLoanCount,
|
||||||
href: '/manage',
|
href: '#',
|
||||||
ctaText: 'Handle Returns',
|
ctaText: 'Handle Returns',
|
||||||
shouldAnimate: false,
|
shouldAnimate: false,
|
||||||
},
|
},
|
||||||
@ -128,7 +122,7 @@ const UserFeed = async (props: Props) => {
|
|||||||
name: 'Currently Holding',
|
name: 'Currently Holding',
|
||||||
iconSrc: '/images/book-shelf.svg',
|
iconSrc: '/images/book-shelf.svg',
|
||||||
value: currentlyHoldingCount,
|
value: currentlyHoldingCount,
|
||||||
href: '/manage',
|
href: '#',
|
||||||
ctaText: 'Loan Out',
|
ctaText: 'Loan Out',
|
||||||
shouldAnimate: false,
|
shouldAnimate: false,
|
||||||
},
|
},
|
||||||
@ -137,7 +131,7 @@ const UserFeed = async (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<div className="my-6">
|
<div className="my-6">
|
||||||
<h3 className="text-lg font-semibold text-foreground">Loan Activity</h3>
|
<h3 className="text-lg font-semibold text-foreground">Outbound Activity</h3>
|
||||||
<dl className="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
<dl className="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||||
{stats.map((s) => {
|
{stats.map((s) => {
|
||||||
return (
|
return (
|
||||||
@ -182,11 +176,11 @@ const UserFeed = async (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="my-6">
|
<div className="my-6">
|
||||||
<h2 className="text-lg font-semibold text-foreground">Borrow Activity</h2>
|
<h2 className="text-lg font-semibold text-foreground">Inbound Activity</h2>
|
||||||
<div className="my-3">
|
<div className="my-3">
|
||||||
<h3 className="text-base font-semibold text-muted-foreground mb-4">Your Holds</h3>
|
<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">
|
<ul className="grid grid-cols-1 gap-y-6 sm:grid-cols-3 md:grid-cols-4 last-child-adjustment">
|
||||||
{borrowRequests.docs?.map((h) => {
|
{holdRequests.docs?.map((h) => {
|
||||||
const book = h.book as Book
|
const book = h.book as Book
|
||||||
const repository = h.repository as Repository
|
const repository = h.repository as Repository
|
||||||
|
|
||||||
|
|||||||
@ -1,228 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { Author, Book, Checkout, Copy, Repository } from '@/payload-types'
|
|
||||||
import { useGlobal } from '@/providers/GlobalProvider'
|
|
||||||
import { getUserBorrows } from '@/serverActions/GetUserBorrows'
|
|
||||||
import { loaneeReturnCheckout } from '@/serverActions/ReturnCheckout'
|
|
||||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
|
|
||||||
import { EllipsisVerticalIcon } from '@heroicons/react/20/solid'
|
|
||||||
import clsx from 'clsx'
|
|
||||||
import { Loader2Icon } from 'lucide-react'
|
|
||||||
import { PaginatedDocs, User } from 'payload'
|
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
|
|
||||||
const statuses = {
|
|
||||||
'Passed Due': 'text-gray-200 bg-red-500 ring-red-700/10',
|
|
||||||
'Due Soon': 'text-amber-800 bg-amber-50 ring-amber-600/20',
|
|
||||||
'Owner Action': 'text-emerald-800 bg-emerald-50 ring-emerald-600/20',
|
|
||||||
'': '',
|
|
||||||
}
|
|
||||||
|
|
||||||
type Row = {
|
|
||||||
id: number
|
|
||||||
title: string
|
|
||||||
authors: string[]
|
|
||||||
owners: string[]
|
|
||||||
repositoryAbbreviation: string
|
|
||||||
status: 'Passed Due' | 'Due Soon' | ''
|
|
||||||
dueDate: Date
|
|
||||||
href: string
|
|
||||||
doesNeedOwnerVerify: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListProps = {
|
|
||||||
rows: Row[]
|
|
||||||
onUpdate: () => Promise<void>
|
|
||||||
}
|
|
||||||
const BorrowedBooksList = (props: ListProps) => {
|
|
||||||
const { rows, onUpdate } = props
|
|
||||||
|
|
||||||
const [returningBookId, setReturningBookId] = useState(0)
|
|
||||||
|
|
||||||
const isReturningBook = useCallback((id: number) => id === returningBookId, [returningBookId])
|
|
||||||
|
|
||||||
const handleReturnClick = useCallback(
|
|
||||||
async (id: number) => {
|
|
||||||
if (returningBookId) return
|
|
||||||
setReturningBookId(id)
|
|
||||||
|
|
||||||
const updatedCheckout = await loaneeReturnCheckout({ checkoutId: id })
|
|
||||||
if (!updatedCheckout) {
|
|
||||||
setReturningBookId(0)
|
|
||||||
toast('There was an issue returning your book')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await onUpdate()
|
|
||||||
setReturningBookId(0)
|
|
||||||
toast('Book was returned, awaiting owner verification.')
|
|
||||||
},
|
|
||||||
[returningBookId, setReturningBookId, onUpdate],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ul role="list" className="divide-y divide-gray-100">
|
|
||||||
{rows.map((r) => (
|
|
||||||
<li
|
|
||||||
key={r.id}
|
|
||||||
className={clsx(
|
|
||||||
'flex relative items-center justify-between gap-x-6 py-5',
|
|
||||||
isReturningBook(r.id) ? 'pointer-events-none opacity-30' : '',
|
|
||||||
r.doesNeedOwnerVerify ? 'pointer-events-none opacity-30' : '',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="min-w-0">
|
|
||||||
<div className="flex items-start gap-x-3">
|
|
||||||
<p className="text-sm/6 text-foreground">
|
|
||||||
<span className="font-semibold">{`[${r.repositoryAbbreviation}] `}</span>
|
|
||||||
<span>{r.title}</span>
|
|
||||||
</p>
|
|
||||||
{!!r.status && (
|
|
||||||
<p
|
|
||||||
className={clsx(
|
|
||||||
statuses[r.status as keyof typeof statuses],
|
|
||||||
'mt-0.5 rounded-md px-1.5 py-0.5 text-xs font-medium whitespace-nowrap ring-1 ring-inset',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{r.status}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="mt-1 flex items-center gap-x-2 text-xs/5 text-gray-500">
|
|
||||||
<p className="whitespace-nowrap">
|
|
||||||
{r.doesNeedOwnerVerify ? (
|
|
||||||
<span>Awaiting Owner Verification</span>
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
Due on{' '}
|
|
||||||
<time dateTime={r.dueDate.toString()}>{r.dueDate.toLocaleDateString()}</time>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<svg viewBox="0 0 2 2" className="size-0.5 fill-current">
|
|
||||||
<circle r={1} cx={1} cy={1} />
|
|
||||||
</svg>
|
|
||||||
<p className="truncate">Borrowed from {r.owners.join(', ')}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-none items-center gap-x-4">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled={!!returningBookId}
|
|
||||||
className="hidden disabled:opacity-20 cursor-pointer disabled:cursor-auto rounded-md bg-background px-2.5 py-1.5 text-sm font-semibold text-muted-foreground shadow-xs ring-1 ring-gray-300 ring-inset hover:bg-gray-50 sm:flex items-center gap-1"
|
|
||||||
onClick={() => handleReturnClick(r.id)}
|
|
||||||
>
|
|
||||||
{isReturningBook(r.id) && <Loader2Icon className="animate-spin" />}
|
|
||||||
<span>Return</span>
|
|
||||||
</button>
|
|
||||||
<Menu as="div" className="relative flex-none">
|
|
||||||
<MenuButton className="-m-2.5 block p-2.5 text-gray-500 hover:text-gray-900">
|
|
||||||
<span className="sr-only">Open options</span>
|
|
||||||
<EllipsisVerticalIcon aria-hidden="true" className="size-5" />
|
|
||||||
</MenuButton>
|
|
||||||
<MenuItems
|
|
||||||
transition
|
|
||||||
className="absolute right-0 z-10 mt-2 w-32 origin-top-right rounded-md bg-white py-2 shadow-lg ring-1 ring-gray-900/5 transition focus:outline-hidden data-closed:scale-95 data-closed:transform data-closed:opacity-0 data-enter:duration-100 data-enter:ease-out data-leave:duration-75 data-leave:ease-in"
|
|
||||||
>
|
|
||||||
<MenuItem>
|
|
||||||
<button
|
|
||||||
className="block w-full text-left cursor-pointer px-3 py-1 text-sm/6 text-gray-900 data-focus:bg-gray-50 data-focus:outline-hidden"
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleReturnClick(r.id)}
|
|
||||||
>
|
|
||||||
Return<span className="sr-only">, {r.title}</span>
|
|
||||||
</button>
|
|
||||||
</MenuItem>
|
|
||||||
{/*<MenuItem>
|
|
||||||
<a
|
|
||||||
href={`/repositories/${r.repositoryAbbreviation}`}
|
|
||||||
className="block px-3 py-1 text-sm/6 text-gray-900 data-focus:bg-gray-50 data-focus:outline-hidden"
|
|
||||||
>
|
|
||||||
Repository<span className="sr-only">, {r.repositoryAbbreviation}</span>
|
|
||||||
</a>
|
|
||||||
</MenuItem>*/}
|
|
||||||
<MenuItem>
|
|
||||||
<a
|
|
||||||
href={r.href}
|
|
||||||
className="block px-3 py-1 text-sm/6 text-gray-900 data-focus:bg-gray-50 data-focus:outline-hidden"
|
|
||||||
>
|
|
||||||
View<span className="sr-only"> {r.title}</span>
|
|
||||||
</a>
|
|
||||||
</MenuItem>
|
|
||||||
</MenuItems>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = { initialBorrows: PaginatedDocs<Checkout> | null }
|
|
||||||
const BorrowedBooks = (props: Props) => {
|
|
||||||
const { initialBorrows } = props
|
|
||||||
|
|
||||||
const { user } = useGlobal()
|
|
||||||
|
|
||||||
const [borrows, setBorrows] = useState<PaginatedDocs<Checkout> | null>(initialBorrows)
|
|
||||||
|
|
||||||
const onUpdate = useCallback(async () => {
|
|
||||||
if (!user) return
|
|
||||||
|
|
||||||
const updatedUserBorrows = await getUserBorrows({ userId: user.id })
|
|
||||||
setBorrows(updatedUserBorrows)
|
|
||||||
}, [setBorrows, user])
|
|
||||||
|
|
||||||
const rows: Row[] = useMemo(
|
|
||||||
() =>
|
|
||||||
borrows?.docs?.map((c) => {
|
|
||||||
const copy = c.copy as Copy
|
|
||||||
const book = copy.book as Book
|
|
||||||
const authors = book.authors as Author[]
|
|
||||||
const repository = copy.repository as Repository
|
|
||||||
const owners = repository.owner as (number | User)[]
|
|
||||||
|
|
||||||
const parsedDateDue = c.dateDue ? new Date(c.dateDue) : new Date()
|
|
||||||
const milisecondsUntilDue = parsedDateDue.getTime() - new Date().getTime()
|
|
||||||
const secondsUntilDue = Math.floor(milisecondsUntilDue / 1000)
|
|
||||||
const minutesUntilDue = Math.floor(secondsUntilDue / 60)
|
|
||||||
const hoursUntilDue = Math.floor(minutesUntilDue / 60)
|
|
||||||
const daysUntilDue = Math.floor(hoursUntilDue / 24)
|
|
||||||
const doesNeedOwnerVerify = c.loaneeReturnedDate && !c.ownerVerifiedReturnedDate
|
|
||||||
|
|
||||||
let status = ''
|
|
||||||
if (doesNeedOwnerVerify) status = 'Owner Action'
|
|
||||||
else if (daysUntilDue <= 0) status = 'Passed Due'
|
|
||||||
else if (daysUntilDue <= 5) status = 'Due Soon'
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: c.id || 0,
|
|
||||||
title: book.title || '',
|
|
||||||
authors: authors.map((a) => a.lf) || ([] as string[]),
|
|
||||||
owners:
|
|
||||||
owners.map((o) => `${(o as User).firstName} ${(o as User).lastName}`) ||
|
|
||||||
([] as string[]),
|
|
||||||
repositoryAbbreviation: repository.abbreviation || '',
|
|
||||||
status,
|
|
||||||
dueDate: parsedDateDue,
|
|
||||||
href: `/books/${book.id}`,
|
|
||||||
doesNeedOwnerVerify,
|
|
||||||
} as Row
|
|
||||||
}) || [],
|
|
||||||
[borrows],
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!rows.length) return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="py-6">
|
|
||||||
<div className="mb-4 flex justify-between items-end">
|
|
||||||
<h3 className="px-4 text-lg font-semibold">Borrowed Books</h3>
|
|
||||||
</div>
|
|
||||||
<BorrowedBooksList onUpdate={onUpdate} rows={rows} />
|
|
||||||
</section>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BorrowedBooks
|
|
||||||
@ -1,16 +1,10 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { Author, Book, Checkout, Copy, Repository } from '@/payload-types'
|
import { Author, Book, Checkout, Copy, Repository } from '@/payload-types'
|
||||||
import { useGlobal } from '@/providers/GlobalProvider'
|
|
||||||
import { getUserLoans } from '@/serverActions/GetUserLoans'
|
|
||||||
import { ownerReturnCheckout } from '@/serverActions/ReturnCheckout'
|
|
||||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
|
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
|
||||||
import { EllipsisVerticalIcon } from '@heroicons/react/20/solid'
|
import { EllipsisVerticalIcon } from '@heroicons/react/20/solid'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Loader2Icon } from 'lucide-react'
|
import { Loader2Icon } from 'lucide-react'
|
||||||
import { PaginatedDocs, User } from 'payload'
|
import { PaginatedDocs, User } from 'payload'
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import { toast } from 'sonner'
|
|
||||||
|
|
||||||
const statuses = {
|
const statuses = {
|
||||||
'Passed Due': 'text-gray-200 bg-red-500 ring-red-700/10',
|
'Passed Due': 'text-gray-200 bg-red-500 ring-red-700/10',
|
||||||
@ -29,46 +23,22 @@ type Row = {
|
|||||||
href: string
|
href: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListProps = {
|
type ListProps = { rows: Row[] }
|
||||||
rows: Row[]
|
const CheckedOutBooksList = (props: ListProps) => {
|
||||||
onUpdate: () => Promise<void>
|
const { rows } = props
|
||||||
}
|
|
||||||
const LoanedBooksList = (props: ListProps) => {
|
|
||||||
const { rows, onUpdate } = props
|
|
||||||
|
|
||||||
const [returningBookId, setReturningBookId] = useState(0)
|
const [returningBookId, setReturningBookId] = useState(0)
|
||||||
|
|
||||||
const isReturningBook = useCallback((id: number) => id === returningBookId, [returningBookId])
|
const isReturningBook = useCallback((id: number) => id === returningBookId, [returningBookId])
|
||||||
|
|
||||||
const handleReturnClick = useCallback(
|
const handleReturnClick = useCallback((id: number) => {
|
||||||
async (id: number) => {
|
setReturningBookId(id)
|
||||||
if (returningBookId) return
|
}, [])
|
||||||
setReturningBookId(id)
|
|
||||||
|
|
||||||
const updatedCheckout = await ownerReturnCheckout({ checkoutId: id })
|
|
||||||
if (!updatedCheckout) {
|
|
||||||
setReturningBookId(0)
|
|
||||||
toast('There was an issue returning your book')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await onUpdate()
|
|
||||||
setReturningBookId(0)
|
|
||||||
toast('Book was returned into your collection')
|
|
||||||
},
|
|
||||||
[returningBookId, setReturningBookId, onUpdate],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul role="list" className="divide-y divide-gray-100">
|
<ul role="list" className="divide-y divide-gray-100">
|
||||||
{rows.map((r) => (
|
{rows.map((r) => (
|
||||||
<li
|
<li key={r.id} className="flex items-center justify-between gap-x-6 py-5">
|
||||||
key={r.id}
|
|
||||||
className={clsx(
|
|
||||||
'flex relative items-center justify-between gap-x-6 py-5',
|
|
||||||
isReturningBook(r.id) ? 'pointer-events-none opacity-30' : '',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="flex items-start gap-x-3">
|
<div className="flex items-start gap-x-3">
|
||||||
<p className="text-sm/6 text-foreground">
|
<p className="text-sm/6 text-foreground">
|
||||||
@ -88,10 +58,7 @@ const LoanedBooksList = (props: ListProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-1 flex items-center gap-x-2 text-xs/5 text-gray-500">
|
<div className="mt-1 flex items-center gap-x-2 text-xs/5 text-gray-500">
|
||||||
<p className="whitespace-nowrap">
|
<p className="whitespace-nowrap">
|
||||||
<span>
|
Due on <time dateTime={r.dueDate.toString()}>{r.dueDate.toLocaleDateString()}</time>
|
||||||
Due on{' '}
|
|
||||||
<time dateTime={r.dueDate.toString()}>{r.dueDate.toLocaleDateString()}</time>
|
|
||||||
</span>
|
|
||||||
</p>
|
</p>
|
||||||
<svg viewBox="0 0 2 2" className="size-0.5 fill-current">
|
<svg viewBox="0 0 2 2" className="size-0.5 fill-current">
|
||||||
<circle r={1} cx={1} cy={1} />
|
<circle r={1} cx={1} cy={1} />
|
||||||
@ -120,21 +87,21 @@ const LoanedBooksList = (props: ListProps) => {
|
|||||||
>
|
>
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
<button
|
<button
|
||||||
className="block w-full text-left cursor-pointer px-3 py-1 text-sm/6 text-gray-900 data-focus:bg-gray-50 data-focus:outline-hidden"
|
className="block cursor-pointer px-3 py-1 text-sm/6 text-gray-900 data-focus:bg-gray-50 data-focus:outline-hidden"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleReturnClick(r.id)}
|
onClick={() => console.log('return')}
|
||||||
>
|
>
|
||||||
Return<span className="sr-only">, {r.title}</span>
|
Return<span className="sr-only">, {r.title}</span>
|
||||||
</button>
|
</button>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/*<MenuItem>
|
<MenuItem>
|
||||||
<a
|
<a
|
||||||
href={`/repositories/${r.repositoryAbbreviation}`}
|
href={`/repositories/${r.repositoryAbbreviation}`}
|
||||||
className="block px-3 py-1 text-sm/6 text-gray-900 data-focus:bg-gray-50 data-focus:outline-hidden"
|
className="block px-3 py-1 text-sm/6 text-gray-900 data-focus:bg-gray-50 data-focus:outline-hidden"
|
||||||
>
|
>
|
||||||
Repository<span className="sr-only">, {r.repositoryAbbreviation}</span>
|
Repository<span className="sr-only">, {r.repositoryAbbreviation}</span>
|
||||||
</a>
|
</a>
|
||||||
</MenuItem>*/}
|
</MenuItem>
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
<a
|
<a
|
||||||
href={r.href}
|
href={r.href}
|
||||||
@ -152,67 +119,53 @@ const LoanedBooksList = (props: ListProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = { initialLoans: PaginatedDocs<Checkout> | null }
|
type Props = { initialCheckoutPage: PaginatedDocs<Checkout> | null }
|
||||||
const LoanedBooks = (props: Props) => {
|
const CheckedOutBooks = (props: Props) => {
|
||||||
const { initialLoans } = props
|
const { initialCheckoutPage } = props
|
||||||
|
|
||||||
const { user } = useGlobal()
|
const checkouts = initialCheckoutPage?.docs
|
||||||
|
const rows: Row[] =
|
||||||
|
checkouts?.map((c) => {
|
||||||
|
const copy = c.copy as Copy
|
||||||
|
const book = copy.book as Book
|
||||||
|
const authors = book.authors as Author[]
|
||||||
|
const repository = copy.repository as Repository
|
||||||
|
const owners = repository.owner as (number | User)[]
|
||||||
|
|
||||||
const [loans, setLoans] = useState<PaginatedDocs<Checkout> | null>(initialLoans)
|
const parsedDateDue = c.dateDue ? new Date(c.dateDue) : new Date()
|
||||||
|
const milisecondsUntilDue = parsedDateDue.getTime() - new Date().getTime()
|
||||||
|
const secondsUntilDue = Math.floor(milisecondsUntilDue / 1000)
|
||||||
|
const minutesUntilDue = Math.floor(secondsUntilDue / 60)
|
||||||
|
const hoursUntilDue = Math.floor(minutesUntilDue / 60)
|
||||||
|
const daysUntilDue = Math.floor(hoursUntilDue / 24)
|
||||||
|
|
||||||
const onUpdate = useCallback(async () => {
|
let status = ''
|
||||||
if (!user) return
|
if (daysUntilDue <= 0) status = 'Passed Due'
|
||||||
|
else if (daysUntilDue <= 5) status = 'Due Soon'
|
||||||
|
|
||||||
const updatedUserLoans = await getUserLoans({ userId: user.id })
|
return {
|
||||||
setLoans(updatedUserLoans)
|
id: c.id || 0,
|
||||||
}, [setLoans, user])
|
title: book.title || '',
|
||||||
|
authors: authors.map((a) => a.lf) || ([] as string[]),
|
||||||
const rows: Row[] = useMemo(
|
owners:
|
||||||
() =>
|
owners.map((o) => `${(o as User).firstName} ${(o as User).lastName}`) || ([] as string[]),
|
||||||
loans?.docs?.map((c) => {
|
repositoryAbbreviation: repository.abbreviation || '',
|
||||||
const copy = c.copy as Copy
|
status,
|
||||||
const book = copy.book as Book
|
dueDate: parsedDateDue,
|
||||||
const authors = book.authors as Author[]
|
href: `/books/${book.id}`,
|
||||||
const repository = copy.repository as Repository
|
} as Row
|
||||||
const owners = repository.owner as (number | User)[]
|
}) || []
|
||||||
|
|
||||||
const parsedDateDue = c.dateDue ? new Date(c.dateDue) : new Date()
|
|
||||||
const milisecondsUntilDue = parsedDateDue.getTime() - new Date().getTime()
|
|
||||||
const secondsUntilDue = Math.floor(milisecondsUntilDue / 1000)
|
|
||||||
const minutesUntilDue = Math.floor(secondsUntilDue / 60)
|
|
||||||
const hoursUntilDue = Math.floor(minutesUntilDue / 60)
|
|
||||||
const daysUntilDue = Math.floor(hoursUntilDue / 24)
|
|
||||||
|
|
||||||
let status = ''
|
|
||||||
if (daysUntilDue <= 0) status = 'Passed Due'
|
|
||||||
else if (daysUntilDue <= 5) status = 'Due Soon'
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: c.id || 0,
|
|
||||||
title: book.title || '',
|
|
||||||
authors: authors.map((a) => a.lf) || ([] as string[]),
|
|
||||||
owners:
|
|
||||||
owners.map((o) => `${(o as User).firstName} ${(o as User).lastName}`) ||
|
|
||||||
([] as string[]),
|
|
||||||
repositoryAbbreviation: repository.abbreviation || '',
|
|
||||||
status,
|
|
||||||
dueDate: parsedDateDue,
|
|
||||||
href: `/books/${book.id}`,
|
|
||||||
} as Row
|
|
||||||
}) || [],
|
|
||||||
[loans],
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!rows.length) return null
|
if (!rows.length) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-6">
|
<section className="py-6">
|
||||||
<div className="mb-4 flex justify-between items-end">
|
<div className="mb-4 flex justify-between items-end">
|
||||||
<h3 className="px-4 text-lg font-semibold">Loaned Out Books</h3>
|
<h3 className="px-4 text-lg font-semibold">Inbound Checked Out Books</h3>
|
||||||
</div>
|
</div>
|
||||||
<LoanedBooksList onUpdate={onUpdate} rows={rows} />
|
<CheckedOutBooksList rows={rows} />
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoanedBooks
|
export default CheckedOutBooks
|
||||||
@ -19,13 +19,7 @@ const HoldRequestNotifications = (props: Props) => {
|
|||||||
|
|
||||||
const [openedModalId, setOpenedModalId] = useState<number | null>(null)
|
const [openedModalId, setOpenedModalId] = useState<number | null>(null)
|
||||||
|
|
||||||
const totalHoldNotifications =
|
const totalHoldNotifications = repos?.docs.flatMap((r) => r.holdRequests?.docs).length || 0
|
||||||
repos?.docs
|
|
||||||
.flatMap((r) => r.holdRequests?.docs)
|
|
||||||
.filter((r) => {
|
|
||||||
const request = r as HoldRequest
|
|
||||||
return !request.isCheckedOut && !request.isRejected
|
|
||||||
}).length || 0
|
|
||||||
|
|
||||||
const [isRejectingId, setIsRejectingId] = useState<number | null>()
|
const [isRejectingId, setIsRejectingId] = useState<number | null>()
|
||||||
|
|
||||||
@ -161,7 +155,7 @@ const HoldRequestNotifications = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<section className="py-6">
|
<section className="py-6">
|
||||||
<div className="mb-4 flex justify-between items-end">
|
<div className="mb-4 flex justify-between items-end">
|
||||||
<h3 className="px-4 text-lg font-semibold">Incoming Hold Requests</h3>
|
<h3 className="px-4 text-lg font-semibold">Inbound Hold Requests</h3>
|
||||||
{!!totalHoldNotifications && (
|
{!!totalHoldNotifications && (
|
||||||
<span className="font-bold">{totalHoldNotifications} Unaddressed</span>
|
<span className="font-bold">{totalHoldNotifications} Unaddressed</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -4,17 +4,15 @@ import { Checkout, Repository, User } from '@/payload-types'
|
|||||||
import { PaginatedDocs } from 'payload'
|
import { PaginatedDocs } from 'payload'
|
||||||
import RepoList from './RepoList'
|
import RepoList from './RepoList'
|
||||||
import HoldRequestNotifications from './HoldRequests'
|
import HoldRequestNotifications from './HoldRequests'
|
||||||
import BorrowedBooks from './BorrowedBooks'
|
import CheckedOutBooks from './CheckedOutBooks'
|
||||||
import LoanedBooks from './LoanedBooks'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
repos: PaginatedDocs<Repository> | null
|
repos: PaginatedDocs<Repository> | null
|
||||||
borrows: PaginatedDocs<Checkout> | null
|
checkouts: PaginatedDocs<Checkout> | null
|
||||||
loans: PaginatedDocs<Checkout> | null
|
|
||||||
user: User | null
|
user: User | null
|
||||||
}
|
}
|
||||||
const Manage = (props: Props) => {
|
const Manage = (props: Props) => {
|
||||||
const { repos, borrows, loans, user } = props
|
const { repos, checkouts, user } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
@ -25,11 +23,7 @@ const Manage = (props: Props) => {
|
|||||||
<div>{user && <HoldRequestNotifications repos={repos} user={user} />}</div>
|
<div>{user && <HoldRequestNotifications repos={repos} user={user} />}</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<BorrowedBooks initialBorrows={borrows} />
|
<CheckedOutBooks initialCheckoutPage={checkouts} />
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<LoanedBooks initialLoans={loans} />
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -28,24 +28,21 @@ import {
|
|||||||
import { StackedLayout } from '@/components/stacked-layout'
|
import { StackedLayout } from '@/components/stacked-layout'
|
||||||
import { Media } from '@/payload-types'
|
import { Media } from '@/payload-types'
|
||||||
import { useGlobal } from '@/providers/GlobalProvider'
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
import { ArrowRightStartOnRectangleIcon, UserIcon } from '@heroicons/react/16/solid'
|
import { ArrowRightStartOnRectangleIcon, LightBulbIcon, UserIcon } from '@heroicons/react/16/solid'
|
||||||
import { MagnifyingGlassIcon, SunIcon, MoonIcon } from '@heroicons/react/20/solid'
|
import { MagnifyingGlassIcon, SunIcon, MoonIcon } from '@heroicons/react/20/solid'
|
||||||
import { useTheme } from 'next-themes'
|
import { useTheme } from 'next-themes'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React, { ReactNode, useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
|
|
||||||
type NavItem = {
|
const navItems = [
|
||||||
label: string
|
{ label: 'Home', url: '/' },
|
||||||
href: string
|
{ label: 'Browse', url: '/books' },
|
||||||
newTab: boolean
|
{ label: 'Events', url: '/events' },
|
||||||
}
|
{ label: 'Settings', url: '/settings' },
|
||||||
|
]
|
||||||
|
|
||||||
type Props = {
|
export default function SiteNavigation(props: { children: React.ReactNode }) {
|
||||||
children: ReactNode
|
const { children } = props
|
||||||
navItems: NavItem[]
|
|
||||||
}
|
|
||||||
export default function SiteNavigation(props: Props) {
|
|
||||||
const { children, navItems } = props
|
|
||||||
const { theme, setTheme } = useTheme()
|
const { theme, setTheme } = useTheme()
|
||||||
const { user, setUser } = useGlobal()
|
const { user, setUser } = useGlobal()
|
||||||
|
|
||||||
@ -73,17 +70,19 @@ export default function SiteNavigation(props: Props) {
|
|||||||
navbar={
|
navbar={
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<Dropdown>
|
<Dropdown>
|
||||||
<DropdownButton as={NavbarItem} className="max-lg:hidden">
|
<DropdownButton
|
||||||
<Link href="/" className="flex items-center justify-around gap-3">
|
as={NavbarItem}
|
||||||
<Avatar src="/api/media/file/bethel-logo.jpg" className="size-8" />
|
onClick={() => (window.location.href = '/')}
|
||||||
<NavbarLabel>Midrashim</NavbarLabel>
|
className="max-lg:hidden"
|
||||||
</Link>
|
>
|
||||||
|
<Avatar src="/api/media/file/bethel-logo.jpg" />
|
||||||
|
<NavbarLabel>Midrashim</NavbarLabel>
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<NavbarDivider className="max-lg:hidden" />
|
<NavbarDivider className="max-lg:hidden" />
|
||||||
<NavbarSection className="max-lg:hidden">
|
<NavbarSection className="max-lg:hidden">
|
||||||
{navItems.map(({ label, href, newTab }) => (
|
{navItems.map(({ label, url }) => (
|
||||||
<NavbarItem key={label} target={newTab ? '_blank' : ''} href={href}>
|
<NavbarItem key={label} href={url}>
|
||||||
{label}
|
{label}
|
||||||
</NavbarItem>
|
</NavbarItem>
|
||||||
))}
|
))}
|
||||||
@ -117,6 +116,10 @@ export default function SiteNavigation(props: Props) {
|
|||||||
<DropdownLabel>My profile</DropdownLabel>
|
<DropdownLabel>My profile</DropdownLabel>
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
)}
|
)}
|
||||||
|
<DropdownItem href="/feedback">
|
||||||
|
<LightBulbIcon />
|
||||||
|
<DropdownLabel>Share feedback</DropdownLabel>
|
||||||
|
</DropdownItem>
|
||||||
|
|
||||||
{!!user && (
|
{!!user && (
|
||||||
<>
|
<>
|
||||||
@ -146,8 +149,8 @@ export default function SiteNavigation(props: Props) {
|
|||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarBody>
|
<SidebarBody>
|
||||||
<SidebarSection>
|
<SidebarSection>
|
||||||
{navItems.map(({ label, href, newTab }) => (
|
{navItems.map(({ label, url }) => (
|
||||||
<SidebarItem key={label} href={href} target={newTab ? '_blank' : ''}>
|
<SidebarItem key={label} href={url}>
|
||||||
{label}
|
{label}
|
||||||
</SidebarItem>
|
</SidebarItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -8,8 +8,6 @@ import { Label } from '@/components/ui/label'
|
|||||||
import { useRouter, useSearchParams } from 'next/navigation'
|
import { useRouter, useSearchParams } from 'next/navigation'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useGlobal } from '@/providers/GlobalProvider'
|
import { useGlobal } from '@/providers/GlobalProvider'
|
||||||
import { Loader2 } from 'lucide-react'
|
|
||||||
import { toast } from 'sonner'
|
|
||||||
|
|
||||||
export function LoginForm({ className, ...props }: React.ComponentProps<'div'>) {
|
export function LoginForm({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -41,19 +39,14 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
|
|||||||
password,
|
password,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const loginResponse = await loginReq.json()
|
const user = await loginReq.json()
|
||||||
|
|
||||||
if (loginResponse.errors?.length) {
|
setUser(user.user)
|
||||||
loginResponse.errors.forEach((e: Error) => {
|
|
||||||
toast(e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loginResponse.user) setUser(loginResponse.user)
|
if (user.token) router.push(searchParams.get('redirectUrl') || '/')
|
||||||
|
} catch (error) {
|
||||||
if (loginResponse.token) router.push(searchParams.get('redirectUrl') || '/')
|
console.error('Login failed:', error)
|
||||||
} catch (_) {
|
} finally {
|
||||||
toast('Unknown issue while authenticating. Try again')
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,11 +85,16 @@ export function LoginForm({ className, ...props }: React.ComponentProps<'div'>)
|
|||||||
</div>
|
</div>
|
||||||
<Input id="password" name="password" type="password" required />
|
<Input id="password" name="password" type="password" required />
|
||||||
</div>
|
</div>
|
||||||
<Button disabled={isLoading} type="submit" className="w-full">
|
<Button type="submit" className="w-full">
|
||||||
{isLoading && <Loader2 />}
|
Login
|
||||||
<span>Login</span>
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="text-center text-sm">
|
||||||
|
Don't have an account?{' '}
|
||||||
|
<a href="/requestAccess" className="underline underline-offset-4">
|
||||||
|
Request Access
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
import { loadEnvConfig } from '@next/env'
|
|
||||||
|
|
||||||
const projectDir = process.cwd()
|
|
||||||
loadEnvConfig(projectDir)
|
|
||||||
@ -21,8 +21,9 @@ export const Header: GlobalConfig = {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'href',
|
name: 'page',
|
||||||
type: 'text'
|
type: 'relationship',
|
||||||
|
relationTo: 'pages'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'newTab',
|
name: 'newTab',
|
||||||
|
|||||||
@ -350,7 +350,7 @@ export interface Checkout {
|
|||||||
isReturned?: boolean | null;
|
isReturned?: boolean | null;
|
||||||
dateDue?: string | null;
|
dateDue?: string | null;
|
||||||
ownerVerifiedReturnedDate?: string | null;
|
ownerVerifiedReturnedDate?: string | null;
|
||||||
loaneeReturnedDate?: string | null;
|
loanerReturnedDate?: string | null;
|
||||||
book?: {
|
book?: {
|
||||||
docs?: (number | Copy)[];
|
docs?: (number | Copy)[];
|
||||||
hasNextPage?: boolean;
|
hasNextPage?: boolean;
|
||||||
@ -598,7 +598,7 @@ export interface CheckoutsSelect<T extends boolean = true> {
|
|||||||
isReturned?: T;
|
isReturned?: T;
|
||||||
dateDue?: T;
|
dateDue?: T;
|
||||||
ownerVerifiedReturnedDate?: T;
|
ownerVerifiedReturnedDate?: T;
|
||||||
loaneeReturnedDate?: T;
|
loanerReturnedDate?: T;
|
||||||
book?: T;
|
book?: T;
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
@ -678,7 +678,7 @@ export interface Header {
|
|||||||
headerLinks?:
|
headerLinks?:
|
||||||
| {
|
| {
|
||||||
label?: string | null;
|
label?: string | null;
|
||||||
href?: string | null;
|
page?: (number | null) | Page;
|
||||||
newTab?: boolean | null;
|
newTab?: boolean | null;
|
||||||
id?: string | null;
|
id?: string | null;
|
||||||
}[]
|
}[]
|
||||||
@ -696,7 +696,7 @@ export interface HeaderSelect<T extends boolean = true> {
|
|||||||
| T
|
| T
|
||||||
| {
|
| {
|
||||||
label?: T;
|
label?: T;
|
||||||
href?: T;
|
page?: T;
|
||||||
newTab?: T;
|
newTab?: T;
|
||||||
id?: T;
|
id?: T;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,8 +19,6 @@ import { Pages } from './collections/Pages/Pages'
|
|||||||
import HoldRequests from './collections/Checkouts/HoldRequests'
|
import HoldRequests from './collections/Checkouts/HoldRequests'
|
||||||
import Checkouts from './collections/Checkouts/Checkouts'
|
import Checkouts from './collections/Checkouts/Checkouts'
|
||||||
|
|
||||||
import './envConfig.ts'
|
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url)
|
const filename = fileURLToPath(import.meta.url)
|
||||||
const dirname = path.dirname(filename)
|
const dirname = path.dirname(filename)
|
||||||
|
|
||||||
@ -32,8 +30,8 @@ export default buildConfig({
|
|||||||
},
|
},
|
||||||
dateFormat: 'MM/dd/yyyy',
|
dateFormat: 'MM/dd/yyyy',
|
||||||
},
|
},
|
||||||
cors: [process.env.SERVER_URL || ''],
|
//cors: [process.env.DOMAIN_NAME || ''],
|
||||||
csrf: [process.env.SERVER_URL || ''],
|
//csrf: [process.env.DOMAIN_NAME || ''],
|
||||||
upload: {
|
upload: {
|
||||||
limits: {
|
limits: {
|
||||||
fileSize: 5000000, // in bytes
|
fileSize: 5000000, // in bytes
|
||||||
|
|||||||
@ -8,7 +8,7 @@ type GlobalProps = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GlobalState = {
|
type GlobalState = {
|
||||||
setUser: (user?: User) => void
|
setUser: (users?: User) => void
|
||||||
} & GlobalProps
|
} & GlobalProps
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
|
|||||||
@ -1,53 +0,0 @@
|
|||||||
'use server'
|
|
||||||
|
|
||||||
import { getPayload } from 'payload'
|
|
||||||
import config from '@/payload.config'
|
|
||||||
import type { PaginatedDocs } from "payload"
|
|
||||||
import { Checkout } from '@/payload-types'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
userId: number
|
|
||||||
}
|
|
||||||
export const getUserBorrows = async (props: Props) => {
|
|
||||||
const { userId } = props
|
|
||||||
|
|
||||||
const payloadConfig = await config
|
|
||||||
const payload = await getPayload({ config: payloadConfig })
|
|
||||||
|
|
||||||
let userBorrows: PaginatedDocs<Checkout> | null = null
|
|
||||||
if (userId)
|
|
||||||
userBorrows = await payload.find({
|
|
||||||
collection: 'checkouts',
|
|
||||||
depth: 3,
|
|
||||||
limit: 10,
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
copy: true,
|
|
||||||
dateDue: true,
|
|
||||||
ownerVerifiedReturnedDate: true,
|
|
||||||
loaneeReturnedDate: true,
|
|
||||||
},
|
|
||||||
sort: 'dateDue',
|
|
||||||
where: {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
isReturned: {
|
|
||||||
not_equals: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'user.id': {
|
|
||||||
equals: userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ownerVerifiedReturnedDate: {
|
|
||||||
equals: null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}) as PaginatedDocs<Checkout>
|
|
||||||
|
|
||||||
return userBorrows
|
|
||||||
}
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
'use server'
|
|
||||||
|
|
||||||
import { getPayload } from 'payload'
|
|
||||||
import config from '@/payload.config'
|
|
||||||
import type { PaginatedDocs } from "payload"
|
|
||||||
import { Checkout } from '@/payload-types'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
userId: number
|
|
||||||
}
|
|
||||||
export const getUserLoans = async (props: Props) => {
|
|
||||||
const { userId } = props
|
|
||||||
|
|
||||||
const payloadConfig = await config
|
|
||||||
const payload = await getPayload({ config: payloadConfig })
|
|
||||||
|
|
||||||
let userLoans: PaginatedDocs<Checkout> | null = null
|
|
||||||
if (userId)
|
|
||||||
userLoans = await payload.find({
|
|
||||||
collection: 'checkouts',
|
|
||||||
depth: 3,
|
|
||||||
limit: 10,
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
copy: true,
|
|
||||||
dateDue: true,
|
|
||||||
ownerVerifiedReturnedDate: true,
|
|
||||||
loaneeReturnedDate: true,
|
|
||||||
},
|
|
||||||
sort: 'dateDue',
|
|
||||||
where: {
|
|
||||||
and: [
|
|
||||||
{
|
|
||||||
isReturned: {
|
|
||||||
not_equals: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'user.id': {
|
|
||||||
equals: userId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}) as PaginatedDocs<Checkout>
|
|
||||||
|
|
||||||
return userLoans
|
|
||||||
}
|
|
||||||
@ -17,14 +17,16 @@ export const resetPassword = async (props: Props): Promise<boolean> => {
|
|||||||
const payload = await getPayload({ config: payloadConfig })
|
const payload = await getPayload({ config: payloadConfig })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await payload.resetPassword({
|
const result = await payload.resetPassword({
|
||||||
collection: 'users',
|
collection: 'users',
|
||||||
overrideAccess: true,
|
overrideAccess: false,
|
||||||
data: {
|
data: {
|
||||||
password: password,
|
password: password,
|
||||||
token: token,
|
token: token,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
console.log('result')
|
||||||
|
console.log(result)
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
|
|||||||
@ -1,51 +0,0 @@
|
|||||||
'use server'
|
|
||||||
|
|
||||||
import { getPayload } from 'payload'
|
|
||||||
import config from '@/payload.config'
|
|
||||||
import { Checkout } from '@/payload-types'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
checkoutId: number
|
|
||||||
}
|
|
||||||
export const loaneeReturnCheckout = async (props: Props): Promise<Checkout | null> => {
|
|
||||||
const { checkoutId } = props
|
|
||||||
|
|
||||||
const payloadConfig = await config
|
|
||||||
const payload = await getPayload({ config: payloadConfig })
|
|
||||||
|
|
||||||
try {
|
|
||||||
const updatedCheckout = await payload.update({
|
|
||||||
collection: 'checkouts',
|
|
||||||
id: checkoutId,
|
|
||||||
data: {
|
|
||||||
loaneeReturnedDate: new Date().toDateString(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return updatedCheckout
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ownerReturnCheckout = async (props: Props): Promise<Checkout | null> => {
|
|
||||||
const { checkoutId } = props
|
|
||||||
|
|
||||||
const payloadConfig = await config
|
|
||||||
const payload = await getPayload({ config: payloadConfig })
|
|
||||||
|
|
||||||
try {
|
|
||||||
const updatedCheckout = await payload.update({
|
|
||||||
collection: 'checkouts',
|
|
||||||
id: checkoutId,
|
|
||||||
data: {
|
|
||||||
ownerVerifiedReturnedDate: new Date().toDateString(),
|
|
||||||
isReturned: true,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return updatedCheckout
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user