From f23bc1976f81d4ba5918f8531cc149f01042d64c Mon Sep 17 00:00:00 2001 From: Yehoshua Sandler Date: Tue, 22 Apr 2025 13:14:04 -0500 Subject: [PATCH] feat: initial user feed, and some page ui --- components/motion-primitives/border-trail.tsx | 43 +++++++ next.config.mjs | 4 +- src/app/(frontend)/page.tsx | 72 +++++++---- src/app/(frontend)/serverCalls/requestHold.ts | 10 +- src/collections/Users.ts | 10 +- src/components/Feed/UserFeed.tsx | 121 ++++++++++++++++++ src/components/ui/card.tsx | 78 +++++++++++ src/payload-types.ts | 4 + 8 files changed, 316 insertions(+), 26 deletions(-) create mode 100644 components/motion-primitives/border-trail.tsx create mode 100644 src/components/Feed/UserFeed.tsx create mode 100644 src/components/ui/card.tsx diff --git a/components/motion-primitives/border-trail.tsx b/components/motion-primitives/border-trail.tsx new file mode 100644 index 0000000..634b3dc --- /dev/null +++ b/components/motion-primitives/border-trail.tsx @@ -0,0 +1,43 @@ +'use client'; +import { cn } from '@/lib/utils'; +import { motion, Transition } from 'motion/react'; + +export type BorderTrailProps = { + className?: string; + size?: number; + transition?: Transition; + onAnimationComplete?: () => void; + style?: React.CSSProperties; +}; + +export function BorderTrail({ + className, + size = 60, + transition, + onAnimationComplete, + style, +}: BorderTrailProps) { + const defaultTransition: Transition = { + repeat: Infinity, + duration: 5, + ease: 'linear', + }; + + return ( +
+ +
+ ); +} diff --git a/next.config.mjs b/next.config.mjs index de1c37d..3e309f8 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -2,7 +2,9 @@ import { withPayload } from '@payloadcms/next/withPayload' /** @type {import('next').NextConfig} */ const nextConfig = { - // Your Next.js config here + images: { + domains: ['covers.openlibrary.org'], + }, } export default withPayload(nextConfig, { devBundleServerPackages: false }) diff --git a/src/app/(frontend)/page.tsx b/src/app/(frontend)/page.tsx index 5f93747..f73ba71 100644 --- a/src/app/(frontend)/page.tsx +++ b/src/app/(frontend)/page.tsx @@ -5,6 +5,7 @@ import { fileURLToPath } from 'url' import config from '@/payload.config' import BookList from '@/components/BookList' +import UserFeed from '@/components/Feed/UserFeed' import { Book } from '@/payload-types' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { TextShimmer } from '@/components/ui/text-shimmer' @@ -35,26 +36,60 @@ export default async function HomePage() { return (
-
-
-
-

- Engage In Our Community Resources -

+
+ -

+ ) } diff --git a/src/app/(frontend)/serverCalls/requestHold.ts b/src/app/(frontend)/serverCalls/requestHold.ts index c8ce006..89fe748 100644 --- a/src/app/(frontend)/serverCalls/requestHold.ts +++ b/src/app/(frontend)/serverCalls/requestHold.ts @@ -1,5 +1,5 @@ 'use server' - +import { headers as nextHeaders } from 'next/headers' import { getPayload } from "payload" import configPromise from '@payload-config' @@ -10,11 +10,19 @@ type Props = { const requestHold = async (props: Props) => { const payload = await getPayload({ config: configPromise }) + const headers = await nextHeaders() + const authResponse = await payload.auth({ headers }) + console.log(authResponse.user); + + if (!authResponse.user?.id) return + const requestHoldResponse = await payload.create({ collection: 'holdRequests', data: { repository: props.repositoryId, book: props.bookId, + userRequested: authResponse.user.id, + dateRequested: new Date().toUTCString(), }, }) diff --git a/src/collections/Users.ts b/src/collections/Users.ts index cd71f32..25f9832 100644 --- a/src/collections/Users.ts +++ b/src/collections/Users.ts @@ -14,6 +14,14 @@ export const Users: CollectionConfig = { type: 'select', options: ['admin', 'user'], saveToJWT: true - } + }, + { + name: 'firstName', + type: 'text', + }, + { + name: 'lastName', + type: 'text', + }, ], } diff --git a/src/components/Feed/UserFeed.tsx b/src/components/Feed/UserFeed.tsx new file mode 100644 index 0000000..155dcf3 --- /dev/null +++ b/src/components/Feed/UserFeed.tsx @@ -0,0 +1,121 @@ +import type { Book, Copy, HoldRequest, Repository, User } from '@/payload-types' +import { getPayload, PaginatedDocs } from 'payload' +import config from '@/payload.config' +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '../ui/card' +import Image from 'next/image' +import { BorderTrail } from 'components/motion-primitives/border-trail' +import clsx from 'clsx' + +const stats = [ + { name: 'Outbound Loans', stat: '13' }, + { name: 'Active Holds', stat: '6' }, + { name: 'Hold Requests', stat: '3', shouldHighlight: true }, +] + +type Props = { + user?: User +} +const UserFeed = async (props: Props) => { + const { user } = props + const isLoggedIn = !!user + + const payloadConfig = await config + const payload = await getPayload({ config: payloadConfig }) + + const holdRequests = (await payload.find({ + collection: 'holdRequests', + limit: 6, + depth: 3, + select: { + copy: true, + dateRequested: true, + repository: true, + book: true, + }, + where: { + userRequested: { + equals: user?.id, + }, + }, + })) as PaginatedDocs + + return ( +
+
+

Outbound Activity

+
+ {stats.map((item) => ( +
+ {!!item.shouldHighlight && ( + + )} +
{item.name}
+
+ {item.stat} +
+
+ ))} +
+
+ +
+

Inbound Activity

+
+

Your Holds

+
    + {holdRequests.docs?.map((h) => { + const book = h.book as Book + const repository = h.repository as Repository + + const formatedDateRequested = h.dateRequested + ? new Date(h.dateRequested).toDateString() + : '' + + return ( +
  • + + + {book.title} + {repository.abbreviation} + + + book cover + + + Requested: + {formatedDateRequested} + + +
  • + ) + })} +
+
+
+
+ ) +} + +export default UserFeed diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..6b2e2ab --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,78 @@ +import * as React from 'react' + +import { cn } from '@/lib/utils' + +function Card({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<'div'>) { + return
+} + +function CardFooter({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ) +} + +export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent } diff --git a/src/payload-types.ts b/src/payload-types.ts index dff600a..126820b 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -156,6 +156,8 @@ export interface UserAuthOperations { export interface User { id: number; role?: ('admin' | 'user') | null; + firstName?: string | null; + lastName?: string | null; updatedAt: string; createdAt: string; email: string; @@ -458,6 +460,8 @@ export interface PayloadMigration { */ export interface UsersSelect { role?: T; + firstName?: T; + lastName?: T; updatedAt?: T; createdAt?: T; email?: T;