Proyectos de Subversion LeadersLinked - SPA

Rev

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