diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/AddCommentField.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/AddCommentField.tsx
new file mode 100644
index 0000000000..5af6441d2c
--- /dev/null
+++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/AddCommentField.tsx
@@ -0,0 +1,44 @@
+import { FC } from 'react';
+import { Box, Button, styled, TextField } from '@mui/material';
+import { StyledAvatar } from './StyledAvatar';
+
+const AddCommentWrapper = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ gap: theme.spacing(2),
+ marginTop: theme.spacing(2),
+ marginBottom: theme.spacing(1),
+}));
+
+export const AddCommentField: FC<{
+ imageUrl: string;
+ commentText: string;
+ onAddComment: () => void;
+ onTypeComment: (text: string) => void;
+}> = ({ imageUrl, commentText, onTypeComment, onAddComment }) => (
+ <>
+
+
+ onTypeComment(e.target.value)}
+ value={commentText}
+ />
+
+
+
+
+ >
+);
diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/ChangeRequestComment.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/ChangeRequestComment.tsx
new file mode 100644
index 0000000000..df2e6c800b
--- /dev/null
+++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/ChangeRequestComment.tsx
@@ -0,0 +1,47 @@
+import { FC } from 'react';
+import Paper from '@mui/material/Paper';
+import { Box, styled, Typography } from '@mui/material';
+import TimeAgo from 'react-timeago';
+import { StyledAvatar } from './StyledAvatar';
+import { IChangeRequestComment } from '../../changeRequest.types';
+
+const ChangeRequestCommentWrapper = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ gap: theme.spacing(2),
+ marginTop: theme.spacing(2),
+}));
+const CommentPaper = styled(Paper)(({ theme }) => ({
+ width: '100%',
+ padding: theme.spacing(2),
+ backgroundColor: theme.palette.tertiary.light,
+}));
+
+const CommentHeader = styled(Box)(({ theme }) => ({
+ display: 'flex',
+ borderBottom: '1px solid',
+ borderColor: theme.palette.divider,
+ paddingBottom: theme.spacing(1),
+}));
+
+export const ChangeRequestComment: FC<{ comment: IChangeRequestComment }> = ({
+ comment,
+}) => (
+
+
+
+
+
+ {comment.createdBy.username}{' '}
+
+ commented{' '}
+
+
+
+
+ {comment.text}
+
+
+);
diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/StyledAvatar.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/StyledAvatar.tsx
new file mode 100644
index 0000000000..5aa775b090
--- /dev/null
+++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestComments/StyledAvatar.tsx
@@ -0,0 +1,7 @@
+import { Avatar, styled } from '@mui/material';
+
+export const StyledAvatar = styled(Avatar)(({ theme }) => ({
+ height: '30px',
+ width: '30px',
+ marginTop: theme.spacing(1),
+}));
diff --git a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx
index bab9d3359d..ba8270e6e0 100644
--- a/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx
+++ b/frontend/src/component/changeRequest/ChangeRequestOverview/ChangeRequestOverview.tsx
@@ -1,5 +1,5 @@
-import { Alert, styled } from '@mui/material';
-import { FC, useContext } from 'react';
+import { Alert, Button, styled, TextField, Typography } from '@mui/material';
+import { FC, useContext, useState } from 'react';
import { Box } from '@mui/material';
import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest';
import { ChangeRequestHeader } from './ChangeRequestHeader/ChangeRequestHeader';
@@ -19,6 +19,14 @@ import PermissionButton from 'component/common/PermissionButton/PermissionButton
import { APPLY_CHANGE_REQUEST } from 'component/providers/AccessProvider/permissions';
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
import AccessContext from 'contexts/AccessContext';
+import {
+ StyledAvatar,
+ StyledCard,
+} from './ChangeRequestHeader/ChangeRequestHeader.styles';
+import TimeAgo from 'react-timeago';
+import { InputProps as StandardInputProps } from '@mui/material/Input/Input';
+import { ChangeRequestComment } from './ChangeRequestComments/ChangeRequestComment';
+import { AddCommentField } from './ChangeRequestComments/AddCommentField';
const StyledAsideBox = styled(Box)(({ theme }) => ({
width: '30%',
@@ -48,13 +56,14 @@ export const ChangeRequestOverview: FC = () => {
const projectId = useRequiredPathParam('projectId');
const { user } = useAuthUser();
const { isAdmin } = useContext(AccessContext);
+ const [commentText, setCommentText] = useState('');
const id = useRequiredPathParam('id');
const { data: changeRequest, refetchChangeRequest } = useChangeRequest(
projectId,
id
);
- const { changeState } = useChangeRequestApi();
+ const { changeState, addComment } = useChangeRequestApi();
const { setToastData, setToastApiError } = useToast();
if (!changeRequest) {
@@ -77,6 +86,21 @@ export const ChangeRequestOverview: FC = () => {
}
};
+ const onAddComment = async () => {
+ try {
+ await addComment(projectId, id, commentText);
+ setCommentText('');
+ refetchChangeRequest();
+ setToastData({
+ type: 'success',
+ title: 'Success',
+ text: 'Comment added',
+ });
+ } catch (error: unknown) {
+ setToastApiError(formatUnknownError(error));
+ }
+ };
+
const isSelfReview =
changeRequest?.createdBy.id === user?.id &&
changeRequest.state === 'In review' &&
@@ -109,6 +133,18 @@ export const ChangeRequestOverview: FC = () => {
Changes
+ {changeRequest.comments?.map(comment => (
+
+ ))}
+
({
position: 'sticky',
top: -1,
- zIndex: 200 /* has to lower than header.zIndex */,
+ zIndex: 250 /* has to lower than header.zIndex and higher than body.zIndex */,
borderTop: `1px solid ${theme.palette.warning.border}`,
borderBottom: `1px solid ${theme.palette.warning.border}`,
backgroundColor: theme.palette.warning.light,
diff --git a/frontend/src/component/changeRequest/changeRequest.types.ts b/frontend/src/component/changeRequest/changeRequest.types.ts
index 4b79378302..64460c7361 100644
--- a/frontend/src/component/changeRequest/changeRequest.types.ts
+++ b/frontend/src/component/changeRequest/changeRequest.types.ts
@@ -10,6 +10,7 @@ export interface IChangeRequest {
createdAt: Date;
features: IChangeRequestFeature[];
approvals: IChangeRequestApproval[];
+ comments: IChangeRequestComment[];
conflict?: string;
}
@@ -30,6 +31,13 @@ export interface IChangeRequestApproval {
createdAt: Date;
}
+export interface IChangeRequestComment {
+ text: string;
+ createdBy: Pick;
+ createdAt: Date;
+ id: string;
+}
+
export interface IChangeRequestBase {
id: number;
action: ChangeRequestAction;
diff --git a/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts b/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts
index 0830c17b26..38228ac511 100644
--- a/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts
+++ b/frontend/src/hooks/api/actions/useChangeRequestApi/useChangeRequestApi.ts
@@ -98,12 +98,31 @@ export const useChangeRequestApi = () => {
}
};
+ const addComment = async (
+ projectId: string,
+ changeSetId: string,
+ text: string
+ ) => {
+ const path = `/api/admin/projects/${projectId}/change-requests/${changeSetId}/comments`;
+ const req = createRequest(path, {
+ method: 'POST',
+ body: JSON.stringify({ text }),
+ });
+
+ try {
+ return await makeRequest(req.caller, req.id);
+ } catch (e) {
+ throw e;
+ }
+ };
+
return {
addChangeRequest,
changeState,
discardChangeRequestEvent,
updateChangeRequestEnvironmentConfig,
discardDraft,
+ addComment,
errors,
loading,
};