Proyectos de Subversion LeadersLinked - SPA

Rev

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

Rev Autor Línea Nro. Línea
517 stevensc 1
import React, { memo, useEffect, useRef, useState } from 'react'
679 stevensc 2
import { Link } from 'react-router-dom'
517 stevensc 3
import { axios } from '../../utils'
4
import { useForm } from 'react-hook-form'
5
import { useDispatch } from 'react-redux'
6
import { addNotification } from '../../redux/notification/notification.actions'
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
 
517 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'
679 stevensc 19
import Paraphrase from '../UI/Paraphrase'
5 stevensc 20
 
21
const StyledChatContainer = styled.div`
22
  background-color: var(--bg-color);
23
  border-radius: var(--border-radius);
24
  border: 1px solid var(--border-primary);
25
  height: 80vh;
26
  display: flex;
27
  flex-direction: column;
28
  flex-grow: 1;
440 andreina 29
  padding: 0px 0px !important;
517 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
    }
50
  }
517 stevensc 51
`
5 stevensc 52
 
53
const StyledTitle = styled.h2`
517 stevensc 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
 
61
  @media (min-width: 768px) {
62
    max-width: 30ch;
63
  }
517 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;
517 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;
443 andreina 119
        background-color: #eee;
120
        border-radius: 10px 0px 10px 10px;
5 stevensc 121
        margin-right: 0.5rem;
443 andreina 122
        color: #393939;
517 stevensc 123
      `
5 stevensc 124
    }
125
    return css`
126
      align-self: flex-start;
127
      background-color: var(--chat-received);
128
      border-radius: 0 10px 10px 10px;
129
      margin-left: 0.5rem;
517 stevensc 130
    `
5 stevensc 131
  }}
517 stevensc 132
`
5 stevensc 133
 
134
const StyledForm = styled.form`
135
  border-top: 1px solid var(--border-primary);
136
  padding: 0.5rem;
137
  position: relative;
138
  display: flex;
139
  justify-content: center;
140
  align-items: center;
141
  gap: 0.5rem;
517 stevensc 142
`
5 stevensc 143
 
144
const StyledInput = styled.input`
145
  border: none;
146
  outline: none;
197 stevensc 147
  width: 100%;
5 stevensc 148
  padding: 0.5rem 1rem;
149
  border-radius: 30px;
197 stevensc 150
  background: var(--bg-color-secondary);
5 stevensc 151
 
152
  &:focus {
197 stevensc 153
    background: var(--bg-color-secondary);
5 stevensc 154
  }
517 stevensc 155
`
5 stevensc 156
 
157
const StyledLoader = styled(LoaderContainer)`
158
  max-height: 50px;
159
  max-width: 50px;
517 stevensc 160
`
5 stevensc 161
 
162
function messageAreEqual(oldProps, newProps) {
163
  return oldProps.message.id
164
    ? oldProps.message.id === newProps.message.id
517 stevensc 165
    : oldProps.message.uuid === newProps.message.uuid
5 stevensc 166
}
167
 
168
const Chat = ({ children }) => {
517 stevensc 169
  return <StyledChatContainer>{children}</StyledChatContainer>
170
}
5 stevensc 171
 
172
const Header = ({ children, options = [], onClose }) => {
173
  return (
174
    <StyledChatHeader>
175
      <IconButton onClick={onClose}>
176
        <ArrowBackIcon />
177
      </IconButton>
178
      {children}
679 stevensc 179
      {!!options.length && <Options options={options} right='1rem' />}
5 stevensc 180
    </StyledChatHeader>
517 stevensc 181
  )
182
}
5 stevensc 183
 
184
const Title = ({ children, url }) => {
185
  if (!url) {
517 stevensc 186
    return <StyledTitle>{children}</StyledTitle>
5 stevensc 187
  }
188
 
189
  return (
517 stevensc 190
    <Link to={url} style={{ width: 'fit-content' }}>
5 stevensc 191
      <StyledTitle>{children}</StyledTitle>
517 stevensc 192
    </Link>
193
  )
194
}
5 stevensc 195
 
196
const List = ({ messages = [], onPagination, scrollRef, loading }) => {
517 stevensc 197
  const loadMoreEl = useRef(null)
5 stevensc 198
 
199
  useEffect(() => {
517 stevensc 200
    const observer = new IntersectionObserver(onPagination)
5 stevensc 201
 
202
    if (loadMoreEl.current) {
517 stevensc 203
      observer.observe(loadMoreEl.current)
5 stevensc 204
    }
205
 
206
    return () => {
517 stevensc 207
      observer.disconnect()
208
    }
209
  }, [messages])
5 stevensc 210
 
211
  return (
212
    <StyledMessageList ref={scrollRef}>
213
      {messages.map((message) => (
214
        <List.Message message={message} key={message.id} />
215
      ))}
216
      <span ref={loadMoreEl}>.</span>
217
      {loading && (
218
        <StyledLoader>
219
          <Spinner />
220
        </StyledLoader>
221
      )}
222
    </StyledMessageList>
517 stevensc 223
  )
224
}
5 stevensc 225
 
226
const Message = memo(({ message }) => {
227
  const senderName = (message) => {
517 stevensc 228
    if (message.type === 'group' && !message.u === 1) {
679 stevensc 229
      return <span className='user_name'>{message.user_name}</span>
5 stevensc 230
    }
517 stevensc 231
  }
5 stevensc 232
 
233
  const messagesContent = {
681 stevensc 234
    text: (message.m || message.message) && (
679 stevensc 235
      <Paraphrase>
680 stevensc 236
        {emojione.shortnameToImage(message.m || message.message)}
679 stevensc 237
      </Paraphrase>
5 stevensc 238
    ),
679 stevensc 239
    image: <img src={message.m || message.filename} alt='chat_image' />,
5 stevensc 240
    video: (
679 stevensc 241
      <video src={message.m || message.filename} preload='none' controls />
5 stevensc 242
    ),
243
    document: (
244
      <a href={message.m || message.filename} download>
679 stevensc 245
        <img className='pdf' src='/images/extension/pdf.png' alt='pdf' />
5 stevensc 246
      </a>
679 stevensc 247
    )
517 stevensc 248
  }
5 stevensc 249
 
250
  return (
251
    <>
252
      {senderName(message)}
253
      <StyledMessage
517 stevensc 254
        send={message.u ? message.u === 1 : message.side === 'left'}
5 stevensc 255
      >
256
        {messagesContent[message.mtype || message.type]}
679 stevensc 257
        <label className='time'>
5 stevensc 258
          {!message.not_received && (
259
            <i
679 stevensc 260
              className='fa fa-check'
517 stevensc 261
              style={message.seen ? { color: 'blue' } : { color: 'gray' }}
5 stevensc 262
            />
263
          )}
264
          {message.time || message.date}
265
        </label>
266
      </StyledMessage>
267
    </>
517 stevensc 268
  )
269
}, messageAreEqual)
5 stevensc 270
 
271
const SubmitForm = ({ sendUrl, uploadUrl, onSubmit: onComplete }) => {
517 stevensc 272
  const [showEmojione, setShowEmojione] = useState(false)
273
  const [isShowFileModal, setIsShowFileModal] = useState(false)
274
  const [isSending, setIsSending] = useState(false)
275
  const dispatch = useDispatch()
5 stevensc 276
 
517 stevensc 277
  const { handleSubmit, setValue, register, reset, getValues } = useForm()
5 stevensc 278
 
279
  const onSubmit = handleSubmit(({ message }) => {
517 stevensc 280
    const formData = new FormData()
5 stevensc 281
    // eslint-disable-next-line no-undef
517 stevensc 282
    formData.append('message', emojione.toShort(message))
5 stevensc 283
 
284
    axios.post(sendUrl, formData).then((response) => {
517 stevensc 285
      const { success, data } = response.data
5 stevensc 286
 
287
      if (!success) {
288
        const errorMessage =
517 stevensc 289
          typeof data === 'string' ? data : 'Ha ocurrido un error'
5 stevensc 290
 
517 stevensc 291
        setShowEmojione(false)
292
        dispatch(addNotification({ style: 'danger', msg: errorMessage }))
293
        return
5 stevensc 294
      }
295
 
517 stevensc 296
      setShowEmojione(false)
297
      onComplete()
298
      reset()
299
    })
300
  })
5 stevensc 301
 
302
  const sendFile = (file) => {
517 stevensc 303
    setIsSending(true)
304
    const formData = new FormData()
305
    formData.append('file', file)
5 stevensc 306
 
307
    axios
308
      .post(uploadUrl, formData)
309
      .then(({ data: response }) => {
517 stevensc 310
        const { success, data } = response
5 stevensc 311
        if (!success) {
312
          const errorMessage =
517 stevensc 313
            typeof data === 'string' ? data : 'Ha ocurrido un error'
314
          dispatch(addNotification({ style: 'success', msg: errorMessage }))
315
          return
5 stevensc 316
        }
317
 
517 stevensc 318
        toggleFileModal()
319
        onComplete()
5 stevensc 320
      })
517 stevensc 321
      .finally(() => setIsSending(false))
322
  }
5 stevensc 323
 
324
  const toggleEmojione = () => {
517 stevensc 325
    setShowEmojione(!showEmojione)
326
  }
5 stevensc 327
 
328
  const toggleFileModal = () => {
517 stevensc 329
    setIsShowFileModal(!isShowFileModal)
330
  }
5 stevensc 331
 
332
  const onClickEmoji = (event) => {
517 stevensc 333
    const shortname = event.currentTarget.dataset.shortname
334
    const currentMessage = getValues('message')
5 stevensc 335
    // eslint-disable-next-line no-undef
517 stevensc 336
    const unicode = emojione.shortnameToUnicode(shortname)
337
    setValue('message', `${currentMessage}${unicode}`)
338
  }
5 stevensc 339
 
340
  return (
341
    <>
342
      <StyledForm onSubmit={onSubmit}>
343
        {showEmojione && <Emojione onClickEmoji={onClickEmoji} />}
230 stevensc 344
        <IconButton onClick={toggleFileModal}>
5 stevensc 345
          <AttachFileIcon />
346
        </IconButton>
347
        <IconButton onClick={toggleEmojione}>
348
          <InsertEmoticonIcon />
349
        </IconButton>
350
        <StyledInput
679 stevensc 351
          type='text'
352
          name='message'
353
          placeholder='Escribe un mensaje'
5 stevensc 354
          ref={register({ required: true })}
355
        />
679 stevensc 356
        <IconButton type='submit'>
5 stevensc 357
          <SendIcon />
358
        </IconButton>
359
      </StyledForm>
360
      <FileModal
361
        isShow={isShowFileModal}
362
        onHide={toggleFileModal}
363
        onComplete={sendFile}
364
        loading={isSending}
365
      />
366
    </>
517 stevensc 367
  )
368
}
5 stevensc 369
 
517 stevensc 370
Chat.Header = Header
371
Chat.Title = Title
372
Chat.List = List
373
List.Message = Message
374
Chat.SubmitForm = SubmitForm
5 stevensc 375
 
517 stevensc 376
export default Chat