Proyectos de Subversion LeadersLinked - Antes de SPA

Rev

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

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