Retrieve post comments
This commit is contained in:
86
client/src/components/CommentInputModule.tsx
Normal file
86
client/src/components/CommentInputModule.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Formik, Form, FormikHelpers } from "formik";
|
||||
import { useEffect, useState } from "react";
|
||||
import config from '../config';
|
||||
import * as Yup from "yup";
|
||||
import NextUIFormikTextarea from "./NextUIFormikTextarea";
|
||||
import instance from '../security/http';
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { retrieveUserInformation } from "../security/users";
|
||||
import { Button } from "@nextui-org/react";
|
||||
import { PaperAirplaneIcon } from "../icons";
|
||||
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
content: Yup.string()
|
||||
.trim()
|
||||
.min(3, "Content must be at least 3 characters")
|
||||
.max(500, "Content must be at most 500 characters")
|
||||
});
|
||||
|
||||
export default function CommentInputModule() {
|
||||
const navigate = useNavigate();
|
||||
const [userId, setUserId] = useState(null);
|
||||
const { id } = useParams(); // Retrieve 'id' from the route
|
||||
|
||||
const initialValues = {
|
||||
content: "",
|
||||
};
|
||||
|
||||
const postId = id;
|
||||
|
||||
useEffect(() => {
|
||||
const getUserInformation = async () => {
|
||||
try {
|
||||
const user = await retrieveUserInformation(); // Get the user ID
|
||||
setUserId(user.id); // Set the user ID in the state
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
getUserInformation();
|
||||
}, []);
|
||||
|
||||
const submitComment = async (values: any, { resetForm }: FormikHelpers<{ content: string }>) => {
|
||||
const response = await instance.post(config.serverAddress + `/post/${postId}/comments`,
|
||||
{ ...values, userId, postId }
|
||||
);
|
||||
console.log("Comment created succesfully", response.data);
|
||||
resetForm(); // Reset the form after successful submission
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full">
|
||||
<div className="w-2/12"></div>
|
||||
<div className="w-8/12 mx-auto mt-5">
|
||||
<div>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={submitComment}
|
||||
>
|
||||
{({ isValid, dirty }) => (
|
||||
<Form>
|
||||
<div className="relative">
|
||||
<NextUIFormikTextarea
|
||||
label="Comment"
|
||||
name="content"
|
||||
placeholder="Write your comment here"
|
||||
/>
|
||||
<div className="flex justify-end my-2">
|
||||
<Button isIconOnly
|
||||
type="submit"
|
||||
disabled={!isValid || !dirty}
|
||||
className="bg-primary-950 text-white">
|
||||
<PaperAirplaneIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-2/12"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,85 +1,118 @@
|
||||
import { Formik, Form } from "formik";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import config from '../config';
|
||||
import * as Yup from "yup";
|
||||
import NextUIFormikTextarea from "../components/NextUIFormikTextarea";
|
||||
import instance from '../security/http';
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { retrieveUserInformation } from "../security/users";
|
||||
import { Button } from "@nextui-org/react";
|
||||
import { PaperAirplaneIcon } from "../icons";
|
||||
import instance from "../security/http";
|
||||
import config from "../config";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Avatar, Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger } from "@nextui-org/react";
|
||||
import { ChatBubbleOvalLeftEllipsisIcon, EllipsisHorizontalIcon, HandThumbsUpIcon } from "../icons";
|
||||
|
||||
interface Comment {
|
||||
id: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const validationSchema = Yup.object({
|
||||
content: Yup.string()
|
||||
.trim()
|
||||
.min(3, "Content must be at least 3 characters")
|
||||
.max(500, "Content must be at most 500 characters")
|
||||
});
|
||||
// type User = {
|
||||
// id: string;
|
||||
// firstName: string;
|
||||
// lastName: string;
|
||||
// };
|
||||
|
||||
export default function CommentsModule() {
|
||||
const navigate = useNavigate();
|
||||
const [userId, setUserId] = useState(null);
|
||||
const { id } = useParams(); // Retrieve 'id' from the route
|
||||
|
||||
const initialValues = {
|
||||
content: "",
|
||||
const { id } = useParams();
|
||||
const [commentList, setCommentList] = useState<Comment[]>([]);
|
||||
|
||||
let postId = id
|
||||
|
||||
const getComments = async () => {
|
||||
instance
|
||||
.get(config.serverAddress + `/post/${postId}/getComments`).then((res) => {
|
||||
setCommentList(res.data);
|
||||
});
|
||||
};
|
||||
|
||||
const postId = id;
|
||||
|
||||
useEffect(() => {
|
||||
const getUserInformation = async () => {
|
||||
try {
|
||||
const user = await retrieveUserInformation(); // Get the user ID
|
||||
setUserId(user.id); // Set the user ID in the state
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
getUserInformation();
|
||||
}, []);
|
||||
|
||||
const submitComment = async (values: any) => {
|
||||
const response = await instance.post(config.serverAddress + `/post/${postId}/comments`,
|
||||
{ ...values, userId, postId }
|
||||
);
|
||||
console.log("Comment created succesfully", response.data);
|
||||
};
|
||||
getComments();
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<div className="flex w-full">
|
||||
<div className="w-2/12"></div>
|
||||
<div className="w-full mx-auto mt-5">
|
||||
<div>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={submitComment}
|
||||
>
|
||||
{({ isValid, dirty }) => (
|
||||
<Form>
|
||||
<div className="relative">
|
||||
<NextUIFormikTextarea
|
||||
label="Comment"
|
||||
name="content"
|
||||
placeholder="Write your comment here"
|
||||
/>
|
||||
<div className="flex justify-end my-2">
|
||||
<Button isIconOnly
|
||||
type="submit"
|
||||
disabled={!isValid || !dirty}
|
||||
className="bg-primary-950 text-white">
|
||||
<PaperAirplaneIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
<>
|
||||
<div className="flex w-full">
|
||||
<div className="w-2/12"></div>
|
||||
<div className="w-8/12 ml-5 mb-2 text-md font-bold opacity-65">Comment Section</div>
|
||||
<div className="w-2/12"></div>
|
||||
</div>
|
||||
<div className="w-2/12"></div>
|
||||
</div>
|
||||
<div className="flex w-full h-full">
|
||||
<div className="w-2/12"></div>
|
||||
<div className="w-8/12 mx-auto">
|
||||
{commentList.length > 0 ? (
|
||||
commentList.map((comment) => {
|
||||
return (
|
||||
<div className="flex flex-col w-full bg-primary-50 dark:bg-primary-950 rounded-xl mb-2 p-3 mx-auto"
|
||||
key={comment.id}>
|
||||
<div className="flex flex-row w-full">
|
||||
<div>
|
||||
<Avatar
|
||||
src="https://pbs.twimg.com/media/GOva9x5a0AAK8Bn?format=jpg&name=large"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col w-11/12 text-sm ml-3">
|
||||
<div className="font-bold">Name</div>
|
||||
<div className="break-words whitespace-pre-wrap">{comment.content}</div>
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<div className="flex flex-row-reverse justify-center items-center size-7">
|
||||
<Dropdown>
|
||||
<div>
|
||||
<DropdownTrigger
|
||||
className="justify-center items-center"
|
||||
>
|
||||
<Button isIconOnly variant="light">
|
||||
<EllipsisHorizontalIcon />
|
||||
</Button>
|
||||
</DropdownTrigger>
|
||||
</div>
|
||||
<DropdownMenu aria-label="Static Actions">
|
||||
<DropdownItem
|
||||
key="edit"
|
||||
textValue="Edit"
|
||||
>
|
||||
Edit
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
key="delete"
|
||||
textValue="Delete"
|
||||
className="text-danger"
|
||||
color="danger"
|
||||
>
|
||||
Delete
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row ml-14 mt-2 gap-3 w-11/12">
|
||||
<Button variant="light" isIconOnly>
|
||||
<HandThumbsUpIcon />
|
||||
</Button>
|
||||
<Button variant="light" isIconOnly className="w-20">
|
||||
<div>
|
||||
<ChatBubbleOvalLeftEllipsisIcon />
|
||||
</div>
|
||||
<div>
|
||||
Reply
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="flex w-full justify-center m-auto p-20 text-gray-500">No comments... It's so lonely 🥹</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-2/12"></div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -617,18 +617,17 @@ export const TrophyIcon = () => {
|
||||
|
||||
export const PaperAirplaneIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="size-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
className="size-6">
|
||||
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { retrieveUserInformationById } from "../security/usersbyid";
|
||||
import { retrieveUserInformation } from "../security/users";
|
||||
import { number } from "yup";
|
||||
// import UserPostImage from "../components/UserPostImage";
|
||||
|
||||
interface Post {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import instance from "../security/http";
|
||||
import config from "../config";
|
||||
import {
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
ArrowUTurnLeftIcon,
|
||||
} from "../icons";
|
||||
import { retrieveUserInformationById } from "../security/usersbyid";
|
||||
import CommentInputModule from "../components/CommentInputModule";
|
||||
import CommentsModule from "../components/CommentsModule";
|
||||
|
||||
interface Post {
|
||||
@@ -114,7 +115,7 @@ const PostPage: React.FC = () => {
|
||||
<ArrowUTurnLeftIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-row w-full gap-4 mx-auto ">
|
||||
<div className="flex flex-row w-8/12 gap-4 mx-auto">
|
||||
<div className="flex flex-col gap-8 w-full">
|
||||
<div className="flex flex-col gap-4">
|
||||
<section
|
||||
@@ -136,7 +137,8 @@ const PostPage: React.FC = () => {
|
||||
</div>
|
||||
<div className="flex flex-row-reverse justify-center items-center">
|
||||
<Dropdown>
|
||||
<DropdownTrigger className="justify-center items-center">
|
||||
<DropdownTrigger
|
||||
className="justify-center items-center">
|
||||
<Button isIconOnly variant="light">
|
||||
<EllipsisHorizontalIcon />
|
||||
</Button>
|
||||
@@ -146,7 +148,7 @@ const PostPage: React.FC = () => {
|
||||
key="edit"
|
||||
textValue="Edit"
|
||||
onClick={() => {
|
||||
navigate(`edit/${post.id}`);
|
||||
navigate(`../edit/${post.id}`);
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
@@ -201,10 +203,13 @@ const PostPage: React.FC = () => {
|
||||
</div>
|
||||
<div className="w-2/12"></div>
|
||||
</section>
|
||||
<section>
|
||||
<CommentInputModule />
|
||||
</section>
|
||||
<section>
|
||||
<CommentsModule />
|
||||
</section>
|
||||
|
||||
|
||||
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
|
||||
<ModalContent>
|
||||
{(onClose) => (
|
||||
|
||||
@@ -45,32 +45,6 @@ router.post("/", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/:id/comments", async (req, res) => {
|
||||
let data = req.body;
|
||||
|
||||
// Validate request body
|
||||
let validationSchema = yup.object({
|
||||
content: yup.string().trim().min(3).max(500).required(),
|
||||
userId: yup.string().required(),
|
||||
postId: yup.string().required()
|
||||
});
|
||||
|
||||
try {
|
||||
console.log("Validating schema...");
|
||||
data = await validationSchema.validate(data, { abortEarly: false });
|
||||
|
||||
console.log("Creating comment...");
|
||||
let result = await Comment.create(data);
|
||||
|
||||
res.json(result);
|
||||
console.log("Success!");
|
||||
} catch (err) {
|
||||
console.log("Error caught! Info: " + err);
|
||||
res.status(400).json({ errors: err.errors });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// // sequelize method findAll is used to generate a standard SELECT query which will retrieve all entries from the table
|
||||
// router.get("/", async (req, res) => {
|
||||
// let list = await Tutorial.findAll({
|
||||
@@ -180,4 +154,46 @@ router.delete("/:id", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/:id/comments", async (req, res) => {
|
||||
let data = req.body;
|
||||
|
||||
// Validate request body
|
||||
let validationSchema = yup.object({
|
||||
content: yup.string().trim().min(3).max(500).required(),
|
||||
userId: yup.string().required(),
|
||||
postId: yup.string().required()
|
||||
});
|
||||
|
||||
try {
|
||||
console.log("Validating schema...");
|
||||
data = await validationSchema.validate(data, { abortEarly: false });
|
||||
|
||||
console.log("Creating comment...");
|
||||
let result = await Comment.create(data);
|
||||
|
||||
res.json(result);
|
||||
console.log("Success!");
|
||||
} catch (err) {
|
||||
console.log("Error caught! Info: " + err);
|
||||
res.status(400).json({ errors: err.errors });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/:id/getComments", async (req, res) => {
|
||||
let id = req.params.id;
|
||||
|
||||
let condition = {
|
||||
where: { postId: id },
|
||||
order: [['createdAt', 'DESC']]
|
||||
};
|
||||
|
||||
// Check id not found
|
||||
let comments = await Comment.findAll(condition);
|
||||
if (!comments) {
|
||||
res.sendStatus(404);
|
||||
return;
|
||||
}
|
||||
res.json(comments);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user