Proyectos de Subversion LeadersLinked - SPA

Rev

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

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