From f291efcc18412a8b4a47da77246bbd5636e021f9 Mon Sep 17 00:00:00 2001 From: Yehoshua Sandler Date: Mon, 28 Apr 2025 16:26:18 -0500 Subject: [PATCH] feat: theme control, user manage feed, globl provider --- package-lock.json | 47 +++++ package.json | 1 + public/images/approve.svg | 1 + public/images/library.svg | 1 + public/images/reject.svg | 1 + src/app/(frontend)/layout.tsx | 162 +---------------- src/app/(frontend)/page.tsx | 39 ++++- src/app/globals.css | 2 +- src/app/layout.tsx | 12 +- src/collections/Repositories/Repositories.ts | 13 +- src/collections/Users.ts | 15 +- src/components/BookList/index.tsx | 10 +- src/components/Feed/UserFeed.tsx | 11 ++ src/components/Manage/HoldRequests.tsx | 91 ++++++++++ src/components/Manage/Manage.tsx | 27 +++ src/components/Manage/RepoList.tsx | 79 +++++++++ src/components/Search/SearchBooks.tsx | 4 +- .../Search/SearchBooksInlineForm.tsx | 11 +- src/components/SiteNavigation.tsx | 164 ++++++++++++++++++ src/components/ThemeProvider.tsx | 7 + src/components/stacked-layout.tsx | 2 +- src/components/ui/separator.tsx | 28 +++ src/payload-types.ts | 157 +++++++++-------- src/providers/GlobalProvider.tsx | 35 ++++ .../GetHoldRequestsOnUserRepo.ts | 35 ++++ 25 files changed, 700 insertions(+), 255 deletions(-) create mode 100644 public/images/approve.svg create mode 100644 public/images/library.svg create mode 100644 public/images/reject.svg create mode 100644 src/components/Manage/HoldRequests.tsx create mode 100644 src/components/Manage/Manage.tsx create mode 100644 src/components/Manage/RepoList.tsx create mode 100644 src/components/SiteNavigation.tsx create mode 100644 src/components/ThemeProvider.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/providers/GlobalProvider.tsx create mode 100644 src/serverActions/GetHoldRequestsOnUserRepo.ts diff --git a/package-lock.json b/package-lock.json index 39c6bec..9bae96e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@payloadcms/payload-cloud": "3.31.0", "@payloadcms/richtext-lexical": "3.31.0", "@radix-ui/react-label": "^2.1.4", + "@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.4", "@tailwindcss/cli": "^4.1.4", @@ -4285,6 +4286,52 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.4.tgz", + "integrity": "sha512-2fTm6PSiUm8YPq9W0E4reYuv01EE3aFSzt8edBiXqPHshF8N9+Kymt/k0/R+F3dkY5lQyB/zPtrP82phskLi7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz", + "integrity": "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", diff --git a/package.json b/package.json index 9214cec..38212b5 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@payloadcms/payload-cloud": "3.31.0", "@payloadcms/richtext-lexical": "3.31.0", "@radix-ui/react-label": "^2.1.4", + "@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.4", "@tailwindcss/cli": "^4.1.4", diff --git a/public/images/approve.svg b/public/images/approve.svg new file mode 100644 index 0000000..2be0320 --- /dev/null +++ b/public/images/approve.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/library.svg b/public/images/library.svg new file mode 100644 index 0000000..3a0f013 --- /dev/null +++ b/public/images/library.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/reject.svg b/public/images/reject.svg new file mode 100644 index 0000000..4534217 --- /dev/null +++ b/public/images/reject.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/(frontend)/layout.tsx b/src/app/(frontend)/layout.tsx index 437435d..1bc73fa 100644 --- a/src/app/(frontend)/layout.tsx +++ b/src/app/(frontend)/layout.tsx @@ -1,169 +1,13 @@ -import { Avatar } from '@/components/avatar' -import { - Dropdown, - DropdownButton, - DropdownDivider, - DropdownItem, - DropdownLabel, - DropdownMenu, -} from '@/components/dropdown' -import { - Navbar, - NavbarDivider, - NavbarItem, - NavbarLabel, - NavbarSection, - NavbarSpacer, -} from '@/components/navbar' -import { - Sidebar, - SidebarBody, - SidebarHeader, - SidebarItem, - SidebarLabel, - SidebarSection, -} from '@/components/sidebar' -import { StackedLayout } from '@/components/stacked-layout' -import { - ArrowRightStartOnRectangleIcon, - ChevronDownIcon, - Cog8ToothIcon, - LightBulbIcon, - PlusIcon, - ShieldCheckIcon, - UserIcon, -} from '@heroicons/react/16/solid' -import { InboxIcon, MagnifyingGlassIcon } from '@heroicons/react/20/solid' -import { ThemeProvider } from 'next-themes' +import SiteNavigation from '@/components/SiteNavigation' import React from 'react' export const metadata = { - description: "A Community's Collection", + description: 'House of Study for Temple Beth El', title: 'Midrashim', } -const navItems = [ - { label: 'Home', url: '/' }, - { label: 'Events', url: '/events' }, - { label: 'Explore', url: '/browse' }, - { label: 'Settings', url: '/settings' }, -] - -function TeamDropdownMenu() { - return ( - - - - Settings - - - - - Beth-El - - - - Emanuel - - - - - New Repository… - - - ) -} - export default async function RootLayout(props: { children: React.ReactNode }) { const { children } = props - return ( - - - - - - Midrashim - - - - - - - {navItems.map(({ label, url }) => ( - - {label} - - ))} - - - - - - - - - - - - - - - - - My profile - - - - Settings - - - - - Privacy policy - - - - Share feedback - - - - - Sign out - - - - - - } - sidebar={ - - - - - - Midrashim - - - - - - - - {navItems.map(({ label, url }) => ( - - {label} - - ))} - - - - } - > - {children} - - - ) + return {children} } diff --git a/src/app/(frontend)/page.tsx b/src/app/(frontend)/page.tsx index 8c1ad17..bbe9645 100644 --- a/src/app/(frontend)/page.tsx +++ b/src/app/(frontend)/page.tsx @@ -2,15 +2,14 @@ import { headers as getHeaders } from 'next/headers.js' import { getPayload, PaginatedDocs } from 'payload' import config from '@/payload.config' import React from 'react' -import { fileURLToPath } from 'url' -import BookList from '@/components/BookList' import UserFeed from '@/components/Feed/UserFeed' -import { Book } from '@/payload-types' +import { Book, HoldRequest, Repository } from '@/payload-types' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { TextShimmer } from '@/components/ui/text-shimmer' import { LoginForm } from '@/components/login-form' import SearchBooks from '@/components/Search/SearchBooks' +import Manage from '@/components/Manage/Manage' export default async function HomePage() { const headers = await getHeaders() @@ -18,8 +17,6 @@ export default async function HomePage() { const payload = await getPayload({ config: payloadConfig }) const { user } = await payload.auth({ headers }) - const fileURL = `vscode://file/${fileURLToPath(import.meta.url)}` - const initBrowseBooks = (await payload.find({ collection: 'books', depth: 10, @@ -36,6 +33,32 @@ export default async function HomePage() { }, })) as PaginatedDocs + let userRepos: PaginatedDocs | 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 + return (
@@ -103,7 +126,7 @@ export default async function HomePage() { Your Feed Search - Your Repos + Manage {user && } @@ -112,7 +135,9 @@ export default async function HomePage() { - Your Repos + + + ) : (
diff --git a/src/app/globals.css b/src/app/globals.css index a3a403c..ad0520c 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -134,4 +134,4 @@ html[data-theme='dark'], html[data-theme='light'] { opacity: initial; -} \ No newline at end of file +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c49729e..84a1776 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,4 +1,5 @@ import { ReactNode } from 'react' +import { ThemeProvider } from '@/components/ThemeProvider' type LayoutProps = { children: ReactNode @@ -6,12 +7,19 @@ type LayoutProps = { import './globals.css' import { Toaster } from '@/components/ui/sonner' +import { GlobalProvider } from '@/providers/GlobalProvider' const Layout = ({ children }: LayoutProps) => { return ( - {children} - + + + + {children} + + + + ) } diff --git a/src/collections/Repositories/Repositories.ts b/src/collections/Repositories/Repositories.ts index 48feaa0..85c8872 100644 --- a/src/collections/Repositories/Repositories.ts +++ b/src/collections/Repositories/Repositories.ts @@ -1,7 +1,8 @@ import { CollectionConfig } from "payload"; export const Repositories: CollectionConfig = { - slug: 'repositories', admin: { + slug: 'repositories', + admin: { useAsTitle: 'name' }, fields: [ @@ -20,6 +21,15 @@ export const Repositories: CollectionConfig = { description: 'This is used to help identify which copies belong to this repo.' } }, + { + name: 'image', + type: 'relationship', + relationTo: 'media' + }, + { + name: 'description', + type: 'textarea', + }, { name: 'owner', type: 'relationship', @@ -37,6 +47,7 @@ export const Repositories: CollectionConfig = { collection: 'holdRequests', on: 'repository', defaultSort: 'dateRequested', + maxDepth: 3, where: { or: [ { diff --git a/src/collections/Users.ts b/src/collections/Users.ts index bd425ff..f8e55c2 100644 --- a/src/collections/Users.ts +++ b/src/collections/Users.ts @@ -1,5 +1,5 @@ import { defaultAccess } from '@/lib/utils' -import type { CollectionConfig, PayloadRequest } from 'payload' +import type { CollectionConfig } from 'payload' export const Users: CollectionConfig = { slug: 'users', @@ -35,6 +35,17 @@ export const Users: CollectionConfig = { { name: 'isOwnershipClaimed', type: 'checkbox', - } + }, + { + name: 'repositories', + type: 'join', + collection: 'repositories', + on: 'owner', + }, + { + name: 'profilePicture', + type: 'relationship', + relationTo: 'media', + }, ], } diff --git a/src/components/BookList/index.tsx b/src/components/BookList/index.tsx index a5559c0..d7ce940 100644 --- a/src/components/BookList/index.tsx +++ b/src/components/BookList/index.tsx @@ -9,10 +9,8 @@ import { PaginationPrevious, } from '@/components/ui/pagination' import { Author, Book, Genre } from '@/payload-types' -import { Avatar } from '../avatar' import { PaginatedDocs } from 'payload' import Link from 'next/link' -import Image from 'next/image' type Props = { books: PaginatedDocs @@ -57,11 +55,9 @@ export default function BookList(props: Props) { {books?.map((b) => (
  • -
  • -
    +

    {makeAuthorsLabel(b)}

    {!b.copies?.docs?.length ? (

    No copies found

    diff --git a/src/components/Feed/UserFeed.tsx b/src/components/Feed/UserFeed.tsx index c4ca486..2111e84 100644 --- a/src/components/Feed/UserFeed.tsx +++ b/src/components/Feed/UserFeed.tsx @@ -6,6 +6,7 @@ import Image from 'next/image' import { BorderTrail } from 'components/motion-primitives/border-trail' import clsx from 'clsx' import Link from 'next/link' +import { LoginForm } from '../login-form' const stats = [ { name: 'Outbound Loans', stat: '13' }, @@ -20,6 +21,13 @@ const UserFeed = async (props: Props) => { const { user } = props const isLoggedIn = !!user + if (!isLoggedIn) + return ( +
    + +
    + ) + const payloadConfig = await config const payload = await getPayload({ config: payloadConfig }) @@ -37,6 +45,9 @@ const UserFeed = async (props: Props) => { userRequested: { equals: user?.id, }, + isCheckedOut: { + not_equals: true, + }, }, })) as PaginatedDocs diff --git a/src/components/Manage/HoldRequests.tsx b/src/components/Manage/HoldRequests.tsx new file mode 100644 index 0000000..d9488c8 --- /dev/null +++ b/src/components/Manage/HoldRequests.tsx @@ -0,0 +1,91 @@ +import { PaginatedDocs } from 'payload' +import { Author, Book, HoldRequest, Repository } from '@/payload-types' +import { Button } from '../ui/button' +import Image from 'next/image' + +type Props = { + repos: PaginatedDocs | null +} +const HoldRequestNotifications = (props: Props) => { + const { repos } = props + + const totalHoldNotifications = repos?.docs.flatMap((r) => r.holdRequests?.docs).length || 0 + + const holdRequestsByRepoElements = repos?.docs.map((r) => { + return ( +
      + {r.holdRequests?.docs?.map((h) => { + const hold = h as HoldRequest + const book = hold.book as Book + const authors = book.authors as Author[] + return ( +
    • +
      +
      +
      +

      {book.title}

      +
      +

      + {authors.map((a) => a.lf).join(' | ')} +

      + {hold.isHolding ? ( + + Hold Until + + + ) : ( + + Requested + {!!hold.dateRequested && ( + + )} + + )} +
      + +
      +
      +
      + + +
      +
      +
    • + ) + })} +
    + ) + }) + + return ( +
    +
    +

    Inbound Hold Requests

    + {!!totalHoldNotifications && ( + {totalHoldNotifications} Unaddressed + )} +
    + {holdRequestsByRepoElements} +
    + ) +} + +export default HoldRequestNotifications diff --git a/src/components/Manage/Manage.tsx b/src/components/Manage/Manage.tsx new file mode 100644 index 0000000..4cffe5f --- /dev/null +++ b/src/components/Manage/Manage.tsx @@ -0,0 +1,27 @@ +'use client' + +import { Repository } from '@/payload-types' +import { PaginatedDocs } from 'payload' +import RepoList from './RepoList' +import HoldRequestNotifications from './HoldRequests' + +type Props = { + repos: PaginatedDocs | null +} +const Manage = (props: Props) => { + const { repos } = props + + return ( +
    +
    + +
    + +
    + +
    +
    + ) +} + +export default Manage diff --git a/src/components/Manage/RepoList.tsx b/src/components/Manage/RepoList.tsx new file mode 100644 index 0000000..5a1c259 --- /dev/null +++ b/src/components/Manage/RepoList.tsx @@ -0,0 +1,79 @@ +import { ArrowRight, Plus } from 'lucide-react' +import React from 'react' + +import { Button } from '@/components/ui/button' +import { Separator } from '@/components/ui/separator' +import { PaginatedDocs } from 'payload' +import { Media, Repository } from '@/payload-types' +import Image from 'next/image' +import Link from 'next/link' + +type Props = { + repos: PaginatedDocs | null +} + +const RepoList = (props: Props) => { + const { repos } = props + + return ( +
    +
    +

    Your Repositories

    + + + Add New + +
    + + {repos?.docs.map((item, index) => { + const image = (item.image as Media) || undefined + + return ( + +
    +
    + + + +
    +

    {item.name}

    +

    {item.abbreviation}

    +

    + {item.dateOpenToPublic ? ( + Opened: {item.dateOpenToPublic} + ) : ( + Not Opened to Public + )} +

    +

    {item.description}

    +
    +
    + +
    + +
    + ) + })} +
    + ) +} + +export default RepoList diff --git a/src/components/Search/SearchBooks.tsx b/src/components/Search/SearchBooks.tsx index b75b11a..0d3fb71 100644 --- a/src/components/Search/SearchBooks.tsx +++ b/src/components/Search/SearchBooks.tsx @@ -19,9 +19,9 @@ const SearchBooks = (props: Props) => { } return ( -
    +
    - {bookList && } +
    {bookList && }
    ) } diff --git a/src/components/Search/SearchBooksInlineForm.tsx b/src/components/Search/SearchBooksInlineForm.tsx index 6feec76..ac65017 100644 --- a/src/components/Search/SearchBooksInlineForm.tsx +++ b/src/components/Search/SearchBooksInlineForm.tsx @@ -60,7 +60,10 @@ const SearchBooksInlineForm = (props: Props) => { return (
    - + { )} /> - diff --git a/src/components/SiteNavigation.tsx b/src/components/SiteNavigation.tsx new file mode 100644 index 0000000..e1d2994 --- /dev/null +++ b/src/components/SiteNavigation.tsx @@ -0,0 +1,164 @@ +'use client' + +import { Avatar } from '@/components/avatar' +import { + Dropdown, + DropdownButton, + DropdownDivider, + DropdownItem, + DropdownLabel, + DropdownMenu, +} from '@/components/dropdown' +import { + Navbar, + NavbarDivider, + NavbarItem, + NavbarLabel, + NavbarSection, + NavbarSpacer, +} from '@/components/navbar' +import { + Sidebar, + SidebarBody, + SidebarHeader, + SidebarItem, + SidebarLabel, + SidebarSection, +} from '@/components/sidebar' +import { StackedLayout } from '@/components/stacked-layout' +import { Media } from '@/payload-types' +import { useGlobal } from '@/providers/GlobalProvider' +import { ArrowRightStartOnRectangleIcon, LightBulbIcon, UserIcon } from '@heroicons/react/16/solid' +import { MagnifyingGlassIcon, SunIcon, MoonIcon } from '@heroicons/react/20/solid' +import { useTheme } from 'next-themes' +import React, { useMemo } from 'react' + +const navItems = [ + { label: 'Home', url: '/' }, + { label: 'Events', url: '/events' }, + { label: 'Explore', url: '/browse' }, + { label: 'Settings', url: '/settings' }, +] + +export default function SiteNavigation(props: { children: React.ReactNode }) { + const { children } = props + const { theme, setTheme } = useTheme() + const { user, setUser } = useGlobal() + + useMemo(() => { + if (user) return + fetch('/api/users/me', { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + }, + }).then(async (response) => { + const userRequest = await response.json() + setUser(userRequest.user) + console.log(userRequest.user) + }) + }, [user?.id]) + + const profilePicture = user?.profilePicture as Media | undefined + const initials = user?.firstName + ? `${user?.firstName.slice(0, 1) || ''}${user?.lastName?.slice(0, 1)}` + : 'U' + + return ( + + + (window.location.href = '/')} + className="max-lg:hidden" + > + + Midrashim + + + + + {navItems.map(({ label, url }) => ( + + {label} + + ))} + + + + + + + setTheme(theme === 'dark' ? 'light' : 'dark')} + > + + + + + + + + + + + My profile + + {/* + + Settings + + + + + Privacy policy + */} + + + Share feedback + + + + + Sign out + + + + + + } + sidebar={ + + + + (window.location.href = '/')} + > + + Midrashim + + + + + + {navItems.map(({ label, url }) => ( + + {label} + + ))} + + + + } + > + {children} + + ) +} diff --git a/src/components/ThemeProvider.tsx b/src/components/ThemeProvider.tsx new file mode 100644 index 0000000..f0e58dd --- /dev/null +++ b/src/components/ThemeProvider.tsx @@ -0,0 +1,7 @@ +'use client' + +import { ThemeProvider as NextThemesProvider, ThemeProviderProps } from 'next-themes' + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return {children} +} diff --git a/src/components/stacked-layout.tsx b/src/components/stacked-layout.tsx index f4d81ff..1c59d1f 100644 --- a/src/components/stacked-layout.tsx +++ b/src/components/stacked-layout.tsx @@ -56,7 +56,7 @@ export function StackedLayout({ let [showSidebar, setShowSidebar] = useState(false) return ( -
    +
    {/* Sidebar on mobile */} setShowSidebar(false)}> {sidebar} diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx new file mode 100644 index 0000000..67c73e5 --- /dev/null +++ b/src/components/ui/separator.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Separator } diff --git a/src/payload-types.ts b/src/payload-types.ts index 90eaf46..ff9f8b6 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -82,6 +82,9 @@ export interface Config { 'payload-migrations': PayloadMigration; }; collectionsJoins: { + users: { + repositories: 'repositories'; + }; books: { copies: 'copies'; }; @@ -159,6 +162,12 @@ export interface User { firstName?: string | null; lastName?: string | null; isOwnershipClaimed?: boolean | null; + repositories?: { + docs?: (number | Repository)[]; + hasNextPage?: boolean; + totalDocs?: number; + }; + profilePicture?: (number | null) | Media; updatedAt: string; createdAt: string; email: string; @@ -170,6 +179,29 @@ export interface User { lockUntil?: string | null; password?: string | null; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "repositories". + */ +export interface Repository { + id: number; + name: string; + /** + * This is used to help identify which copies belong to this repo. + */ + abbreviation: string; + image?: (number | null) | Media; + description?: string | null; + 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` "media". @@ -189,6 +221,56 @@ export interface Media { focalX?: number | null; focalY?: number | null; } +/** + * 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` "copies". + */ +export interface Copy { + id: number; + label?: string | null; + condition?: number | null; + book?: (number | null) | Book; + repository?: (number | null) | Repository; + notes?: { + root: { + type: string; + children: { + type: string; + version: number; + [k: string]: unknown; + }[]; + direction: ('ltr' | 'rtl') | null; + format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; + indent: number; + version: number; + }; + [k: string]: unknown; + } | 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` "books". @@ -256,77 +338,6 @@ export interface Genre { updatedAt: string; createdAt: string; } -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "copies". - */ -export interface Copy { - id: number; - label?: string | null; - condition?: number | null; - book?: (number | null) | Book; - repository?: (number | null) | Repository; - notes?: { - root: { - type: string; - children: { - type: string; - version: number; - [k: string]: unknown; - }[]; - direction: ('ltr' | 'rtl') | null; - format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | ''; - indent: number; - version: number; - }; - [k: string]: unknown; - } | 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` "repositories". - */ -export interface Repository { - id: number; - name: string; - /** - * This is used to help identify which copies belong to this repo. - */ - 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". @@ -464,6 +475,8 @@ export interface UsersSelect { firstName?: T; lastName?: T; isOwnershipClaimed?: T; + repositories?: T; + profilePicture?: T; updatedAt?: T; createdAt?: T; email?: T; @@ -532,6 +545,8 @@ export interface AuthorsSelect { export interface RepositoriesSelect { name?: T; abbreviation?: T; + image?: T; + description?: T; owner?: T; dateOpenToPublic?: T; holdRequests?: T; diff --git a/src/providers/GlobalProvider.tsx b/src/providers/GlobalProvider.tsx new file mode 100644 index 0000000..0b1e389 --- /dev/null +++ b/src/providers/GlobalProvider.tsx @@ -0,0 +1,35 @@ +'use client' + +import { User } from '@/payload-types' +import { createContext, ReactNode, useContext, useState } from 'react' + +type GlobalProps = { + user?: User +} + +type GlobalState = { + setUser: (users?: User) => void +} & GlobalProps + +const defaultState = { + user: undefined, + setUser: (user?: User) => {}, +} + +const GlobalContext = createContext(defaultState) + +type Props = { children: ReactNode; globalProps: GlobalProps } +export function GlobalProvider({ children, globalProps }: Props) { + const [user, setUser] = useState(globalProps.user) + + const value = { + user, + setUser, + } + + return {children} +} + +export function useGlobal() { + return useContext(GlobalContext) +} diff --git a/src/serverActions/GetHoldRequestsOnUserRepo.ts b/src/serverActions/GetHoldRequestsOnUserRepo.ts new file mode 100644 index 0000000..8e56eec --- /dev/null +++ b/src/serverActions/GetHoldRequestsOnUserRepo.ts @@ -0,0 +1,35 @@ +'use server' + +import { getPayload, PaginatedDocs } from 'payload' +import config from '@/payload.config' +import { HoldRequest } from '@/payload-types' + +type Props = { + userId: number +} +export const getHoldRequestOnUserRepo = async (props: Props): Promise | null> => { + const { userId } = props + + const payloadConfig = await config + const payload = await getPayload({ config: payloadConfig }) + + const findResponse = (await payload.find({ + collection: 'holdRequests', + depth: 2, + limit: 25, + overrideAccess: false, + where: { + 'repository.owner': { + equals: userId + }, + }, + select: { + id: true, + copy: true, + repository: true, + book: true, + }, + })) as PaginatedDocs + + return findResponse +}