@defra/flood-webchat 1.0.10 → 1.0.12
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/dist/server.js +1 -1
- package/package.json +12 -12
- package/src/client/components/availability/availability.jsx +13 -23
- package/src/client/components/chat/chat.jsx +26 -36
- package/src/client/components/live-region.jsx +2 -4
- package/src/client/components/panel/panel-header.jsx +4 -4
- package/src/client/components/panel/panel.jsx +39 -34
- package/src/client/components/panel/panel.scss +1 -1
- 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/lib/history.js +0 -4
- package/src/client/store/{app/AppProvider.jsx → AppProvider.jsx} +86 -35
- package/src/client/store/{app/actions-map.js → actions-map.js} +48 -15
- package/src/client/store/constants.js +0 -1
- package/src/client/store/{app/reducer.js → reducer.js} +4 -1
- package/src/client/store/useChatSdk.js +37 -0
- package/src/server/lib/client.js +2 -2
- package/src/server/lib/utils.js +1 -7
- 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, { useEffect, useState, useCallback } from 'react'
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
|
-
|
|
4
3
|
import { classnames } from '../../lib/classnames.js'
|
|
4
|
+
|
|
5
5
|
import { PreChat } from '../screens/pre-chat.jsx'
|
|
6
6
|
import { RequestChat } from '../screens/request-chat.jsx'
|
|
7
7
|
import { Chat } from '../chat/chat.jsx'
|
|
@@ -11,17 +11,18 @@ import { Settings } from '../screens/settings.jsx'
|
|
|
11
11
|
import { Feedback } from '../screens/feedback.jsx'
|
|
12
12
|
import { LiveRegion } from '../live-region.jsx'
|
|
13
13
|
|
|
14
|
-
import { useApp } from '../../store/
|
|
14
|
+
import { useApp } from '../../store/useApp.js'
|
|
15
|
+
import { useChatSdk } from '../../store/useChatSdk.js'
|
|
15
16
|
import { useFocusedElements } from '../../hooks/useFocusedElements.js'
|
|
16
17
|
import { historyReplaceState } from '../../lib/history.js'
|
|
17
|
-
import { SdkProvider } from '../../store/sdk/SdkProvider.jsx'
|
|
18
18
|
|
|
19
|
-
export function Panel (
|
|
20
|
-
const { sdk,
|
|
19
|
+
export function Panel () {
|
|
20
|
+
const { sdk, availability, instigatorId, thread, threadId, setThread, setThreadId, setChatVisibility, setMessages, setUnseenCount, isMobile, isKeyboard } = useApp()
|
|
21
|
+
const { fetchThread, fetchMessages } = useChatSdk(sdk)
|
|
21
22
|
|
|
22
23
|
const [screen, setScreen] = useState(threadId ? 2 : 0)
|
|
23
24
|
|
|
24
|
-
useFocusedElements(screen
|
|
25
|
+
useFocusedElements(screen)
|
|
25
26
|
|
|
26
27
|
const onEscapeKey = useCallback(e => {
|
|
27
28
|
if (e.key === 'Escape' || e.key === 'Esc') {
|
|
@@ -38,19 +39,18 @@ export function Panel ({ initSdk, playSound }) {
|
|
|
38
39
|
* Focus previously focused element when closing the webchat
|
|
39
40
|
*/
|
|
40
41
|
useEffect(() => {
|
|
41
|
-
|
|
42
|
+
return () => {
|
|
42
43
|
document.getElementById(instigatorId)?.focus()
|
|
43
44
|
}
|
|
44
|
-
}, [
|
|
45
|
+
}, [])
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
* We need ammend classes on the body element to handle mobile behaviour
|
|
49
|
+
*/
|
|
49
50
|
useEffect(() => {
|
|
50
51
|
document.body.classList.remove('wc-u-hidden')
|
|
51
52
|
document.getElementsByTagName('html')[0].classList.add('wc-u-html')
|
|
52
53
|
document.body.classList.add('wc-u-body')
|
|
53
|
-
|
|
54
54
|
return () => {
|
|
55
55
|
document.getElementsByTagName('html')[0].classList.remove('wc-u-html')
|
|
56
56
|
document.body.classList.remove('wc-u-body')
|
|
@@ -69,15 +69,33 @@ export function Panel ({ initSdk, playSound }) {
|
|
|
69
69
|
}, [onEscapeKey, setChatVisibility])
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
72
|
+
* Recovers the thread if there is a threadId but no thread loaded in to state
|
|
73
73
|
*/
|
|
74
74
|
useEffect(() => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const recover = async () => {
|
|
76
|
+
try {
|
|
77
|
+
const fetchedThread = await fetchThread(threadId)
|
|
78
|
+
setThread(fetchedThread)
|
|
79
|
+
|
|
80
|
+
const fetchedMessages = await fetchMessages(fetchedThread, threadId)
|
|
81
|
+
setMessages(fetchedMessages)
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.log('[Chat Error] fetchThread', err)
|
|
84
|
+
|
|
85
|
+
setThreadId()
|
|
86
|
+
setThread()
|
|
87
|
+
setScreen(0)
|
|
78
88
|
}
|
|
89
|
+
}
|
|
79
90
|
|
|
80
|
-
|
|
91
|
+
if (threadId) {
|
|
92
|
+
if (thread) {
|
|
93
|
+
thread.lastMessageSeen()
|
|
94
|
+
setUnseenCount(0)
|
|
95
|
+
setScreen(2)
|
|
96
|
+
} else {
|
|
97
|
+
recover()
|
|
98
|
+
}
|
|
81
99
|
}
|
|
82
100
|
}, [thread, threadId])
|
|
83
101
|
|
|
@@ -100,38 +118,25 @@ export function Panel ({ initSdk, playSound }) {
|
|
|
100
118
|
ScreenComponent = <PreChat onContinue={goToRequestChatScreen} />
|
|
101
119
|
break
|
|
102
120
|
case 1:
|
|
103
|
-
ScreenComponent = <RequestChat
|
|
121
|
+
ScreenComponent = <RequestChat onPreChatScreen={goToPreChatScreen} />
|
|
104
122
|
break
|
|
105
123
|
case 2:
|
|
106
|
-
ScreenComponent =
|
|
107
|
-
<SdkProvider sdk={sdk} playSound={playSound} onRecoverError={() => setScreen(0)}>
|
|
108
|
-
<Chat onSettingsScreen={goToSettingsScreen} onEndChatScreen={goToEndChatScreen} />
|
|
109
|
-
</SdkProvider>
|
|
110
|
-
)
|
|
124
|
+
ScreenComponent = <Chat onSettingsScreen={goToSettingsScreen} onEndChatScreen={goToEndChatScreen} />
|
|
111
125
|
break
|
|
112
126
|
case 3:
|
|
113
|
-
ScreenComponent =
|
|
114
|
-
<SdkProvider sdk={sdk} playSound={playSound}>
|
|
115
|
-
<EndChat onChatScreen={goToChatScreen} onEndChatConfirm={goToFeedbackScreen} />
|
|
116
|
-
</SdkProvider>
|
|
117
|
-
)
|
|
127
|
+
ScreenComponent = <EndChat onChatScreen={goToChatScreen} onEndChatConfirm={goToFeedbackScreen} />
|
|
118
128
|
break
|
|
119
129
|
case 4:
|
|
120
130
|
ScreenComponent = (
|
|
121
131
|
<Feedback onCancel={() => {
|
|
122
132
|
setChatVisibility(false)
|
|
123
133
|
historyReplaceState()
|
|
124
|
-
setScreen(0)
|
|
125
134
|
}}
|
|
126
135
|
/>
|
|
127
136
|
)
|
|
128
137
|
break
|
|
129
138
|
case 5:
|
|
130
|
-
ScreenComponent =
|
|
131
|
-
<SdkProvider sdk={sdk} playSound={playSound}>
|
|
132
|
-
<Settings onCancel={goToChatScreen} />
|
|
133
|
-
</SdkProvider>
|
|
134
|
-
)
|
|
139
|
+
ScreenComponent = <Settings onCancel={goToChatScreen} />
|
|
135
140
|
break
|
|
136
141
|
default:
|
|
137
142
|
ScreenComponent = <PreChat onContinue={goToRequestChatScreen} />
|
|
@@ -142,7 +147,7 @@ export function Panel ({ initSdk, playSound }) {
|
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
const Component = (
|
|
145
|
-
<div id='wc-panel' role='dialog'
|
|
150
|
+
<div id='wc-panel' role='dialog' className={classnames('wc-panel', isKeyboard && 'wc-focus-visible')} tabIndex='-1' aria-modal='true' aria-labelledby='wc-header wc-subtitle'>
|
|
146
151
|
<div className='wc-panel__inner'>
|
|
147
152
|
{ScreenComponent}
|
|
148
153
|
</div>
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import React, { useEffect } from 'react'
|
|
2
2
|
import { PanelHeader } from '../panel/panel-header.jsx'
|
|
3
|
-
import { useApp } from '../../store/
|
|
4
|
-
import { useSdk } from '../../store/sdk/useSdk.js'
|
|
3
|
+
import { useApp } from '../../store/useApp.js'
|
|
5
4
|
|
|
6
5
|
export function EndChat ({ onChatScreen, onEndChatConfirm }) {
|
|
7
|
-
const {
|
|
8
|
-
const { agentStatus, setMessages } = useSdk()
|
|
6
|
+
const { setCustomerId, setThreadId, setMessages, agentStatus, thread, threadId, setUnseenCount, isKeyboard } = useApp()
|
|
9
7
|
|
|
10
8
|
const confirmEndChat = async e => {
|
|
11
9
|
e.preventDefault()
|
|
12
10
|
|
|
13
11
|
window.localStorage.setItem('tmpThreadId', threadId)
|
|
14
|
-
thread?.lastMessageSeen()
|
|
15
|
-
setSdk()
|
|
16
12
|
setThreadId()
|
|
17
13
|
setMessages([])
|
|
18
14
|
setCustomerId()
|
|
15
|
+
thread.lastMessageSeen()
|
|
19
16
|
setUnseenCount(0)
|
|
20
17
|
|
|
21
18
|
// End chat if still open
|
|
@@ -3,14 +3,15 @@ import * as uuid from 'uuid'
|
|
|
3
3
|
|
|
4
4
|
import { classnames } from '../../lib/classnames.js'
|
|
5
5
|
import { PanelHeader } from '../panel/panel-header.jsx'
|
|
6
|
-
import { useApp } from '../../store/
|
|
6
|
+
import { useApp } from '../../store/useApp.js'
|
|
7
|
+
import { useChatSdk } from '../../store/useChatSdk.js'
|
|
7
8
|
import { ErrorSummary } from '../errorSummary/error-summary.jsx'
|
|
8
9
|
|
|
9
10
|
const QUESTION_MAX_LENGTH = 500
|
|
10
11
|
|
|
11
|
-
export function RequestChat ({
|
|
12
|
-
const {
|
|
13
|
-
|
|
12
|
+
export function RequestChat ({ onPreChatScreen }) {
|
|
13
|
+
const { sdk, setCustomerId, setThreadId, setThread, setLiveRegionText } = useApp()
|
|
14
|
+
const { fetchCustomerId, fetchThread } = useChatSdk(sdk)
|
|
14
15
|
const [errors, setErrors] = useState({})
|
|
15
16
|
const [questionLength, setQuestionLength] = useState(0)
|
|
16
17
|
const [isButtonDisabled, setButtonDisabled] = useState(false)
|
|
@@ -71,22 +72,17 @@ export function RequestChat ({ initSdk, onPreChatScreen }) {
|
|
|
71
72
|
try {
|
|
72
73
|
setButtonDisabled(true)
|
|
73
74
|
const threadId = uuid.v4()
|
|
75
|
+
const customerId = await fetchCustomerId()
|
|
76
|
+
const thread = await fetchThread(threadId)
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
const conn = await sdk.authorize()
|
|
77
|
-
const customerId = conn?.consumerIdentity.idOnExternalPlatform
|
|
78
|
-
|
|
79
|
-
const thread = await sdk.getThread(threadId)
|
|
80
|
-
thread.setCustomField('threadid', threadId)
|
|
81
|
-
|
|
82
|
-
await sdk.getCustomer().setName(nameRef.current.value)
|
|
78
|
+
sdk.getCustomer().setName(nameRef.current.value)
|
|
83
79
|
|
|
84
80
|
await thread.startChat(questionRef.current.value || 'Begin conversation')
|
|
85
81
|
|
|
86
|
-
setSdk(sdk)
|
|
87
82
|
setCustomerId(customerId)
|
|
88
83
|
setThreadId(threadId)
|
|
89
84
|
setThread(thread)
|
|
85
|
+
thread.setCustomField('threadid', threadId)
|
|
90
86
|
} catch (err) {
|
|
91
87
|
console.log('[Request chat Error]', err)
|
|
92
88
|
}
|
|
@@ -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,8 +1,5 @@
|
|
|
1
|
-
const { stripPageTitle } = require('../../server/lib/utils')
|
|
2
|
-
|
|
3
1
|
export const historyPushState = () => {
|
|
4
2
|
const url = window.location.href.split('#')[0]
|
|
5
|
-
document.title = stripPageTitle(document.title)
|
|
6
3
|
window.history.pushState({ history: true }, null, `${url}#webchat`)
|
|
7
4
|
}
|
|
8
5
|
|
|
@@ -11,7 +8,6 @@ export const historyReplaceState = () => {
|
|
|
11
8
|
return window.history.back()
|
|
12
9
|
}
|
|
13
10
|
|
|
14
|
-
document.title = stripPageTitle(document.title)
|
|
15
11
|
const url = window.location.href.split('#')[0]
|
|
16
12
|
return window.history.replaceState(null, null, url)
|
|
17
13
|
}
|
|
@@ -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
|
|
5
|
+
import { CUSTOMER_ID_STORAGE_KEY, THREAD_ID_STORAGE_KEY, SETTINGS_STORAGE_KEY } 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
|
*/
|
|
@@ -18,7 +88,6 @@ export const AppProvider = ({ availability, children }) => {
|
|
|
18
88
|
setCustomerId(window.localStorage.getItem(CUSTOMER_ID_STORAGE_KEY))
|
|
19
89
|
setThreadId(window.localStorage.getItem(THREAD_ID_STORAGE_KEY))
|
|
20
90
|
setSettings(JSON.parse(window.localStorage.getItem(SETTINGS_STORAGE_KEY)) || state.settings)
|
|
21
|
-
setUnseenCount(window.localStorage.getItem(MESSAGES_UNSEEN_COUNT))
|
|
22
91
|
}, [])
|
|
23
92
|
|
|
24
93
|
/**
|
|
@@ -44,38 +113,9 @@ export const AppProvider = ({ availability, children }) => {
|
|
|
44
113
|
}
|
|
45
114
|
}, [])
|
|
46
115
|
|
|
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
116
|
/**
|
|
73
117
|
* State update functions
|
|
74
118
|
*/
|
|
75
|
-
const setSdk = payload => {
|
|
76
|
-
dispatch({ type: 'SET_SDK', payload })
|
|
77
|
-
}
|
|
78
|
-
|
|
79
119
|
const setChatVisibility = payload => {
|
|
80
120
|
dispatch({ type: 'SET_CHAT_VISIBILITY', payload })
|
|
81
121
|
}
|
|
@@ -92,6 +132,10 @@ export const AppProvider = ({ availability, children }) => {
|
|
|
92
132
|
dispatch({ type: 'SET_THREAD', payload: thread })
|
|
93
133
|
}
|
|
94
134
|
|
|
135
|
+
const setMessages = messages => {
|
|
136
|
+
dispatch({ type: 'SET_MESSAGES', payload: messages })
|
|
137
|
+
}
|
|
138
|
+
|
|
95
139
|
const setSettings = data => {
|
|
96
140
|
dispatch({ type: 'SET_SETTINGS', payload: data })
|
|
97
141
|
}
|
|
@@ -113,15 +157,22 @@ export const AppProvider = ({ availability, children }) => {
|
|
|
113
157
|
*/
|
|
114
158
|
const store = useMemo(() => ({
|
|
115
159
|
...state,
|
|
116
|
-
|
|
160
|
+
sdk,
|
|
117
161
|
setSettings,
|
|
118
162
|
setCustomerId,
|
|
119
163
|
setThreadId,
|
|
120
164
|
setThread,
|
|
165
|
+
setMessages,
|
|
121
166
|
setUnseenCount,
|
|
122
167
|
setChatVisibility,
|
|
123
168
|
setInstigatorId,
|
|
124
|
-
setLiveRegionText
|
|
169
|
+
setLiveRegionText,
|
|
170
|
+
onLiveChatRecovered,
|
|
171
|
+
onAssignedAgentChanged,
|
|
172
|
+
onAgentTypingStarted,
|
|
173
|
+
onAgentTypingEnded,
|
|
174
|
+
onMessageCreated,
|
|
175
|
+
onContactStatusChanged
|
|
125
176
|
}))
|
|
126
177
|
|
|
127
178
|
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 } 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,22 +61,43 @@ 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
|
|
|
70
100
|
const setUnseenCount = (state, payload) => {
|
|
71
|
-
window.localStorage.setItem(MESSAGES_UNSEEN_COUNT, payload)
|
|
72
101
|
return {
|
|
73
102
|
...state,
|
|
74
103
|
unseenCount: payload
|
|
@@ -104,13 +133,17 @@ const toggleIsKeyboard = (state, payload) => {
|
|
|
104
133
|
}
|
|
105
134
|
|
|
106
135
|
export const actionsMap = {
|
|
107
|
-
SET_SDK: setSdk,
|
|
108
|
-
SET_THREAD: setThread,
|
|
109
136
|
SET_CHAT_VISIBILITY: setChatVisibility,
|
|
110
137
|
SET_AVAILABILITY: setAvailability,
|
|
111
138
|
SET_SETTINGS: setSettings,
|
|
112
139
|
SET_CUSTOMER_ID: setCustomerId,
|
|
113
140
|
SET_THREAD_ID: setThreadId,
|
|
141
|
+
SET_THREAD: setThread,
|
|
142
|
+
SET_MESSAGE: setMessage,
|
|
143
|
+
SET_MESSAGES: setMessages,
|
|
144
|
+
SET_AGENT: setAgent,
|
|
145
|
+
SET_AGENT_TYPING: setAgentIsTyping,
|
|
146
|
+
SET_AGENT_STATUS: setAgentStatus,
|
|
114
147
|
SET_UNSEEN_COUNT: setUnseenCount,
|
|
115
148
|
SET_INSTIGATOR_ID: setInstigatorId,
|
|
116
149
|
SET_LIVE_REGION_TEXT: setLiveRegionText,
|
|
@@ -2,4 +2,3 @@ export const SETTINGS_STORAGE_KEY = 'webchat_settings'
|
|
|
2
2
|
export const CUSTOMER_ID_STORAGE_KEY = 'webchat_customer_id'
|
|
3
3
|
export const THREAD_ID_STORAGE_KEY = 'webchat_thread_id'
|
|
4
4
|
export const TYPING_INDICATOR_DURATION = 4000
|
|
5
|
-
export const MESSAGES_UNSEEN_COUNT = 'message_unseen_count'
|