@@ -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
+
+ {hold.holdingUntilDate}
+
+
+ ) : (
+
+ Requested
+ {!!hold.dateRequested && (
+
+ {new Date(hold.dateRequested).toLocaleDateString()}
+
+ )}
+
+ )}
+
+
+
+
+
+
+
+ Decline
+
+
+
+ Approve
+
+
+
+
+ )
+ })}
+
+ )
+ })
+
+ 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}
+
+
+
+
+ View Repo
+
+
+
+
+
+
+ )
+ })}
+
+ )
+}
+
+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
+}