Proyectos de Subversion LeadersLinked - SPA

Rev

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