Skip to content

Instantly share code, notes, and snippets.

@florianwalther-private
Last active March 1, 2023 10:04
Show Gist options
  • Save florianwalther-private/892900c654c2de774e37e417a34ce2df to your computer and use it in GitHub Desktop.
Save florianwalther-private/892900c654c2de774e37e417a34ce2df to your computer and use it in GitHub Desktop.
Updated Comment component
import UserProfileLink from "@/components/UserProfileLink";
import { formatRelativeDate } from "@/helpers/utils";
import { useAuthenticatedUser } from "@/hooks/useAuthenticatedUser";
import { Comment as CommentModel } from "@/models/comment";
import { NotFoundError } from "@/network/http-errors";
import { AppContext } from "@/pages/_app";
import { useContext, useState } from "react";
import { Button } from "react-bootstrap";
import CreateCommentBox from "./CreateCommentBox";
import EditCommentBox from "./EditCommentBox";
import * as BlogApi from "@/network/api/blog";
interface CommentProps {
comment: CommentModel,
onReplyCreated: (reply: CommentModel) => void,
onCommentUpdated: (updatedComment: CommentModel) => void,
onCommentDeleted: (comment: CommentModel) => void,
}
const Comment = ({ comment, onReplyCreated, onCommentUpdated, onCommentDeleted }: CommentProps) => {
const { user } = useAuthenticatedUser();
const appContext = useContext(AppContext);
const [showEditBox, setShowEditBox] = useState(false);
const [showReplyBox, setShowReplyBox] = useState(false);
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
const [deleteInProgress, setDeleteInProgress] = useState(false);
async function deleteComment() {
try {
setDeleteInProgress(true);
await BlogApi.deleteComment(comment._id);
onCommentDeleted(comment);
} catch (error) {
console.error(error);
if (error instanceof NotFoundError) {
onCommentDeleted(comment);
} else {
alert(error);
}
} finally {
setDeleteInProgress(false);
}
}
function handleReplyClick() {
if (user) {
setShowReplyBox(true);
} else {
appContext.showLoginModal();
}
}
function handleEditClick() {
setShowEditBox(true);
setShowDeleteConfirmation(false);
}
function handleReplyCreated(newReply: CommentModel) {
onReplyCreated(newReply);
setShowReplyBox(false);
}
function handleCommentUpdated(updatedComment: CommentModel) {
onCommentUpdated(updatedComment);
setShowEditBox(false);
}
return (
<div className="w-100">
<hr />
{showEditBox
? <EditCommentBox
comment={comment}
onCommentUpdated={handleCommentUpdated}
onCancel={() => setShowEditBox(false)}
/>
: <CommentLayout
comment={comment}
onReplyClicked={handleReplyClick}
onEditClicked={handleEditClick}
onDeleteClicked={() => setShowDeleteConfirmation(true)}
/>}
{showDeleteConfirmation &&
<DeleteConfirmationSection
deleteDisabled={deleteInProgress}
onDeleteConfirmed={deleteComment}
onCancel={() => setShowDeleteConfirmation(false)}
/>
}
{showReplyBox &&
<CreateCommentBox
blogPostId={comment.blogPostId}
title="Write a reply"
defaultText={comment.parentCommentId ? `@${comment.author.username} ` : ""}
onCommentCreated={handleReplyCreated}
parentCommentId={comment.parentCommentId || comment._id}
onCancel={() => setShowReplyBox(false)}
showCancel
/>
}
</div>
);
}
export default Comment;
interface CommentLayoutProps {
comment: CommentModel,
onReplyClicked: () => void,
onEditClicked: () => void,
onDeleteClicked: () => void,
}
const CommentLayout = ({ comment, onReplyClicked, onEditClicked, onDeleteClicked }: CommentLayoutProps) => {
const { user } = useAuthenticatedUser();
return (
<div>
<div className="mb-2">{comment.text}</div>
<div className="d-flex gap-2 align-items-center">
<UserProfileLink user={comment.author} />
{formatRelativeDate(comment.createdAt)}
{comment.updatedAt > comment.createdAt && <span>(Edited)</span>}
</div>
<div className="mt-1 d-flex gap-2">
<Button
variant="link"
onClick={onReplyClicked}>
<small>Reply</small>
</Button>
{user?._id === comment.author._id &&
<>
<Button
variant="link"
onClick={onEditClicked}>
<small>Edit</small>
</Button>
<Button
variant="link"
onClick={onDeleteClicked}>
<small>Delete</small>
</Button>
</>
}
</div>
</div>
);
}
interface DeleteConfirmationSectionProps {
deleteDisabled: boolean,
onDeleteConfirmed: () => void,
onCancel: () => void,
}
const DeleteConfirmationSection = ({ deleteDisabled: disableDelete, onDeleteConfirmed, onCancel }: DeleteConfirmationSectionProps) => {
return (
<div>
<p className="text-danger">Do you really want to delete this comment?</p>
<Button
variant="danger"
className="me-2"
onClick={onDeleteConfirmed}
disabled={disableDelete}>
Delete
</Button>
<Button
variant="outline-danger"
onClick={onCancel}>
Cancel
</Button>
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment