@defra/flood-webchat 1.0.9 → 1.0.11
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.
- package/dist/client.js +1 -1
- package/package.json +1 -1
- package/src/client/components/availability/availability.jsx +12 -12
- package/src/client/components/chat/chat.jsx +30 -32
- package/src/client/components/live-region.jsx +2 -4
- package/src/client/components/panel/panel-header.jsx +1 -1
- package/src/client/components/panel/panel.jsx +39 -34
- package/src/client/components/screens/end-chat.jsx +3 -6
- package/src/client/components/screens/request-chat.jsx +9 -13
- package/src/client/components/screens/settings.jsx +1 -1
- package/src/client/components/screens/unavailable.jsx +10 -12
- package/src/client/components/skip-link.jsx +1 -1
- package/src/client/hooks/useFocusedElements.js +6 -8
- package/src/client/index.jsx +9 -22
- package/src/client/store/{app/AppProvider.jsx → AppProvider.jsx} +86 -34
- package/src/client/store/{app/actions-map.js → actions-map.js} +48 -14
- package/src/client/store/{app/reducer.js → reducer.js} +4 -1
- package/src/client/store/useChatSdk.js +37 -0
- package/src/client/store/sdk/SdkProvider.jsx +0 -141
- package/src/client/store/sdk/actions-map.js +0 -45
- package/src/client/store/sdk/reducer.js +0 -21
- package/src/client/store/sdk/useSdk.js +0 -5
- /package/src/client/store/{app/useApp.js → useApp.js} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { PanelHeader } from '../panel/panel-header.jsx'
|
|
3
3
|
|
|
4
|
-
import { useApp } from '../../store/
|
|
4
|
+
import { useApp } from '../../store/useApp.js'
|
|
5
5
|
|
|
6
6
|
export function Settings ({ onCancel }) {
|
|
7
7
|
const { settings, setSettings } = useApp()
|
|
@@ -7,18 +7,16 @@ export function Unavailable () {
|
|
|
7
7
|
<PanelHeader />
|
|
8
8
|
|
|
9
9
|
<div className='wc-body'>
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<p className='govuk-body-s'>We're running webchat as a trial, it will not always be available.</p>
|
|
21
|
-
</div>
|
|
10
|
+
<h3 id='wc-subtitle' className='govuk-heading-m'>Webchat is currently not available</h3>
|
|
11
|
+
<p className='govuk-body-s'>Try again later, or call Floodline:</p>
|
|
12
|
+
<p className='govuk-body-s'>
|
|
13
|
+
<strong>Floodline helpline</strong>
|
|
14
|
+
<br />Telephone: 0345 988 1188
|
|
15
|
+
<br />Textphone: 0345 602 6340
|
|
16
|
+
<br />Open 24 hours a day, 7 days a week
|
|
17
|
+
<br /><a className='govuk-link' href='https://gov.uk/call-charges'>Find out more about call charges</a>
|
|
18
|
+
</p>
|
|
19
|
+
<p className='govuk-body-s'>We're running webchat as a trial, it will not always be available.</p>
|
|
22
20
|
</div>
|
|
23
21
|
</>
|
|
24
22
|
)
|
|
@@ -32,7 +32,7 @@ export const getFocusableElements = () => {
|
|
|
32
32
|
return Array.from(elements).filter(e => !e.closest('[hidden]') && !e.closest('[aria-hidden="true"]'))
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const useFocusedElements =
|
|
35
|
+
const useFocusedElements = screen => {
|
|
36
36
|
const [panelElements, setPanelElements] = useState([])
|
|
37
37
|
|
|
38
38
|
const onKeyDown = useCallback(e => {
|
|
@@ -63,20 +63,18 @@ const useFocusedElements = (screen, isChatOpen) => {
|
|
|
63
63
|
}, [screen])
|
|
64
64
|
|
|
65
65
|
useEffect(() => {
|
|
66
|
-
|
|
67
|
-
setAriaHidden(true)
|
|
66
|
+
setAriaHidden(true)
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
const panelElement = document.querySelector('#wc-panel')
|
|
69
|
+
panelElement.focus()
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
}
|
|
71
|
+
document.addEventListener('keydown', onKeyDown)
|
|
74
72
|
|
|
75
73
|
return () => {
|
|
76
74
|
setAriaHidden()
|
|
77
75
|
document.removeEventListener('keydown', onKeyDown)
|
|
78
76
|
}
|
|
79
|
-
}, [panelElements
|
|
77
|
+
}, [panelElements])
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
export { useFocusedElements }
|
package/src/client/index.jsx
CHANGED
|
@@ -3,44 +3,31 @@ import { createRoot } from 'react-dom/client'
|
|
|
3
3
|
import { ChatSdk } from '@nice-devone/nice-cxone-chat-web-sdk'
|
|
4
4
|
import { Availability } from './components/availability/availability.jsx'
|
|
5
5
|
import { checkAvailability } from './lib/check-availability'
|
|
6
|
-
import { AppProvider } from './store/
|
|
6
|
+
import { AppProvider } from './store/AppProvider.jsx'
|
|
7
7
|
import { CUSTOMER_ID_STORAGE_KEY } from './store/constants.js'
|
|
8
8
|
import { messageNotification } from './lib/message-notification.js'
|
|
9
|
-
import { SkipLink } from './components/skip-link.jsx'
|
|
10
|
-
import { Panel } from './components/panel/panel.jsx'
|
|
11
9
|
|
|
12
10
|
export async function init (container, options) {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
customerId: window.localStorage.getItem(CUSTOMER_ID_STORAGE_KEY) || '',
|
|
20
|
-
environment: options.environment,
|
|
21
|
-
onError: error => {
|
|
22
|
-
console.log('ChatSdk onError', error)
|
|
23
|
-
}
|
|
24
|
-
})
|
|
25
|
-
}
|
|
11
|
+
const sdk = new ChatSdk({
|
|
12
|
+
brandId: options.brandId,
|
|
13
|
+
channelId: options.channelId,
|
|
14
|
+
customerId: window.localStorage.getItem(CUSTOMER_ID_STORAGE_KEY) || '',
|
|
15
|
+
environment: options.environment
|
|
16
|
+
})
|
|
26
17
|
|
|
18
|
+
const root = createRoot(container)
|
|
27
19
|
let availability
|
|
28
20
|
let playSound
|
|
29
|
-
|
|
30
21
|
try {
|
|
31
22
|
const result = await checkAvailability(options.availabilityEndpoint)
|
|
32
|
-
|
|
33
23
|
playSound = await messageNotification(options.audioUrl)
|
|
34
24
|
availability = result.availability
|
|
35
25
|
} catch (e) {
|
|
36
26
|
availability = 'UNAVAILABLE'
|
|
37
27
|
}
|
|
38
|
-
|
|
39
28
|
root.render(
|
|
40
|
-
<AppProvider availability={availability}>
|
|
29
|
+
<AppProvider sdk={sdk} availability={availability} playSound={playSound}>
|
|
41
30
|
<Availability />
|
|
42
|
-
<SkipLink />
|
|
43
|
-
<Panel initSdk={initSdk} playSound={playSound} />
|
|
44
31
|
</AppProvider>
|
|
45
32
|
)
|
|
46
33
|
}
|
|
@@ -1,14 +1,84 @@
|
|
|
1
1
|
import React, { createContext, useEffect, useReducer, useMemo } from 'react'
|
|
2
|
-
|
|
2
|
+
import { ChatEvent } from '@nice-devone/nice-cxone-chat-web-sdk'
|
|
3
3
|
|
|
4
4
|
import { initialState, reducer } from './reducer.js'
|
|
5
|
-
import { CUSTOMER_ID_STORAGE_KEY, THREAD_ID_STORAGE_KEY, SETTINGS_STORAGE_KEY, MESSAGES_UNSEEN_COUNT } from '
|
|
5
|
+
import { CUSTOMER_ID_STORAGE_KEY, THREAD_ID_STORAGE_KEY, SETTINGS_STORAGE_KEY, MESSAGES_UNSEEN_COUNT } from './constants.js'
|
|
6
6
|
|
|
7
7
|
export const AppContext = createContext(initialState)
|
|
8
8
|
|
|
9
|
-
export const AppProvider = ({ availability, children }) => {
|
|
9
|
+
export const AppProvider = ({ sdk, availability, playSound, children }) => {
|
|
10
10
|
const [state, dispatch] = useReducer(reducer, initialState)
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* SDK event handlers
|
|
14
|
+
*/
|
|
15
|
+
const onLiveChatRecovered = e => {
|
|
16
|
+
dispatch({ type: 'SET_AGENT', payload: e.detail.data.inboxAssignee })
|
|
17
|
+
dispatch({ type: 'SET_AGENT_STATUS', payload: e.detail.data.contact.status })
|
|
18
|
+
dispatch({ type: 'SET_UNSEEN_COUNT', payload: e.detail.data.contact.customerStatistics.unseenMessagesCount })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const onAssignedAgentChanged = e => {
|
|
22
|
+
dispatch({ type: 'SET_AGENT', payload: e.detail.data.inboxAssignee })
|
|
23
|
+
dispatch({ type: 'SET_AGENT_STATUS', payload: e.detail.data.case.status })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const onAgentTypingStarted = () => {
|
|
27
|
+
dispatch({ type: 'SET_AGENT_TYPING', payload: true })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const onAgentTypingEnded = () => {
|
|
31
|
+
dispatch({ type: 'SET_AGENT_TYPING', payload: false })
|
|
32
|
+
dispatch({ type: 'SET_LIVE_REGION_TEXT' })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const onMessageCreated = e => {
|
|
36
|
+
dispatch({ type: 'SET_MESSAGE', payload: e.detail.data.message })
|
|
37
|
+
dispatch({ type: 'SET_AGENT_STATUS', payload: e.detail.data.case.status })
|
|
38
|
+
dispatch({ type: 'SET_UNSEEN_COUNT', payload: e.detail.data.case.customerStatistics.unseenMessagesCount })
|
|
39
|
+
|
|
40
|
+
const isAudioOn = JSON.parse(window.localStorage.getItem(SETTINGS_STORAGE_KEY)).audio
|
|
41
|
+
|
|
42
|
+
if (isAudioOn && e.detail.data.message.direction === 'outbound' && playSound) {
|
|
43
|
+
playSound()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const onContactStatusChanged = e => {
|
|
48
|
+
dispatch({ type: 'SET_AGENT_STATUS', payload: e.detail.data.case.status })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const onMatchMedia = e => {
|
|
52
|
+
dispatch({ type: 'TOGGLE_IS_MOBILE', payload: e.matches })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const onKeydown = () => {
|
|
56
|
+
dispatch({ type: 'TOGGLE_IS_KEYBOARD', payload: true })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const onPointerdown = () => {
|
|
60
|
+
dispatch({ type: 'TOGGLE_IS_KEYBOARD', payload: false })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
sdk.onChatEvent(ChatEvent.LIVECHAT_RECOVERED, onLiveChatRecovered)
|
|
65
|
+
sdk.onChatEvent(ChatEvent.MESSAGE_CREATED, onMessageCreated)
|
|
66
|
+
sdk.onChatEvent(ChatEvent.AGENT_TYPING_STARTED, onAgentTypingStarted)
|
|
67
|
+
sdk.onChatEvent(ChatEvent.AGENT_TYPING_ENDED, onAgentTypingEnded)
|
|
68
|
+
sdk.onChatEvent(ChatEvent.ASSIGNED_AGENT_CHANGED, onAssignedAgentChanged)
|
|
69
|
+
sdk.onChatEvent(ChatEvent.CONTACT_STATUS_CHANGED, onContactStatusChanged)
|
|
70
|
+
// We need to know if it is a mobile and if it is a keyboard interaction
|
|
71
|
+
window.matchMedia('(max-width: 640px)').addEventListener('change', onMatchMedia)
|
|
72
|
+
window.addEventListener('keydown', onKeydown)
|
|
73
|
+
window.addEventListener('pointerdown', onPointerdown)
|
|
74
|
+
// Tidying up
|
|
75
|
+
return () => {
|
|
76
|
+
window.removeEventListener('change', onMatchMedia)
|
|
77
|
+
window.removeEventListener('keydown', onKeydown)
|
|
78
|
+
window.removeEventListener('pointerdown', onPointerdown)
|
|
79
|
+
}
|
|
80
|
+
}, [sdk])
|
|
81
|
+
|
|
12
82
|
/**
|
|
13
83
|
* Initialize customerId, threadId and whether the webchat should be open
|
|
14
84
|
*/
|
|
@@ -44,38 +114,9 @@ export const AppProvider = ({ availability, children }) => {
|
|
|
44
114
|
}
|
|
45
115
|
}, [])
|
|
46
116
|
|
|
47
|
-
const onMatchMedia = e => {
|
|
48
|
-
dispatch({ type: 'TOGGLE_IS_MOBILE', payload: e.matches })
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const onKeydown = () => {
|
|
52
|
-
dispatch({ type: 'TOGGLE_IS_KEYBOARD', payload: true })
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const onPointerdown = () => {
|
|
56
|
-
dispatch({ type: 'TOGGLE_IS_KEYBOARD', payload: false })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
// We need to know if it is a mobile and if it is a keyboard interaction
|
|
61
|
-
window.matchMedia('(max-width: 640px)').addEventListener('change', onMatchMedia)
|
|
62
|
-
window.addEventListener('keydown', onKeydown)
|
|
63
|
-
window.addEventListener('pointerdown', onPointerdown)
|
|
64
|
-
|
|
65
|
-
return () => {
|
|
66
|
-
window.removeEventListener('change', onMatchMedia)
|
|
67
|
-
window.removeEventListener('keydown', onKeydown)
|
|
68
|
-
window.removeEventListener('pointerdown', onPointerdown)
|
|
69
|
-
}
|
|
70
|
-
}, [])
|
|
71
|
-
|
|
72
117
|
/**
|
|
73
118
|
* State update functions
|
|
74
119
|
*/
|
|
75
|
-
const setSdk = payload => {
|
|
76
|
-
dispatch({ type: 'SET_SDK', payload })
|
|
77
|
-
}
|
|
78
|
-
|
|
79
120
|
const setChatVisibility = payload => {
|
|
80
121
|
dispatch({ type: 'SET_CHAT_VISIBILITY', payload })
|
|
81
122
|
}
|
|
@@ -92,6 +133,10 @@ export const AppProvider = ({ availability, children }) => {
|
|
|
92
133
|
dispatch({ type: 'SET_THREAD', payload: thread })
|
|
93
134
|
}
|
|
94
135
|
|
|
136
|
+
const setMessages = messages => {
|
|
137
|
+
dispatch({ type: 'SET_MESSAGES', payload: messages })
|
|
138
|
+
}
|
|
139
|
+
|
|
95
140
|
const setSettings = data => {
|
|
96
141
|
dispatch({ type: 'SET_SETTINGS', payload: data })
|
|
97
142
|
}
|
|
@@ -113,15 +158,22 @@ export const AppProvider = ({ availability, children }) => {
|
|
|
113
158
|
*/
|
|
114
159
|
const store = useMemo(() => ({
|
|
115
160
|
...state,
|
|
116
|
-
|
|
161
|
+
sdk,
|
|
117
162
|
setSettings,
|
|
118
163
|
setCustomerId,
|
|
119
164
|
setThreadId,
|
|
120
165
|
setThread,
|
|
166
|
+
setMessages,
|
|
121
167
|
setUnseenCount,
|
|
122
168
|
setChatVisibility,
|
|
123
169
|
setInstigatorId,
|
|
124
|
-
setLiveRegionText
|
|
170
|
+
setLiveRegionText,
|
|
171
|
+
onLiveChatRecovered,
|
|
172
|
+
onAssignedAgentChanged,
|
|
173
|
+
onAgentTypingStarted,
|
|
174
|
+
onAgentTypingEnded,
|
|
175
|
+
onMessageCreated,
|
|
176
|
+
onContactStatusChanged
|
|
125
177
|
}))
|
|
126
178
|
|
|
127
179
|
return (
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
const setSdk = (state, payload) => {
|
|
4
|
-
return {
|
|
5
|
-
...state,
|
|
6
|
-
sdk: payload
|
|
7
|
-
}
|
|
8
|
-
}
|
|
1
|
+
import { transformMessages, transformMessage } from '../lib/transform-messages'
|
|
2
|
+
import { CUSTOMER_ID_STORAGE_KEY, THREAD_ID_STORAGE_KEY, SETTINGS_STORAGE_KEY, MESSAGES_UNSEEN_COUNT } from './constants'
|
|
9
3
|
|
|
10
4
|
const setSettings = (state, payload) => {
|
|
11
5
|
if (payload) {
|
|
@@ -46,6 +40,20 @@ const setThreadId = (state, payload) => {
|
|
|
46
40
|
}
|
|
47
41
|
}
|
|
48
42
|
|
|
43
|
+
const setChatVisibility = (state, payload) => {
|
|
44
|
+
return {
|
|
45
|
+
...state,
|
|
46
|
+
isChatOpen: payload
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const setAvailability = (state, payload) => {
|
|
51
|
+
return {
|
|
52
|
+
...state,
|
|
53
|
+
availability: payload
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
const setThread = (state, payload) => {
|
|
50
58
|
return {
|
|
51
59
|
...state,
|
|
@@ -53,17 +61,39 @@ const setThread = (state, payload) => {
|
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
const
|
|
64
|
+
const setMessages = (state, payload) => {
|
|
57
65
|
return {
|
|
58
66
|
...state,
|
|
59
|
-
|
|
67
|
+
messages: transformMessages(payload).reverse()
|
|
60
68
|
}
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
const
|
|
71
|
+
const setMessage = (state, payload) => {
|
|
64
72
|
return {
|
|
65
73
|
...state,
|
|
66
|
-
|
|
74
|
+
message: transformMessage(payload),
|
|
75
|
+
messages: [...state.messages, transformMessage(payload)]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const setAgent = (state, payload) => {
|
|
80
|
+
return {
|
|
81
|
+
...state,
|
|
82
|
+
agent: payload
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const setAgentIsTyping = (state, payload) => {
|
|
87
|
+
return {
|
|
88
|
+
...state,
|
|
89
|
+
isAgentTyping: payload
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const setAgentStatus = (state, payload) => {
|
|
94
|
+
return {
|
|
95
|
+
...state,
|
|
96
|
+
agentStatus: payload
|
|
67
97
|
}
|
|
68
98
|
}
|
|
69
99
|
|
|
@@ -104,13 +134,17 @@ const toggleIsKeyboard = (state, payload) => {
|
|
|
104
134
|
}
|
|
105
135
|
|
|
106
136
|
export const actionsMap = {
|
|
107
|
-
SET_SDK: setSdk,
|
|
108
|
-
SET_THREAD: setThread,
|
|
109
137
|
SET_CHAT_VISIBILITY: setChatVisibility,
|
|
110
138
|
SET_AVAILABILITY: setAvailability,
|
|
111
139
|
SET_SETTINGS: setSettings,
|
|
112
140
|
SET_CUSTOMER_ID: setCustomerId,
|
|
113
141
|
SET_THREAD_ID: setThreadId,
|
|
142
|
+
SET_THREAD: setThread,
|
|
143
|
+
SET_MESSAGE: setMessage,
|
|
144
|
+
SET_MESSAGES: setMessages,
|
|
145
|
+
SET_AGENT: setAgent,
|
|
146
|
+
SET_AGENT_TYPING: setAgentIsTyping,
|
|
147
|
+
SET_AGENT_STATUS: setAgentStatus,
|
|
114
148
|
SET_UNSEEN_COUNT: setUnseenCount,
|
|
115
149
|
SET_INSTIGATOR_ID: setInstigatorId,
|
|
116
150
|
SET_LIVE_REGION_TEXT: setLiveRegionText,
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { actionsMap } from './actions-map'
|
|
2
2
|
|
|
3
3
|
export const initialState = {
|
|
4
|
-
sdk: null,
|
|
5
4
|
availability: null,
|
|
6
5
|
customerId: null,
|
|
7
6
|
threadId: null,
|
|
8
7
|
thread: null,
|
|
8
|
+
messages: [],
|
|
9
9
|
unseenCount: 0,
|
|
10
10
|
instigatorId: null,
|
|
11
|
+
agent: null,
|
|
12
|
+
agentStatus: null,
|
|
13
|
+
isAgentTyping: false,
|
|
11
14
|
isChatOpen: false,
|
|
12
15
|
isMobile: window.matchMedia('(max-width: 640px)').matches,
|
|
13
16
|
isKeyboard: false,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const useChatSdk = sdk => {
|
|
2
|
+
const connect = async () => sdk.authorize()
|
|
3
|
+
|
|
4
|
+
const fetchCustomerId = async () => {
|
|
5
|
+
const response = await connect()
|
|
6
|
+
return response?.consumerIdentity.idOnExternalPlatform
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const fetchThread = async threadId => {
|
|
10
|
+
await connect()
|
|
11
|
+
return sdk.getThread(threadId)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const fetchMessages = async (thread, threadId) => {
|
|
15
|
+
await connect()
|
|
16
|
+
|
|
17
|
+
const recovered = await thread.recover(threadId)
|
|
18
|
+
|
|
19
|
+
const allMessages = []
|
|
20
|
+
let fetchedMessages = recovered.messages
|
|
21
|
+
|
|
22
|
+
while (fetchedMessages.length) {
|
|
23
|
+
fetchedMessages.map(msg => allMessages.push(msg))
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const response = await thread.loadMoreMessages()
|
|
27
|
+
fetchedMessages = response.data.messages
|
|
28
|
+
} catch (err) {
|
|
29
|
+
fetchedMessages = []
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return allMessages
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { connect, fetchCustomerId, fetchThread, fetchMessages }
|
|
37
|
+
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useEffect, useReducer, useMemo } from 'react'
|
|
2
|
-
import { ChatEvent } from '@nice-devone/nice-cxone-chat-web-sdk'
|
|
3
|
-
|
|
4
|
-
import { initialState, reducer } from './reducer.js'
|
|
5
|
-
import { SETTINGS_STORAGE_KEY } from '../constants.js'
|
|
6
|
-
import { useApp } from '../app/useApp.js'
|
|
7
|
-
|
|
8
|
-
export const SdkContext = createContext(initialState)
|
|
9
|
-
|
|
10
|
-
export const SdkProvider = ({ sdk, playSound, onRecoverError, children }) => {
|
|
11
|
-
const [state, dispatch] = useReducer(reducer, initialState)
|
|
12
|
-
const { thread, threadId, setThread, setThreadId, setUnseenCount } = useApp()
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* SDK event handlers
|
|
16
|
-
*/
|
|
17
|
-
const onLiveChatRecovered = e => {
|
|
18
|
-
dispatch({ type: 'SET_AGENT', payload: e.detail.data.inboxAssignee })
|
|
19
|
-
dispatch({ type: 'SET_AGENT_STATUS', payload: e.detail.data.contact.status })
|
|
20
|
-
setUnseenCount(e.detail.data.contact.customerStatistics.unseenMessagesCount)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const onAssignedAgentChanged = e => {
|
|
24
|
-
dispatch({ type: 'SET_AGENT', payload: e.detail.data.inboxAssignee })
|
|
25
|
-
dispatch({ type: 'SET_AGENT_STATUS', payload: e.detail.data.case.status })
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const onAgentTypingStarted = () => {
|
|
29
|
-
dispatch({ type: 'SET_AGENT_TYPING', payload: true })
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const onAgentTypingEnded = () => {
|
|
33
|
-
dispatch({ type: 'SET_AGENT_TYPING', payload: false })
|
|
34
|
-
dispatch({ type: 'SET_LIVE_REGION_TEXT' })
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const onMessageCreated = e => {
|
|
38
|
-
dispatch({ type: 'SET_MESSAGE', payload: e.detail.data.message })
|
|
39
|
-
dispatch({ type: 'SET_AGENT_STATUS', payload: e.detail.data.case.status })
|
|
40
|
-
|
|
41
|
-
setUnseenCount(e.detail.data.case.customerStatistics.unseenMessagesCount)
|
|
42
|
-
|
|
43
|
-
const isAudioOn = JSON.parse(window.localStorage.getItem(SETTINGS_STORAGE_KEY)).audio
|
|
44
|
-
|
|
45
|
-
if (isAudioOn && e.detail.data.message.direction === 'outbound' && playSound) {
|
|
46
|
-
playSound()
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const onContactStatusChanged = e => {
|
|
51
|
-
dispatch({ type: 'SET_AGENT_STATUS', payload: e.detail.data.case.status })
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
useEffect(() => {
|
|
55
|
-
if (sdk) {
|
|
56
|
-
sdk.onChatEvent(ChatEvent.LIVECHAT_RECOVERED, onLiveChatRecovered)
|
|
57
|
-
sdk.onChatEvent(ChatEvent.MESSAGE_CREATED, onMessageCreated)
|
|
58
|
-
sdk.onChatEvent(ChatEvent.AGENT_TYPING_STARTED, onAgentTypingStarted)
|
|
59
|
-
sdk.onChatEvent(ChatEvent.AGENT_TYPING_ENDED, onAgentTypingEnded)
|
|
60
|
-
sdk.onChatEvent(ChatEvent.ASSIGNED_AGENT_CHANGED, onAssignedAgentChanged)
|
|
61
|
-
sdk.onChatEvent(ChatEvent.CONTACT_STATUS_CHANGED, onContactStatusChanged)
|
|
62
|
-
}
|
|
63
|
-
}, [sdk])
|
|
64
|
-
|
|
65
|
-
const fetchMessages = async threadItem => {
|
|
66
|
-
const recovered = await threadItem.recover()
|
|
67
|
-
|
|
68
|
-
const allMessages = []
|
|
69
|
-
|
|
70
|
-
let fetchedMessages = recovered.messages
|
|
71
|
-
|
|
72
|
-
while (fetchedMessages.length) {
|
|
73
|
-
fetchedMessages.map(msg => allMessages.push(msg))
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
const response = await threadItem.loadMoreMessages()
|
|
77
|
-
fetchedMessages = response.data.messages
|
|
78
|
-
} catch (err) {
|
|
79
|
-
fetchedMessages = []
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return allMessages
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Recovers the thread if there is a threadId but no thread loaded in to state
|
|
88
|
-
*/
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
const recover = async () => {
|
|
91
|
-
try {
|
|
92
|
-
await sdk.authorize()
|
|
93
|
-
|
|
94
|
-
const fetchedThread = await sdk.getThread(threadId)
|
|
95
|
-
const fetchedMessages = await fetchMessages(fetchedThread)
|
|
96
|
-
|
|
97
|
-
setThread(fetchedThread)
|
|
98
|
-
setMessages(fetchedMessages)
|
|
99
|
-
} catch (err) {
|
|
100
|
-
console.log('[Chat Error] fetchThread', err)
|
|
101
|
-
setThreadId()
|
|
102
|
-
setThread()
|
|
103
|
-
onRecoverError()
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (sdk && threadId && !thread) {
|
|
108
|
-
console.log('recovering chat...')
|
|
109
|
-
recover()
|
|
110
|
-
}
|
|
111
|
-
}, [sdk, thread, threadId])
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* State update functions
|
|
115
|
-
*/
|
|
116
|
-
const setMessages = messages => {
|
|
117
|
-
dispatch({ type: 'SET_MESSAGES', payload: messages })
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Application-wide state and state functions
|
|
122
|
-
*/
|
|
123
|
-
const store = useMemo(() => ({
|
|
124
|
-
...state,
|
|
125
|
-
sdk,
|
|
126
|
-
fetchMessages,
|
|
127
|
-
setMessages,
|
|
128
|
-
onLiveChatRecovered,
|
|
129
|
-
onAssignedAgentChanged,
|
|
130
|
-
onAgentTypingStarted,
|
|
131
|
-
onAgentTypingEnded,
|
|
132
|
-
onMessageCreated,
|
|
133
|
-
onContactStatusChanged
|
|
134
|
-
}))
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
<SdkContext.Provider value={store}>
|
|
138
|
-
{children}
|
|
139
|
-
</SdkContext.Provider>
|
|
140
|
-
)
|
|
141
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { transformMessages, transformMessage } from '../../lib/transform-messages'
|
|
2
|
-
|
|
3
|
-
const setMessages = (state, payload) => {
|
|
4
|
-
return {
|
|
5
|
-
...state,
|
|
6
|
-
messages: transformMessages(payload).reverse()
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const setMessage = (state, payload) => {
|
|
11
|
-
return {
|
|
12
|
-
...state,
|
|
13
|
-
message: transformMessage(payload),
|
|
14
|
-
messages: [...state.messages, transformMessage(payload)]
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const setAgent = (state, payload) => {
|
|
19
|
-
return {
|
|
20
|
-
...state,
|
|
21
|
-
agent: payload
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const setAgentIsTyping = (state, payload) => {
|
|
26
|
-
return {
|
|
27
|
-
...state,
|
|
28
|
-
isAgentTyping: payload
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const setAgentStatus = (state, payload) => {
|
|
33
|
-
return {
|
|
34
|
-
...state,
|
|
35
|
-
agentStatus: payload
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const actionsMap = {
|
|
40
|
-
SET_MESSAGE: setMessage,
|
|
41
|
-
SET_MESSAGES: setMessages,
|
|
42
|
-
SET_AGENT: setAgent,
|
|
43
|
-
SET_AGENT_TYPING: setAgentIsTyping,
|
|
44
|
-
SET_AGENT_STATUS: setAgentStatus
|
|
45
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { actionsMap } from './actions-map'
|
|
2
|
-
|
|
3
|
-
export const initialState = {
|
|
4
|
-
messages: [],
|
|
5
|
-
agent: null,
|
|
6
|
-
agentStatus: null,
|
|
7
|
-
isAgentTyping: false
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const reducer = (state, action) => {
|
|
11
|
-
const { type, payload } = action
|
|
12
|
-
|
|
13
|
-
const fn = actionsMap[type]
|
|
14
|
-
|
|
15
|
-
if (fn) {
|
|
16
|
-
const actionFunction = fn.bind(this, state, payload)
|
|
17
|
-
return actionFunction()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return state
|
|
21
|
-
}
|
|
File without changes
|