@defra/flood-webchat 0.0.1-beta.9 → 1.0.2

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 (49) hide show
  1. package/README.md +6 -0
  2. package/dist/client.js +1 -329
  3. package/dist/server.js +1 -286
  4. package/main.scss +13 -8
  5. package/package.json +29 -22
  6. package/src/client/components/availability/availability.jsx +94 -65
  7. package/src/client/components/availability/availability.scss +20 -32
  8. package/src/client/components/chat/chat.jsx +239 -0
  9. package/src/client/components/chat/chat.scss +258 -0
  10. package/src/client/components/errorSummary/error-summary.jsx +34 -0
  11. package/src/client/components/live-region.jsx +55 -0
  12. package/src/client/components/message/message.jsx +21 -0
  13. package/src/client/components/panel/panel-footer.jsx +9 -0
  14. package/src/client/components/panel/panel-header.jsx +75 -0
  15. package/src/client/components/panel/panel.jsx +163 -0
  16. package/src/client/components/panel/panel.scss +112 -0
  17. package/src/client/components/screens/end-chat.jsx +77 -0
  18. package/src/client/components/screens/feedback.jsx +65 -0
  19. package/src/client/components/screens/pre-chat.jsx +62 -0
  20. package/src/client/components/screens/request-chat.jsx +183 -0
  21. package/src/client/components/screens/settings.jsx +97 -0
  22. package/src/client/components/screens/unavailable.jsx +23 -0
  23. package/src/client/components/skip-link.jsx +42 -0
  24. package/src/client/hooks/useFocusedElements.js +80 -0
  25. package/src/client/hooks/useTextareaAutosize.js +13 -0
  26. package/src/client/index.jsx +18 -1
  27. package/src/client/lib/agent-status-headline.js +17 -0
  28. package/src/client/lib/check-availability.js +1 -0
  29. package/src/client/lib/history.js +13 -0
  30. package/src/client/lib/message-notification.js +30 -0
  31. package/src/client/lib/transform-messages.js +47 -0
  32. package/src/client/scss/_objects.scss +33 -0
  33. package/src/client/scss/_settings.scss +101 -0
  34. package/src/client/scss/_tools.scss +33 -0
  35. package/src/client/scss/_utilities.scss +25 -0
  36. package/src/client/store/AppProvider.jsx +183 -0
  37. package/src/client/store/actions-map.js +152 -0
  38. package/src/client/store/constants.js +4 -0
  39. package/src/client/store/reducer.js +31 -0
  40. package/src/client/store/useApp.js +5 -0
  41. package/src/client/store/useChatSdk.js +37 -0
  42. package/src/server/lib/client.js +2 -2
  43. package/src/server/lib/utils.js +16 -8
  44. package/webpack.config.mjs +0 -2
  45. package/webpack.prod.mjs +7 -0
  46. package/dist/client.js.map +0 -1
  47. package/dist/server.js.map +0 -1
  48. package/src/client/lib/external-stores.js +0 -4
  49. package/src/client/lib/external-sync-store.js +0 -42
@@ -0,0 +1,239 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+
3
+ import { TYPING_INDICATOR_DURATION } from '../../store/constants.js'
4
+
5
+ import { PanelHeader } from '../panel/panel-header.jsx'
6
+ import { PanelFooter } from '../panel/panel-footer.jsx'
7
+ import { Message } from '../message/message.jsx'
8
+
9
+ import { useApp } from '../../store/useApp.js'
10
+ import { useTextareaAutosize } from '../../hooks/useTextareaAutosize.js'
11
+ import { formatTranscript } from '../../lib/transform-messages.js'
12
+ import { agentStatusHeadline } from '../../lib/agent-status-headline.js'
13
+
14
+ export function Chat ({ onEndChatScreen, onSettingsScreen }) {
15
+ const { availability, thread, messages, agent, agentStatus, isAgentTyping, isChatOpen, settings, isKeyboard, setLiveRegionText } = useApp()
16
+
17
+ const [userMessage, setUserMessage] = useState('')
18
+ const [focusVisibleWithin, setFocusVisibleWithin] = useState(false)
19
+
20
+ const messageRef = useRef()
21
+
22
+ useTextareaAutosize(messageRef.current, userMessage)
23
+
24
+ useEffect(() => {
25
+ if (settings.scroll && messages.length !== 0) {
26
+ const chatBody = document.querySelector('.wc-body')
27
+ chatBody.scrollTop = chatBody.scrollHeight
28
+ }
29
+ }, [messages, isAgentTyping])
30
+
31
+ useEffect(() => {
32
+ const label = document.querySelector('.wc-form__label')
33
+
34
+ if (userMessage.length === 0) {
35
+ label.classList.remove('govuk-visually-hidden')
36
+ } else {
37
+ label.classList.add('govuk-visually-hidden')
38
+ }
39
+ }, [userMessage])
40
+
41
+ const onChange = e => {
42
+ setUserMessage(e.target?.value)
43
+ }
44
+
45
+ const agentName = agent?.nickname || agent?.firstName
46
+
47
+ const connectionHeadlineText = agentStatusHeadline(availability, agentStatus, agentName)
48
+
49
+ useEffect(() => {
50
+ if (connectionHeadlineText) {
51
+ setLiveRegionText(`Floodline webchat - ${connectionHeadlineText}`)
52
+ }
53
+
54
+ return () => {
55
+ setLiveRegionText()
56
+ }
57
+ }, [connectionHeadlineText])
58
+
59
+ useEffect(() => {
60
+ if (isAgentTyping && isChatOpen) {
61
+ setLiveRegionText(`${agentName} is typing...`)
62
+ }
63
+
64
+ return () => {
65
+ setLiveRegionText()
66
+ }
67
+ }, [isAgentTyping, isChatOpen])
68
+
69
+ useEffect(() => {
70
+ const lastAgentMessage = messages[messages.length - 1]
71
+
72
+ if (agentStatus !== 'closed' && lastAgentMessage?.direction === 'outbound') {
73
+ setLiveRegionText(`${agentName} said: ${lastAgentMessage.text}`)
74
+ }
75
+
76
+ return () => {
77
+ setLiveRegionText()
78
+ }
79
+ }, [messages])
80
+
81
+ const sendMessage = () => {
82
+ if (messageRef.current.value.length === 0 || agentStatus === 'closed') {
83
+ return
84
+ }
85
+
86
+ try {
87
+ const message = messageRef.current.value.trim()
88
+ thread.sendTextMessage(message)
89
+ setLiveRegionText(`You said: ${message}`)
90
+ } catch (err) {
91
+ console.log('[Chat Error] sendMessage', err)
92
+ }
93
+
94
+ setUserMessage('')
95
+ }
96
+
97
+ const handleSubmit = e => {
98
+ e.preventDefault()
99
+ sendMessage()
100
+ }
101
+
102
+ const saveChat = () => {
103
+ const transcript = formatTranscript(messages)
104
+
105
+ const saveChatLink = document.querySelector('#transcript-download')
106
+
107
+ saveChatLink.setAttribute('href', `data:text/plain;charset=utf-8,${transcript}`)
108
+ }
109
+
110
+ const handleKeyPress = e => {
111
+ if (e.key === 'Enter' && !e.shiftKey) {
112
+ switch (e.target.id) {
113
+ case 'text-area':
114
+ e.preventDefault()
115
+ sendMessage()
116
+ break
117
+ case 'end-chat':
118
+ onEndChatScreen(e)
119
+ break
120
+ case 'wc-settings':
121
+ onSettingsScreen(e)
122
+ break
123
+ case 'transcript-download':
124
+ saveChat()
125
+ break
126
+ default:
127
+ break
128
+ }
129
+ } else if (e.key === ' ') {
130
+ if (e.target.id === 'end-chat') {
131
+ onEndChatScreen(e)
132
+ }
133
+ if (e.target.id === 'wc-settings') {
134
+ onSettingsScreen(e)
135
+ }
136
+ } else {
137
+ thread.keystroke()
138
+ setTimeout(() => {
139
+ thread.stopTyping()
140
+ }, TYPING_INDICATOR_DURATION)
141
+ }
142
+ }
143
+
144
+ return (
145
+ <>
146
+ <PanelHeader />
147
+
148
+ <div className='wc-status'>
149
+ <p className='wc-status__availability'>{connectionHeadlineText}</p>
150
+ <a
151
+ id='end-chat'
152
+ className='wc-status__link'
153
+ href='#'
154
+ data-module='govuk-button'
155
+ role='button'
156
+ onClick={onEndChatScreen}
157
+ onKeyDown={handleKeyPress}
158
+ >
159
+ End chat
160
+ </a>
161
+ </div>
162
+
163
+ <div className='wc-body' tabIndex='0'>
164
+ <ul className='wc-chat'>
165
+ {messages.length
166
+ ? messages.map((msg, index) => <Message key={msg.id} message={msg} previousMessage={messages[index - 1]} />)
167
+ : null}
168
+ {isAgentTyping
169
+ ? (
170
+ <li className='wc-chat__message outbound'>
171
+ <div className='wc-chat__from'>{agentName} is typing</div>
172
+ <div className='wc-chat__text outbound'>
173
+ <svg width='28' height='16' x='0px' y='0px' viewBox='0 0 28 16'>
174
+ <circle stroke='none' cx='3' cy='8' r='3' fill='currentColor' />
175
+ <circle stroke='none' cx='14' cy='8' r='3' fill='currentColor' />
176
+ <circle stroke='none' cx='25' cy='8' r='3' fill='currentColor' />
177
+ </svg>
178
+ </div>
179
+ </li>
180
+ )
181
+ : null}
182
+ </ul>
183
+ </div>
184
+
185
+ <PanelFooter>
186
+ <form className={`wc-form${focusVisibleWithin ? ' wc-focus-within' : ''}`} noValidate onSubmit={handleSubmit}>
187
+ <label className='govuk-label wc-form__label' htmlFor='wc-form-textarea'>
188
+ Your message<span className='govuk-visually-hidden'> (enter key submits)</span>
189
+ </label>
190
+
191
+ <textarea
192
+ ref={messageRef}
193
+ rows='1'
194
+ aria-required='true'
195
+ className='wc-form__textarea'
196
+ id='text-area'
197
+ name='message'
198
+ onChange={onChange}
199
+ onKeyDown={handleKeyPress}
200
+ onFocus={() => { setFocusVisibleWithin(isKeyboard) }}
201
+ onBlur={() => { setFocusVisibleWithin(false) }}
202
+ value={userMessage}
203
+ />
204
+
205
+ <input
206
+ type='submit'
207
+ className='wc-form__button govuk-button'
208
+ value='Send'
209
+ data-prevent-double-click='true'
210
+ />
211
+ </form>
212
+
213
+ <div className='wc-footer__settings'>
214
+ <a
215
+ href='#'
216
+ id='wc-settings'
217
+ className='wc-footer__settings-link'
218
+ data-module='govuk-button'
219
+ onKeyDown={handleKeyPress}
220
+ onClick={onSettingsScreen}
221
+ >
222
+ Settings
223
+ </a>
224
+ <a
225
+ href='#'
226
+ id='transcript-download'
227
+ className='wc-footer__settings-link'
228
+ data-module='govuk-button'
229
+ download='floodline-webchat-transcript.txt'
230
+ onKeyDown={handleKeyPress}
231
+ onClick={saveChat}
232
+ >
233
+ Save chat
234
+ </a>
235
+ </div>
236
+ </PanelFooter>
237
+ </>
238
+ )
239
+ }
@@ -0,0 +1,258 @@
1
+ @keyframes blink {
2
+ 50% {
3
+ fill: transparent
4
+ }
5
+ }
6
+
7
+ .wc-status {
8
+ display: flex;
9
+ position: relative;
10
+ padding: 15px;
11
+ @include mq ($from: tablet) {
12
+ padding: 10px;
13
+ }
14
+
15
+ &::after {
16
+ position: absolute;
17
+ content: "";
18
+ bottom: 0;
19
+ left: 15px;
20
+ right: 15px;
21
+ height: 0;
22
+ border-bottom: 1px solid $govuk-border-colour;
23
+ @include mq ($from: tablet) {
24
+ left: 10px;
25
+ right: 10px;
26
+ }
27
+ }
28
+
29
+ &__availability {
30
+ font-size: 16px;
31
+ margin-bottom: 0;
32
+ }
33
+
34
+ &__link{
35
+ @extend %wc-link-button;
36
+ margin-left: auto;
37
+
38
+ &:link {
39
+ color: govuk-colour('black');
40
+ }
41
+ }
42
+ }
43
+
44
+ .wc-body[tabindex="0"]:focus-visible {
45
+ outline: 3px solid transparent;
46
+ outline-offset: -2px;
47
+ box-shadow:
48
+ inset 0 0 0 2px $govuk-focus-colour,
49
+ inset 0 0 0 4px govuk-colour('black'),
50
+ 0 0 0 1px $govuk-focus-colour;
51
+ }
52
+
53
+ .wc-chat {
54
+ @extend %govuk-body-s;
55
+ line-height: 1.25;
56
+ font-size: 14px;
57
+ overflow: auto;
58
+ list-style: none;
59
+ padding: 0;
60
+
61
+ &__message {
62
+ margin: 5px 15px 0;
63
+ max-width: 80%;
64
+ width: fit-content;
65
+
66
+ &.inbound {
67
+ margin-left: auto;
68
+ }
69
+
70
+ @include mq ($from: tablet) {
71
+ margin-left: 10px;
72
+ margin-right: 10px;
73
+ }
74
+
75
+ &:first-child {
76
+ padding-top: 15px;
77
+ }
78
+ }
79
+
80
+ &__from {
81
+ line-height: 1.25;
82
+ color: govuk-colour('dark-grey');
83
+ margin-top: 2px;
84
+ margin-bottom: 2px;
85
+
86
+ .inbound {
87
+ display: block;
88
+ text-align: right;
89
+ }
90
+ }
91
+
92
+ &__text {
93
+ font-size: 16px;
94
+ padding: 10px 12.5px;
95
+ margin-bottom: 0;
96
+ display: inline-block;
97
+ border: 1px solid transparent;
98
+ overflow-wrap: anywhere;
99
+
100
+ @media (forced-colors: active) {
101
+ border-color: currentColor;
102
+ }
103
+
104
+ &.inbound {
105
+ color: govuk-colour('white');
106
+ background-color: govuk-colour('blue');
107
+ border-radius: 10px 0 10px 10px;
108
+
109
+ .govuk-link:visited {
110
+ color: govuk-colour('white');
111
+ }
112
+
113
+ .govuk-link:focus {
114
+ color: govuk-colour('black');
115
+ }
116
+ }
117
+
118
+ &.outbound {
119
+ background-color: govuk-colour('light-grey');
120
+ border-radius: 0 10px 10px;
121
+ }
122
+
123
+ svg {
124
+ display: block;
125
+ color: govuk-colour('dark-grey');
126
+ }
127
+
128
+ @media (forced-colors: active) {
129
+ svg {
130
+ color: currentColor;
131
+ }
132
+ }
133
+
134
+
135
+ svg circle {
136
+ animation: 1s blink infinite;
137
+ fill: currentColor;
138
+
139
+ &:nth-child(2) {
140
+ animation-delay: 250ms
141
+ }
142
+
143
+ &:nth-child(3) {
144
+ animation-delay: 500ms
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ .wc-form {
151
+ position: relative;
152
+ display: flex;
153
+ flex-wrap: nowrap;
154
+ align-items: baseline;
155
+ border-top: 1px solid govuk-colour('black');
156
+ border-bottom: 1px solid govuk-colour('black');
157
+
158
+ &__label {
159
+ position: absolute;
160
+ pointer-events: none;
161
+ font-size: 16px;
162
+ line-height: 20px;
163
+ margin: 20px;
164
+ @include mq ($from: tablet) {
165
+ margin: 18px 18px 19px;
166
+ }
167
+ left: 0;
168
+ bottom: 0;
169
+ color: $govuk-secondary-text-colour;
170
+ z-index: 3;
171
+ }
172
+
173
+ &__textarea {
174
+ @extend %govuk-body-s;
175
+ box-sizing: border-box;
176
+ min-height: auto;
177
+ font-size: 16px;
178
+ line-height: 20px;
179
+ max-height: 120px;
180
+ flex: 1 1 auto;
181
+ align-self: flex-end;
182
+ margin: 20px;
183
+ @include mq ($from: tablet) {
184
+ margin: 18px 18px 19px;
185
+ }
186
+ padding: 0;
187
+ border: 0;
188
+ height: 20px;
189
+ resize: none;
190
+ overflow-x: hidden;
191
+ overscroll-behavior: contain;
192
+
193
+ &:focus {
194
+ outline: none;
195
+ box-shadow: none;
196
+ z-index: 2;
197
+ }
198
+ }
199
+
200
+ &__button {
201
+ margin: 10px 15px 12px 0;
202
+ @include mq ($from: tablet) {
203
+ margin-right: 10px;
204
+ }
205
+ font-size: 16px;
206
+ position: relative;
207
+ flex: 0 0 auto;
208
+ align-self: flex-end;
209
+ display: flex;
210
+ flex-direction: row;
211
+ flex-wrap: nowrap;
212
+ align-items: center;
213
+ width: auto;
214
+ cursor: pointer;
215
+ }
216
+
217
+ &.wc-focus-within::after {
218
+ @include focus($glow: 5px, $strong: 2px, $background: 0, $inset: 0);
219
+ left: 4px;
220
+ right: 4px;
221
+ top: 4px;
222
+ bottom: 4px;
223
+ }
224
+ }
225
+
226
+ .wc-body:focus-visible + .wc-footer .wc-form {
227
+ border-top: 1px solid transparent;
228
+ }
229
+
230
+ .wc-footer {
231
+ position: relative;
232
+ flex: 0 0 0;
233
+
234
+ &__settings {
235
+ @include govuk-responsive-padding(2);
236
+
237
+ display: flex;
238
+ flex-direction: row;
239
+
240
+ &-link {
241
+ @extend %wc-link-button;
242
+
243
+ &:first-child {
244
+ margin-right: govuk-spacing(3);
245
+ }
246
+
247
+ &:link {
248
+ color: govuk-colour('black');
249
+ }
250
+ }
251
+ }
252
+
253
+ &__input {
254
+ @include govuk-responsive-padding(2);
255
+
256
+ border-top: 1px solid govuk-colour('black');
257
+ }
258
+ }
@@ -0,0 +1,34 @@
1
+ import React from 'react'
2
+
3
+ export const ErrorSummary = ({ errors }) => {
4
+ const errs = Object.keys(errors)
5
+
6
+ const goToInput = e => {
7
+ e.preventDefault()
8
+ const key = e.target.getAttribute('data-key')
9
+ document.querySelector(`#wc-${key}`).focus()
10
+ }
11
+
12
+ if (errs.length === 0) {
13
+ return null
14
+ }
15
+
16
+ return (
17
+ <div className='govuk-error-summary govuk-!-static-margin-bottom-7' data-module='govuk-error-summary' tabIndex='-1'>
18
+ <div role='alert'>
19
+ <h2 id='wc-error' className='wc-error-summary__title govuk-error-summary__title'>
20
+ There is a problem
21
+ </h2>
22
+ <div className='govuk-error-summary__body'>
23
+ <ul className='govuk-list govuk-error-summary__list wc-error-summary__list'>
24
+ {errs.map(key => (
25
+ <li key={key}>
26
+ <a href='#' data-key={key} onClick={goToInput}>{errors[key]}</a>
27
+ </li>
28
+ ))}
29
+ </ul>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ )
34
+ }
@@ -0,0 +1,55 @@
1
+ import React, { useEffect, useState, useCallback } from 'react'
2
+ import debounce from 'lodash.debounce'
3
+ import { useApp } from '../store/useApp'
4
+
5
+ const DEBOUNCE_MILLISECONDS = 2000
6
+
7
+ export const LiveRegion = () => {
8
+ const { agentStatus, liveRegionText, setLiveRegionText } = useApp()
9
+
10
+ const [textA, setTextA] = useState()
11
+ const [textB, setTextB] = useState()
12
+
13
+ const isSessionEnded = agentStatus === 'closed' || agentStatus === null
14
+
15
+ const clearText = () => {
16
+ setTextA()
17
+ setTextB()
18
+ }
19
+
20
+ const setText = useCallback(debounce(text => {
21
+ if (textA) {
22
+ setTextB(text)
23
+ } else {
24
+ setTextA(text)
25
+ }
26
+ }, DEBOUNCE_MILLISECONDS), [textA, textB])
27
+
28
+ useEffect(() => {
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' role='status' aria-atomic='true'>
48
+ {textA}
49
+ </div>
50
+ <div className='wc-live' role='status' aria-atomic='true'>
51
+ {textB}
52
+ </div>
53
+ </>
54
+ )
55
+ }
@@ -0,0 +1,21 @@
1
+ import React from 'react'
2
+ import { classnames } from '../../lib/classnames'
3
+ import { formatMessage } from '../../lib/transform-messages'
4
+
5
+ export function Message ({ message, previousMessage }) {
6
+ const outbound = message.direction === 'outbound'
7
+
8
+ const messageOwnerSameAsPrevious = message?.direction === previousMessage?.direction
9
+
10
+ return (
11
+ <li className={classnames('wc-chat__message', outbound ? 'outbound' : 'inbound')}>
12
+ <div className='wc-chat__from'>
13
+ {messageOwnerSameAsPrevious ? null : <span className={classnames(outbound ? 'outbound' : 'inbound')}>{outbound ? message.assignee : 'You'}</span>}
14
+ <span className='govuk-visually-hidden'>:</span>
15
+ </div>
16
+ <div className={classnames('wc-chat__text', outbound ? 'outbound' : 'inbound')}>
17
+ {formatMessage(message.text)}
18
+ </div>
19
+ </li>
20
+ )
21
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react'
2
+
3
+ export function PanelFooter ({ children }) {
4
+ return (
5
+ <div className='wc-footer'>
6
+ {children}
7
+ </div>
8
+ )
9
+ }
@@ -0,0 +1,75 @@
1
+ import React from 'react'
2
+ import { useApp } from '../../store/useApp.js'
3
+ import { historyReplaceState } from '../../lib/history.js'
4
+
5
+ export function PanelHeader () {
6
+ const { thread, threadId, setChatVisibility, setUnseenCount, isMobile } = useApp()
7
+
8
+ const onClose = e => {
9
+ e.preventDefault()
10
+ setChatVisibility(false)
11
+ historyReplaceState()
12
+
13
+ if (threadId) {
14
+ thread?.lastMessageSeen()
15
+ setUnseenCount(0)
16
+ }
17
+ }
18
+
19
+ const BackButtonComponent = (
20
+ <button className='wc-header__back' aria-label='Close the webchat' onClick={onClose}>
21
+ <svg aria-hidden='true' focusable='false' width='20' height='20' viewBox='0 0 20 20'>
22
+ <path d='M4.828,11L12.314,18.485L10.899,19.899L1,10L10.899,0.101L12.314,1.515L4.828,9L19,9L19,11L4.828,11Z' fill='currentColor' />
23
+ </svg>
24
+ </button>
25
+ )
26
+
27
+ const CloseButtonComponent = (
28
+ <button className='wc-header__close' aria-label='Close the webchat' onClick={onClose}>
29
+ <svg aria-hidden='true' focusable='false' width='20' height='20' viewBox='0 0 20 20'>
30
+ <path d='M10,8.6L15.6,3L17,4.4L11.4,10L17,15.6L15.6,17L10,11.4L4.4,17L3,15.6L8.6,10L3,4.4L4.4,3L10,8.6Z' fill='currentColor' />
31
+ </svg>
32
+ </button>
33
+ )
34
+
35
+ const MinimiseButtonComponent = (
36
+ <button className='wc-header__hide' aria-label='Minimise the webchat' onClick={onClose}>
37
+ <svg aria-hidden='true' focusable='false' width='20' height='20' viewBox='0 0 20 20'>
38
+ <path d='M10 14.4l-7-7L4.4 6l5.6 5.6L15.6 6 17 7.4l-7 7z' fill='currentColor' />
39
+ </svg>
40
+ </button>
41
+ )
42
+
43
+ const isMobileAndHasHistory = isMobile && window.history.state
44
+ const isMobileAndNoHistory = isMobile && !window.history.state
45
+
46
+ let RightButtonComponent = CloseButtonComponent
47
+
48
+ if (threadId) {
49
+ RightButtonComponent = MinimiseButtonComponent
50
+
51
+ if (isMobileAndNoHistory) {
52
+ RightButtonComponent = MinimiseButtonComponent
53
+ }
54
+ }
55
+
56
+ if (!threadId && isMobileAndNoHistory) {
57
+ RightButtonComponent = CloseButtonComponent
58
+ }
59
+
60
+ if (isMobileAndHasHistory) {
61
+ RightButtonComponent = null
62
+ }
63
+
64
+ return (
65
+ <div className='wc-header'>
66
+ {isMobileAndHasHistory ? BackButtonComponent : null}
67
+
68
+ <h2 id='wc-header' className='wc-header__title'>
69
+ Floodline webchat
70
+ </h2>
71
+
72
+ {RightButtonComponent}
73
+ </div>
74
+ )
75
+ }