Proyectos de Subversion LeadersLinked - SPA

Rev

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