Issue Comments and Management System
Issue Comments and Management System
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from "react";
import { useAuth } from "@/lib/contexts/AuthContext";
import { Comment, Issue } from "./IssuesMain";
createcomments:
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Form, Formik } from "formik";
import React from "react";
import TextInput1 from "../common/Input/TextInput1";
import { useMutation } from "react-query";
import { CREATE_ISSUE_COMMENT } from "@/lib/ReactQueryKeys";
import useHttpClient from "@/lib/hooks/useHttpClient";
import { usePopup } from "@/lib/contexts/PopupContext";
interface Comment {
comment: string;
}
issueslist:
issuesmain:
useEffect(() => {
if (issue._id) joinSocket(issue._id, accessToken);
if (socket) {
socketListenComments();
}
scrollBottomMessage();
}, [issue._id, socket, allComments.length]);
const { data: { data: issues = [] } = {}, refetch: issueRefetch } =
useQuery(GET_ISSUES.key, () => axios.get(GET_ISSUES.path), {
onSuccess: ({ data: issues }) => {
setIssue(issues[0]);
},
enabled: !issue._id,
refetchOnMount: false,
refetchOnWindowFocus: false,
});
useQuery(
[GET_ISSUE_COMMENTS.key, issue._id],
() => axios.get(`${GET_ISSUE_COMMENTS.path}/${issue._id}`),
{
onSuccess: ({ data: comments }) => {
setAllComments(comments);
},
enabled: !!issue._id,
refetchOnMount: false,
refetchOnWindowFocus: false,
}
);
return (
<div className="p-5 bg-lightBG-60">
<div className="flex justify-between">
<h1 className="font-bold text-xl ml-3">Issues</h1>
<button
onClick={() => setCreateIssue(true)}
className="px-2 py-1 border-2 bg-red-600 text-white rounded-md"
>
Create Issue
</button>
</div>
{issues.length > 0 ? (
<div className="md:flex h-[490px] max-w-[1200px] rounded-xl shadow-
lg border-2 mt-8">
<IssuesList
issues={issues}
setIssue={setIssue}
issue={issue}
/>
<div className="w-full border-l-2 rounded-tr-lg rounded-br-lg
border-primary-600">
<Comments
lastMessageRef={lastMessageRef}
issue={issue}
allComments={allComments}
/>
<CreateComment
issueId={issue._id}
scrollBottomMessage={scrollBottomMessage}
/>
</div>
</div>
) : (
<div className="ml-3 mt-2 text-lg text-center text-primary-100">
Issues not Found
</div>
)}
{createIssue && (
<div className="max-h-screen absolute bg-opacity-50 flex justify-
center bg-primary-1000 inset-0 z-10">
<CreateIssue
setCreateIssue={setCreateIssue}
postIssue={postIssue}
issueLoading={issueLoading}
/>
</div>
)}
</div>
);
}
createIssues:
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Form, Formik } from "formik";
import React from "react";
import TextInput1 from "../common/Input/TextInput1";
import LittleLoader from "../common/LittleLoader";
import * as Yup from "yup";
interface Issue {
issueName: string;
description: string;
}
index.tsx:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NjVhZjU2ZmE0M2ViZGU5YTMwZTQ3YmIiL
CJuYW1lIjoiQWtzaGl0aGEgUmF2aWNoYW5kcmFuIiwiZW1haWwiOiJha3NoaXRoYXJhdmljaGFuZHJhbjE3
QGdtYWlsLmNvbSIsInJvbGVzIjpbM10sImlhdCI6MTcxNzQwMTg4OSwiZXhwIjoxNzE3NDg4Mjg5fQ.xHbc
cM_qh-13_dQmKYcwB39jNWUBRH06SdSxkgGqKsI",
Comments:
import React, { useEffect, useRef } from "react";
import { useQuery, useMutation, useQueryClient } from "react-query";
import axios from "axios";
import { io, Socket } from "socket.io-client";
import { useAuth } from '@/lib/contexts/AuthContext';
import useHttpClient from "@/lib/hooks/useHttpClient";
import { usePopup } from "@/lib/contexts/PopupContext";
interface Comment {
id: string;
senderName: string;
comment: string;
createdAt: string;
}
useEffect(() => {
if (!accessToken) return;
socket.current = io("http://localhost:3000", {
query: {
accessToken,
},
});
socket.current.on("connect", () => {
console.log("Socket connected");
socket.current?.emit("join", { issueId });
});
socket.current.on("new-comment", () => {
console.log("Received new comment");
queryClient.invalidateQueries(["comments", issueId]);
scrollToBottom();
});
socket.current.on("disconnect", () => {
console.log("Socket disconnected");
});
return () => {
socket.current?.disconnect();
};
}, [issueId, queryClient, accessToken]);
if (error) {
return <div>Error fetching comments</div>;
}
return (
<div className="mx-auto max-w-2xl rounded-lg bg-white p-6 shadow-md">
<h2 className="mb-6 text-2xl font-semibold">Comments</h2>
<div className="mb-6">
<textarea
ref={commentInputRef}
className="w-full rounded-lg bg-gray-100 p-4 text-gray-700
focus:border-blue-500 focus:bg-white focus:outline-none"
rows={4}
placeholder="Write a comment..."
></textarea>
<div className="mt-2 flex justify-end">
<button
onClick={handlePostComment}
className="rounded-lg bg-blue-500 px-4 py-2 text-white
hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-
opacity-50"
>
Post Comment
</button>
</div>
</div>
<div className="space-y-6">
{comments.length > 0 ? (
comments.map((comment) => (
<div key={comment.id} className="flex space-x-4">
<div>
<img
src="https://via.placeholder.com/50"
alt="User Avatar"
className="h-12 w-12 rounded-full"
/>
</div>
<div>
<div className="rounded-lg bg-gray-100 p-4">
<div className="mb-2 flex items-center justify-
between">
<h3 className="font-
semibold">{comment.senderName}</h3>
<span className="text-sm text-gray-
500">{new Date(comment.createdAt).toLocaleString()}</span>
</div>
<p className="text-gray-
700">{comment.comment}</p>
</div>
</div>
</div>
))
) : (
<p>No comments found</p>
)}
<div ref={lastMessageRef} />
</div>
</div>
);
}
{/* Issue Header */}
{/* <div className="bg-blue-500 text-white px-6 py-4 rounded-t-lg">
<h2 className="text-xl font-semibold">{issue?.issueName}</h2>
<p className="text-sm text-gray-200">{issue.createdAt?.slice(0,
10)}</p>
<p className="mt-2 text-gray-100">{issue.description}</p>
</div> */}
createcomments.tsx:
import { Form, Formik } from "formik";
import React from "react";
import TextInput1 from "../common/Input/TextInput1";
import { useMutation } from "react-query";
import { CREATE_ISSUE_COMMENT } from "@/lib/ReactQueryKeys";
import useHttpClient from "@/lib/hooks/useHttpClient";
import { usePopup } from "@/lib/contexts/PopupContext";
interface Comment {
comment: string;
}
issueId,
}: {
scrollBottomMessage: any;
issueId: string;
}) {
const axios = useHttpClient();
const { showError } = usePopup();
const { mutate: postComment } = useMutation(
CREATE_ISSUE_COMMENT.key,
(values: Comment) =>
axios.post(`${CREATE_ISSUE_COMMENT.path}/${issueId}`, values),
{
onError: ({
response: {
data: { message },
},
}) => {
showError(message);
},
}
);
return (
<Formik
initialValues={{
comment: "",
}}
onSubmit={(values: Comment, { setSubmitting, resetForm }) => {
postComment(values);
setSubmitting(false);
resetForm();
}}
>
{({ isSubmitting, values }) => (
<Form>
<div className="p-5 border-t-2 rounded-br-lg rounded-bl-lg bg-
white shadow-md">
<div className="flex w-full space-x-4">
<TextInput1
name="comment"
inputstyle="min-w-[300px] lg:min-w-[400px] w-full
rounded-lg bg-gray-100 p-4 text-gray-700 focus:border-blue-500 focus:bg-white
focus:outline-none"
placeholder="Write a comment..."
/>
<button
type="submit"
disabled={isSubmitting || !values.comment}
className="rounded-lg bg-blue-500 px-4 py-2 text-
white hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500
focus:ring-opacity-50"
>
Post Comment
</button>
</div>
</div>
</Form>
)}
</Formik>
);
}
commments.tsx:
import React from "react";
import { useAuth } from "@/lib/contexts/AuthContext";
import { Comment, Issue } from "./IssuesMain";
interface CommentsProps {
allComments: Array<Comment>;
issue: Issue;
lastMessageRef: any;
}
return (
<div className="max-w-2xl mx-auto mt-6 p-4 bg-white shadow-md rounded-lg">
<div className="overflow-y-auto max-h-[300px] mt-4">
{allComments.length > 0 ? (
allComments.map((comment: Comment) => {
const isCurrentUser = user._id === comment.senderId;
const commentCss = isCurrentUser
? "bg-blue-500 text-white self-end "
: "bg-white text-gray-800 border border-gray-300 self-
start shadow";
return (
<div
key={comment._id}
className={`flex flex-col items-start mb-4 $
{isCurrentUser && "self-end"}`}
>
<div
className={`rounded-lg py-2 px-4 max-w-[70%]
text-sm break-words ${commentCss}`}
>
<div className="mb-2 flex items-center justify-
between">
<h3 className="font-semibold">User</h3>
</div>
<p className="text-base">{comment.comment}</p>
useEffect(() => {
if (issue._id && accessToken && typeof joinSocket === "function") {
joinSocket(issue._id, accessToken);
}
if (socket) {
socketListenComments();
}
scrollBottomMessage();
}, [issue._id, socket, allComments.length]);
useQuery(
[GET_ISSUE_COMMENTS.key, issue._id],
() => axios.get(`${GET_ISSUE_COMMENTS.path}/${issue._id}`),
{
onSuccess: ({ data: comments }) => {
setAllComments(comments);
},
enabled: !!issue._id,
refetchOnMount: false,
refetchOnWindowFocus: false,
}
);
return (
<div className="p-5 bg-lightBG-60">
<div className="flex justify-between">
<h1 className="font-bold text-xl ml-3">Issues</h1>
<button
onClick={() => setCreateIssue(true)}
className="px-2 py-1 border-2 bg-red-600 text-white rounded-md"
>
Create Issue
</button>
</div>
{issues.length > 0 ? (
<div className="md:flex h-[490px] max-w-[1200px] rounded-xl shadow-
lg border-2 mt-8">
<div className="w-full border-l-2 rounded-tr-lg rounded-br-lg
border-primary-600">
<PostComment
issueId={issue._id}
scrollBottomMessage={scrollBottomMessage}
/>
<Comments
lastMessageRef={lastMessageRef}
issue={issue}
allComments={allComments}
/>
</div>
</div>
) : (
<div className="ml-3 mt-2 text-lg text-center text-primary-100">
Issues not Found
</div>
)}
{createIssue && (
<div className="max-h-screen absolute bg-opacity-50 flex justify-
center bg-primary-1000 inset-0 z-10">
<CreateIssues
setCreateIssue={setCreateIssue}
postIssue={postIssue}
issueLoading={issueLoading}
/>
</div>
)}
</div>
);
}