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>
  );
};