diff --git a/app/(content)/(home)/home/[postid]/page.tsx b/app/(content)/(home)/home/[postid]/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f27b88f28b2fb2c99c755338150dbf2ea1b6da68 --- /dev/null +++ b/app/(content)/(home)/home/[postid]/page.tsx @@ -0,0 +1,108 @@ +import CommentButton from "@/components/comment-button"; +import LikeButton from "@/components/like-button"; +import PostCommentForm from "@/components/post-comment"; +import PostItem from "@/components/post-item"; +import { db } from "@/lib/db"; +import { Prisma } from "@prisma/client"; +export const revalidate = 5; // revalidate this page every 5 seconds + +type messageType = any // Prisma.PostUncheckedCreateInput +type messageItemProps = { + msg: messageType; +}; + +export default async function PostDetail({ params }: { params: { postid: string } }) { + const postid = params.postid + let comments = null + let message = null + + try { + comments = await db.comment.findMany({ + where: { + postId: postid + }, + orderBy: { + createdAt: "desc" + }, + include: { + user: true, + Like: true + }, + }) + + message = await db.post.findUnique({ + where: { + id: postid + }, + include: { + user: true, + Comment: true, + Like: true, + }, + }) + + } catch (error) { + console.log("the database is not running, try: 'npx prisma migrate dev --name init' if you want to use the database") + } + + return ( + <div> + <h1>Post Section</h1> + <p>This will be where all comments show up.</p> + <p>Needs a reload after posting!!</p> + <PostItem msg={message} /> + + <PostCommentForm postid={postid} /> + + {comments ? + <> + {comments.map((msg) => ( + <CommentItem msg={msg} key={msg.id} /> + ))} + + </> + : + <p>no comments / no database</p>} + </div> + ) +} + +const CommentItem = ({ msg }: any) => { + if (!msg.id) { + return <div></div>; + } + return ( + <div className="flex border-b border-gray-200 py-4"> + <div className="flex-shrink-0"> + <div className="h-10 w-10 rounded-full bg-gray-300"></div> + </div> + <div className="ml-4 flex flex-col flex-grow"> + <div> + <div className="flex items-center"> + <span className="font-bold mr-2">{msg.user.name}</span> + <span className="text-gray-500 text-sm"> + {formatDate(new Date(msg.createdAt!))} + </span> + </div> + <div className="text-gray-800">{msg.message}</div> + </div> + <div className="mt-4 flex"> + <div className="bg-gray-200 rounded-lg py-10 px-20 mr-2"> + + </div> + </div> + <div className="flex justify-end" > + <LikeButton data={msg} /> + </div> + </div> + </div> + ); +}; + +function formatDate(date: Date) { + return date.toLocaleDateString("en-US", { + day: "numeric", + month: "short", + year: "numeric" + }); +} \ No newline at end of file diff --git a/app/(content)/(home)/home/page.tsx b/app/(content)/(home)/home/page.tsx index d50aa7cc9cba4c4bb91b58f7db809d86fd5cf7ac..d3989d4aa18118717bdc7b4ccc0d621344753a06 100644 --- a/app/(content)/(home)/home/page.tsx +++ b/app/(content)/(home)/home/page.tsx @@ -1,10 +1,10 @@ -import LikeButton from "@/components/like-button"; +import PostItem from "@/components/post-item"; import PostMessageForm from "@/components/post-messages"; import { db } from "@/lib/db"; import { Prisma } from "@prisma/client"; -/* export const revalidate = 5; */ // revalidate this page every 60 seconds -type messageType = Prisma.PostUncheckedCreateInput + +type messageType = any // Prisma.PostUncheckedCreateInput type messageItemProps = { msg: messageType; }; @@ -15,7 +15,12 @@ export default async function HomePage() { messages = await db.post.findMany({ orderBy: { createdAt: "desc" - } + }, + include: { + user: true, + Comment: true, + Like: true + }, }) } catch (error) { @@ -25,13 +30,13 @@ export default async function HomePage() { return ( <div> <h1>Home WIP</h1> - <p>This will be where all messages show up.</p> + <p>This will be where all Posts show up.</p> <p>Needs a reload after posting!!</p> - <PostMessageForm data={messages} /> + <PostMessageForm /> {messages ? <> {messages.map((msg) => ( - <MessageItem msg={msg} key={msg.id} /> + <PostItem msg={msg} key={msg.id} /> ))} </> @@ -41,46 +46,3 @@ export default async function HomePage() { ) } -const MessageItem = ({ msg }: messageItemProps) => { - if (!msg.id) { - return <div></div> - } - return ( - <div className="flex border-b border-gray-200 py-4"> - <div className="flex-shrink-0"> - <div className="h-10 w-10 rounded-full bg-gray-300"></div> {/* Profile picture */} - </div> - <div className="ml-4 flex flex-col"> - <div> - <div className="flex items-center"> - <span className="font-bold mr-2">{msg.userId}</span> - <span className="text-gray-500 text-sm"> - {formatDate(new Date(msg.createdAt!))} - </span> - </div> - <div className="text-gray-800">{msg.content}</div> - </div> - <div className="mt-4"> - <div className="flex items-center"> - <div className="bg-gray-200 rounded-lg py-10 px-20 mr-2"> - {/* potential Image */} - </div> - </div> - </div> - <LikeButton data={{ - postId: msg.id, - userId: msg.userId - }} /> - <span className="text-gray-600">Like Count: {msg.likeCount} | <span className="text-gray-600">ReplyButton (Number of Replies)</span></span> - </div> - </div> - ); -}; - -function formatDate(date: Date) { - return date.toLocaleDateString("en-US", { - day: "numeric", - month: "short", - year: "numeric" - }); -} \ No newline at end of file diff --git a/app/api/comments/route.ts b/app/api/comments/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..86001bf164177dd5cc2a60d1d072445afa0c5656 --- /dev/null +++ b/app/api/comments/route.ts @@ -0,0 +1,63 @@ +import { authOptions } from "@/lib/auth"; +import { db } from "@/lib/db"; +import { Prisma } from "@prisma/client"; +import { getServerSession } from "next-auth/next"; +import { revalidatePath } from "next/cache"; +import { NextRequest, NextResponse } from "next/server"; + +type comment = Prisma.CommentUncheckedCreateInput + +export async function POST(req: NextRequest) { + const session = await getServerSession(authOptions); + + if (!session) { + return NextResponse.json({ status: 401 }); + } + + const userId = session.user.id + const data = await req.json() + + console.log("router data: " + data.message, "status:") + + try { + await db.comment.create({ + data: { + message: data.message, + postId: data.postId, + userId: userId, + } + }) + console.log("created") + const path = req.nextUrl.searchParams.get('path') || '/'; + revalidatePath(path); + + return NextResponse.json({ status: 201, message: 'Message Created' }) + + } catch (error: any) { + console.log("fail" + error); + } + console.log("post") +} + +export async function GET(req: NextRequest, res: NextResponse) { + try { + const data = await req.json() + console.log("router data: " + data, "status:") + } catch (error) { + + } + + try { + const messages = await db.comment.findMany({ + orderBy: { + createdAt: "desc" + } + }) + + return NextResponse.json({ status: 200, messages: messages }) + } catch (error) { + console.log("fail" + error); + // res.status(400) + } + console.log("get") +} \ No newline at end of file diff --git a/app/api/likes/likeService.ts b/app/api/likes/likeService.ts index 43539457e7f170749af1e8414e8693c8e4982b2b..18f98072e9ac76646a899f7f4caa5c5b25704ade 100644 --- a/app/api/likes/likeService.ts +++ b/app/api/likes/likeService.ts @@ -14,35 +14,36 @@ export async function putLike(like: likeType): Promise<likeType | undefined> { try { const actualLike = await db.like.findFirst({ where: { - id: like.id, + // id: like.id, postId: like.postId, userId: like.userId } }) - + console.log("found like: ", actualLike?.id) if (actualLike == null) { - console.log("like is null") + console.log("like is null", "postid:", like.postId, "so create it") throw Error("Message was not liked by this user") } - + console.log("delete like", like.postId, "likeid: ", actualLike?.id) await db.like.delete({ where: { id: actualLike.id } }) - const msg = await db.post.update({ - where: { - id: like.postId - }, - data: { - likeCount: { increment: -1 } - } - }) + /* const msg = await db.post.update({ + where: { + id: like.postId + }, + data: { + likeCount: { increment: -1 } + } + }) */ return undefined; } catch { + const createdLike = await db.like.create({ data: { postId: like.postId, @@ -58,7 +59,59 @@ export async function putLike(like: likeType): Promise<likeType | undefined> { likeCount: { increment: 1 } } }) + } +} + +export async function putLikeComment(like: likeType) { + // check if like exists by this user and for this post + // if exists delete + // if not create + try { + const actualLike = await db.like.findFirst({ + where: { + // id: like.id, + postId: like.postId, + commentId: like.commentId, + userId: like.userId + } + }) + console.log("found like: ", actualLike?.id) + if (actualLike == null) { + console.log("like is null", like.commentId, "so create it") + const createdLike = await db.like.create({ + data: { + postId: like.postId, + userId: like.userId, + commentId: like.commentId + } + }) + } else { + console.log("delete like", like.commentId, "postid:", like.postId, "likeid: ", actualLike?.id) + await db.like.delete({ + where: { + id: actualLike.id + } + }) + } + + /* const msg = await db.comment.update({ + where: { + id: like.postId + }, + data: { + likeCount: { increment: -1 } + } + }) */ + + } catch { - return createdLike + /* const updatedMessage = await db.comment.update({ + where: { + id: like.postId + }, + data: { + likeCount: { increment: 1 } + } + }) */ } } \ No newline at end of file diff --git a/app/api/likes/route.ts b/app/api/likes/route.ts index cb35639e5da0638936e2e9832cf0af7db064a396..35619aaa6a76f2bcbc09d2a19aa2664b108146b7 100644 --- a/app/api/likes/route.ts +++ b/app/api/likes/route.ts @@ -1,15 +1,33 @@ import { Prisma } from "@prisma/client"; import { NextRequest, NextResponse } from "next/server"; -import { putLike } from "./likeService"; +import { putLike, putLikeComment } from "./likeService"; +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/lib/auth"; +import { revalidatePath } from "next/cache"; type like = Prisma.LikeUncheckedCreateInput export async function PUT(req: NextRequest) { + const session = await getServerSession(authOptions); + + if (!session) { + return NextResponse.json({ status: 401 }); + } + + const userId = session.user.id + const data: like = await req.json() + data.userId = userId; console.log("router data: " + data, "status:") try { - const msg = await putLike(data) + if (data.commentId == undefined) { + const msg = await putLike(data) + } else { + putLikeComment(data) + } + const path = req.nextUrl.searchParams.get('path') || '/'; + revalidatePath(path); return NextResponse.json({ status: 200, message: 'Like handled' }) } catch (error) { diff --git a/components/comment-button.tsx b/components/comment-button.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7a72cdec71b37b0028d97346f4c992200c7f94fc --- /dev/null +++ b/components/comment-button.tsx @@ -0,0 +1,25 @@ +"use client" + +import { Prisma } from "@prisma/client"; +import { useRouter } from "next/navigation"; +import { startTransition } from "react"; +import { Icons } from "./icons"; +import { Button } from "./ui/button"; +import Link from "next/link"; + +export default function CommentButton(props: { data: any }) { + + const postid = props.data.id + const replyCount = props.data.Comment.length + //const replyCount = props.data.likeCount + return ( + <div> + <Link href={`/home/${postid}`}> + <Button type="submit" variant="ghost" size="lg" className="float-right" > + {replyCount} + <Icons.messagecircle className="h-3 w-3" /> + </Button> + </Link> + </div> + ) +} \ No newline at end of file diff --git a/components/like-button.tsx b/components/like-button.tsx index 6a089346b95e2f390ea7879607c8db5e3ac9b17a..31c36cf7343fd445c617130572503591bd40fa94 100644 --- a/components/like-button.tsx +++ b/components/like-button.tsx @@ -7,16 +7,34 @@ import { Icons } from "./icons"; import { Button } from "./ui/button"; type likeType = Prisma.LikeUncheckedCreateInput +type postType = Prisma.PostUncheckedCreateInput +type commentType = Prisma.CommentUncheckedCreateInput +//type commentWithLikes = Prisma.CommentGetPayload<typeof commentWithPosts> -export default function LikeButton(props: { data: likeType }) { +export default function LikeButton(props: { data: any }) { const router = useRouter(); + const likeCount = props.data.Like.length + /* const likeCount = props.data.likeCount */ + const likeArray = props.data.Like + /* const likeCount = countLikes(likeArray, props.data); */ + + + + async function postLike(e: any) { e.preventDefault() - const msgLikeData = props.data; + const postLikeData = props.data; const likeData = {} as likeType - likeData.userId = msgLikeData.userId - likeData.postId = msgLikeData.postId + + if (postLikeData.postId == undefined) { + likeData.postId = postLikeData.id! + } else { + likeData.postId = postLikeData.postId + likeData.commentId = postLikeData.id + } + likeData.userId = postLikeData.userId + console.log(likeData.commentId) const response = await fetch('http://localhost:3000/api/likes', { method: 'PUT', @@ -35,9 +53,32 @@ export default function LikeButton(props: { data: likeType }) { <div> <form onSubmit={postLike}> <Button type="submit" variant="ghost" size="lg" className="float-right" > + {likeCount} <Icons.heart className="h-3 w-3" /> </Button> </form> </div> ) -} \ No newline at end of file +} + +function countLikes(likeArray: any, msg: any): number { + let likeCount = 0; + + if (msg.postId == undefined) { + likeArray.forEach(function (like: any) { + if (like.postId == undefined) { + likeCount++; + } + }) + } else { + likeArray.forEach(function (like: any) { + if (like.postId != undefined) { + likeCount++; + } + }) + } + + + return likeCount; +} + diff --git a/components/post-comment.tsx b/components/post-comment.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6f80ff3de18c341db3d554f2c21dfcbe7a2fd309 --- /dev/null +++ b/components/post-comment.tsx @@ -0,0 +1,64 @@ +"use client" + +import { Post, Prisma } from "@prisma/client"; +import { useRouter } from "next/navigation"; +import { startTransition, useEffect, useState } from "react"; + +type commentType = Prisma.CommentUncheckedCreateInput + +export default function PostCommentForm(props: { postid: string }) { + + const [formData, setFormData] = useState<commentType>({ message: "" } as commentType); + const router = useRouter(); + const postid = props.postid + + async function postComment(e: any) { + e.preventDefault() + + formData.postId = postid; + + const response = await fetch('http://localhost:3000/api/comments', { + method: 'POST', + body: JSON.stringify(formData) + }) + + startTransition(() => { + // Refresh the current route and fetch new data from the server without + // losing client-side browser or React state. + router.refresh(); + }); + return await response.json() + } + + const characterCount = formData.message.length; + const isOverLimit = characterCount >= 1000; + + const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { + const { value } = e.target; + setFormData({ ...formData, message: value }); + }; + + return ( + <div className="p-4 pb-20"> + <form onSubmit={postComment}> + <textarea + placeholder="Write something..." + name="message" + value={formData.message} + onChange={handleInputChange} + className="w-full p-2 border border-gray-600 rounded-xl resize-none" + rows={5} + maxLength={1000} + ></textarea> + <div className="flex justify-end mt-2"> + <span className={`${isOverLimit ? "text-red-500" : "text-gray-500"} text-sm`}> + {characterCount}/{1000} + </span> + </div> + <button type="submit" className="mt-2 bg-gray-300 text-gray-800 px-4 py-2 rounded float-right"> + Post + </button> + </form> + </div> + ) +} \ No newline at end of file diff --git a/components/post-item.tsx b/components/post-item.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c348a94568387e8bb1927e4f6ea92d101e2ee4c1 --- /dev/null +++ b/components/post-item.tsx @@ -0,0 +1,47 @@ +import CommentButton from "./comment-button"; +import LikeButton from "./like-button"; + +type messageType = any // Prisma.PostUncheckedCreateInput +type messageItemProps = { + msg: messageType; +}; + +export default function PostItem({ msg }: any) { + if (!msg.id) { + return <div></div>; + } + return ( + <div className="flex border-b border-gray-200 py-4"> + <div className="flex-shrink-0"> + <div className="h-10 w-10 rounded-full bg-gray-300"></div> + </div> + <div className="ml-4 flex flex-col flex-grow"> + <div> + <div className="flex items-center"> + <span className="font-bold mr-2">{msg.user.name}</span> + <span className="text-gray-500 text-sm"> + {formatDate(new Date(msg.createdAt!))} + </span> + </div> + <div className="text-gray-800">{msg.content}</div> + </div> + <div className="mt-4 flex"> + <div className="bg-gray-200 rounded-lg py-10 px-20 mr-2"> + </div> + </div> + <div className="flex justify-end" > + <LikeButton data={msg} /> + <CommentButton data={msg} /> + </div> + </div> + </div> + ) +}; + +function formatDate(date: Date) { + return date.toLocaleDateString("en-US", { + day: "numeric", + month: "short", + year: "numeric" + }); +} \ No newline at end of file diff --git a/components/post-messages.tsx b/components/post-messages.tsx index a870e16e391d2d34bd17b962d7f1c189486fd24a..85ee1cfc6da452448e85a9cad33c073ab86f5d99 100644 --- a/components/post-messages.tsx +++ b/components/post-messages.tsx @@ -6,7 +6,7 @@ import { startTransition, useEffect, useState } from "react"; type messageType = Prisma.PostUncheckedCreateInput -export default function PostMessageForm(props: { data: Post[] | null }) { +export default function PostMessageForm() { const [formData, setFormData] = useState<messageType>({ content: "" } as messageType); const router = useRouter();