@defra/flood-webchat 0.0.1-beta.38-draft → 0.0.1-beta.39

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/flood-webchat",
3
- "version": "0.0.1-beta.38-draft",
3
+ "version": "0.0.1-beta.39",
4
4
  "description": "",
5
5
  "main": "dist/server.js",
6
6
  "browser": "dist/client.js",
@@ -62,7 +62,6 @@
62
62
  "@nice-devone/nice-cxone-chat-web-sdk": "^1.3.0",
63
63
  "axios": "^1.5.0",
64
64
  "jwt-decode": "^3.1.2",
65
- "lodash.debounce": "^4.0.8",
66
65
  "luxon": "^3.4.3",
67
66
  "querystring": "^0.2.1",
68
67
  "react": "^18.2.0",
@@ -6,10 +6,9 @@ import { SkipLink } from '../skip-link.jsx'
6
6
 
7
7
  import { useApp } from '../../store/useApp'
8
8
  import { historyPushState, historyReplaceState } from '../../lib/history.js'
9
- import { LiveRegion } from '../live-region.jsx'
10
9
 
11
10
  export function Availability () {
12
- const { availability, isChatOpen, setChatVisibility, unseenCount, threadId, setUnseenCount, setInstigatorId, setLiveRegionText } = useApp()
11
+ const { availability, isChatOpen, setChatVisibility, unseenCount, threadId, setUnseenCount, setInstigatorId } = useApp()
13
12
 
14
13
  const buttonRef = useRef()
15
14
 
@@ -39,16 +38,6 @@ export function Availability () {
39
38
  }
40
39
  }
41
40
 
42
- useEffect(() => {
43
- if (!isChatOpen && unseenCount > 0) {
44
- setLiveRegionText(`Floodline Webchat - ${unseenCount} new messages`)
45
- }
46
-
47
- return () => {
48
- setLiveRegionText()
49
- }
50
- }, [unseenCount, isChatOpen])
51
-
52
41
  useEffect(() => {
53
42
  const onScroll = () => {
54
43
  if (!threadId) {
@@ -104,14 +93,7 @@ export function Availability () {
104
93
  data-module='govuk-button'
105
94
  >
106
95
  {TextComponent}
107
- {unseenCount > 0 && !isChatOpen
108
- ? (
109
- <>
110
- <span className='wc-availability__unseen'>{unseenCount}</span>
111
- <LiveRegion />
112
- </>
113
- )
114
- : null}
96
+ {unseenCount > 0 && !isChatOpen ? <span className='wc-availability__unseen'>{unseenCount}</span> : null}
115
97
  </a>
116
98
  </div>
117
99
  </div>
@@ -11,6 +11,10 @@
11
11
  right: 0;
12
12
  padding-left: 15px;
13
13
  padding-right: 15px;
14
+ border: 1px solid transparent;
15
+ @media (forced-colors: active) {
16
+ border-color: currentColor;
17
+ }
14
18
 
15
19
  @include mq ($from: 'tablet') {
16
20
  padding-left: 30px;
@@ -47,6 +51,10 @@
47
51
  .wc-availability__unseen {
48
52
  display: inline-block;
49
53
  position: relative;
54
+ border: 1px solid transparent;
55
+ @media (forced-colors: active) {
56
+ border-color: currentColor;
57
+ }
50
58
  border-radius: 10px;
51
59
  background-color: govuk-colour('black');
52
60
  color: govuk-colour('white');
@@ -10,7 +10,7 @@ import { formatTranscript } from '../../lib/transform-messages.js'
10
10
  import { agentStatusHeadline } from '../../lib/agent-status-headline.js'
11
11
 
12
12
  export function Chat ({ onEndChatScreen, onSettingsScreen }) {
13
- const { availability, thread, messages, agent, agentStatus, isAgentTyping, isChatOpen, settings, isKeyboard, setLiveRegionText } = useApp()
13
+ const { availability, thread, messages, agent, agentStatus, isAgentTyping, settings, isKeyboard } = useApp()
14
14
 
15
15
  const [userMessage, setUserMessage] = useState('')
16
16
  const [focusVisibleWithin, setFocusVisibleWithin] = useState(false)
@@ -44,47 +44,13 @@ export function Chat ({ onEndChatScreen, onSettingsScreen }) {
44
44
 
45
45
  const connectionHeadlineText = agentStatusHeadline(availability, agentStatus, agentName)
46
46
 
47
- useEffect(() => {
48
- if (connectionHeadlineText) {
49
- setLiveRegionText(`Floodline Webchat - ${connectionHeadlineText}`)
50
- }
51
-
52
- return () => {
53
- setLiveRegionText()
54
- }
55
- }, [connectionHeadlineText])
56
-
57
- useEffect(() => {
58
- if (isAgentTyping && isChatOpen) {
59
- setLiveRegionText(`${agentName} is typing...`)
60
- }
61
-
62
- return () => {
63
- setLiveRegionText()
64
- }
65
- }, [isAgentTyping, isChatOpen])
66
-
67
- useEffect(() => {
68
- const lastAgentMessage = messages[messages.length - 1]
69
-
70
- if (lastAgentMessage?.direction === 'outbound') {
71
- setLiveRegionText(`${agentName} said: ${lastAgentMessage.text}`)
72
- }
73
-
74
- return () => {
75
- setLiveRegionText()
76
- }
77
- }, [messages])
78
-
79
47
  const sendMessage = () => {
80
- if (messageRef.current.value.length === 0) {
48
+ if (messageRef.current.value.length === 0 || agentStatus === 'closed') {
81
49
  return
82
50
  }
83
51
 
84
52
  try {
85
- const message = messageRef.current.value.trim()
86
- thread.sendTextMessage(message)
87
- setLiveRegionText(`You said: ${message}`)
53
+ thread.sendTextMessage(messageRef.current.value.trim())
88
54
  } catch (err) {
89
55
  console.log('[Chat Error] sendMessage', err)
90
56
  }
@@ -124,10 +90,12 @@ export function Chat ({ onEndChatScreen, onSettingsScreen }) {
124
90
  default:
125
91
  break
126
92
  }
127
- } else if (e.key === ' ') {
93
+ }
94
+ if (e.key === ' ') {
128
95
  if (e.target.id === 'end-chat') {
129
96
  onEndChatScreen(e)
130
- } else if (e.target.id === 'wc-settings') {
97
+ }
98
+ if (e.target.id === 'wc-settings') {
131
99
  onSettingsScreen(e)
132
100
  }
133
101
  }
@@ -82,6 +82,11 @@
82
82
  padding: 10px 12.5px;
83
83
  margin-bottom: 0;
84
84
  display: inline-block;
85
+ border: 1px solid transparent;
86
+
87
+ @media (forced-colors: active) {
88
+ border-color: currentColor;
89
+ }
85
90
 
86
91
  &.inbound {
87
92
  color: govuk-colour('white');
@@ -98,10 +103,17 @@
98
103
  display: block;
99
104
  color: govuk-colour('dark-grey');
100
105
  }
106
+
107
+ @media (forced-colors: active) {
108
+ svg {
109
+ color: currentColor;
110
+ }
111
+ }
112
+
101
113
 
102
114
  svg circle {
103
115
  animation: 1s blink infinite;
104
- fill: currentcolor;
116
+ fill: currentColor;
105
117
 
106
118
  &:nth-child(2) {
107
119
  animation-delay: 250ms
@@ -9,7 +9,6 @@ import { Unavailable } from '../screens/unavailable.jsx'
9
9
  import { EndChat } from '../screens/end-chat.jsx'
10
10
  import { Settings } from '../screens/settings.jsx'
11
11
  import { Feedback } from '../screens/feedback.jsx'
12
- import { LiveRegion } from '../live-region.jsx'
13
12
 
14
13
  import { useApp } from '../../store/useApp.js'
15
14
  import { useChatSdk } from '../../store/useChatSdk.js'
@@ -137,7 +136,6 @@ export function Panel () {
137
136
  <div className='wc-panel__inner'>
138
137
  {ScreenComponent}
139
138
  </div>
140
- <LiveRegion />
141
139
  </div>
142
140
  )
143
141
 
@@ -1,14 +1,14 @@
1
1
  import React, { useEffect } from 'react'
2
2
  import { PanelHeader } from '../panel/panel-header.jsx'
3
3
  import { useApp } from '../../store/useApp.js'
4
- import { historyReplaceState } from '../../lib/history.js'
5
4
 
6
5
  export function EndChat ({ onChatScreen, onEndChatConfirm }) {
7
- const { setCustomerId, setThreadId, setMessages, agentStatus, thread, setUnseenCount, isKeyboard } = useApp()
6
+ const { setCustomerId, setThreadId, setMessages, agentStatus, thread, threadId, setUnseenCount, isKeyboard } = useApp()
8
7
 
9
8
  const confirmEndChat = async e => {
10
9
  e.preventDefault()
11
10
 
11
+ window.localStorage.setItem('tmpThreadId', threadId)
12
12
  setThreadId()
13
13
  setMessages([])
14
14
  setCustomerId()
@@ -20,7 +20,6 @@ export function EndChat ({ onChatScreen, onEndChatConfirm }) {
20
20
  thread.endChat()
21
21
  }
22
22
 
23
- historyReplaceState()
24
23
  onEndChatConfirm(e)
25
24
  }
26
25
 
@@ -35,7 +34,8 @@ export function EndChat ({ onChatScreen, onEndChatConfirm }) {
35
34
  const handleKeyPress = e => {
36
35
  if ((e.key === 'Enter' || e.key === ' ') && e.target.id === 'confirm-endchat') {
37
36
  confirmEndChat(e)
38
- } else if ((e.key === 'Enter' || e.key === ' ') && e.target.id === 'resume-chat') {
37
+ }
38
+ if ((e.key === 'Enter' || e.key === ' ') && e.target.id === 'resume-chat') {
39
39
  onChatScreen(e)
40
40
  }
41
41
  }
@@ -1,14 +1,25 @@
1
1
  import React from 'react'
2
2
  import { PanelHeader } from '../panel/panel-header.jsx'
3
-
4
3
  export function Feedback ({ onCancel }) {
5
- const feedbackCancel = async e => {
4
+ const feedbackSend = e => {
5
+ const tmpThreadId = window.localStorage.getItem('tmpThreadId')
6
+ window.location.href = `https://defragroup.eu.qualtrics.com/jfe/form/SV_8dgFSJcxxIfqx5Y?Id=${tmpThreadId}&Source=${window.location.href}`
7
+ window.localStorage.removeItem('tmpThreadId')
8
+ onCancel(e)
9
+ }
10
+
11
+ const feedbackClose = async e => {
6
12
  onCancel(e)
7
13
  }
8
14
 
9
15
  const handleKeyPress = e => {
10
- if ((e.key === 'Enter' || e.key === ' ') && e.target.id === 'feedback-cancel') {
11
- onCancel(e)
16
+ if (e.key === 'Enter' || e.key === ' ') {
17
+ if (e.target.id === 'feedback-close') {
18
+ feedbackClose(e)
19
+ }
20
+ if (e.target.id === 'feedback-send') {
21
+ feedbackSend(e)
22
+ }
12
23
  }
13
24
  }
14
25
 
@@ -22,23 +33,26 @@ export function Feedback ({ onCancel }) {
22
33
  <p>Please note we’re unable to respond to feedback.</p>
23
34
  <p>
24
35
  <a
36
+ id='feedback-send'
25
37
  className='govuk-link'
26
38
  href='#'
27
39
  target='_blank'
28
40
  rel='noreferrer'
41
+ onKeyDown={handleKeyPress}
42
+ onClick={feedbackSend}
29
43
  >
30
- Feedback Link
44
+ Leave Feedback
31
45
  </a>
32
46
  </p>
33
47
  <p>
34
48
  <a
35
- id='feedback-cancel'
49
+ id='feedback-close'
36
50
  className='wc-link govuk-link'
37
51
  href='#'
38
52
  data-module='govuk-button'
39
53
  role='button'
40
54
  onKeyDown={handleKeyPress}
41
- onClick={feedbackCancel}
55
+ onClick={feedbackClose}
42
56
  >
43
57
  Close
44
58
  </a>
@@ -44,6 +44,10 @@ export function RequestChat ({ onPreChatScreen }) {
44
44
  const onRequestChat = async e => {
45
45
  e.preventDefault()
46
46
 
47
+ if (isButtonDisabled) {
48
+ return
49
+ }
50
+
47
51
  const errs = {}
48
52
 
49
53
  if (nameRef.current.value.length === 0) {
@@ -161,7 +165,7 @@ export function RequestChat ({ onPreChatScreen }) {
161
165
  className='wc-button govuk-button govuk-!-margin-top-1'
162
166
  data-module='govuk-button'
163
167
  onClick={onRequestChat}
164
- disabled={isButtonDisabled}
168
+ aria-disabled={isButtonDisabled}
165
169
  >
166
170
  {buttonLabel}
167
171
  </button>
@@ -3,9 +3,9 @@ import { createPortal } from 'react-dom'
3
3
  import { useApp } from '../store/useApp'
4
4
 
5
5
  export const SkipLink = () => {
6
- const { thread, setInstigatorId } = useApp()
6
+ const { threadId, setInstigatorId } = useApp()
7
7
 
8
- if (!thread) {
8
+ if (!threadId) {
9
9
  return null
10
10
  }
11
11
 
@@ -1,19 +1,17 @@
1
1
  export const agentStatusHeadline = (availability, agentStatus, agentName) => {
2
- let connectionHeadlineText = 'Connecting to Floodline'
3
-
4
- if (agentStatus === 'closed') {
5
- connectionHeadlineText = agentName ? `${agentName} ended the session` : 'Session ended by advisor'
6
- } else if (agentStatus) {
7
- if (agentName) {
8
- connectionHeadlineText = `You are speaking with ${agentName}`
9
- } else {
10
- connectionHeadlineText = 'No advisers currently available'
11
- }
2
+ if (availability === 'UNAVAILABLE') {
3
+ return 'Webchat is not currently available'
12
4
  }
13
5
 
14
- if (availability === 'UNAVAILABLE') {
15
- connectionHeadlineText = 'Webchat is not currently available'
6
+ if (!agentStatus) {
7
+ return 'Connecting to Floodline'
16
8
  }
17
9
 
18
- return connectionHeadlineText
10
+ switch (agentStatus) {
11
+ case 'closed':
12
+ case 'resolved':
13
+ return agentName ? `${agentName} ended the session` : 'Session ended by advisor'
14
+ default:
15
+ return agentName ? `You are speaking with ${agentName}` : 'No advisers currently available'
16
+ }
19
17
  }
@@ -30,6 +30,11 @@
30
30
  font-size: 16px;
31
31
  }
32
32
 
33
+ .wc-button[aria-disabled="true"] {
34
+ cursor: not-allowed;
35
+ opacity: 0.5;
36
+ }
37
+
33
38
  .wc-label {
34
39
  font-size: 16px;
35
40
  line-height: 1.25;
@@ -73,24 +78,3 @@
73
78
  line-height: 1.25;
74
79
  margin: 0 0 25px;
75
80
  }
76
-
77
- // stylelint-disable declaration-no-important
78
- .wc-live {
79
- position: absolute !important;
80
- width: 1px !important;
81
- height: 1px !important;
82
- margin: 0 !important;
83
- padding: 0 !important;
84
- overflow: hidden !important;
85
- clip: rect(0 0 0 0) !important;
86
- -webkit-clip-path: inset(50%) !important;
87
- clip-path: inset(50%) !important;
88
- border: 0 !important;
89
- white-space: nowrap !important;
90
-
91
- &::before,
92
- &::after {
93
- content: ' ';
94
- }
95
- }
96
- // stylelint-enable declaration-no-important
@@ -27,13 +27,12 @@ export const AppProvider = ({ sdk, availability, options, children }) => {
27
27
  dispatch({ type: 'SET_AGENT_STATUS', payload: e.detail.data.case.status })
28
28
  }
29
29
 
30
- const onAgentTypingStarted = e => {
30
+ const onAgentTypingStarted = () => {
31
31
  dispatch({ type: 'SET_AGENT_TYPING', payload: true })
32
32
  }
33
33
 
34
34
  const onAgentTypingEnded = () => {
35
35
  dispatch({ type: 'SET_AGENT_TYPING', payload: false })
36
- dispatch({ type: 'SET_LIVE_REGION_TEXT' })
37
36
  }
38
37
 
39
38
  const onMessageCreated = e => {
@@ -150,10 +149,6 @@ export const AppProvider = ({ sdk, availability, options, children }) => {
150
149
  dispatch({ type: 'SET_INSTIGATOR_ID', payload: id })
151
150
  }
152
151
 
153
- const setLiveRegionText = text => {
154
- dispatch({ type: 'SET_LIVE_REGION_TEXT', payload: text })
155
- }
156
-
157
152
  /**
158
153
  * Application-wide state and state functions
159
154
  */
@@ -168,7 +163,6 @@ export const AppProvider = ({ sdk, availability, options, children }) => {
168
163
  setUnseenCount,
169
164
  setChatVisibility,
170
165
  setInstigatorId,
171
- setLiveRegionText,
172
166
  onLiveChatRecovered,
173
167
  onAssignedAgentChanged,
174
168
  onAgentTypingStarted,
@@ -61,18 +61,17 @@ const setThread = (state, payload) => {
61
61
  }
62
62
  }
63
63
 
64
- const setMessages = (state, payload) => {
64
+ const setMessage = (state, payload) => {
65
65
  return {
66
66
  ...state,
67
- messages: transformMessages(payload).reverse()
67
+ messages: [...state.messages, transformMessage(payload)]
68
68
  }
69
69
  }
70
70
 
71
- const setMessage = (state, payload) => {
71
+ const setMessages = (state, payload) => {
72
72
  return {
73
73
  ...state,
74
- message: transformMessage(payload),
75
- messages: [...state.messages, transformMessage(payload)]
74
+ messages: transformMessages(payload).reverse()
76
75
  }
77
76
  }
78
77
 
@@ -111,13 +110,6 @@ const setInstigatorId = (state, payload) => {
111
110
  }
112
111
  }
113
112
 
114
- const setLiveRegionText = (state, payload) => {
115
- return {
116
- ...state,
117
- liveRegionText: payload
118
- }
119
- }
120
-
121
113
  const toggleIsMobile = (state, payload) => {
122
114
  return {
123
115
  ...state,
@@ -146,7 +138,6 @@ export const actionsMap = {
146
138
  SET_AGENT_STATUS: setAgentStatus,
147
139
  SET_UNSEEN_COUNT: setUnseenCount,
148
140
  SET_INSTIGATOR_ID: setInstigatorId,
149
- SET_LIVE_REGION_TEXT: setLiveRegionText,
150
141
  TOGGLE_IS_MOBILE: toggleIsMobile,
151
142
  TOGGLE_IS_KEYBOARD: toggleIsKeyboard
152
143
  }
@@ -1,55 +0,0 @@
1
- import React, { useEffect, useState, useCallback } from 'react'
2
- import debounce from 'lodash.debounce'
3
- import { useApp } from '../store/useApp'
4
-
5
- export const LiveRegion = () => {
6
- const { agentStatus, liveRegionText, setLiveRegionText } = useApp()
7
-
8
- const [textA, setTextA] = useState()
9
- const [textB, setTextB] = useState()
10
-
11
- const isSessionEnded = agentStatus === 'closed' || agentStatus === null
12
-
13
- const clearText = () => {
14
- setTextA()
15
- setTextB()
16
- }
17
-
18
- const setText = useCallback(debounce(text => {
19
- if (textA) {
20
- setTextB(text)
21
- } else {
22
- setTextA(text)
23
- }
24
- }, 2000), [textA, textB])
25
-
26
- useEffect(() => {
27
- console.log('liveRegionText', liveRegionText)
28
-
29
- if (liveRegionText) {
30
- clearText()
31
- setText(liveRegionText)
32
- }
33
-
34
- if (isSessionEnded) {
35
- clearText()
36
- setLiveRegionText()
37
- }
38
-
39
- return () => {
40
- clearText()
41
- setLiveRegionText()
42
- }
43
- }, [liveRegionText])
44
-
45
- return (
46
- <>
47
- <div className='wc-live' aria-live='polite' aria-atomic='true'>
48
- {textA}
49
- </div>
50
- <div className='wc-live' aria-live='polite' aria-atomic='true'>
51
- {textB}
52
- </div>
53
- </>
54
- )
55
- }