Proyectos de Subversion LeadersLinked - SPA

Rev

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