Proyectos de Subversion LeadersLinked - SPA

Rev

Rev 442 | Rev 448 | Ir a la última revisión | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
197 stevensc 1
import React, { memo, useEffect, useRef, useState } from "react";
2
import { axios } from "../../utils";
3
import { useForm } from "react-hook-form";
4
import { useDispatch } from "react-redux";
5
import { addNotification } from "../../redux/notification/notification.actions";
6
import parse from "html-react-parser";
7
import styled, { css } from "styled-components";
8
import SendIcon from "@mui/icons-material/Send";
9
import IconButton from "@mui/material/IconButton";
10
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
11
import AttachFileIcon from "@mui/icons-material/AttachFile";
12
import InsertEmoticonIcon from "@mui/icons-material/InsertEmoticon";
5 stevensc 13
 
197 stevensc 14
import Options from "../UI/Option";
15
import Emojione from "./emojione/Emojione";
16
import FileModal from "../modals/FileModal";
17
import LoaderContainer from "../UI/LoaderContainer";
18
import Spinner from "../UI/Spinner";
5 stevensc 19
 
20
const StyledChatContainer = styled.div`
21
  background-color: var(--bg-color);
22
  border-radius: var(--border-radius);
23
  border: 1px solid var(--border-primary);
24
  height: 80vh;
25
  display: flex;
26
  flex-direction: column;
27
  flex-grow: 1;
440 andreina 28
  padding: 0px 0px !important;
442 andreina 29
 
440 andreina 30
 
197 stevensc 31
`;
5 stevensc 32
 
33
const StyledChatHeader = styled.div`
34
  align-items: center;
35
  border-bottom: 1px solid var(--border-primary);
36
  display: flex;
37
  justify-content: center;
380 andreina 38
  padding: 1rem 0.5rem;
5 stevensc 39
  position: relative;
40
 
41
  & > button:first-child {
42
    position: absolute;
43
    left: 1rem;
44
    top: 50%;
45
    transform: translateY(-50%);
46
    display: inline-flex;
47
 
48
    @media (min-width: 768px) {
49
      display: none;
50
    }
440 andreina 51
 
5 stevensc 52
  }
197 stevensc 53
`;
5 stevensc 54
 
55
const StyledTitle = styled.h2`
443 andreina 56
font-size: 16px;
57
font-weight: 600;
58
width: fit-content;
59
max-width: 20ch;
60
text-align: center;
61
color: #1d315c;
5 stevensc 62
 
443 andreina 63
 
5 stevensc 64
  @media (min-width: 768px) {
65
    max-width: 30ch;
66
  }
197 stevensc 67
`;
5 stevensc 68
 
69
const StyledMessageList = styled.div`
70
  gap: 0.5rem;
71
  display: flex;
72
  flex-direction: column-reverse;
73
  height: -webkit-fill-available;
74
  padding: 0.5rem 0;
75
  overflow: auto;
197 stevensc 76
`;
5 stevensc 77
 
78
const StyledMessage = styled.div`
79
  box-shadow: var(--light-shadow);
80
  display: inline-flex;
81
  flex-direction: column;
82
  gap: 0.5rem;
83
  margin-bottom: 0.5rem;
84
  max-width: 70%;
85
  min-width: 4rem;
86
  padding: 0.5rem;
87
  position: relative;
88
 
89
  & > p {
90
    color: var(--chat-color);
91
    max-width: 100%;
92
    overflow: hidden;
93
    text-overflow: ellipsis;
94
    word-break: break-word;
95
  }
96
 
97
  & > img,
98
  & > video {
99
    max-width: 250px;
100
    max-height: 250px;
101
    object-fit: contain;
102
  }
103
 
104
  &:first-child {
105
    margin-top: 0.5rem;
106
  }
107
 
108
  .time {
197 stevensc 109
    color: var(--subtitle-color);
5 stevensc 110
    font-size: 0.8rem;
111
  }
112
 
113
  .emojione {
114
    width: 1rem;
115
    height: 1rem;
116
  }
117
 
118
  ${(props) => {
119
    if (props.send) {
120
      return css`
121
        align-self: flex-end;
443 andreina 122
        background-color: #eee;
123
        border-radius: 10px 0px 10px 10px;
5 stevensc 124
        margin-right: 0.5rem;
443 andreina 125
        color: #393939;
197 stevensc 126
      `;
5 stevensc 127
    }
128
    return css`
129
      align-self: flex-start;
130
      background-color: var(--chat-received);
131
      border-radius: 0 10px 10px 10px;
132
      margin-left: 0.5rem;
197 stevensc 133
    `;
5 stevensc 134
  }}
197 stevensc 135
`;
5 stevensc 136
 
137
const StyledForm = styled.form`
138
  border-top: 1px solid var(--border-primary);
139
  padding: 0.5rem;
140
  position: relative;
141
  display: flex;
142
  justify-content: center;
143
  align-items: center;
144
  gap: 0.5rem;
197 stevensc 145
`;
5 stevensc 146
 
147
const StyledInput = styled.input`
148
  border: none;
149
  outline: none;
197 stevensc 150
  width: 100%;
5 stevensc 151
  padding: 0.5rem 1rem;
152
  border-radius: 30px;
197 stevensc 153
  background: var(--bg-color-secondary);
5 stevensc 154
 
155
  &:focus {
197 stevensc 156
    background: var(--bg-color-secondary);
5 stevensc 157
  }
197 stevensc 158
`;
5 stevensc 159
 
160
const StyledLoader = styled(LoaderContainer)`
161
  max-height: 50px;
162
  max-width: 50px;
197 stevensc 163
`;
5 stevensc 164
 
165
function messageAreEqual(oldProps, newProps) {
166
  return oldProps.message.id
167
    ? oldProps.message.id === newProps.message.id
197 stevensc 168
    : oldProps.message.uuid === newProps.message.uuid;
5 stevensc 169
}
170
 
171
const Chat = ({ children }) => {
197 stevensc 172
  return <StyledChatContainer>{children}</StyledChatContainer>;
173
};
5 stevensc 174
 
175
const Header = ({ children, options = [], onClose }) => {
176
  return (
177
    <StyledChatHeader>
178
      <IconButton onClick={onClose}>
179
        <ArrowBackIcon />
180
      </IconButton>
181
      {children}
182
      {!!options.length && <Options options={options} right="1rem" />}
183
    </StyledChatHeader>
197 stevensc 184
  );
185
};
5 stevensc 186
 
187
const Title = ({ children, url }) => {
188
  if (!url) {
197 stevensc 189
    return <StyledTitle>{children}</StyledTitle>;
5 stevensc 190
  }
191
 
192
  return (
197 stevensc 193
    <a href={url} style={{ width: "fit-content" }}>
5 stevensc 194
      <StyledTitle>{children}</StyledTitle>
195
    </a>
197 stevensc 196
  );
197
};
5 stevensc 198
 
199
const List = ({ messages = [], onPagination, scrollRef, loading }) => {
197 stevensc 200
  const loadMoreEl = useRef(null);
5 stevensc 201
 
202
  useEffect(() => {
197 stevensc 203
    const observer = new IntersectionObserver(onPagination);
5 stevensc 204
 
205
    if (loadMoreEl.current) {
197 stevensc 206
      observer.observe(loadMoreEl.current);
5 stevensc 207
    }
208
 
209
    return () => {
197 stevensc 210
      observer.disconnect();
211
    };
212
  }, [messages]);
5 stevensc 213
 
214
  return (
215
    <StyledMessageList ref={scrollRef}>
216
      {messages.map((message) => (
217
        <List.Message message={message} key={message.id} />
218
      ))}
219
      <span ref={loadMoreEl}>.</span>
220
      {loading && (
221
        <StyledLoader>
222
          <Spinner />
223
        </StyledLoader>
224
      )}
225
    </StyledMessageList>
197 stevensc 226
  );
227
};
5 stevensc 228
 
229
// eslint-disable-next-line react/display-name
230
const Message = memo(({ message }) => {
231
  const senderName = (message) => {
197 stevensc 232
    if (message.type === "group" && !message.u === 1) {
233
      return <span className="user_name">{message.user_name}</span>;
5 stevensc 234
    }
197 stevensc 235
  };
5 stevensc 236
 
237
  const messagesContent = {
238
    text: (
239
      // eslint-disable-next-line no-undef
240
      <p>{parse(emojione.shortnameToImage(message.m || message.message))}</p>
241
    ),
242
    image: <img src={message.m || message.filename} alt="chat_image" />,
243
    video: (
244
      <video src={message.m || message.filename} preload="none" controls />
245
    ),
246
    document: (
247
      <a href={message.m || message.filename} download>
248
        <img className="pdf" src="/images/extension/pdf.png" alt="pdf" />
249
      </a>
250
    ),
197 stevensc 251
  };
5 stevensc 252
 
253
  return (
254
    <>
255
      {senderName(message)}
256
      <StyledMessage
197 stevensc 257
        send={message.u ? message.u === 1 : message.side === "left"}
5 stevensc 258
      >
259
        {messagesContent[message.mtype || message.type]}
260
        <label className="time">
261
          {!message.not_received && (
262
            <i
263
              className="fa fa-check"
197 stevensc 264
              style={message.seen ? { color: "blue" } : { color: "gray" }}
5 stevensc 265
            />
266
          )}
267
          {message.time || message.date}
268
        </label>
269
      </StyledMessage>
270
    </>
197 stevensc 271
  );
272
}, messageAreEqual);
5 stevensc 273
 
274
const SubmitForm = ({ sendUrl, uploadUrl, onSubmit: onComplete }) => {
197 stevensc 275
  const [showEmojione, setShowEmojione] = useState(false);
276
  const [isShowFileModal, setIsShowFileModal] = useState(false);
277
  const [isSending, setIsSending] = useState(false);
278
  const dispatch = useDispatch();
5 stevensc 279
 
197 stevensc 280
  const { handleSubmit, setValue, register, reset, getValues } = useForm();
5 stevensc 281
 
282
  const onSubmit = handleSubmit(({ message }) => {
197 stevensc 283
    const formData = new FormData();
5 stevensc 284
    // eslint-disable-next-line no-undef
197 stevensc 285
    formData.append("message", emojione.toShort(message));
5 stevensc 286
 
287
    axios.post(sendUrl, formData).then((response) => {
197 stevensc 288
      const { success, data } = response.data;
5 stevensc 289
 
290
      if (!success) {
291
        const errorMessage =
197 stevensc 292
          typeof data === "string" ? data : "Ha ocurrido un error";
5 stevensc 293
 
197 stevensc 294
        setShowEmojione(false);
295
        dispatch(addNotification({ style: "danger", msg: errorMessage }));
296
        return;
5 stevensc 297
      }
298
 
197 stevensc 299
      setShowEmojione(false);
300
      onComplete();
301
      reset();
302
    });
303
  });
5 stevensc 304
 
305
  const sendFile = (file) => {
197 stevensc 306
    setIsSending(true);
307
    const formData = new FormData();
308
    formData.append("file", file);
5 stevensc 309
 
310
    axios
311
      .post(uploadUrl, formData)
312
      .then(({ data: response }) => {
197 stevensc 313
        const { success, data } = response;
5 stevensc 314
        if (!success) {
315
          const errorMessage =
197 stevensc 316
            typeof data === "string" ? data : "Ha ocurrido un error";
317
          dispatch(addNotification({ style: "success", msg: errorMessage }));
318
          return;
5 stevensc 319
        }
320
 
197 stevensc 321
        toggleFileModal();
322
        onComplete();
5 stevensc 323
      })
197 stevensc 324
      .finally(() => setIsSending(false));
325
  };
5 stevensc 326
 
327
  const toggleEmojione = () => {
197 stevensc 328
    setShowEmojione(!showEmojione);
329
  };
5 stevensc 330
 
331
  const toggleFileModal = () => {
197 stevensc 332
    setIsShowFileModal(!isShowFileModal);
333
  };
5 stevensc 334
 
335
  const onClickEmoji = (event) => {
197 stevensc 336
    const shortname = event.currentTarget.dataset.shortname;
337
    const currentMessage = getValues("message");
5 stevensc 338
    // eslint-disable-next-line no-undef
197 stevensc 339
    const unicode = emojione.shortnameToUnicode(shortname);
340
    setValue("message", `${currentMessage}${unicode}`);
341
  };
5 stevensc 342
 
343
  return (
344
    <>
345
      <StyledForm onSubmit={onSubmit}>
346
        {showEmojione && <Emojione onClickEmoji={onClickEmoji} />}
230 stevensc 347
        <IconButton onClick={toggleFileModal}>
5 stevensc 348
          <AttachFileIcon />
349
        </IconButton>
350
        <IconButton onClick={toggleEmojione}>
351
          <InsertEmoticonIcon />
352
        </IconButton>
353
        <StyledInput
354
          type="text"
355
          name="message"
356
          placeholder="Escribe un mensaje"
357
          ref={register({ required: true })}
358
        />
359
        <IconButton type="submit">
360
          <SendIcon />
361
        </IconButton>
362
      </StyledForm>
363
      <FileModal
364
        isShow={isShowFileModal}
365
        onHide={toggleFileModal}
366
        onComplete={sendFile}
367
        loading={isSending}
368
      />
369
    </>
197 stevensc 370
  );
371
};
5 stevensc 372
 
197 stevensc 373
Chat.Header = Header;
374
Chat.Title = Title;
375
Chat.List = List;
376
List.Message = Message;
377
Chat.SubmitForm = SubmitForm;
5 stevensc 378
 
197 stevensc 379
export default Chat;