From 5c35fd1f3e8f369fba9cc3a54b1f9a54f9b902ad Mon Sep 17 00:00:00 2001 From: meet Date: Wed, 5 Jun 2024 13:33:20 +0530 Subject: [PATCH 1/3] Added social media icons and sharing option --- frontend/package-lock.json | 39 +++++++++++++++++++++++++++++++++++++ frontend/package.json | 1 + frontend/src/pages/Post.tsx | 24 +++++++++++++++++++++-- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 05defeb0..a0a062d4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,6 +20,7 @@ "react-hot-toast": "^2.4.1", "react-icons": "^5.2.1", "react-router-dom": "^6.23.1", + "react-share": "^5.1.0", "recoil": "^0.7.7", "tailwind-merge": "^2.3.0", "zxcvbn": "^4.4.2" @@ -1962,6 +1963,11 @@ "node": ">= 6" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -3051,6 +3057,27 @@ "node": ">=6" } }, + "node_modules/jsonp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/jsonp/-/jsonp-0.2.1.tgz", + "integrity": "sha512-pfog5gdDxPdV4eP7Kg87M8/bHgshlZ5pybl+yKxAnCZ5O7lCIn7Ixydj03wOlnDQesky2BPyA91SQ+5Y/mNwzw==", + "dependencies": { + "debug": "^2.1.3" + } + }, + "node_modules/jsonp/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/jsonp/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3739,6 +3766,18 @@ "react-dom": ">=16.8" } }, + "node_modules/react-share": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-share/-/react-share-5.1.0.tgz", + "integrity": "sha512-OvyfMtj/0UzH1wi90OdHhZVJ6WUC/+IeWvBwppeZozwIGyAjQgyR0QXlHOrxVHVECqnGvcpBaFTXVrqouTieaw==", + "dependencies": { + "classnames": "^2.3.2", + "jsonp": "^0.2.1" + }, + "peerDependencies": { + "react": "^17 || ^18" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6c7df991..cb920619 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,6 +22,7 @@ "react-hot-toast": "^2.4.1", "react-icons": "^5.2.1", "react-router-dom": "^6.23.1", + "react-share": "^5.1.0", "recoil": "^0.7.7", "tailwind-merge": "^2.3.0", "zxcvbn": "^4.4.2" diff --git a/frontend/src/pages/Post.tsx b/frontend/src/pages/Post.tsx index 94f216ff..5e832f9e 100644 --- a/frontend/src/pages/Post.tsx +++ b/frontend/src/pages/Post.tsx @@ -6,6 +6,7 @@ import DOMPurify from 'dompurify'; import { BiDislike,BiLike,BiSolidDislike,BiSolidLike } from "react-icons/bi"; import Loader from '../components/Loader' import toast from 'react-hot-toast'; +import { TwitterShareButton, LinkedinShareButton, FacebookShareButton, TelegramShareButton, LinkedinIcon, FacebookIcon, TelegramIcon, XIcon, WhatsappShareButton, WhatsappIcon } from 'react-share'; const Post = () => { const { id } = useParams<{ id: string }>(); @@ -30,6 +31,8 @@ const Post = () => { const [height, setHeight] = useState('0px'); const [userLiked, setUserLiked] = useState(false); const [userDisliked, setUserDisliked] = useState(false); + const shareUrl=`http://style-share.vercel.app/app/posts/${post.id}` + const title= `👋 Hey ! I found amazing tailwind css 💅 component ${post.title} have a look, The design is done by ${post.author.username} check out the link it's amazing 😀` const onLoad = () => { setHeight(ref.current?.contentWindow?.document.body.scrollHeight + 'px'); @@ -257,9 +260,26 @@ const Post = () => { ))} -
+

Author

-

Username: {post.author.username}

+

Username: {post.author.username}

+
+
+ + + + + + + + + + + + + + +
)} From 78dfbe0a6e86f4bd97d6732b789dc181b2035dc4 Mon Sep 17 00:00:00 2001 From: meet Date: Wed, 5 Jun 2024 19:24:58 +0530 Subject: [PATCH 2/3] Added comment section --- backend/prisma/schema.prisma | 13 +++ backend/src/routes/post/controller.ts | 96 +++++++++++++++++------ backend/src/routes/post/route.ts | 6 +- frontend/src/pages/Comment.tsx | 109 ++++++++++++++++++++++++++ frontend/src/pages/Post.tsx | 19 +++-- frontend/src/types.ts | 15 +++- 6 files changed, 223 insertions(+), 35 deletions(-) create mode 100644 frontend/src/pages/Comment.tsx diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 6b2c5d91..a0a80afe 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -24,6 +24,7 @@ model User { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt interactions UserPostInteraction[] + comments Comment[] @relation("userComments") } model Post { @@ -39,6 +40,7 @@ model Post { likes Int @default(0) dislikes Int @default(0) interactions UserPostInteraction[] + comments Comment[] @relation("postComments") } model UserPostInteraction { @@ -51,4 +53,15 @@ model UserPostInteraction { post Post @relation(fields: [postId], references: [id]) @@unique([userId, postId]) +} + +model Comment { + id String @id @default(auto()) @map("_id") @db.ObjectId + content String + userId String @db.ObjectId + postId String @db.ObjectId + user User @relation("userComments", fields: [userId], references: [id]) + post Post @relation("postComments", fields: [postId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } \ No newline at end of file diff --git a/backend/src/routes/post/controller.ts b/backend/src/routes/post/controller.ts index 4298abdc..04f72c75 100644 --- a/backend/src/routes/post/controller.ts +++ b/backend/src/routes/post/controller.ts @@ -83,29 +83,6 @@ export const createPostController = async ( } }; -// export const getPostsController = async (req: Request, res: Response) => { -// const posts = await prisma.post.findMany({ -// select: { -// id: true, -// title: true, -// codeSnippet: true, -// description: true, -// tags: true, -// author: { -// select: { -// id: true, -// username: true, -// email: true, -// }, -// }, -// }, -// }); - -// res.status(200).json({ -// posts, -// }); -// }; - export const getPostController = async (req: Request, res: Response) => { try { const postId = req.params.id; @@ -128,6 +105,7 @@ export const getPostController = async (req: Request, res: Response) => { username: true, }, }, + comments:true }, }); @@ -313,3 +291,75 @@ export const dislikePostController = async (req: UserAuthRequest, res: Response) }); } }; + +export const createCommentController = async (req: UserAuthRequest, res: Response) => { + try { + const userId = req.userId; + const { postId } = req.params; + const { content } = req.body; + + if (!userId) { + return res.status(403).json({ error: "Invalid user" }); + } + + const comment = await prisma.comment.create({ + data: { + content, + userId, + postId + }, + select: { + id: true, + content: true, + user: { + select: { + id: true, + username: true, + } + }, + createdAt: true + } + }); + + res.status(201).json({ + message: "Successfully created comment!", + comment, + }); + } catch (error) { + res.status(500).json({ + error: "An unexpected exception occurred!", + }); + } +}; + +export const getCommentsController = async (req: Request, res: Response) => { + try { + const { postId } = req.params; + + const comments = await prisma.comment.findMany({ + where: { postId }, + select: { + id: true, + content: true, + user: { + select: { + id: true, + username: true, + } + }, + createdAt: true + }, + orderBy: { + createdAt: 'desc', + } + }); + + res.status(200).json({ + comments, + }); + } catch (error) { + res.status(500).json({ + error: "Failed to fetch comments", + }); + } +}; diff --git a/backend/src/routes/post/route.ts b/backend/src/routes/post/route.ts index 4a7ed5a3..540fa112 100644 --- a/backend/src/routes/post/route.ts +++ b/backend/src/routes/post/route.ts @@ -1,7 +1,7 @@ import { Router } from "express"; import authMiddleware from "../../middleware/auth" -import { createPostController, dislikePostController, getPostController, getPostsWithPagination, likePostController } from "./controller"; +import { createCommentController, createPostController, dislikePostController, getCommentsController, getPostController, getPostsWithPagination, likePostController } from "./controller"; const postRouter = Router(); @@ -15,4 +15,8 @@ postRouter.post('/:id/like', authMiddleware, likePostController); postRouter.post('/:id/dislike', authMiddleware, dislikePostController); +postRouter.post('/:postId/comments', authMiddleware, createCommentController); + +postRouter.get('/:postId/comments', getCommentsController); + export default postRouter; \ No newline at end of file diff --git a/frontend/src/pages/Comment.tsx b/frontend/src/pages/Comment.tsx new file mode 100644 index 00000000..58cccb37 --- /dev/null +++ b/frontend/src/pages/Comment.tsx @@ -0,0 +1,109 @@ +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import axios, { AxiosError } from 'axios'; +import { IComment } from '../types'; +import Loader from '../components/Loader'; +import toast from 'react-hot-toast'; + +const Comment = () => { + const { id } = useParams<{ id: string }>(); + const [loading, setLoading] = useState(true); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + const [commentContent, setCommentContent] = useState(""); + const [comments, setComments] = useState([]); + + const fetchComments = async () => { + setLoading(true); + try { + const response = await axios.get(`/api/v1/posts/${id}/comments`); + setComments(response.data.comments); + setLoading(false); + } catch (error) { + const axiosError = error as AxiosError<{ error: string }>; + setError(axiosError.response?.data.error || 'Failed to fetch comments'); + setLoading(false); + } + }; + + useEffect(() => { + fetchComments(); + }, [id]); + + const handleCommentSubmit = async (e: any) => { + e.preventDefault(); + const token = localStorage.getItem('token'); + if (!token) { + toast.error('Please login to comment'); + return; + } + setSubmitting(true); + try { + const response = await axios.post(`/api/v1/posts/${id}/comments`, { content: commentContent }, { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + fetchComments(); + setCommentContent(''); + toast.success(response.data.message); + } catch (error) { + toast.error('Failed to add comment'); + } finally { + setSubmitting(false); + } + }; + + if (loading) { + return ; + } + + if (error) { + return ( +
+ {error} +
+ ); + } + + return ( +
+
+

{comments.length} Comments

+ {comments.length > 0 ? ( +
    + {comments.map((comment: IComment) => ( +
  • +

    @{comment.user.username} {new Date(comment.createdAt).toLocaleString()}

    +

    {comment.content}

    +
  • + ))} +
+ ) : ( +

Be the first one to comment...

+ )} +
+
+