Skip to content

Commit

Permalink
Merge pull request #4407 from dfe-analytical-services/EES-4544
Browse files Browse the repository at this point in the history
EES-4544 comments on data and embed blocks
  • Loading branch information
amyb-hiveit authored Nov 22, 2023
2 parents 91c7255 + e2aa4bf commit 0ff9fa6
Show file tree
Hide file tree
Showing 26 changed files with 1,635 additions and 414 deletions.
Original file line number Diff line number Diff line change
@@ -1,47 +1,54 @@
import styles from '@admin/components/comments/Comment.module.scss';
import CommentEditForm from '@admin/components/comments/CommentEditForm';
import { useCommentsContext } from '@admin/contexts/CommentsContext';
import { Comment as CommentType } from '@admin/services/types/content';
import { Comment as CommentData } from '@admin/services/types/content';
import FormattedDate from '@common/components/FormattedDate';
import { useAuthContext } from '@admin/contexts/AuthContext';
import classNames from 'classnames';
import Button from '@common/components/Button';
import ButtonGroup from '@common/components/ButtonGroup';
import ButtonText from '@common/components/ButtonText';
import useToggle from '@common/hooks/useToggle';
import React, { useEffect, useRef } from 'react';
import React, { ReactNode, useEffect, useRef } from 'react';

export type CommentType = 'inline' | 'block';

interface Props {
comment: CommentType;
comment: CommentData;
type: CommentType;
}

const Comment = ({ comment }: Props) => {
const { content, created, createdBy, id, resolved, resolvedBy, updated } =
comment;
export default function Comment({ comment, type }: Props) {
return type === 'inline' ? (
<InlineComment comment={comment} />
) : (
<BlockComment comment={comment} />
);
}

function InlineComment({ comment }: { comment: CommentData }) {
const {
selectedComment,
removeComment,
resolveComment,
unresolveComment,
setSelectedComment,
} = useCommentsContext();
const [isEditingComment, toggleIsEditingComment] = useToggle(false);
const [isFocused, toggleIsFocused] = useToggle(false);
const ref = useRef<HTMLDivElement>(null);
const { user } = useAuthContext();

useEffect(() => {
if (selectedComment.id === id) {
if (selectedComment.id === comment.id) {
ref.current?.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'start',
});
}
}, [id, selectedComment]);
}, [comment.id, selectedComment]);

const handleCommentSelection = () => {
if (!resolved) {
if (!comment.resolved) {
setSelectedComment({ id: comment.id });
}
};
Expand All @@ -51,57 +58,15 @@ const Comment = ({ comment }: Props) => {
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<div
className={classNames(styles.comment, {
[styles.active]: selectedComment.id === id,
[styles.active]: selectedComment.id === comment.id,
[styles.focused]: isFocused,
})}
ref={ref}
onClick={handleCommentSelection}
>
<p className="govuk-!-margin-bottom-0 govuk-body-s">
<strong data-testid="comment-author">
{`${createdBy.firstName} ${createdBy.lastName} `}
</strong>
<span className="govuk-visually-hidden"> commented on </span>
<br />
<FormattedDate format="d MMM yyyy, HH:mm">{created}</FormattedDate>
{updated && (
<>
<br />
(Updated{' '}
<FormattedDate format="d MMM yyyy, HH:mm">
{updated}
</FormattedDate>
)
</>
)}
</p>
{isEditingComment ? (
<div className={styles.form}>
<CommentEditForm
comment={comment}
id={id}
onCancel={toggleIsEditingComment.off}
onSubmit={toggleIsEditingComment.off}
/>
</div>
) : (
<div
className="govuk-!-margin-bottom-3 govuk-!-margin-top-2"
data-testid="comment-content"
>
{content}
</div>
)}

{resolved && (
<p className="govuk-!-margin-bottom-0 govuk-body-s">
Resolved by {resolvedBy?.firstName} {resolvedBy?.lastName} on{' '}
<FormattedDate format="d MMM yyyy, HH:mm">{resolved}</FormattedDate>
</p>
)}

{!isEditingComment && (
<>
<BaseComment
comment={comment}
highlightButton={
<button
aria-hidden
className="govuk-visually-hidden"
Expand All @@ -116,45 +81,129 @@ const Comment = ({ comment }: Props) => {
>
Highlight comment in content
</button>
{resolved ? (
<ButtonText
onClick={async () => {
await unresolveComment.current(comment.id, true);
}}
>
Unresolve
</ButtonText>
) : (
<ButtonGroup className="govuk-!-margin-bottom-0">
<Button
onClick={async () => {
await resolveComment.current(comment.id, true);
}}
>
Resolve
</Button>
{user?.id === createdBy.id && (
<>
<ButtonText onClick={toggleIsEditingComment.on}>
Edit
</ButtonText>
}
onRemove={() => {
removeComment.current(comment.id);
}}
onResolve={async () => {
await resolveComment.current(comment.id, true);
}}
onUnresolve={async () => {
await unresolveComment.current(comment.id, true);
}}
/>
</div>
</li>
);
}

<ButtonText
onClick={async () => {
removeComment.current(comment.id);
}}
>
Delete
</ButtonText>
</>
)}
</ButtonGroup>
)}
</>
)}
function BlockComment({ comment }: { comment: CommentData }) {
const { deleteComment, resolveComment, unresolveComment } =
useCommentsContext();

return (
<li className={styles.container} data-testid="comment">
<div className={styles.comment}>
<BaseComment
comment={comment}
onRemove={async () => {
await deleteComment(comment.id);
}}
onResolve={async () => {
await resolveComment.current(comment.id, true);
}}
onUnresolve={async () => {
await unresolveComment.current(comment.id, true);
}}
/>
</div>
</li>
);
};
}

interface BaseCommentProps {
comment: CommentData;
highlightButton?: ReactNode;
onRemove: () => void;
onResolve: () => void;
onUnresolve: () => void;
}

function BaseComment({
comment,
highlightButton,
onRemove,
onResolve,
onUnresolve,
}: BaseCommentProps) {
const { content, created, createdBy, id, resolved, resolvedBy, updated } =
comment;

const { user } = useAuthContext();

const [isEditingComment, toggleIsEditingComment] = useToggle(false);

export default Comment;
return (
<>
<p className="govuk-!-margin-bottom-0 govuk-body-s">
<strong data-testid="comment-author">
{`${createdBy.firstName} ${createdBy.lastName} `}
</strong>
<span className="govuk-visually-hidden"> commented on </span>
<br />
<FormattedDate format="d MMM yyyy, HH:mm">{created}</FormattedDate>
{updated && (
<>
<br />
(Updated{' '}
<FormattedDate format="d MMM yyyy, HH:mm">{updated}</FormattedDate>)
</>
)}
</p>
{isEditingComment ? (
<div className={styles.form}>
<CommentEditForm
comment={comment}
id={id}
onCancel={toggleIsEditingComment.off}
onSubmit={toggleIsEditingComment.off}
/>
</div>
) : (
<div
className="govuk-!-margin-bottom-3 govuk-!-margin-top-2"
data-testid="comment-content"
>
{content}
</div>
)}
{resolved && (
<p className="govuk-!-margin-bottom-0 govuk-body-s">
Resolved by {resolvedBy?.firstName} {resolvedBy?.lastName} on{' '}
<FormattedDate format="d MMM yyyy, HH:mm">{resolved}</FormattedDate>
</p>
)}
{!isEditingComment && (
<>
{highlightButton}
{resolved ? (
<ButtonText onClick={onUnresolve}>Unresolve</ButtonText>
) : (
<ButtonGroup className="govuk-!-margin-bottom-0">
<Button onClick={onResolve}>Resolve</Button>
{user?.id === createdBy.id && (
<>
<ButtonText onClick={toggleIsEditingComment.on}>
Edit
</ButtonText>

<ButtonText onClick={onRemove}>Delete</ButtonText>
</>
)}
</ButtonGroup>
)}
</>
)}
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
.container {
background: govuk-colour('white');
border: 1px solid govuk-colour('dark-grey');
margin-left: -$dfe-comments-gutter/2;
margin-bottom: govuk-spacing(2);
padding: govuk-spacing(2);
position: absolute;
top: 0;
width: $dfe-comments-width;
z-index: 9;

@include govuk-media-query($from: desktop) {
margin-bottom: 0;
position: absolute;
top: 0;
z-index: 9;
width: $dfe-comments-width - $dfe-comments-gutter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ interface Props {
onSave: () => void;
}

const CommentAddForm = ({ baseId, containerRef, onCancel, onSave }: Props) => {
export default function CommentAddForm({
baseId,
containerRef,
onCancel,
onSave,
}: Props) {
const { addComment, setCurrentInteraction } = useCommentsContext();

const ref = useRef<HTMLDivElement>(null);
Expand All @@ -46,7 +51,11 @@ const CommentAddForm = ({ baseId, containerRef, onCancel, onSave }: Props) => {
};

return (
<div className={classNames(styles.container, positionStyle)} ref={ref}>
<div
className={classNames(styles.container, positionStyle)}
data-testid="comment-add-form"
ref={ref}
>
<FormProvider
initialValues={{
content: '',
Expand All @@ -73,6 +82,7 @@ const CommentAddForm = ({ baseId, containerRef, onCancel, onSave }: Props) => {
<Button type="submit" disabled={formState.isSubmitting}>
Add comment
</Button>

<ButtonText
onClick={() => {
setCurrentInteraction?.({
Expand All @@ -91,6 +101,4 @@ const CommentAddForm = ({ baseId, containerRef, onCancel, onSave }: Props) => {
</FormProvider>
</div>
);
};

export default CommentAddForm;
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ interface Props {
onSubmit: () => void;
}

const CommentEditForm = ({ comment, id, onCancel, onSubmit }: Props) => {
export default function CommentEditForm({
comment,
id,
onCancel,
onSubmit,
}: Props) {
const { content } = comment;

const { updateComment } = useCommentsContext();
Expand Down Expand Up @@ -74,6 +79,4 @@ const CommentEditForm = ({ comment, id, onCancel, onSubmit }: Props) => {
}}
</FormProvider>
);
};

export default CommentEditForm;
}
Loading

0 comments on commit 0ff9fa6

Please sign in to comment.