@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.
Files changed (29) hide show
  1. package/dist/client.js +1 -1
  2. package/dist/server.js +1 -1
  3. package/package.json +12 -12
  4. package/src/client/components/availability/availability.jsx +13 -23
  5. package/src/client/components/chat/chat.jsx +26 -36
  6. package/src/client/components/live-region.jsx +2 -4
  7. package/src/client/components/panel/panel-header.jsx +4 -4
  8. package/src/client/components/panel/panel.jsx +39 -34
  9. package/src/client/components/panel/panel.scss +1 -1
  10. package/src/client/components/screens/end-chat.jsx +3 -6
  11. package/src/client/components/screens/request-chat.jsx +9 -13
  12. package/src/client/components/screens/settings.jsx +1 -1
  13. package/src/client/components/screens/unavailable.jsx +10 -12
  14. package/src/client/components/skip-link.jsx +1 -1
  15. package/src/client/hooks/useFocusedElements.js +6 -8
  16. package/src/client/index.jsx +9 -22
  17. package/src/client/lib/history.js +0 -4
  18. package/src/client/store/{app/AppProvider.jsx → AppProvider.jsx} +86 -35
  19. package/src/client/store/{app/actions-map.js → actions-map.js} +48 -15
  20. package/src/client/store/constants.js +0 -1
  21. package/src/client/store/{app/reducer.js → reducer.js} +4 -1
  22. package/src/client/store/useChatSdk.js +37 -0
  23. package/src/server/lib/client.js +2 -2
  24. package/src/server/lib/utils.js +1 -7
  25. package/src/client/store/sdk/SdkProvider.jsx +0 -141
  26. package/src/client/store/sdk/actions-map.js +0 -45
  27. package/src/client/store/sdk/reducer.js +0 -21
  28. package/src/client/store/sdk/useSdk.js +0 -5
  29. /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/app/useApp.js'
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 ({ initSdk, playSound }) {
20
- const { sdk, thread, availability, instigatorId, threadId, setSdk, setChatVisibility, setUnseenCount, isMobile, isKeyboard, isChatOpen } = useApp()
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, isChatOpen)
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
- if (!isChatOpen) {
42
+ return () => {
42
43
  document.getElementById(instigatorId)?.focus()
43
44
  }
44
- }, [isChatOpen])
45
+ }, [])
45
46
 
46
47
  /**
47
- * We need ammend classes on the body element to handle mobile behaviour
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
- * Initialize SDK if theres a thread ID
72
+ * Recovers the thread if there is a threadId but no thread loaded in to state
73
73
  */
74
74
  useEffect(() => {
75
- if (threadId) {
76
- if (!thread) {
77
- setSdk(initSdk())
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
- setScreen(2)
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 initSdk={initSdk} onPreChatScreen={goToPreChatScreen} />
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' style={{ display: isChatOpen ? 'flex' : 'none' }} className={classnames('wc-panel', isKeyboard && 'wc-focus-visible')} tabIndex='-1' aria-modal='true' aria-labelledby='wc-header wc-subtitle' open={isChatOpen}>
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>
@@ -53,7 +53,7 @@
53
53
  display: flex;
54
54
  flex-direction: row;
55
55
  align-items: center;
56
- background-color: govuk-colour('black');
56
+ background-color: govuk-colour('blue');
57
57
  border-bottom: 1px solid govuk-colour('black');
58
58
  padding: 3px;
59
59
 
@@ -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/app/useApp.js'
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 { thread, setSdk, setCustomerId, setThreadId, threadId, setUnseenCount, isKeyboard } = useApp()
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/app/useApp.js'
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 ({ initSdk, onPreChatScreen }) {
12
- const { setSdk, setCustomerId, setThreadId, setThread, setLiveRegionText } = useApp()
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
- const sdk = initSdk()
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/app/useApp.js'
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
- <div className='wc-content'>
11
- <h3 id='wc-subtitle' className='govuk-heading-m'>Webchat is currently not available</h3>
12
- <p className='govuk-body-s'>Try again later, or call Floodline:</p>
13
- <p className='govuk-body-s'>
14
- <strong>Floodline helpline</strong>
15
- <br />Telephone: 0345 988 1188
16
- <br />Textphone: 0345 602 6340
17
- <br />Open 24 hours a day, 7 days a week
18
- <br /><a className='govuk-link' href='https://gov.uk/call-charges'>Find out more about call charges</a>
19
- </p>
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
  )
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { createPortal } from 'react-dom'
3
- import { useApp } from '../store/app/useApp'
3
+ import { useApp } from '../store/useApp'
4
4
 
5
5
  export const SkipLink = () => {
6
6
  const { threadId, setInstigatorId } = useApp()
@@ -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 = (screen, isChatOpen) => {
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
- if (isChatOpen) {
67
- setAriaHidden(true)
66
+ setAriaHidden(true)
68
67
 
69
- const panelElement = document.querySelector('#wc-panel')
70
- panelElement.focus()
68
+ const panelElement = document.querySelector('#wc-panel')
69
+ panelElement.focus()
71
70
 
72
- document.addEventListener('keydown', onKeyDown)
73
- }
71
+ document.addEventListener('keydown', onKeyDown)
74
72
 
75
73
  return () => {
76
74
  setAriaHidden()
77
75
  document.removeEventListener('keydown', onKeyDown)
78
76
  }
79
- }, [panelElements, isChatOpen])
77
+ }, [panelElements])
80
78
  }
81
79
 
82
80
  export { useFocusedElements }
@@ -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/app/AppProvider.jsx'
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 root = createRoot(container)
14
-
15
- const initSdk = () => {
16
- return new ChatSdk({
17
- brandId: options.brandId,
18
- channelId: options.channelId,
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
- // import { ChatEvent } from '@nice-devone/nice-cxone-chat-web-sdk'
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 '../constants.js'
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
- setSdk,
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 { CUSTOMER_ID_STORAGE_KEY, THREAD_ID_STORAGE_KEY, SETTINGS_STORAGE_KEY, MESSAGES_UNSEEN_COUNT } from '../constants'
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 setChatVisibility = (state, payload) => {
64
+ const setMessages = (state, payload) => {
57
65
  return {
58
66
  ...state,
59
- isChatOpen: payload
67
+ messages: transformMessages(payload).reverse()
60
68
  }
61
69
  }
62
70
 
63
- const setAvailability = (state, payload) => {
71
+ const setMessage = (state, payload) => {
64
72
  return {
65
73
  ...state,
66
- availability: payload
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'