Last active
February 25, 2023 11:53
-
-
Save florianwalther-private/fe0845f7607ab0c08e83edee6d2d010f to your computer and use it in GitHub Desktop.
Improved file structure from feedback
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: () => 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(); | |
} catch (error) { | |
console.error(error); | |
if (error instanceof NotFoundError) { | |
onCommentDeleted(); | |
} else { | |
alert(error); | |
} | |
} finally { | |
setDeleteInProgress(false); | |
} | |
} | |
function handleReplyClick() { | |
if (user) { | |
setShowReplyBox(true); | |
} else { | |
appContext.showLoginModal(); | |
} | |
} | |
function handleEditClick() { | |
setShowEditBox(true); | |
setShowDeleteConfirmation(false); | |
} | |
function handleNewReply(newReply: CommentModel) { | |
onReplyCreated(newReply); | |
setShowReplyBox(false); | |
} | |
function handleCommentUpdate(updatedComment: CommentModel) { | |
onCommentUpdated(updatedComment); | |
setShowEditBox(false); | |
} | |
return ( | |
<div className="w-100"> | |
<hr /> | |
{showEditBox | |
? <EditCommentBox | |
comment={comment} | |
onCommentUpdated={handleCommentUpdate} | |
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={handleNewReply} | |
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