@defra/flood-webchat 0.0.1-beta.16 → 0.0.1-beta.18
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 +263 -39
- package/dist/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/components/availability/availability.jsx +2 -1
- package/src/client/components/availability/availability.scss +105 -5
- package/src/client/components/chat/chat.jsx +5 -3
- package/src/client/components/panel/panel-header.jsx +3 -1
- package/src/client/components/panel/panel.jsx +15 -4
- package/src/client/components/screens/settings.jsx +67 -0
- package/src/client/index.jsx +3 -3
- package/src/client/lib/message-notification.js +36 -0
- package/src/client/store/AppProvider.jsx +26 -2
- package/src/client/store/actions-map.js +24 -2
- package/src/client/store/constants.js +3 -0
- package/src/client/store/reducer.js +3 -4
|
@@ -6,13 +6,14 @@ import { RequestChat } from '../screens/request-chat.jsx'
|
|
|
6
6
|
import { Chat } from '../chat/chat.jsx'
|
|
7
7
|
import { Unavailable } from '../screens/unavailable.jsx'
|
|
8
8
|
import { EndChat } from '../screens/end-chat.jsx'
|
|
9
|
+
import { Settings } from '../screens/settings.jsx'
|
|
9
10
|
|
|
10
11
|
import { useApp } from '../../store/useApp.js'
|
|
11
12
|
import { useChatSdk } from '../../store/useChatSdk.js'
|
|
12
13
|
import { useFocusedElements } from '../../hooks/useFocusedElements.js'
|
|
13
14
|
|
|
14
15
|
export function Panel () {
|
|
15
|
-
const { sdk, availability, thread, threadId, setThread, setThreadId, setChatVisibility, setMessages } = useApp()
|
|
16
|
+
const { sdk, availability, thread, threadId, setThread, setThreadId, setChatVisibility, setMessages, setUnseenCount } = useApp()
|
|
16
17
|
const { fetchThread, fetchMessages } = useChatSdk(sdk)
|
|
17
18
|
|
|
18
19
|
const [screen, setScreen] = useState(threadId ? 2 : 0)
|
|
@@ -51,7 +52,13 @@ export function Panel () {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
if (threadId) {
|
|
54
|
-
(thread)
|
|
55
|
+
if (thread) {
|
|
56
|
+
thread.lastMessageSeen()
|
|
57
|
+
setUnseenCount(0)
|
|
58
|
+
setScreen(2)
|
|
59
|
+
} else {
|
|
60
|
+
recover()
|
|
61
|
+
}
|
|
55
62
|
}
|
|
56
63
|
}, [thread, threadId])
|
|
57
64
|
|
|
@@ -70,6 +77,7 @@ export function Panel () {
|
|
|
70
77
|
const onBack = handleScreenChange(screen - 1)
|
|
71
78
|
const onEndChat = handleScreenChange(3)
|
|
72
79
|
const onResume = handleScreenChange(2)
|
|
80
|
+
const onSettings = handleScreenChange(5)
|
|
73
81
|
|
|
74
82
|
const onEndChatConfirm = e => {
|
|
75
83
|
e.preventDefault()
|
|
@@ -86,11 +94,14 @@ export function Panel () {
|
|
|
86
94
|
ScreenComponent = <RequestChat onBack={onBack} />
|
|
87
95
|
break
|
|
88
96
|
case 2:
|
|
89
|
-
ScreenComponent = <Chat
|
|
97
|
+
ScreenComponent = <Chat onSettings={onSettings} onEndChat={onEndChat} />
|
|
90
98
|
break
|
|
91
99
|
case 3:
|
|
92
100
|
ScreenComponent = <EndChat onResume={onResume} onEndChatConfirm={onEndChatConfirm} />
|
|
93
101
|
break
|
|
102
|
+
case 5:
|
|
103
|
+
ScreenComponent = <Settings onCancel={onResume} />
|
|
104
|
+
break
|
|
94
105
|
default:
|
|
95
106
|
ScreenComponent = <PreChat onForward={onForward} />
|
|
96
107
|
}
|
|
@@ -100,7 +111,7 @@ export function Panel () {
|
|
|
100
111
|
}
|
|
101
112
|
|
|
102
113
|
const Component = (
|
|
103
|
-
<div id='wc-panel' className='wc-panel'
|
|
114
|
+
<div id='wc-panel' role='dialog' className='wc-panel' tabIndex='-1' aria-modal='true' aria-labelledby='wc-header-title'>
|
|
104
115
|
{ScreenComponent}
|
|
105
116
|
</div>
|
|
106
117
|
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { PanelHeader } from '../panel/panel-header.jsx'
|
|
3
|
+
|
|
4
|
+
import { useApp } from '../../store/useApp.js'
|
|
5
|
+
|
|
6
|
+
export function Settings ({ onCancel }) {
|
|
7
|
+
const { settings, setSettings } = useApp()
|
|
8
|
+
|
|
9
|
+
const [optionAudio, setOptionAudio] = useState(settings.audio)
|
|
10
|
+
const [optionScroll, setOptionScroll] = useState(settings.scroll)
|
|
11
|
+
|
|
12
|
+
const onSave = e => {
|
|
13
|
+
e.preventDefault()
|
|
14
|
+
|
|
15
|
+
setSettings({ audio: optionAudio, scroll: optionScroll })
|
|
16
|
+
onCancel(e)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<PanelHeader />
|
|
22
|
+
|
|
23
|
+
<div className='wc-body'>
|
|
24
|
+
<fieldset className='govuk-fieldset govuk-!-margin-bottom-4'>
|
|
25
|
+
<legend className='govuk-fieldset__legend govuk-fieldset__legend'>
|
|
26
|
+
<h3 id='wc-subtitle' className='govuk-heading-s'>Change settings</h3>
|
|
27
|
+
</legend>
|
|
28
|
+
<div className='govuk-checkboxes govuk-checkboxes--small' data-module='govuk-checkboxes'>
|
|
29
|
+
<div className='govuk-checkboxes__item'>
|
|
30
|
+
<input
|
|
31
|
+
id='audio'
|
|
32
|
+
name='audio'
|
|
33
|
+
type='checkbox'
|
|
34
|
+
value='audio'
|
|
35
|
+
className='govuk-checkboxes__input'
|
|
36
|
+
defaultChecked={optionAudio}
|
|
37
|
+
onChange={() => setOptionAudio(!optionAudio)}
|
|
38
|
+
/>
|
|
39
|
+
<label className='govuk-label govuk-checkboxes__label govuk-!-font-size-16' htmlFor='audio'>
|
|
40
|
+
Play a sound when receiving a new message
|
|
41
|
+
</label>
|
|
42
|
+
</div>
|
|
43
|
+
<div className='govuk-checkboxes__item'>
|
|
44
|
+
<input
|
|
45
|
+
id='scroll'
|
|
46
|
+
name='scroll'
|
|
47
|
+
type='checkbox'
|
|
48
|
+
value='scroll'
|
|
49
|
+
className='govuk-checkboxes__input'
|
|
50
|
+
defaultChecked={optionScroll}
|
|
51
|
+
onChange={() => setOptionScroll(!optionScroll)}
|
|
52
|
+
/>
|
|
53
|
+
<label className='govuk-label govuk-checkboxes__label govuk-!-font-size-16' htmlFor='scroll'>
|
|
54
|
+
Scroll automatically to a new message
|
|
55
|
+
</label>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</fieldset>
|
|
59
|
+
|
|
60
|
+
<div className='govuk-button-group'>
|
|
61
|
+
<a id='settings-save' href='#' className='govuk-button govuk-!-font-size-16' data-module='govuk-button' onClick={onSave}>Save</a>
|
|
62
|
+
<a id='settings-cancel' href='#' className='govuk-link govuk-!-font-size-16' data-module='govuk-button' onClick={onCancel}>Cancel</a>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</>
|
|
66
|
+
)
|
|
67
|
+
}
|
package/src/client/index.jsx
CHANGED
|
@@ -4,7 +4,7 @@ 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
6
|
import { AppProvider } from './store/AppProvider.jsx'
|
|
7
|
-
import { CUSTOMER_ID_STORAGE_KEY } from './store/
|
|
7
|
+
import { CUSTOMER_ID_STORAGE_KEY } from './store/constants.js'
|
|
8
8
|
|
|
9
9
|
export async function init (container, options) {
|
|
10
10
|
const sdk = new ChatSdk({
|
|
@@ -23,8 +23,8 @@ export async function init (container, options) {
|
|
|
23
23
|
availability = 'UNAVAILABLE'
|
|
24
24
|
}
|
|
25
25
|
root.render(
|
|
26
|
-
<AppProvider sdk={sdk} availability={availability}>
|
|
27
|
-
<Availability
|
|
26
|
+
<AppProvider sdk={sdk} availability={availability} options={options}>
|
|
27
|
+
<Availability />
|
|
28
28
|
</AppProvider>
|
|
29
29
|
)
|
|
30
30
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const messageNotification = audioUrl => {
|
|
2
|
+
let buffer
|
|
3
|
+
|
|
4
|
+
const context = new (window.AudioContext || window.webkitAudioContext)()
|
|
5
|
+
|
|
6
|
+
if (context.state === 'suspended') {
|
|
7
|
+
const events = ['touchstart', 'touchend', 'mousedown', 'wheel', 'keydown', 'click']
|
|
8
|
+
|
|
9
|
+
const unlock = _e => {
|
|
10
|
+
events.forEach(event => {
|
|
11
|
+
document.body.removeEventListener(event, unlock)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
context.resume()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
events.forEach(event => {
|
|
18
|
+
document.body.addEventListener(event, unlock, false)
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
fetch(audioUrl)
|
|
23
|
+
.then(response => response.arrayBuffer())
|
|
24
|
+
.then(data => context.decodeAudioData(data))
|
|
25
|
+
.then(decodedData => {
|
|
26
|
+
buffer = decodedData
|
|
27
|
+
})
|
|
28
|
+
.catch(console.error)
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
const source = context.createBufferSource()
|
|
32
|
+
source.buffer = buffer
|
|
33
|
+
source.connect(context.destination)
|
|
34
|
+
source.start()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,19 +1,25 @@
|
|
|
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
|
-
import {
|
|
4
|
+
import { messageNotification } from '../lib/message-notification.js'
|
|
5
|
+
|
|
6
|
+
import { initialState, reducer } from './reducer.js'
|
|
7
|
+
import { CUSTOMER_ID_STORAGE_KEY, THREAD_ID_STORAGE_KEY, SETTINGS_STORAGE_KEY } from './constants.js'
|
|
5
8
|
|
|
6
9
|
export const AppContext = createContext(initialState)
|
|
7
10
|
|
|
8
|
-
export const AppProvider = ({ sdk, availability, children }) => {
|
|
11
|
+
export const AppProvider = ({ sdk, availability, options, children }) => {
|
|
9
12
|
const [state, dispatch] = useReducer(reducer, initialState)
|
|
10
13
|
|
|
14
|
+
const playSound = messageNotification(options.audioUrl)
|
|
15
|
+
|
|
11
16
|
/**
|
|
12
17
|
* SDK event handlers
|
|
13
18
|
*/
|
|
14
19
|
const onLiveChatRecovered = e => {
|
|
15
20
|
dispatch({ type: 'SET_AGENT', payload: e.detail.data.inboxAssignee })
|
|
16
21
|
dispatch({ type: 'SET_AGENT_STATUS', payload: e.detail.data.contact.status })
|
|
22
|
+
dispatch({ type: 'SET_UNSEEN_COUNT', payload: e.detail.data.contact.customerStatistics.unseenMessagesCount })
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
const onAssignedAgentChanged = e => {
|
|
@@ -32,6 +38,13 @@ export const AppProvider = ({ sdk, availability, children }) => {
|
|
|
32
38
|
const onMessageCreated = e => {
|
|
33
39
|
dispatch({ type: 'SET_MESSAGE', payload: e.detail.data.message })
|
|
34
40
|
dispatch({ type: 'SET_AGENT_STATUS', payload: e.detail.data.case.status })
|
|
41
|
+
dispatch({ type: 'SET_UNSEEN_COUNT', payload: 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') {
|
|
46
|
+
playSound()
|
|
47
|
+
}
|
|
35
48
|
}
|
|
36
49
|
|
|
37
50
|
const onContactStatusChanged = e => {
|
|
@@ -55,6 +68,7 @@ export const AppProvider = ({ sdk, availability, children }) => {
|
|
|
55
68
|
|
|
56
69
|
setCustomerId(window.localStorage.getItem(CUSTOMER_ID_STORAGE_KEY))
|
|
57
70
|
setThreadId(window.localStorage.getItem(THREAD_ID_STORAGE_KEY))
|
|
71
|
+
setSettings(JSON.parse(window.localStorage.getItem(SETTINGS_STORAGE_KEY)) || state.settings)
|
|
58
72
|
|
|
59
73
|
if (window.location.hash === '#webchat') {
|
|
60
74
|
setChatVisibility(true)
|
|
@@ -88,16 +102,26 @@ export const AppProvider = ({ sdk, availability, children }) => {
|
|
|
88
102
|
dispatch({ type: 'SET_MESSAGES', payload: messages })
|
|
89
103
|
}
|
|
90
104
|
|
|
105
|
+
const setSettings = data => {
|
|
106
|
+
dispatch({ type: 'SET_SETTINGS', payload: data })
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const setUnseenCount = unseenCount => {
|
|
110
|
+
dispatch({ type: 'SET_UNSEEN_COUNT', payload: unseenCount })
|
|
111
|
+
}
|
|
112
|
+
|
|
91
113
|
/**
|
|
92
114
|
* Application-wide state and state functions
|
|
93
115
|
*/
|
|
94
116
|
const store = useMemo(() => ({
|
|
95
117
|
...state,
|
|
96
118
|
sdk,
|
|
119
|
+
setSettings,
|
|
97
120
|
setCustomerId,
|
|
98
121
|
setThreadId,
|
|
99
122
|
setThread,
|
|
100
123
|
setMessages,
|
|
124
|
+
setUnseenCount,
|
|
101
125
|
setChatVisibility,
|
|
102
126
|
onLiveChatRecovered,
|
|
103
127
|
onAssignedAgentChanged,
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { transformMessages, transformMessage } from '../lib/transform-messages'
|
|
2
|
-
import { CUSTOMER_ID_STORAGE_KEY, THREAD_ID_STORAGE_KEY } from './
|
|
2
|
+
import { CUSTOMER_ID_STORAGE_KEY, THREAD_ID_STORAGE_KEY, SETTINGS_STORAGE_KEY } from './constants'
|
|
3
|
+
|
|
4
|
+
const setSettings = (state, payload) => {
|
|
5
|
+
if (payload) {
|
|
6
|
+
window.localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(payload))
|
|
7
|
+
} else {
|
|
8
|
+
window.localStorage.removeItem(SETTINGS_STORAGE_KEY)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
...state,
|
|
13
|
+
settings: payload
|
|
14
|
+
}
|
|
15
|
+
}
|
|
3
16
|
|
|
4
17
|
const setCustomerId = (state, payload) => {
|
|
5
18
|
if (payload) {
|
|
@@ -83,9 +96,17 @@ const setAgentStatus = (state, payload) => {
|
|
|
83
96
|
}
|
|
84
97
|
}
|
|
85
98
|
|
|
99
|
+
const setUnseenCount = (state, payload) => {
|
|
100
|
+
return {
|
|
101
|
+
...state,
|
|
102
|
+
unseenCount: payload
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
86
106
|
export const actionsMap = {
|
|
87
107
|
SET_CHAT_VISIBILITY: setChatVisibility,
|
|
88
108
|
SET_AVAILABILITY: setAvailability,
|
|
109
|
+
SET_SETTINGS: setSettings,
|
|
89
110
|
SET_CUSTOMER_ID: setCustomerId,
|
|
90
111
|
SET_THREAD_ID: setThreadId,
|
|
91
112
|
SET_THREAD: setThread,
|
|
@@ -93,5 +114,6 @@ export const actionsMap = {
|
|
|
93
114
|
SET_MESSAGES: setMessages,
|
|
94
115
|
SET_AGENT: setAgent,
|
|
95
116
|
SET_AGENT_TYPING: setAgentIsTyping,
|
|
96
|
-
SET_AGENT_STATUS: setAgentStatus
|
|
117
|
+
SET_AGENT_STATUS: setAgentStatus,
|
|
118
|
+
SET_UNSEEN_COUNT: setUnseenCount
|
|
97
119
|
}
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { actionsMap } from './actions-map'
|
|
2
2
|
|
|
3
|
-
export const CUSTOMER_ID_STORAGE_KEY = 'webchat_customer_id'
|
|
4
|
-
export const THREAD_ID_STORAGE_KEY = 'webchat_thread_id'
|
|
5
|
-
|
|
6
3
|
export const initialState = {
|
|
7
4
|
availability: null,
|
|
8
5
|
customerId: null,
|
|
9
6
|
threadId: null,
|
|
10
7
|
thread: null,
|
|
11
8
|
messages: [],
|
|
9
|
+
unseenCount: 0,
|
|
12
10
|
agent: null,
|
|
13
11
|
agentStatus: null,
|
|
14
12
|
isAgentTyping: false,
|
|
15
|
-
isChatOpen: false
|
|
13
|
+
isChatOpen: false,
|
|
14
|
+
settings: { audio: true, scroll: true }
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
export const reducer = (state, action) => {
|