agx-chat-web 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +49 -49
  2. package/dist/agx-chat.esm.js +1 -1
  3. package/dist/agx-chat.esm.js.map +1 -1
  4. package/dist/{agx-chat.min.js → agx-chat.umd.js} +2 -2
  5. package/dist/agx-chat.umd.js.map +1 -0
  6. package/dist/esm/app/Messenger/components/IncomingMessage/IncomingMessage.js +7 -3
  7. package/dist/esm/app/Messenger/components/IncomingMessage/IncomingMessage.js.map +1 -1
  8. package/dist/esm/app/Messenger/components/InputFile/InputFile.js +3 -1
  9. package/dist/esm/app/Messenger/components/InputFile/InputFile.js.map +1 -1
  10. package/dist/esm/app/Messenger/components/RenderFileIcon/RenderFileIcon.js +8 -0
  11. package/dist/esm/app/Messenger/components/RenderFileIcon/RenderFileIcon.js.map +1 -1
  12. package/dist/esm/app/Messenger/components/SendMessageForm/SendMessageForm.js +30 -8
  13. package/dist/esm/app/Messenger/components/SendMessageForm/SendMessageForm.js.map +1 -1
  14. package/dist/esm/app/Messenger/components/SenderMessages/SenderMessages.js +11 -8
  15. package/dist/esm/app/Messenger/components/SenderMessages/SenderMessages.js.map +1 -1
  16. package/dist/esm/app/Messenger/icons/CSVFileIcon.d.ts +4 -0
  17. package/dist/esm/app/Messenger/icons/CSVFileIcon.js +7 -0
  18. package/dist/esm/app/Messenger/icons/CSVFileIcon.js.map +1 -0
  19. package/dist/esm/app/Messenger/icons/MP4FileIcon.d.ts +4 -0
  20. package/dist/esm/app/Messenger/icons/MP4FileIcon.js +7 -0
  21. package/dist/esm/app/Messenger/icons/MP4FileIcon.js.map +1 -0
  22. package/package.json +91 -91
  23. package/src/__tests__/app/Messenger/classes/slaCalculations.spec.ts +122 -122
  24. package/src/app/ChatProvider/ChatProvider.tsx +20 -20
  25. package/src/app/Messenger/classes/slaCalculations.ts +197 -197
  26. package/src/app/Messenger/components/ChatButton/ChatButton.tsx +64 -64
  27. package/src/app/Messenger/components/ChatTabs/ChatTabs.less +18 -18
  28. package/src/app/Messenger/components/ChatTabs/ChatTabs.tsx +32 -32
  29. package/src/app/Messenger/components/DocMessage/DocMessage.less +71 -71
  30. package/src/app/Messenger/components/DocMessage/DocMessage.tsx +50 -50
  31. package/src/app/Messenger/components/ImagesContainer/ImagesContainer.less +79 -79
  32. package/src/app/Messenger/components/ImagesContainer/ImagesContainer.tsx +51 -51
  33. package/src/app/Messenger/components/IncomingMessage/IncomingMessage.tsx +170 -166
  34. package/src/app/Messenger/components/InfiniteScroll/InfiniteScroll.tsx +80 -80
  35. package/src/app/Messenger/components/InputFile/InputFile.tsx +147 -145
  36. package/src/app/Messenger/components/InputFile/inputFile.less +59 -59
  37. package/src/app/Messenger/components/MessageBallon/MessageBalloon.tsx +100 -100
  38. package/src/app/Messenger/components/MessengerAvatar/MessengerAvatar.tsx +29 -29
  39. package/src/app/Messenger/components/MessengerThemeWrapper/MessengerThemeWrapper.tsx +62 -62
  40. package/src/app/Messenger/components/RenderFileIcon/RenderFileIcon.tsx +40 -34
  41. package/src/app/Messenger/components/SearchInput/SearchInput.less +45 -45
  42. package/src/app/Messenger/components/SearchInput/SearchInput.tsx +77 -77
  43. package/src/app/Messenger/components/Select/Select.less +22 -22
  44. package/src/app/Messenger/components/Select/Select.tsx +56 -56
  45. package/src/app/Messenger/components/SendMessageForm/SendMessageForm.tsx +254 -234
  46. package/src/app/Messenger/components/SenderMessages/SenderMessages.tsx +91 -89
  47. package/src/app/Messenger/components/SystemMessage/SystemMessage.tsx +25 -25
  48. package/src/app/Messenger/components/TextArea/TextArea.tsx +35 -35
  49. package/src/app/Messenger/components/TextArea/Textarea.less +22 -22
  50. package/src/app/Messenger/components/Tooltip/Tooltip.less +27 -27
  51. package/src/app/Messenger/components/Tooltip/Tooltip.tsx +17 -17
  52. package/src/app/Messenger/hooks/useConversations.tsx +143 -143
  53. package/src/app/Messenger/hooks/useMessages.tsx +49 -49
  54. package/src/app/Messenger/hooks/useThemes.tsx +14 -14
  55. package/src/app/Messenger/icons/AttachFileIcon.tsx +20 -20
  56. package/src/app/Messenger/icons/CSVFileIcon.tsx +26 -0
  57. package/src/app/Messenger/icons/CloseIcon.tsx +20 -20
  58. package/src/app/Messenger/icons/DOCFileIcon.tsx +54 -54
  59. package/src/app/Messenger/icons/DownloadMinimalistIcon.tsx +37 -37
  60. package/src/app/Messenger/icons/EmptyIcon.tsx +20 -20
  61. package/src/app/Messenger/icons/MP4FileIcon.tsx +26 -0
  62. package/src/app/Messenger/icons/MessageIcon.tsx +27 -27
  63. package/src/app/Messenger/icons/PDFFileIcon.tsx +54 -54
  64. package/src/app/Messenger/icons/ReadIcon.tsx +18 -18
  65. package/src/app/Messenger/icons/SearchIcon.tsx +20 -20
  66. package/src/app/Messenger/icons/TimerIcon.tsx +18 -18
  67. package/src/app/Messenger/icons/TrashIcon.tsx +21 -21
  68. package/src/app/Messenger/views/Messenger.less +623 -623
  69. package/src/app/Messenger/views/MessengerList.tsx +170 -170
  70. package/src/app/Messenger/views/MessengerListItem.tsx +178 -178
  71. package/src/app/Messenger/views/MessengerMessages.tsx +414 -414
  72. package/src/app/Messenger/views/NewFormChat.tsx +145 -145
  73. package/src/app/i18n/index.ts +36 -36
  74. package/src/app/i18n/locales/en.json +64 -64
  75. package/src/app/i18n/locales/pt.json +64 -64
  76. package/src/assets/right-arrow.svg +9 -9
  77. package/src/index.ts +23 -23
  78. package/src/react-app-env.d.ts +19 -19
  79. package/src/setupTests.ts +5 -5
  80. package/src/styles/abstracts/animations.less +8 -8
  81. package/src/styles/abstracts/mixins.less +5 -5
  82. package/src/styles/abstracts/variables.less +31 -31
  83. package/src/styles/base/base.less +6 -6
  84. package/src/styles/index.less +5 -5
  85. package/src/types.ts +174 -174
  86. package/dist/agx-chat.min.js.map +0 -1
@@ -1,414 +1,414 @@
1
- import React, {
2
- Fragment,
3
- useState,
4
- useRef,
5
- MutableRefObject,
6
- useEffect,
7
- } from 'react'
8
- import ImagesContainer from '../components/ImagesContainer/ImagesContainer'
9
- import MessageBalloon from '../components/MessageBallon/MessageBalloon'
10
-
11
- import MessageIcon from '../icons/MessageIcon'
12
-
13
- import { ICommonProps, IList, IMessages, IOption } from 'types'
14
- import useTheme from '../hooks/useThemes'
15
-
16
- import NewChatForm from './NewFormChat'
17
- import EmptyIcon from '../icons/EmptyIcon'
18
- import SendMessageForm from '../components/SendMessageForm/SendMessageForm'
19
- import InfiniteScroll from '../components/InfiniteScroll/InfiniteScroll'
20
- import { formatFileSize } from '../components/DocMessage/DocMessage'
21
- import { useTranslation } from 'react-i18next'
22
-
23
- interface IScrollableContainer {
24
- loading: boolean
25
- onIntersect: () => Promise<void>
26
- hasNext: boolean
27
- }
28
-
29
- type MessengerView = 'empty' | 'messages' | 'newChat'
30
-
31
- interface IMessengerMessages extends ICommonProps, IScrollableContainer {
32
- header: React.ReactElement
33
- current?: IList
34
- creatorId?: string
35
- allowImages: boolean
36
- tab?: 'list' | 'messages'
37
- messegerView: MessengerView
38
- className?: string
39
- messages: IMessages[]
40
- onSubmit: (value: FormData, callback: () => void) => Promise<void>
41
- cancelNewChat: () => void
42
- formatDate: (date: string | Date) => string | undefined
43
- reasons: Array<IOption>
44
- products: Array<IOption>
45
- submitNewChat: (values: FormData) => void
46
- newChatLoading: boolean
47
- maxFileSize: () => Promise<{ data: { data: { maxFileSize: number } } }>
48
- }
49
-
50
- interface IMessengerForm {
51
- children: React.ReactNode
52
- messegerView: MessengerView
53
- reasons: Array<IOption>
54
- products: Array<IOption>
55
- submitNewChat: (values: FormData) => void
56
- cancelNewChat: () => void
57
- newChatLoading: boolean
58
- maxFileSize: string
59
- loadingResources: boolean
60
- }
61
-
62
- function MessagesContainerView ({
63
- children,
64
- messegerView,
65
- submitNewChat,
66
- cancelNewChat,
67
- reasons,
68
- products,
69
- newChatLoading,
70
- maxFileSize,
71
- loadingResources,
72
- }: IMessengerForm) {
73
- const { theme } = useTheme()
74
- const { t } = useTranslation('messengerMessages')
75
-
76
- if (messegerView === 'empty') {
77
- return (
78
- <div
79
- className='messenger__messages-container--empty'
80
- style={{ backgroundColor: theme?.messengerNotSelectedBg }}
81
- >
82
- <MessageIcon width='72' height='72' />
83
- <p>{t('selectConversation')}</p>
84
- </div>
85
- )
86
- }
87
-
88
- if (messegerView === 'newChat') {
89
- return (
90
- <NewChatForm
91
- reasons={reasons}
92
- products={products}
93
- submitNewChat={submitNewChat}
94
- cancelNewChat={cancelNewChat}
95
- loading={newChatLoading}
96
- maxFileSize={maxFileSize}
97
- loadingResources={loadingResources}
98
- />
99
- )
100
- }
101
-
102
- return <>{children}</>
103
- }
104
-
105
- export function SpinLoading () {
106
- return (
107
- <div className='messenger__messages-loading'>
108
- <div className='messenger__messages-loading--loader' />
109
- </div>
110
- )
111
- }
112
-
113
- function Messages ({
114
- messages,
115
- creatorId,
116
- formatDate,
117
- loading,
118
- }: {
119
- messages: IMessages[]
120
- creatorId?: string
121
- formatDate: (date: string | Date) => string | undefined
122
- loading: boolean
123
- }) {
124
- const { theme } = useTheme()
125
- const { t } = useTranslation('messengerMessages')
126
-
127
- if (!messages || (messages.length === 0 && !loading)) {
128
- return (
129
- <div
130
- className='messenger__message-empty'
131
- style={{ color: theme.emptyMessagesFontColor }}
132
- >
133
- <EmptyIcon />
134
- <p>{t('noMessagesFound')}</p>
135
- </div>
136
- )
137
- }
138
-
139
- return (
140
- <>
141
- {messages.map((item, idx) => {
142
- return (
143
- <Fragment key={`${item.ticketId}_${item.senderId}_${idx}`}>
144
- <MessageBalloon
145
- id={`message_${idx}`}
146
- formatDate={formatDate}
147
- creatorId={creatorId}
148
- item={item}
149
- />
150
- </Fragment>
151
- )
152
- })}
153
- </>
154
- )
155
- }
156
-
157
- const onScrollToBottom = (
158
- scrollContainer: MutableRefObject<HTMLDivElement | null>
159
- ) => {
160
- if (scrollContainer.current) {
161
- scrollContainer.current.scrollTop = Number.MAX_SAFE_INTEGER
162
- }
163
- }
164
-
165
- function MessagesContainer ({
166
- children,
167
- fowardRef,
168
- ...rest
169
- }: {
170
- children: React.ReactNode
171
- fowardRef: MutableRefObject<HTMLDivElement | null>
172
- } & React.DetailedHTMLProps<
173
- React.HTMLAttributes<HTMLDivElement>,
174
- HTMLDivElement
175
- >) {
176
- const { theme } = useTheme()
177
-
178
- return (
179
- <div
180
- {...rest}
181
- ref={fowardRef}
182
- className='messenger__messages-container'
183
- style={{
184
- padding: '1rem',
185
- backgroundColor: theme?.messengerMessagesBg,
186
- border: `1px solid ${theme.borderColor}`,
187
- }}
188
- >
189
- {children}
190
- </div>
191
- )
192
- }
193
-
194
- function ScrollableContainer ({
195
- children,
196
- loading,
197
- onIntersect,
198
- hasNext,
199
- scrollContainerFowardRef,
200
- }: {
201
- children: React.ReactNode
202
- scrollContainerFowardRef: MutableRefObject<HTMLDivElement | null>
203
- } & IScrollableContainer) {
204
- const intersetRootMargin = 300
205
-
206
- const getElementScrollHeight = (divRef: HTMLDivElement | null) => {
207
- if (!divRef) return null
208
- return divRef.scrollHeight
209
- }
210
-
211
- const getElementScrollTop = (divRef: HTMLDivElement | null) => {
212
- if (!divRef) return null
213
- return divRef.scrollTop
214
- }
215
-
216
- const calculateScrollToDecrease = (
217
- elementBeforeIntersetHeight: number,
218
- elementAfterIntersectHeight: number,
219
- intersetRootMargin: number
220
- ) => {
221
- return (
222
- Math.abs(elementAfterIntersectHeight - elementBeforeIntersetHeight) +
223
- intersetRootMargin
224
- )
225
- }
226
-
227
- const isNill = (value: number | null | undefined) => {
228
- return value === null && value === undefined
229
- }
230
-
231
- const onFetchInfiniteScroll = async () => {
232
- const elementBeforeIntersetHeight = getElementScrollHeight(
233
- scrollContainerFowardRef.current
234
- )
235
-
236
- await onIntersect()
237
-
238
- const elementAfterIntersectHeight = getElementScrollHeight(
239
- scrollContainerFowardRef.current
240
- )
241
-
242
- if (
243
- scrollContainerFowardRef.current &&
244
- !isNill(elementBeforeIntersetHeight) &&
245
- !isNill(elementAfterIntersectHeight)
246
- ) {
247
- const currentScrollTop =
248
- getElementScrollTop(scrollContainerFowardRef.current) || 0
249
- scrollContainerFowardRef.current.scrollTop = calculateScrollToDecrease(
250
- elementAfterIntersectHeight as number,
251
- elementBeforeIntersetHeight as number,
252
- currentScrollTop
253
- )
254
- }
255
- }
256
-
257
- useEffect(() => {
258
- onScrollToBottom(scrollContainerFowardRef)
259
- }, [])
260
-
261
- return (
262
- <MessagesContainer fowardRef={scrollContainerFowardRef}>
263
- <InfiniteScroll
264
- root={document.querySelector('.messenger__messages-container')}
265
- loading={loading}
266
- loadingCover={<SpinLoading />}
267
- more={hasNext}
268
- fetch={onFetchInfiniteScroll}
269
- rootMargin={intersetRootMargin}
270
- reverse
271
- >
272
- {children}
273
- </InfiniteScroll>
274
- </MessagesContainer>
275
- )
276
- }
277
-
278
- function MessengerMessages ({
279
- header,
280
- tab,
281
- className = '',
282
- current,
283
- messages,
284
- allowImages,
285
- creatorId,
286
- loading,
287
- onSubmit,
288
- submitNewChat,
289
- cancelNewChat,
290
- reasons,
291
- products,
292
- messegerView,
293
- formatDate,
294
- hasNext,
295
- onIntersect,
296
- newChatLoading,
297
- maxFileSize,
298
- }: IMessengerMessages) {
299
- const { t } = useTranslation('messengerMessages')
300
- const [file, setFile] = useState<File | null>()
301
- const scrollContainerRef = useRef<HTMLDivElement | null>(null)
302
- const [isSending, setIsSending] = useState(false)
303
- const [maxSize, setMaxSize] = useState<string>('')
304
-
305
- const { theme } = useTheme()
306
-
307
- const styleClassnames = {
308
- mobile: tab === 'list' ? 'messenger__mobile-hide' : '',
309
- }
310
-
311
- const updateFile = (file: File | null) => {
312
- setFile(file)
313
- }
314
-
315
- const onSubmitNewMessage = async (
316
- message: FormData,
317
- callback: () => void
318
- ) => {
319
- setIsSending(true)
320
- try {
321
- await onSubmit(message, callback)
322
- onScrollToBottom(scrollContainerRef)
323
- } finally {
324
- setIsSending(false)
325
- }
326
- }
327
-
328
- useEffect(() => {
329
- const fetchMaxFileSize = async () => {
330
- try {
331
- const response = await maxFileSize()
332
- const size = formatFileSize(response.data.data.maxFileSize)
333
- setMaxSize(size)
334
- } catch (error) {
335
- console.error(t('errorSearchingMaxFileSize'), error)
336
- }
337
- }
338
-
339
- fetchMaxFileSize()
340
- }, [maxSize])
341
-
342
- return (
343
- <div
344
- className={`messenger__messages ${className} ${styleClassnames.mobile} `}
345
- >
346
- <MessagesContainerView
347
- newChatLoading={newChatLoading}
348
- reasons={reasons}
349
- products={products}
350
- submitNewChat={submitNewChat}
351
- cancelNewChat={cancelNewChat}
352
- messegerView={messegerView}
353
- maxFileSize={maxSize}
354
- loadingResources={loading}
355
- >
356
- {file ? (
357
- <ImagesContainer file={file} onClose={() => setFile(null)} />
358
- ) : (
359
- <>
360
- <div
361
- className='messenger__messages-header'
362
- style={{
363
- backgroundColor: theme?.headerAndSenderBg,
364
- border: `1px solid ${theme.borderColor}`,
365
- }}
366
- >
367
- {React.cloneElement(header)}
368
- </div>
369
-
370
- {loading && !messages.length ? (
371
- <MessagesContainer fowardRef={scrollContainerRef}>
372
- <SpinLoading />
373
- </MessagesContainer>
374
- ) : (
375
- <ScrollableContainer
376
- scrollContainerFowardRef={scrollContainerRef}
377
- loading={loading}
378
- hasNext={hasNext}
379
- onIntersect={onIntersect}
380
- >
381
- <Messages
382
- formatDate={formatDate}
383
- messages={messages}
384
- creatorId={creatorId}
385
- loading={loading}
386
- />
387
- </ScrollableContainer>
388
- )}
389
- </>
390
- )}
391
-
392
- <div
393
- className='messenger__messages-send'
394
- style={{
395
- backgroundColor: theme?.headerAndSenderBg,
396
- border: `1px solid ${theme.borderColor}`,
397
- }}
398
- >
399
- <SendMessageForm
400
- allowImages={allowImages}
401
- onSubmit={onSubmitNewMessage}
402
- file={file}
403
- updateFile={updateFile}
404
- current={current}
405
- isLoading={isSending}
406
- maxFileSize={maxSize}
407
- />
408
- </div>
409
- </MessagesContainerView>
410
- </div>
411
- )
412
- }
413
-
414
- export default MessengerMessages
1
+ import React, {
2
+ Fragment,
3
+ useState,
4
+ useRef,
5
+ MutableRefObject,
6
+ useEffect,
7
+ } from 'react'
8
+ import ImagesContainer from '../components/ImagesContainer/ImagesContainer'
9
+ import MessageBalloon from '../components/MessageBallon/MessageBalloon'
10
+
11
+ import MessageIcon from '../icons/MessageIcon'
12
+
13
+ import { ICommonProps, IList, IMessages, IOption } from 'types'
14
+ import useTheme from '../hooks/useThemes'
15
+
16
+ import NewChatForm from './NewFormChat'
17
+ import EmptyIcon from '../icons/EmptyIcon'
18
+ import SendMessageForm from '../components/SendMessageForm/SendMessageForm'
19
+ import InfiniteScroll from '../components/InfiniteScroll/InfiniteScroll'
20
+ import { formatFileSize } from '../components/DocMessage/DocMessage'
21
+ import { useTranslation } from 'react-i18next'
22
+
23
+ interface IScrollableContainer {
24
+ loading: boolean
25
+ onIntersect: () => Promise<void>
26
+ hasNext: boolean
27
+ }
28
+
29
+ type MessengerView = 'empty' | 'messages' | 'newChat'
30
+
31
+ interface IMessengerMessages extends ICommonProps, IScrollableContainer {
32
+ header: React.ReactElement
33
+ current?: IList
34
+ creatorId?: string
35
+ allowImages: boolean
36
+ tab?: 'list' | 'messages'
37
+ messegerView: MessengerView
38
+ className?: string
39
+ messages: IMessages[]
40
+ onSubmit: (value: FormData, callback: () => void) => Promise<void>
41
+ cancelNewChat: () => void
42
+ formatDate: (date: string | Date) => string | undefined
43
+ reasons: Array<IOption>
44
+ products: Array<IOption>
45
+ submitNewChat: (values: FormData) => void
46
+ newChatLoading: boolean
47
+ maxFileSize: () => Promise<{ data: { data: { maxFileSize: number } } }>
48
+ }
49
+
50
+ interface IMessengerForm {
51
+ children: React.ReactNode
52
+ messegerView: MessengerView
53
+ reasons: Array<IOption>
54
+ products: Array<IOption>
55
+ submitNewChat: (values: FormData) => void
56
+ cancelNewChat: () => void
57
+ newChatLoading: boolean
58
+ maxFileSize: string
59
+ loadingResources: boolean
60
+ }
61
+
62
+ function MessagesContainerView ({
63
+ children,
64
+ messegerView,
65
+ submitNewChat,
66
+ cancelNewChat,
67
+ reasons,
68
+ products,
69
+ newChatLoading,
70
+ maxFileSize,
71
+ loadingResources,
72
+ }: IMessengerForm) {
73
+ const { theme } = useTheme()
74
+ const { t } = useTranslation('messengerMessages')
75
+
76
+ if (messegerView === 'empty') {
77
+ return (
78
+ <div
79
+ className='messenger__messages-container--empty'
80
+ style={{ backgroundColor: theme?.messengerNotSelectedBg }}
81
+ >
82
+ <MessageIcon width='72' height='72' />
83
+ <p>{t('selectConversation')}</p>
84
+ </div>
85
+ )
86
+ }
87
+
88
+ if (messegerView === 'newChat') {
89
+ return (
90
+ <NewChatForm
91
+ reasons={reasons}
92
+ products={products}
93
+ submitNewChat={submitNewChat}
94
+ cancelNewChat={cancelNewChat}
95
+ loading={newChatLoading}
96
+ maxFileSize={maxFileSize}
97
+ loadingResources={loadingResources}
98
+ />
99
+ )
100
+ }
101
+
102
+ return <>{children}</>
103
+ }
104
+
105
+ export function SpinLoading () {
106
+ return (
107
+ <div className='messenger__messages-loading'>
108
+ <div className='messenger__messages-loading--loader' />
109
+ </div>
110
+ )
111
+ }
112
+
113
+ function Messages ({
114
+ messages,
115
+ creatorId,
116
+ formatDate,
117
+ loading,
118
+ }: {
119
+ messages: IMessages[]
120
+ creatorId?: string
121
+ formatDate: (date: string | Date) => string | undefined
122
+ loading: boolean
123
+ }) {
124
+ const { theme } = useTheme()
125
+ const { t } = useTranslation('messengerMessages')
126
+
127
+ if (!messages || (messages.length === 0 && !loading)) {
128
+ return (
129
+ <div
130
+ className='messenger__message-empty'
131
+ style={{ color: theme.emptyMessagesFontColor }}
132
+ >
133
+ <EmptyIcon />
134
+ <p>{t('noMessagesFound')}</p>
135
+ </div>
136
+ )
137
+ }
138
+
139
+ return (
140
+ <>
141
+ {messages.map((item, idx) => {
142
+ return (
143
+ <Fragment key={`${item.ticketId}_${item.senderId}_${idx}`}>
144
+ <MessageBalloon
145
+ id={`message_${idx}`}
146
+ formatDate={formatDate}
147
+ creatorId={creatorId}
148
+ item={item}
149
+ />
150
+ </Fragment>
151
+ )
152
+ })}
153
+ </>
154
+ )
155
+ }
156
+
157
+ const onScrollToBottom = (
158
+ scrollContainer: MutableRefObject<HTMLDivElement | null>
159
+ ) => {
160
+ if (scrollContainer.current) {
161
+ scrollContainer.current.scrollTop = Number.MAX_SAFE_INTEGER
162
+ }
163
+ }
164
+
165
+ function MessagesContainer ({
166
+ children,
167
+ fowardRef,
168
+ ...rest
169
+ }: {
170
+ children: React.ReactNode
171
+ fowardRef: MutableRefObject<HTMLDivElement | null>
172
+ } & React.DetailedHTMLProps<
173
+ React.HTMLAttributes<HTMLDivElement>,
174
+ HTMLDivElement
175
+ >) {
176
+ const { theme } = useTheme()
177
+
178
+ return (
179
+ <div
180
+ {...rest}
181
+ ref={fowardRef}
182
+ className='messenger__messages-container'
183
+ style={{
184
+ padding: '1rem',
185
+ backgroundColor: theme?.messengerMessagesBg,
186
+ border: `1px solid ${theme.borderColor}`,
187
+ }}
188
+ >
189
+ {children}
190
+ </div>
191
+ )
192
+ }
193
+
194
+ function ScrollableContainer ({
195
+ children,
196
+ loading,
197
+ onIntersect,
198
+ hasNext,
199
+ scrollContainerFowardRef,
200
+ }: {
201
+ children: React.ReactNode
202
+ scrollContainerFowardRef: MutableRefObject<HTMLDivElement | null>
203
+ } & IScrollableContainer) {
204
+ const intersetRootMargin = 300
205
+
206
+ const getElementScrollHeight = (divRef: HTMLDivElement | null) => {
207
+ if (!divRef) return null
208
+ return divRef.scrollHeight
209
+ }
210
+
211
+ const getElementScrollTop = (divRef: HTMLDivElement | null) => {
212
+ if (!divRef) return null
213
+ return divRef.scrollTop
214
+ }
215
+
216
+ const calculateScrollToDecrease = (
217
+ elementBeforeIntersetHeight: number,
218
+ elementAfterIntersectHeight: number,
219
+ intersetRootMargin: number
220
+ ) => {
221
+ return (
222
+ Math.abs(elementAfterIntersectHeight - elementBeforeIntersetHeight) +
223
+ intersetRootMargin
224
+ )
225
+ }
226
+
227
+ const isNill = (value: number | null | undefined) => {
228
+ return value === null && value === undefined
229
+ }
230
+
231
+ const onFetchInfiniteScroll = async () => {
232
+ const elementBeforeIntersetHeight = getElementScrollHeight(
233
+ scrollContainerFowardRef.current
234
+ )
235
+
236
+ await onIntersect()
237
+
238
+ const elementAfterIntersectHeight = getElementScrollHeight(
239
+ scrollContainerFowardRef.current
240
+ )
241
+
242
+ if (
243
+ scrollContainerFowardRef.current &&
244
+ !isNill(elementBeforeIntersetHeight) &&
245
+ !isNill(elementAfterIntersectHeight)
246
+ ) {
247
+ const currentScrollTop =
248
+ getElementScrollTop(scrollContainerFowardRef.current) || 0
249
+ scrollContainerFowardRef.current.scrollTop = calculateScrollToDecrease(
250
+ elementAfterIntersectHeight as number,
251
+ elementBeforeIntersetHeight as number,
252
+ currentScrollTop
253
+ )
254
+ }
255
+ }
256
+
257
+ useEffect(() => {
258
+ onScrollToBottom(scrollContainerFowardRef)
259
+ }, [])
260
+
261
+ return (
262
+ <MessagesContainer fowardRef={scrollContainerFowardRef}>
263
+ <InfiniteScroll
264
+ root={document.querySelector('.messenger__messages-container')}
265
+ loading={loading}
266
+ loadingCover={<SpinLoading />}
267
+ more={hasNext}
268
+ fetch={onFetchInfiniteScroll}
269
+ rootMargin={intersetRootMargin}
270
+ reverse
271
+ >
272
+ {children}
273
+ </InfiniteScroll>
274
+ </MessagesContainer>
275
+ )
276
+ }
277
+
278
+ function MessengerMessages ({
279
+ header,
280
+ tab,
281
+ className = '',
282
+ current,
283
+ messages,
284
+ allowImages,
285
+ creatorId,
286
+ loading,
287
+ onSubmit,
288
+ submitNewChat,
289
+ cancelNewChat,
290
+ reasons,
291
+ products,
292
+ messegerView,
293
+ formatDate,
294
+ hasNext,
295
+ onIntersect,
296
+ newChatLoading,
297
+ maxFileSize,
298
+ }: IMessengerMessages) {
299
+ const { t } = useTranslation('messengerMessages')
300
+ const [file, setFile] = useState<File | null>()
301
+ const scrollContainerRef = useRef<HTMLDivElement | null>(null)
302
+ const [isSending, setIsSending] = useState(false)
303
+ const [maxSize, setMaxSize] = useState<string>('')
304
+
305
+ const { theme } = useTheme()
306
+
307
+ const styleClassnames = {
308
+ mobile: tab === 'list' ? 'messenger__mobile-hide' : '',
309
+ }
310
+
311
+ const updateFile = (file: File | null) => {
312
+ setFile(file)
313
+ }
314
+
315
+ const onSubmitNewMessage = async (
316
+ message: FormData,
317
+ callback: () => void
318
+ ) => {
319
+ setIsSending(true)
320
+ try {
321
+ await onSubmit(message, callback)
322
+ onScrollToBottom(scrollContainerRef)
323
+ } finally {
324
+ setIsSending(false)
325
+ }
326
+ }
327
+
328
+ useEffect(() => {
329
+ const fetchMaxFileSize = async () => {
330
+ try {
331
+ const response = await maxFileSize()
332
+ const size = formatFileSize(response.data.data.maxFileSize)
333
+ setMaxSize(size)
334
+ } catch (error) {
335
+ console.error(t('errorSearchingMaxFileSize'), error)
336
+ }
337
+ }
338
+
339
+ fetchMaxFileSize()
340
+ }, [maxSize])
341
+
342
+ return (
343
+ <div
344
+ className={`messenger__messages ${className} ${styleClassnames.mobile} `}
345
+ >
346
+ <MessagesContainerView
347
+ newChatLoading={newChatLoading}
348
+ reasons={reasons}
349
+ products={products}
350
+ submitNewChat={submitNewChat}
351
+ cancelNewChat={cancelNewChat}
352
+ messegerView={messegerView}
353
+ maxFileSize={maxSize}
354
+ loadingResources={loading}
355
+ >
356
+ {file ? (
357
+ <ImagesContainer file={file} onClose={() => setFile(null)} />
358
+ ) : (
359
+ <>
360
+ <div
361
+ className='messenger__messages-header'
362
+ style={{
363
+ backgroundColor: theme?.headerAndSenderBg,
364
+ border: `1px solid ${theme.borderColor}`,
365
+ }}
366
+ >
367
+ {React.cloneElement(header)}
368
+ </div>
369
+
370
+ {loading && !messages.length ? (
371
+ <MessagesContainer fowardRef={scrollContainerRef}>
372
+ <SpinLoading />
373
+ </MessagesContainer>
374
+ ) : (
375
+ <ScrollableContainer
376
+ scrollContainerFowardRef={scrollContainerRef}
377
+ loading={loading}
378
+ hasNext={hasNext}
379
+ onIntersect={onIntersect}
380
+ >
381
+ <Messages
382
+ formatDate={formatDate}
383
+ messages={messages}
384
+ creatorId={creatorId}
385
+ loading={loading}
386
+ />
387
+ </ScrollableContainer>
388
+ )}
389
+ </>
390
+ )}
391
+
392
+ <div
393
+ className='messenger__messages-send'
394
+ style={{
395
+ backgroundColor: theme?.headerAndSenderBg,
396
+ border: `1px solid ${theme.borderColor}`,
397
+ }}
398
+ >
399
+ <SendMessageForm
400
+ allowImages={allowImages}
401
+ onSubmit={onSubmitNewMessage}
402
+ file={file}
403
+ updateFile={updateFile}
404
+ current={current}
405
+ isLoading={isSending}
406
+ maxFileSize={maxSize}
407
+ />
408
+ </div>
409
+ </MessagesContainerView>
410
+ </div>
411
+ )
412
+ }
413
+
414
+ export default MessengerMessages