From 914657334d7c0223c0f01bb0c4e07716fea8548c Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Fri, 20 Oct 2023 17:55:20 +0530 Subject: [PATCH] chore: peekoverview issue comments and comment reactions (#2507) --- .../issue-peek-overview/activity/card.tsx | 135 +++++++++++ .../activity/comment-card.tsx | 209 ++++++++++++++++++ .../activity/comment-editor.tsx | 136 ++++++++++++ .../activity/comment-reaction.tsx | 56 +++++ .../issue-peek-overview/activity/index.ts | 5 + .../issue-peek-overview/activity/view.tsx | 59 +++++ .../issue-peek-overview/issue-detail.tsx | 3 +- .../issue-peek-overview/reactions/root.tsx | 5 +- .../issues/issue-peek-overview/root.tsx | 30 ++- .../issues/issue-peek-overview/view.tsx | 45 +++- web/store/issue/issue_detail.store.ts | 142 ++++++++++-- 11 files changed, 791 insertions(+), 34 deletions(-) create mode 100644 web/components/issues/issue-peek-overview/activity/card.tsx create mode 100644 web/components/issues/issue-peek-overview/activity/comment-card.tsx create mode 100644 web/components/issues/issue-peek-overview/activity/comment-editor.tsx create mode 100644 web/components/issues/issue-peek-overview/activity/comment-reaction.tsx create mode 100644 web/components/issues/issue-peek-overview/activity/index.ts create mode 100644 web/components/issues/issue-peek-overview/activity/view.tsx diff --git a/web/components/issues/issue-peek-overview/activity/card.tsx b/web/components/issues/issue-peek-overview/activity/card.tsx new file mode 100644 index 000000000..b66b5754f --- /dev/null +++ b/web/components/issues/issue-peek-overview/activity/card.tsx @@ -0,0 +1,135 @@ +import { FC } from "react"; +import Link from "next/link"; +import { History } from "lucide-react"; +// packages +import { Loader, Tooltip } from "@plane/ui"; +// components +import { ActivityIcon, ActivityMessage } from "components/core"; +import { IssueCommentCard } from "./comment-card"; +// helpers +import { render24HourFormatTime, renderLongDateFormat, timeAgo } from "helpers/date-time.helper"; + +interface IssueActivityCard { + workspaceSlug: string; + projectId: string; + user: any; + issueComments: any; + issueCommentUpdate: (comment: any) => void; + issueCommentRemove: (commentId: string) => void; + issueCommentReactionCreate: (commentId: string, reaction: string) => void; + issueCommentReactionRemove: (commentId: string, reaction: string) => void; +} + +export const IssueActivityCard: FC = (props) => { + const { + workspaceSlug, + projectId, + user, + issueComments, + issueCommentUpdate, + issueCommentRemove, + issueCommentReactionCreate, + issueCommentReactionRemove, + } = props; + + console.log("issueComments", issueComments); + + return ( +
+
    + {issueComments.map((activityItem: any, index: any) => { + // determines what type of action is performed + const message = activityItem.field ? : "created the issue."; + + if ("field" in activityItem && activityItem.field !== "updated_by") { + return ( +
  • +
    + {issueComments.length > 1 && index !== issueComments.length - 1 ? ( +
    +
  • + ); + } else if ("comment_json" in activityItem) + return ( +
    + +
    + ); + })} +
+
+ ); +}; diff --git a/web/components/issues/issue-peek-overview/activity/comment-card.tsx b/web/components/issues/issue-peek-overview/activity/comment-card.tsx new file mode 100644 index 000000000..193352e9a --- /dev/null +++ b/web/components/issues/issue-peek-overview/activity/comment-card.tsx @@ -0,0 +1,209 @@ +import React, { useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { Check, Globe2, Lock, MessageSquare, Pencil, Trash2, X } from "lucide-react"; +// services +import { FileService } from "services/file.service"; +// ui +import { CustomMenu } from "@plane/ui"; +import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-text-editor"; +// components +import { IssueCommentReaction } from "./comment-reaction"; +// helpers +import { timeAgo } from "helpers/date-time.helper"; +// types +import type { IIssueComment } from "types"; + +// services +const fileService = new FileService(); + +type IIssueCommentCard = { + comment: IIssueComment; + handleCommentDeletion: (comment: string) => void; + onSubmit: (data: Partial) => void; + showAccessSpecifier?: boolean; + workspaceSlug: string; + projectId: string; + user: any; + issueCommentReactionCreate: (commentId: string, reaction: string) => void; + issueCommentReactionRemove: (commentId: string, reaction: string) => void; +}; + +export const IssueCommentCard: React.FC = (props) => { + const { + comment, + handleCommentDeletion, + onSubmit, + showAccessSpecifier = false, + workspaceSlug, + projectId, + user, + issueCommentReactionCreate, + issueCommentReactionRemove, + } = props; + + const editorRef = React.useRef(null); + const showEditorRef = React.useRef(null); + + const [isEditing, setIsEditing] = useState(false); + + const { + formState: { isSubmitting }, + handleSubmit, + setFocus, + watch, + setValue, + } = useForm({ + defaultValues: comment, + }); + + const formSubmit = (formData: Partial) => { + if (isSubmitting) return; + + setIsEditing(false); + + onSubmit({ id: comment.id, ...formData }); + + editorRef.current?.setEditorValue(formData.comment_html); + showEditorRef.current?.setEditorValue(formData.comment_html); + }; + + useEffect(() => { + isEditing && setFocus("comment"); + }, [isEditing, setFocus]); + + return ( +
+
+ {comment.actor_detail.avatar && comment.actor_detail.avatar !== "" ? ( + { + ) : ( +
+ {comment.actor_detail.is_bot + ? comment.actor_detail.first_name.charAt(0) + : comment.actor_detail.display_name.charAt(0)} +
+ )} + + + +
+
+
+
+ {comment.actor_detail.is_bot ? comment.actor_detail.first_name + " Bot" : comment.actor_detail.display_name} +
+

commented {timeAgo(comment.created_at)}

+
+ +
+
+
+ { + setValue("comment_json", comment_json); + setValue("comment_html", comment_html); + }} + /> +
+
+ + + +
+
+ +
+ {showAccessSpecifier && ( +
+ {comment.access === "INTERNAL" ? : } +
+ )} + + +
+ +
+
+
+
+ {user?.id === comment.actor && ( + + setIsEditing(true)} className="flex items-center gap-1"> + + Edit comment + + {showAccessSpecifier && ( + <> + {comment.access === "INTERNAL" ? ( + onSubmit({ id: comment.id, access: "EXTERNAL" })} + className="flex items-center gap-1" + > + + Switch to public comment + + ) : ( + onSubmit({ id: comment.id, access: "INTERNAL" })} + className="flex items-center gap-1" + > + + Switch to private comment + + )} + + )} + { + handleCommentDeletion(comment.id); + }} + className="flex items-center gap-1" + > + + Delete comment + + + )} +
+ ); +}; diff --git a/web/components/issues/issue-peek-overview/activity/comment-editor.tsx b/web/components/issues/issue-peek-overview/activity/comment-editor.tsx new file mode 100644 index 000000000..dabdc355f --- /dev/null +++ b/web/components/issues/issue-peek-overview/activity/comment-editor.tsx @@ -0,0 +1,136 @@ +import React from "react"; +import { useRouter } from "next/router"; +import { useForm, Controller } from "react-hook-form"; + +// services +import { FileService } from "services/file.service"; +// components +import { LiteTextEditorWithRef } from "@plane/lite-text-editor"; +// ui +import { Button, Tooltip } from "@plane/ui"; +import { Globe2, Lock } from "lucide-react"; + +// types +import type { IIssueComment } from "types"; + +const defaultValues: Partial = { + access: "INTERNAL", + comment_html: "", +}; + +type IIssueCommentEditor = { + disabled?: boolean; + onSubmit: (data: IIssueComment) => Promise; + showAccessSpecifier?: boolean; +}; + +type commentAccessType = { + icon: any; + key: string; + label: "Private" | "Public"; +}; +const commentAccess: commentAccessType[] = [ + { + icon: Lock, + key: "INTERNAL", + label: "Private", + }, + { + icon: Globe2, + key: "EXTERNAL", + label: "Public", + }, +]; + +// services +const fileService = new FileService(); + +export const IssueCommentEditor: React.FC = (props) => { + const { disabled = false, onSubmit, showAccessSpecifier = false } = props; + + const editorRef = React.useRef(null); + + const router = useRouter(); + const { workspaceSlug } = router.query; + + const { + control, + formState: { isSubmitting }, + handleSubmit, + reset, + } = useForm({ defaultValues }); + + const handleAddComment = async (formData: IIssueComment) => { + if (!formData.comment_html || isSubmitting) return; + + await onSubmit(formData).then(() => { + reset(defaultValues); + editorRef.current?.clearEditor(); + }); + }; + + return ( +
+
+
+ {showAccessSpecifier && ( +
+ ( +
+ {commentAccess.map((access) => ( + + + + ))} +
+ )} + /> +
+ )} + ( + ( +

" : commentValue} + customClassName="p-3 min-h-[100px] shadow-sm" + debouncedUpdatesEnabled={false} + onChange={(comment_json: Object, comment_html: string) => onCommentChange(comment_html)} + commentAccessSpecifier={{ accessValue, onAccessChange, showAccessSpecifier, commentAccess }} + /> + )} + /> + )} + /> +
+ + +
+
+ ); +}; diff --git a/web/components/issues/issue-peek-overview/activity/comment-reaction.tsx b/web/components/issues/issue-peek-overview/activity/comment-reaction.tsx new file mode 100644 index 000000000..571761cf0 --- /dev/null +++ b/web/components/issues/issue-peek-overview/activity/comment-reaction.tsx @@ -0,0 +1,56 @@ +import { FC } from "react"; +import useSWR from "swr"; +import { observer } from "mobx-react-lite"; +// components +import { IssueReaction } from "../reactions"; +// hooks +import { useMobxStore } from "lib/mobx/store-provider"; +// types +import { RootStore } from "store/root"; + +interface IIssueCommentReaction { + workspaceSlug: any; + projectId: any; + user: any; + + comment: any; + issueCommentReactionCreate: (commentId: string, reaction: string) => void; + issueCommentReactionRemove: (commentId: string, reaction: string) => void; +} + +export const IssueCommentReaction: FC = observer((props) => { + const { workspaceSlug, projectId, user, comment, issueCommentReactionCreate, issueCommentReactionRemove } = props; + + const { issueDetail: issueDetailStore }: RootStore = useMobxStore(); + + const handleCommentReactionCreate = (reaction: string) => { + if (issueCommentReactionCreate && comment?.id) issueCommentReactionCreate(comment?.id, reaction); + }; + + const handleCommentReactionRemove = (reaction: string) => { + if (issueCommentReactionRemove && comment?.id) issueCommentReactionRemove(comment?.id, reaction); + }; + + useSWR( + workspaceSlug && projectId && comment && comment?.id ? `ISSUE+PEEK_OVERVIEW_COMMENT_${comment?.id}` : null, + () => { + if (workspaceSlug && projectId && comment && comment.id) { + issueDetailStore.fetchIssueCommentReactions(workspaceSlug, projectId, comment?.id); + } + } + ); + + const issueReactions = issueDetailStore?.getIssueCommentReactionsByCommentId(comment.id) || []; + + return ( +
+ +
+ ); +}); diff --git a/web/components/issues/issue-peek-overview/activity/index.ts b/web/components/issues/issue-peek-overview/activity/index.ts new file mode 100644 index 000000000..a2196d35d --- /dev/null +++ b/web/components/issues/issue-peek-overview/activity/index.ts @@ -0,0 +1,5 @@ +export * from "./view"; + +export * from "./card"; +export * from "./comment-editor"; +export * from "./comment-reaction"; diff --git a/web/components/issues/issue-peek-overview/activity/view.tsx b/web/components/issues/issue-peek-overview/activity/view.tsx new file mode 100644 index 000000000..f5db0f297 --- /dev/null +++ b/web/components/issues/issue-peek-overview/activity/view.tsx @@ -0,0 +1,59 @@ +import { FC } from "react"; +// components +import { IssueActivityCard } from "./card"; +import { IssueCommentEditor } from "./comment-editor"; + +interface IIssueComment { + workspaceSlug: string; + projectId: string; + user: any; + issueComments: any; + issueCommentCreate: (comment: any) => void; + issueCommentUpdate: (comment: any) => void; + issueCommentRemove: (commentId: string) => void; + issueCommentReactionCreate: (commentId: string, reaction: string) => void; + issueCommentReactionRemove: (commentId: string, reaction: string) => void; +} + +export const IssueComment: FC = (props) => { + const { + workspaceSlug, + projectId, + user, + issueComments, + issueCommentCreate, + issueCommentUpdate, + issueCommentRemove, + issueCommentReactionCreate, + issueCommentReactionRemove, + } = props; + + const handleAddComment = async (formData: any) => { + if (!formData.comment_html) return; + await issueCommentCreate(formData); + }; + + return ( +
+
Activity
+ +
+ + + +
+
+ ); +}; diff --git a/web/components/issues/issue-peek-overview/issue-detail.tsx b/web/components/issues/issue-peek-overview/issue-detail.tsx index b9aff3f8d..2a822e151 100644 --- a/web/components/issues/issue-peek-overview/issue-detail.tsx +++ b/web/components/issues/issue-peek-overview/issue-detail.tsx @@ -30,7 +30,7 @@ export const PeekOverviewIssueDetails: FC = (props) = }, 1500); return ( -
+
{issue?.project_detail?.identifier}-{issue?.sequence_id}
@@ -46,6 +46,7 @@ export const PeekOverviewIssueDetails: FC = (props) = onChange={(description: Object, description_html: string) => { debouncedIssueDescription(description_html); }} + customClassName="p-3 min-h-[80px] shadow-sm" /> void; issueReactionRemove: (reaction: string) => void; + position?: "top" | "bottom"; } export const IssueReaction: FC = (props) => { - const { issueReactions, user, issueReactionCreate, issueReactionRemove } = props; + const { issueReactions, user, issueReactionCreate, issueReactionRemove, position = "bottom" } = props; const handleReaction = (reaction: string) => { const isReactionAvailable = @@ -22,7 +23,7 @@ export const IssueReaction: FC = (props) => { return (
- +
); diff --git a/web/components/issues/issue-peek-overview/root.tsx b/web/components/issues/issue-peek-overview/root.tsx index 6df455436..8b96a3100 100644 --- a/web/components/issues/issue-peek-overview/root.tsx +++ b/web/components/issues/issue-peek-overview/root.tsx @@ -34,13 +34,26 @@ export const IssuePeekOverview: FC = observer((props) => { } }; - const issueReactionCreate = (data: string) => { - issueDetailStore.createIssueReaction(workspaceSlug, projectId, issueId, data); - }; + const issueReactionCreate = (reaction: string) => + issueDetailStore.createIssueReaction(workspaceSlug, projectId, issueId, reaction); - const issueReactionRemove = (data: string) => { - issueDetailStore.removeIssueReaction(workspaceSlug, projectId, issueId, data); - }; + const issueReactionRemove = (reaction: string) => + issueDetailStore.removeIssueReaction(workspaceSlug, projectId, issueId, reaction); + + const issueCommentCreate = (comment: any) => + issueDetailStore.createIssueComment(workspaceSlug, projectId, issueId, comment); + + const issueCommentUpdate = (comment: any) => + issueDetailStore.updateIssueComment(workspaceSlug, projectId, issueId, comment?.id, comment); + + const issueCommentRemove = (commentId: string) => + issueDetailStore.removeIssueComment(workspaceSlug, projectId, issueId, commentId); + + const issueCommentReactionCreate = (commentId: string, reaction: string) => + issueDetailStore.creationIssueCommentReaction(workspaceSlug, projectId, commentId, reaction); + + const issueCommentReactionRemove = (commentId: string, reaction: string) => + issueDetailStore.removeIssueCommentReaction(workspaceSlug, projectId, commentId, reaction); return ( = observer((props) => { issueUpdate={issueUpdate} issueReactionCreate={issueReactionCreate} issueReactionRemove={issueReactionRemove} + issueCommentCreate={issueCommentCreate} + issueCommentUpdate={issueCommentUpdate} + issueCommentRemove={issueCommentRemove} + issueCommentReactionCreate={issueCommentReactionCreate} + issueCommentReactionRemove={issueCommentReactionRemove} > {children} diff --git a/web/components/issues/issue-peek-overview/view.tsx b/web/components/issues/issue-peek-overview/view.tsx index 04e12999a..32dc0347e 100644 --- a/web/components/issues/issue-peek-overview/view.tsx +++ b/web/components/issues/issue-peek-overview/view.tsx @@ -6,6 +6,7 @@ import useSWR from "swr"; // components import { PeekOverviewIssueDetails } from "./issue-detail"; import { PeekOverviewProperties } from "./properties"; +import { IssueComment } from "./activity"; // types import { IIssue } from "types"; import { RootStore } from "store/root"; @@ -19,6 +20,11 @@ interface IIssueView { issueUpdate: (issue: Partial) => void; issueReactionCreate: (reaction: string) => void; issueReactionRemove: (reaction: string) => void; + issueCommentCreate: (comment: any) => void; + issueCommentUpdate: (comment: any) => void; + issueCommentRemove: (commentId: string) => void; + issueCommentReactionCreate: (commentId: string, reaction: string) => void; + issueCommentReactionRemove: (commentId: string, reaction: string) => void; states: any; members: any; priorities: any; @@ -53,6 +59,11 @@ export const IssueView: FC = observer((props) => { issueUpdate, issueReactionCreate, issueReactionRemove, + issueCommentCreate, + issueCommentUpdate, + issueCommentRemove, + issueCommentReactionCreate, + issueCommentReactionRemove, states, members, priorities, @@ -108,6 +119,8 @@ export const IssueView: FC = observer((props) => { const issue = issueDetailStore.getIssue; const issueReactions = issueDetailStore.getIssueReactions; + const issueComments = issueDetailStore.getIssueComments; + const user = userStore?.currentUser; return ( @@ -178,7 +191,7 @@ export const IssueView: FC = observer((props) => { issue && ( <> {["side-peek", "modal"].includes(peekMode) ? ( -
+
= observer((props) => { priorities={priorities} /> - {/*
Activity
*/} +
+ +
) : (
-
+
= observer((props) => { issueReactionRemove={issueReactionRemove} /> - {/*
Activity
*/} +
+ +
Promise; @@ -59,23 +59,16 @@ export interface IIssueDetailStore { ) => Promise; removeIssueComment: (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => Promise; - fetchIssueCommentReactions: ( + fetchIssueCommentReactions: (workspaceSlug: string, projectId: string, commentId: string) => Promise; + creationIssueCommentReaction: ( workspaceSlug: string, projectId: string, - issueId: string, - commentId: string - ) => Promise; - addIssueCommentReaction: ( - workspaceSlug: string, - projectId: string, - issueId: string, commentId: string, reaction: string ) => Promise; removeIssueCommentReaction: ( workspaceSlug: string, projectId: string, - issueId: string, commentId: string, reaction: string ) => Promise; @@ -104,6 +97,7 @@ export class IssueDetailStore implements IIssueDetailStore { // service issueService; issueReactionService; + issueCommentService; constructor(_rootStore: RootStore) { makeObservable(this, { @@ -120,10 +114,11 @@ export class IssueDetailStore implements IIssueDetailStore { getIssue: computed, getIssueReactions: computed, getIssueComments: computed, - getIssueCommentReactions: computed, setPeekId: action, + getIssueCommentReactionsByCommentId: action, + fetchIssueDetails: action, createIssue: action, updateIssue: action, @@ -141,13 +136,14 @@ export class IssueDetailStore implements IIssueDetailStore { removeIssueComment: action, fetchIssueCommentReactions: action, - addIssueCommentReaction: action, + creationIssueCommentReaction: action, removeIssueCommentReaction: action, }); this.rootStore = _rootStore; this.issueService = new IssueService(); this.issueReactionService = new IssueReactionService(); + this.issueCommentService = new IssueCommentService(); } get getIssue() { @@ -168,11 +164,11 @@ export class IssueDetailStore implements IIssueDetailStore { return _comments || null; } - get getIssueCommentReactions() { - if (!this.peekId) return null; - const _reactions = this.issue_comment_reactions[this.peekId]; + getIssueCommentReactionsByCommentId = (commentId: string) => { + if (!commentId) return null; + const _reactions = this.issue_comment_reactions[commentId]; return _reactions || null; - } + }; setPeekId = (issueId: string | null) => (this.peekId = issueId); @@ -426,6 +422,16 @@ export class IssueDetailStore implements IIssueDetailStore { // comments fetchIssueComments = async (workspaceSlug: string, projectId: string, issueId: string) => { try { + const _issueCommentResponse = await this.issueService.getIssueActivities(workspaceSlug, projectId, issueId); + + const _issueComments = { + ...this.issue_comments, + [issueId]: [..._issueCommentResponse], + }; + + runInAction(() => { + this.issue_comments = _issueComments; + }); } catch (error) { console.warn("error creating the issue comment", error); throw error; @@ -433,6 +439,22 @@ export class IssueDetailStore implements IIssueDetailStore { }; createIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => { try { + const _issueCommentResponse = await this.issueCommentService.createIssueComment( + workspaceSlug, + projectId, + issueId, + data, + undefined + ); + + const _issueComments = { + ...this.issue_comments, + [issueId]: [...this.issue_comments[issueId], _issueCommentResponse], + }; + + runInAction(() => { + this.issue_comments = _issueComments; + }); } catch (error) { console.warn("error creating the issue comment", error); throw error; @@ -446,6 +468,25 @@ export class IssueDetailStore implements IIssueDetailStore { data: any ) => { try { + const _issueCommentResponse = await this.issueCommentService.patchIssueComment( + workspaceSlug, + projectId, + issueId, + commentId, + data, + undefined + ); + + const _issueComments = { + ...this.issue_comments, + [issueId]: this.issue_comments[issueId].map((comment: any) => + comment.id === commentId ? _issueCommentResponse : comment + ), + }; + + runInAction(() => { + this.issue_comments = _issueComments; + }); } catch (error) { console.warn("error updating the issue comment", error); throw error; @@ -453,6 +494,16 @@ export class IssueDetailStore implements IIssueDetailStore { }; removeIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => { try { + const _issueComments = { + ...this.issue_comments, + [issueId]: this.issue_comments[issueId].filter((comment: any) => comment.id != commentId), + }; + + await this.issueCommentService.deleteIssueComment(workspaceSlug, projectId, issueId, commentId, undefined); + + runInAction(() => { + this.issue_comments = _issueComments; + }); } catch (error) { console.warn("error removing the issue comment", error); throw error; @@ -460,21 +511,52 @@ export class IssueDetailStore implements IIssueDetailStore { }; // comment reaction - fetchIssueCommentReactions = async (workspaceSlug: string, projectId: string, issueId: string, commentId: string) => { + fetchIssueCommentReactions = async (workspaceSlug: string, projectId: string, commentId: string) => { try { + const _reactions = await this.issueReactionService.listIssueCommentReactions(workspaceSlug, projectId, commentId); + + const _issue_comment_reactions = { + ...this.issue_comment_reactions, + [commentId]: groupReactionEmojis(_reactions), + }; + + runInAction(() => { + this.issue_comment_reactions = _issue_comment_reactions; + }); } catch (error) { console.warn("error removing the issue comment", error); throw error; } }; - addIssueCommentReaction = async ( + creationIssueCommentReaction = async ( workspaceSlug: string, projectId: string, - issueId: string, commentId: string, reaction: string ) => { + let _currentReactions = this.getIssueCommentReactionsByCommentId(commentId); + try { + const _reaction = await this.issueReactionService.createIssueCommentReaction( + workspaceSlug, + projectId, + commentId, + { + reaction, + } + ); + + _currentReactions = { + ..._currentReactions, + [reaction]: [..._currentReactions[reaction], { ..._reaction }], + }; + + runInAction(() => { + this.issue_comment_reactions = { + ...this.issue_comment_reactions, + [commentId]: _currentReactions, + }; + }); } catch (error) { console.warn("error removing the issue comment", error); throw error; @@ -483,11 +565,29 @@ export class IssueDetailStore implements IIssueDetailStore { removeIssueCommentReaction = async ( workspaceSlug: string, projectId: string, - issueId: string, commentId: string, reaction: string ) => { + let _currentReactions = this.getIssueCommentReactionsByCommentId(commentId); + try { + const user = this.rootStore.user.currentUser; + + if (user) { + _currentReactions = { + ..._currentReactions, + [reaction]: [..._currentReactions[reaction].filter((r: any) => r.actor !== user.id)], + }; + + runInAction(() => { + this.issue_comment_reactions = { + ...this.issue_comment_reactions, + [commentId]: _currentReactions, + }; + }); + + await this.issueReactionService.deleteIssueCommentReaction(workspaceSlug, projectId, commentId, reaction); + } } catch (error) { console.warn("error removing the issue comment", error); throw error;