File size: 14,582 Bytes
ec4551b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 | import { FC, Fragment, useCallback, useEffect, useState } from 'react';
import dayjs from 'dayjs';
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
import { useModals } from '@gitroom/frontend/components/layout/new-modal';
import { Textarea } from '@gitroom/react/form/textarea';
import { Button } from '@gitroom/react/form/button';
import clsx from 'clsx';
import { useUser } from '@gitroom/frontend/components/layout/user.context';
import { Input } from '@gitroom/react/form/input';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
export const CommentBox: FC<{
value?: string;
type: 'textarea' | 'input';
onChange: (comment: string) => void;
}> = (props) => {
const { value, onChange, type } = props;
const Component = type === 'textarea' ? Textarea : Input;
const [newComment, setNewComment] = useState(value || '');
const newCommentFunc = useCallback(
(event: {
target: {
value: string;
};
}) => {
setNewComment(event.target.value);
},
[newComment]
);
const changeIt = useCallback(() => {
onChange(newComment);
setNewComment('');
}, [newComment]);
return (
<div
className={clsx(
'flex',
type === 'textarea' ? 'flex-col' : 'flex-row flex items-end gap-[10px]'
)}
>
<div className={clsx(type === 'input' && 'flex-1')}>
<Component
label={type === 'textarea' ? 'Add comment' : ''}
placeholder={type === 'input' ? 'Add comment' : ''}
name="comment"
disableForm={true}
value={newComment}
onChange={newCommentFunc}
/>
</div>
<Button
disabled={newComment.length < 2}
onClick={changeIt}
className={clsx(type === 'input' && 'mb-[27px]')}
>
{value ? 'Update' : 'Add comment'}
</Button>
</div>
);
};
interface Comments {
id: string;
content: string;
user: {
email: string;
id: string;
};
childrenComment: Comments[];
}
export const EditableCommentComponent: FC<{
comment: Comments;
onEdit: (content: string) => void;
onDelete: () => void;
}> = (props) => {
const { comment, onEdit, onDelete } = props;
const [commentContent, setCommentContent] = useState(comment.content);
const [editMode, setEditMode] = useState(false);
const user = useUser();
const updateComment = useCallback((commentValue: string) => {
if (commentValue !== comment.content) {
setCommentContent(commentValue);
onEdit(commentValue);
}
setEditMode(false);
}, []);
const deleteCommentFunction = useCallback(async () => {
if (
await deleteDialog(
'Are you sure you want to delete this comment?',
'Yes, Delete'
)
) {
onDelete();
}
}, []);
if (editMode) {
return (
<CommentBox
type="input"
value={commentContent}
onChange={updateComment}
/>
);
}
return (
<div className="flex gap-[5px]">
<pre className="text-wrap">{commentContent}</pre>
{user?.id === comment.user.id && (
<>
<svg
onClick={() => setEditMode(!editMode)}
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 32 32"
fill="none"
>
<path
d="M28.415 9.17119L22.8288 3.58619C22.643 3.40043 22.4225 3.25307 22.1799 3.15253C21.9372 3.05199 21.6771 3.00024 21.4144 3.00024C21.1517 3.00024 20.8916 3.05199 20.6489 3.15253C20.4062 3.25307 20.1857 3.40043 20 3.58619L4.58626 18.9999C4.39973 19.185 4.25185 19.4053 4.15121 19.648C4.05057 19.8907 3.99917 20.151 4.00001 20.4137V25.9999C4.00001 26.5304 4.21072 27.0391 4.5858 27.4142C4.96087 27.7892 5.46958 27.9999 6.00001 27.9999H27C27.2652 27.9999 27.5196 27.8946 27.7071 27.7071C27.8947 27.5195 28 27.2652 28 26.9999C28 26.7347 27.8947 26.4804 27.7071 26.2928C27.5196 26.1053 27.2652 25.9999 27 25.9999H14.415L28.415 11.9999C28.6008 11.8142 28.7481 11.5937 28.8487 11.351C28.9492 11.1084 29.001 10.8482 29.001 10.5856C29.001 10.3229 28.9492 10.0628 28.8487 9.82009C28.7481 9.57741 28.6008 9.35692 28.415 9.17119ZM11.5863 25.9999H6.00001V20.4137L17 9.41369L22.5863 14.9999L11.5863 25.9999ZM24 13.5862L18.415 7.99994L21.415 4.99994L27 10.5862L24 13.5862Z"
fill="#fff"
/>
</svg>
<svg
onClick={deleteCommentFunction}
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 32 32"
fill="none"
>
<path
d="M27 6H22V5C22 4.20435 21.6839 3.44129 21.1213 2.87868C20.5587 2.31607 19.7956 2 19 2H13C12.2044 2 11.4413 2.31607 10.8787 2.87868C10.3161 3.44129 10 4.20435 10 5V6H5C4.73478 6 4.48043 6.10536 4.29289 6.29289C4.10536 6.48043 4 6.73478 4 7C4 7.26522 4.10536 7.51957 4.29289 7.70711C4.48043 7.89464 4.73478 8 5 8H6V26C6 26.5304 6.21071 27.0391 6.58579 27.4142C6.96086 27.7893 7.46957 28 8 28H24C24.5304 28 25.0391 27.7893 25.4142 27.4142C25.7893 27.0391 26 26.5304 26 26V8H27C27.2652 8 27.5196 7.89464 27.7071 7.70711C27.8946 7.51957 28 7.26522 28 7C28 6.73478 27.8946 6.48043 27.7071 6.29289C27.5196 6.10536 27.2652 6 27 6ZM12 5C12 4.73478 12.1054 4.48043 12.2929 4.29289C12.4804 4.10536 12.7348 4 13 4H19C19.2652 4 19.5196 4.10536 19.7071 4.29289C19.8946 4.48043 20 4.73478 20 5V6H12V5ZM24 26H8V8H24V26ZM14 13V21C14 21.2652 13.8946 21.5196 13.7071 21.7071C13.5196 21.8946 13.2652 22 13 22C12.7348 22 12.4804 21.8946 12.2929 21.7071C12.1054 21.5196 12 21.2652 12 21V13C12 12.7348 12.1054 12.4804 12.2929 12.2929C12.4804 12.1054 12.7348 12 13 12C13.2652 12 13.5196 12.1054 13.7071 12.2929C13.8946 12.4804 14 12.7348 14 13ZM20 13V21C20 21.2652 19.8946 21.5196 19.7071 21.7071C19.5196 21.8946 19.2652 22 19 22C18.7348 22 18.4804 21.8946 18.2929 21.7071C18.1054 21.5196 18 21.2652 18 21V13C18 12.7348 18.1054 12.4804 18.2929 12.2929C18.4804 12.1054 18.7348 12 19 12C19.2652 12 19.5196 12.1054 19.7071 12.2929C19.8946 12.4804 20 12.7348 20 13Z"
fill="#fff"
/>
</svg>
</>
)}
</div>
);
};
export const CommentComponent: FC<{
date: dayjs.Dayjs;
}> = (props) => {
const { date } = props;
const { closeAll } = useModals();
const [commentsList, setCommentsList] = useState<Comments[]>([]);
const user = useUser();
const fetch = useFetch();
const load = useCallback(async () => {
const data = await (
await fetch(`/comments/${date.utc().format('YYYY-MM-DDTHH:mm:00')}`)
).json();
setCommentsList(data);
}, []);
useEffect(() => {
load();
}, []);
const editComment = useCallback(
(comment: Comments) => async (content: string) => {
fetch(`/comments/${comment.id}`, {
method: 'PUT',
body: JSON.stringify({
content,
date: date.utc().format('YYYY-MM-DDTHH:mm:00'),
}),
});
},
[]
);
const addComment = useCallback(
async (content: string) => {
const { id } = await (
await fetch('/comments', {
method: 'POST',
body: JSON.stringify({
content,
date: date.utc().format('YYYY-MM-DDTHH:mm:00'),
}),
})
).json();
setCommentsList((list) => [
{
id,
user: {
email: user?.email!,
id: user?.id!,
},
content,
childrenComment: [],
},
...list,
]);
},
[commentsList, setCommentsList]
);
const deleteComment = useCallback(
(comment: Comments) => async () => {
await fetch(`/comments/${comment.id}`, {
method: 'DELETE',
});
setCommentsList((list) => list.filter((item) => item.id !== comment.id));
},
[commentsList, setCommentsList]
);
const deleteChildrenComment = useCallback(
(parent: Comments, children: Comments) => async () => {
await fetch(`/comments/${children.id}`, {
method: 'DELETE',
});
setCommentsList((list) =>
list.map((item) => {
if (item.id === parent.id) {
return {
...item,
childrenComment: item.childrenComment.filter(
(child) => child.id !== children.id
),
};
}
return item;
})
);
},
[commentsList, setCommentsList]
);
const addChildrenComment = useCallback(
(comment: Comments) => async (content: string) => {
const { id } = await (
await fetch(`/comments/${comment.id}`, {
method: 'POST',
body: JSON.stringify({
content,
date: date.utc().format('YYYY-MM-DDTHH:mm:00'),
}),
})
).json();
setCommentsList((list) =>
list.map((item) => {
if (item.id === comment.id) {
return {
...item,
childrenComment: [
...item.childrenComment,
{
id,
user: {
email: user?.email!,
id: user?.id!,
},
content,
childrenComment: [],
},
],
};
}
return item;
})
);
},
[commentsList]
);
const extractNameFromEmailAndCapitalize = useCallback((email: string) => {
return (
email.split('@')[0].charAt(0).toUpperCase() + email.split('@')[0].slice(1)
);
}, []);
return (
<div className="relative flex gap-[20px] flex-col flex-1 rounded-[4px] border border-customColor6 bg-sixth p-[16px] pt-0">
<TopTitle title={`Comments for ${date.format('DD/MM/YYYY HH:mm')}`} />
<button
onClick={closeAll}
className="outline-none absolute end-[20px] top-[15px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
type="button"
>
<svg
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
>
<path
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
></path>
</svg>
</button>
<div>
{commentsList.map((comment, index) => (
<Fragment key={`comment_${index}_${comment.content}`}>
<div
className={clsx(
`flex relative flex-col`,
comment?.childrenComment?.length && 'gap-[10px]'
)}
>
<div className="flex gap-[8px]">
<div className="w-[40px] flex flex-col items-center">
<div
className={`rounded-full relative z-[2] text-blue-500 font-bold flex justify-center items-center w-[40px] h-[40px] bg-white border-tableBorder border`}
>
{comment.user.email[0].toUpperCase()}
</div>
<div className="flex-1 w-[2px] h-[calc(100%-10px)] bg-customColor25 absolute top-[10px] z-[1]" />
</div>
<div className="flex-1 flex flex-col gap-[4px]">
<div className="flex">
<div className="h-[22px] text-[15px] font-[700]">
{extractNameFromEmailAndCapitalize(comment.user.email)}
</div>
</div>
<EditableCommentComponent
onDelete={deleteComment(comment)}
onEdit={editComment(comment)}
comment={comment}
/>
</div>
</div>
<div className="flex flex-col gap-[10px]">
{comment?.childrenComment?.map((childComment, index2) => (
<div
key={`comment2_${index2}_${childComment.content}`}
className={clsx(`flex gap-[8px] relative`)}
>
<div className="w-[40px] flex flex-col items-center">
<div
className={`rounded-full relative z-[2] text-blue-500 font-bold flex justify-center items-center w-[40px] h-[40px] bg-white border-tableBorder border`}
>
{childComment.user.email[0].toUpperCase()}
</div>
</div>
<div className="flex-1 flex flex-col gap-[4px]">
<div className="flex">
<div className="h-[22px] text-[15px] font-[700]">
{extractNameFromEmailAndCapitalize(
childComment.user.email
)}
</div>
</div>
<EditableCommentComponent
onDelete={deleteChildrenComment(comment, childComment)}
onEdit={editComment(childComment)}
comment={childComment}
/>
</div>
</div>
))}
</div>
</div>
<div className="flex">
<div className="relative w-[40px] flex flex-col items-center">
<div className="h-[30px] w-[2px] bg-customColor25 absolute top-0 z-[1]" />
<div className="h-[2px] w-[21px] bg-customColor25 absolute top-[30px] end-0 z-[1]" />
</div>
<div className="flex-1">
<CommentBox
type="input"
onChange={addChildrenComment(comment)}
/>
</div>
</div>
</Fragment>
))}
<CommentBox type="textarea" onChange={addComment} />
</div>
</div>
);
};
|