Skip to content

Commit

Permalink
feat(tasks): add comment delete confirm dialog (#6009)
Browse files Browse the repository at this point in the history
* feat(tasks): add comment delete confirm dialog

* refactor(tasks): create dedicated comment activity item component
  • Loading branch information
hermanwikner authored Mar 18, 2024
1 parent 217a47d commit c105115
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {CommentsListItem, type CommentsListItemProps} from '../../../../../structure/comments'
import {ActivityItem} from './TasksActivityItem'

const COMMENTS_LIST_ITEM_AVATAR_CONFIG: CommentsListItemProps['avatarConfig'] = {
parentCommentAvatar: false,
threadCommentsAvatar: true,
replyAvatar: true,
avatarSize: 0,
}

interface TasksActivityCommentItemProps extends Omit<CommentsListItemProps, 'mode' | 'isSelected'> {
// ...
}

export function TasksActivityCommentItem(props: TasksActivityCommentItemProps) {
const {parentComment} = props

return (
<ActivityItem userId={parentComment.authorId}>
<CommentsListItem
{...props}
avatarConfig={COMMENTS_LIST_ITEM_AVATAR_CONFIG}
canReply
innerPadding={1}
isSelected={false}
mode="default" // TODO: set dynamic mode?
/>
</ActivityItem>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ import {getJsonStream} from '../../../../../core/store/_legacy/history/history/g
import {
type CommentBaseCreatePayload,
type CommentCreatePayload,
CommentDeleteDialog,
type CommentInputProps,
type CommentReactionOption,
CommentsListItem,
type CommentsListItemProps,
type CommentThreadItem,
type CommentUpdatePayload,
useComments,
Expand All @@ -35,8 +34,8 @@ import {getMentionedUsers} from '../form/utils'
import {type FieldChange, trackFieldChanges} from './helpers/parseTransactions'
import {EditedAt} from './TaskActivityEditedAt'
import {TasksActivityCommentInput} from './TasksActivityCommentInput'
import {TasksActivityCommentItem} from './TasksActivityCommentItem'
import {TasksActivityCreatedAt} from './TasksActivityCreatedAt'
import {ActivityItem} from './TasksActivityItem'
import {TasksSubscribers} from './TasksSubscribers'

function useActivityLog(task: TaskDocument) {
Expand Down Expand Up @@ -107,13 +106,6 @@ const VARIANTS: Variants = {
visible: {opacity: 1, x: 0},
}

const COMMENTS_LIST_ITEM_AVATAR_CONFIG: CommentsListItemProps['avatarConfig'] = {
parentCommentAvatar: false,
threadCommentsAvatar: true,
replyAvatar: true,
avatarSize: 0,
}

const MotionStack = styled(motion(Stack))``

interface TasksActivityLogProps {
Expand All @@ -140,6 +132,10 @@ export function TasksActivityLog(props: TasksActivityLogProps) {
const {title: workspaceTitle, basePath} = useWorkspace()

const {comments, mentionOptions, operation, getComment} = useComments()
const [commentToDeleteId, setCommentToDeleteId] = useState<string | null>(null)
const [commentDeleteError, setCommentDeleteError] = useState<Error | null>(null)
const [commentDeleteLoading, setCommentDeleteLoading] = useState(false)

const loading = comments.loading
const taskComments = comments.data.open

Expand Down Expand Up @@ -247,13 +243,21 @@ export function TasksActivityLog(props: TasksActivityLogProps) {
[operation],
)

const handleCommentRemove = useCallback(
(id: string) => {
// TODO:
// The remove operation is not optimistic. We should display a
// dialog to confirm the removal and wait for the server to respond
// before removing the comment from the UI. (See `CommentsDocumentInspector`)
operation.remove(id)
const handleDeleteCommentStart = useCallback((id: string) => setCommentToDeleteId(id), [])
const handleDeleteCommentCancel = useCallback(() => setCommentToDeleteId(null), [])

const handleDeleteCommentConfirm = useCallback(
async (id: string) => {
try {
setCommentDeleteLoading(true)
setCommentDeleteError(null)
await operation.remove(id)
setCommentToDeleteId(null)
} catch (err) {
setCommentDeleteError(err)
} finally {
setCommentDeleteLoading(false)
}
},
[operation],
)
Expand Down Expand Up @@ -284,80 +288,94 @@ export function TasksActivityLog(props: TasksActivityLogProps) {
.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())
}, [activityData, taskComments])

const commentToDeleteIsParent = useMemo(() => {
const parent = taskComments.find((c) => c.parentComment?._id === commentToDeleteId)
const isParent = Boolean(parent && parent?.replies?.length > 0)

return isParent
}, [commentToDeleteId, taskComments])

return (
<Stack space={5}>
<Flex align="center">
<Box flex={1}>
<Text size={2} weight="semibold">
Activity
</Text>
</Box>

{currentUser?.id && (
<TasksSubscribers
currentUserId={currentUser.id}
value={value}
onChange={onChange}
path={path}
/>
)}
</Flex>

{loading && <LoadingBlock showText title="Loading activity" />}

<AnimatePresence>
{!loading && (
<MotionStack animate="visible" initial="hidden" space={3} variants={VARIANTS}>
{value.createdByUser && (
<Stack paddingBottom={1}>
<TasksActivityCreatedAt createdAt={value.createdByUser} authorId={value.authorId} />
</Stack>
)}

{currentUser && (
<CurrentWorkspaceProvider>
<Stack space={4} marginTop={1}>
{activity.map((item) => {
if (item._type === 'activity') {
return <EditedAt key={item.timestamp} activity={item.payload} />
}
return (
<ActivityItem
key={item.payload.parentComment._id}
userId={item.payload.parentComment.authorId}
>
<CommentsListItem
avatarConfig={COMMENTS_LIST_ITEM_AVATAR_CONFIG}
canReply
<>
{commentToDeleteId && (
<CommentDeleteDialog
commentId={commentToDeleteId}
error={commentDeleteError}
isParent={commentToDeleteIsParent}
loading={commentDeleteLoading}
onClose={handleDeleteCommentCancel}
onConfirm={handleDeleteCommentConfirm}
/>
)}

<Stack space={5}>
<Flex align="center">
<Box flex={1}>
<Text size={2} weight="semibold">
Activity
</Text>
</Box>

{currentUser?.id && (
<TasksSubscribers
currentUserId={currentUser.id}
value={value}
onChange={onChange}
path={path}
/>
)}
</Flex>

{loading && <LoadingBlock showText title="Loading activity" />}

<AnimatePresence>
{!loading && (
<MotionStack animate="visible" initial="hidden" space={3} variants={VARIANTS}>
{value.createdByUser && (
<Stack paddingBottom={1}>
<TasksActivityCreatedAt
createdAt={value.createdByUser}
authorId={value.authorId}
/>
</Stack>
)}

{currentUser && (
<CurrentWorkspaceProvider>
<Stack space={4} marginTop={1}>
{activity.map((item) => {
if (item._type === 'activity') {
return <EditedAt key={item.timestamp} activity={item.payload} />
}

return (
<TasksActivityCommentItem
currentUser={currentUser}
innerPadding={1}
isSelected={false}
key={item.payload.parentComment._id}
mentionOptions={mentionOptions}
mode="default" // TODO: set dynamic mode?
onCreateRetry={handleCommentCreateRetry}
onDelete={handleCommentRemove}
onDelete={handleDeleteCommentStart}
onEdit={handleCommentEdit}
onReactionSelect={handleCommentReact}
onReply={handleCommentReply}
parentComment={item.payload.parentComment}
replies={item.payload.replies}
/>
</ActivityItem>
)
})}

<TasksActivityCommentInput
currentUser={currentUser}
mentionOptions={mentionOptions}
onSubmit={handleCommentCreate}
/>
</Stack>
</CurrentWorkspaceProvider>
)}
</MotionStack>
)}
</AnimatePresence>
</Stack>
)
})}

<TasksActivityCommentInput
currentUser={currentUser}
mentionOptions={mentionOptions}
onSubmit={handleCommentCreate}
/>
</Stack>
</CurrentWorkspaceProvider>
)}
</MotionStack>
)}
</AnimatePresence>
</Stack>
</>
)
}

0 comments on commit c105115

Please sign in to comment.