mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-11 00:08:30 +01:00
first draft of the comments ui (#2440)
This commit is contained in:
parent
8dac08c5aa
commit
51ad239553
@ -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 }) => (
|
||||||
|
<>
|
||||||
|
<AddCommentWrapper>
|
||||||
|
<StyledAvatar src={imageUrl} />
|
||||||
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
placeholder="Add your comment here"
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
minRows={2}
|
||||||
|
onChange={e => onTypeComment(e.target.value)}
|
||||||
|
value={commentText}
|
||||||
|
/>
|
||||||
|
</AddCommentWrapper>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={onAddComment}
|
||||||
|
disabled={
|
||||||
|
commentText.trim().length === 0 ||
|
||||||
|
commentText.trim().length > 1000
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Comment
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
@ -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,
|
||||||
|
}) => (
|
||||||
|
<ChangeRequestCommentWrapper>
|
||||||
|
<StyledAvatar src={comment.createdBy.imageUrl} />
|
||||||
|
<CommentPaper variant="outlined">
|
||||||
|
<CommentHeader>
|
||||||
|
<Box>
|
||||||
|
<strong>{comment.createdBy.username}</strong>{' '}
|
||||||
|
<Typography color="text.secondary" component="span">
|
||||||
|
commented{' '}
|
||||||
|
<TimeAgo
|
||||||
|
minPeriod={60}
|
||||||
|
date={new Date(comment.createdAt)}
|
||||||
|
/>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</CommentHeader>
|
||||||
|
<Box sx={{ paddingTop: 2 }}>{comment.text}</Box>
|
||||||
|
</CommentPaper>
|
||||||
|
</ChangeRequestCommentWrapper>
|
||||||
|
);
|
@ -0,0 +1,7 @@
|
|||||||
|
import { Avatar, styled } from '@mui/material';
|
||||||
|
|
||||||
|
export const StyledAvatar = styled(Avatar)(({ theme }) => ({
|
||||||
|
height: '30px',
|
||||||
|
width: '30px',
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
}));
|
@ -1,5 +1,5 @@
|
|||||||
import { Alert, styled } from '@mui/material';
|
import { Alert, Button, styled, TextField, Typography } from '@mui/material';
|
||||||
import { FC, useContext } from 'react';
|
import { FC, useContext, useState } from 'react';
|
||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest';
|
import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest';
|
||||||
import { ChangeRequestHeader } from './ChangeRequestHeader/ChangeRequestHeader';
|
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 { APPLY_CHANGE_REQUEST } from 'component/providers/AccessProvider/permissions';
|
||||||
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser';
|
||||||
import AccessContext from 'contexts/AccessContext';
|
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 }) => ({
|
const StyledAsideBox = styled(Box)(({ theme }) => ({
|
||||||
width: '30%',
|
width: '30%',
|
||||||
@ -48,13 +56,14 @@ export const ChangeRequestOverview: FC = () => {
|
|||||||
const projectId = useRequiredPathParam('projectId');
|
const projectId = useRequiredPathParam('projectId');
|
||||||
const { user } = useAuthUser();
|
const { user } = useAuthUser();
|
||||||
const { isAdmin } = useContext(AccessContext);
|
const { isAdmin } = useContext(AccessContext);
|
||||||
|
const [commentText, setCommentText] = useState('');
|
||||||
|
|
||||||
const id = useRequiredPathParam('id');
|
const id = useRequiredPathParam('id');
|
||||||
const { data: changeRequest, refetchChangeRequest } = useChangeRequest(
|
const { data: changeRequest, refetchChangeRequest } = useChangeRequest(
|
||||||
projectId,
|
projectId,
|
||||||
id
|
id
|
||||||
);
|
);
|
||||||
const { changeState } = useChangeRequestApi();
|
const { changeState, addComment } = useChangeRequestApi();
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
if (!changeRequest) {
|
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 =
|
const isSelfReview =
|
||||||
changeRequest?.createdBy.id === user?.id &&
|
changeRequest?.createdBy.id === user?.id &&
|
||||||
changeRequest.state === 'In review' &&
|
changeRequest.state === 'In review' &&
|
||||||
@ -109,6 +133,18 @@ export const ChangeRequestOverview: FC = () => {
|
|||||||
<StyledInnerContainer>
|
<StyledInnerContainer>
|
||||||
Changes
|
Changes
|
||||||
<ChangeRequest changeRequest={changeRequest} />
|
<ChangeRequest changeRequest={changeRequest} />
|
||||||
|
{changeRequest.comments?.map(comment => (
|
||||||
|
<ChangeRequestComment
|
||||||
|
key={comment.id}
|
||||||
|
comment={comment}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<AddCommentField
|
||||||
|
imageUrl={changeRequest?.createdBy?.imageUrl}
|
||||||
|
commentText={commentText}
|
||||||
|
onAddComment={onAddComment}
|
||||||
|
onTypeComment={setCommentText}
|
||||||
|
/>
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
condition={isSelfReview}
|
condition={isSelfReview}
|
||||||
show={
|
show={
|
||||||
|
@ -68,7 +68,7 @@ const DraftBannerContent: FC<{
|
|||||||
const StickyBanner = styled(Box)(({ theme }) => ({
|
const StickyBanner = styled(Box)(({ theme }) => ({
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: -1,
|
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}`,
|
borderTop: `1px solid ${theme.palette.warning.border}`,
|
||||||
borderBottom: `1px solid ${theme.palette.warning.border}`,
|
borderBottom: `1px solid ${theme.palette.warning.border}`,
|
||||||
backgroundColor: theme.palette.warning.light,
|
backgroundColor: theme.palette.warning.light,
|
||||||
|
@ -10,6 +10,7 @@ export interface IChangeRequest {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
features: IChangeRequestFeature[];
|
features: IChangeRequestFeature[];
|
||||||
approvals: IChangeRequestApproval[];
|
approvals: IChangeRequestApproval[];
|
||||||
|
comments: IChangeRequestComment[];
|
||||||
conflict?: string;
|
conflict?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +31,13 @@ export interface IChangeRequestApproval {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IChangeRequestComment {
|
||||||
|
text: string;
|
||||||
|
createdBy: Pick<IUser, 'username' | 'imageUrl'>;
|
||||||
|
createdAt: Date;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IChangeRequestBase {
|
export interface IChangeRequestBase {
|
||||||
id: number;
|
id: number;
|
||||||
action: ChangeRequestAction;
|
action: ChangeRequestAction;
|
||||||
|
@ -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 {
|
return {
|
||||||
addChangeRequest,
|
addChangeRequest,
|
||||||
changeState,
|
changeState,
|
||||||
discardChangeRequestEvent,
|
discardChangeRequestEvent,
|
||||||
updateChangeRequestEnvironmentConfig,
|
updateChangeRequestEnvironmentConfig,
|
||||||
discardDraft,
|
discardDraft,
|
||||||
|
addComment,
|
||||||
errors,
|
errors,
|
||||||
loading,
|
loading,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user