@seamly/web-ui 21.0.8 → 22.0.0-beta.1
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/build/dist/lib/components.js +9295 -7845
- package/build/dist/lib/components.js.map +1 -0
- package/build/dist/lib/components.min.js +2 -1
- package/build/dist/lib/components.min.js.LICENSE.txt +2 -2
- package/build/dist/lib/components.min.js.map +1 -0
- package/build/dist/lib/config.js +2 -1
- package/build/dist/lib/config.js.map +1 -0
- package/build/dist/lib/config.min.js +2 -1
- package/build/dist/lib/config.min.js.map +1 -0
- package/build/dist/lib/contexts.js +2 -1
- package/build/dist/lib/contexts.js.map +1 -0
- package/build/dist/lib/contexts.min.js +2 -1
- package/build/dist/lib/contexts.min.js.map +1 -0
- package/build/dist/lib/deprecated-view.css +1 -1
- package/build/dist/lib/deprecated-view.js +1 -1
- package/build/dist/lib/hooks.js +6839 -5731
- package/build/dist/lib/hooks.js.map +1 -0
- package/build/dist/lib/hooks.min.js +2 -1
- package/build/dist/lib/hooks.min.js.map +1 -0
- package/build/dist/lib/index.debug.js +964 -383
- package/build/dist/lib/index.debug.js.map +1 -0
- package/build/dist/lib/index.debug.min.js +2 -1
- package/build/dist/lib/index.debug.min.js.LICENSE.txt +336 -108
- package/build/dist/lib/index.debug.min.js.map +1 -0
- package/build/dist/lib/index.js +2991 -5659
- package/build/dist/lib/index.js.map +1 -0
- package/build/dist/lib/index.min.js +2 -1
- package/build/dist/lib/index.min.js.LICENSE.txt +2 -2
- package/build/dist/lib/index.min.js.map +1 -0
- package/build/dist/lib/sounds/beep.mp3 +0 -0
- package/build/dist/lib/standalone.js +9454 -12449
- package/build/dist/lib/standalone.js.map +1 -0
- package/build/dist/lib/standalone.min.js +2 -1
- package/build/dist/lib/standalone.min.js.LICENSE.txt +1 -1
- package/build/dist/lib/standalone.min.js.map +1 -0
- package/build/dist/lib/storage.js +2 -1
- package/build/dist/lib/storage.js.map +1 -0
- package/build/dist/lib/storage.min.js +2 -1
- package/build/dist/lib/storage.min.js.map +1 -0
- package/build/dist/lib/style-guide.js +1828 -6015
- package/build/dist/lib/style-guide.js.map +1 -0
- package/build/dist/lib/style-guide.min.js +2 -1
- package/build/dist/lib/style-guide.min.js.LICENSE.txt +2 -2
- package/build/dist/lib/style-guide.min.js.map +1 -0
- package/build/dist/lib/styles-default-implementation.css +1 -1
- package/build/dist/lib/styles-default-implementation.js +1 -1
- package/build/dist/lib/styles.css +1 -1
- package/build/dist/lib/styles.js +1 -1
- package/build/dist/lib/utils.js +11601 -14586
- package/build/dist/lib/utils.js.map +1 -0
- package/build/dist/lib/utils.min.js +2 -1
- package/build/dist/lib/utils.min.js.LICENSE.txt +1 -6
- package/build/dist/lib/utils.min.js.map +1 -0
- package/package.json +58 -48
- package/src/javascripts/api/conversation-connector.ts +2 -0
- package/src/javascripts/api/errors/seamly-api-error.ts +15 -0
- package/src/javascripts/api/index.ts +168 -94
- package/src/javascripts/config.ts +1 -1
- package/src/javascripts/config.types.ts +18 -11
- package/src/javascripts/domains/config/selectors.ts +1 -1
- package/src/javascripts/domains/config/slice.ts +12 -0
- package/src/javascripts/domains/forms/forms.types.ts +1 -0
- package/src/javascripts/domains/forms/hooks.ts +10 -2
- package/src/javascripts/domains/i18n/slice.ts +2 -0
- package/src/javascripts/domains/interrupt/hooks.ts +15 -7
- package/src/javascripts/domains/interrupt/selectors.ts +4 -0
- package/src/javascripts/domains/interrupt/slice.ts +2 -2
- package/src/javascripts/domains/store/selectors.ts +23 -10
- package/src/javascripts/domains/store/slice.ts +63 -11
- package/src/javascripts/domains/store/store.types.ts +39 -1
- package/src/javascripts/domains/translations/components/options-button.tsx +1 -4
- package/src/javascripts/domains/translations/components/translation-status.tsx +4 -3
- package/src/javascripts/domains/translations/hooks.ts +11 -4
- package/src/javascripts/domains/translations/slice.ts +2 -0
- package/src/javascripts/index.ts +2 -0
- package/src/javascripts/lib/url-helpers.ts +24 -0
- package/src/javascripts/schema.ts +10 -0
- package/src/javascripts/style-guide/states.js +65 -0
- package/src/javascripts/ui/components/app-options/index.js +4 -3
- package/src/javascripts/ui/components/conversation/conversation.tsx +2 -0
- package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-provider.tsx +2 -0
- package/src/javascripts/ui/components/conversation/event/choice-prompt.js +1 -1
- package/src/javascripts/ui/components/conversation/event/text.js +1 -1
- package/src/javascripts/ui/components/conversation/event/upload.js +50 -9
- package/src/javascripts/ui/components/conversation/use-chat-scroll.ts +3 -2
- package/src/javascripts/ui/components/core/seamly-event-subscriber.ts +16 -14
- package/src/javascripts/ui/components/core/seamly-file-upload.tsx +156 -0
- package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +5 -5
- package/src/javascripts/ui/components/entry/abort-transaction-button/abort-transaction-button.tsx +45 -0
- package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +4 -3
- package/src/javascripts/ui/components/entry/text-entry/hooks.ts +108 -0
- package/src/javascripts/ui/components/entry/text-entry/index.js +7 -4
- package/src/javascripts/ui/components/entry/text-entry/{text-entry-form.js → text-entry-form.tsx} +8 -22
- package/src/javascripts/ui/components/faq/faq.js +5 -4
- package/src/javascripts/ui/components/form-controls/{input.js → input.tsx} +13 -2
- package/src/javascripts/ui/components/form-controls/{wrapper.js → wrapper.tsx} +8 -4
- package/src/javascripts/ui/components/layout/agent-info.js +4 -3
- package/src/javascripts/ui/components/layout/chat-frame.js +7 -8
- package/src/javascripts/ui/components/layout/deprecated-chat-frame.js +7 -8
- package/src/javascripts/ui/components/layout/interrupt.js +6 -15
- package/src/javascripts/ui/components/layout/pre-chat-messages.js +4 -3
- package/src/javascripts/ui/components/suggestions/index.js +5 -4
- package/src/javascripts/ui/components/translation-chat-status/index.tsx +4 -3
- package/src/javascripts/ui/components/view/app-view.js +1 -2
- package/src/javascripts/ui/components/view/deprecated-view.js +1 -2
- package/src/javascripts/ui/components/view/{index.js → index.tsx} +53 -4
- package/src/javascripts/ui/components/view/inline-view.js +1 -11
- package/src/javascripts/ui/components/view/window-view/{index.js → index.tsx} +15 -11
- package/src/javascripts/ui/components/view/window-view/window-open-button.js +4 -3
- package/src/javascripts/ui/components/widgets/{in-out-transition.js → in-out-transition.tsx} +67 -28
- package/src/javascripts/ui/hooks/sounds/beep.mp3 +0 -0
- package/src/javascripts/ui/hooks/use-click-outside.ts +5 -3
- package/src/javascripts/ui/hooks/use-notifications.ts +114 -0
- package/src/javascripts/ui/hooks/{use-seamly-chat.js → use-seamly-chat.ts} +5 -1
- package/src/javascripts/ui/hooks/use-session-expired-command.ts +17 -0
- package/src/javascripts/ui/hooks/use-timeout.ts +20 -0
- package/src/stylesheets/3-chat/_chat.scss +3 -5
- package/src/stylesheets/4-base/_formelements.scss +0 -36
- package/src/stylesheets/5-components/_abort-transaction.scss +10 -0
- package/src/stylesheets/5-components/_buttons.scss +18 -3
- package/src/stylesheets/5-components/_character-limit.scss +2 -2
- package/src/stylesheets/5-components/_chat-status.scss +26 -37
- package/src/stylesheets/5-components/_choice-prompt.scss +9 -10
- package/src/stylesheets/5-components/_conversation.scss +9 -62
- package/src/stylesheets/5-components/_disclaimer.scss +11 -3
- package/src/stylesheets/5-components/_error.scss +3 -2
- package/src/stylesheets/5-components/_idle.scss +3 -8
- package/src/stylesheets/5-components/_input.scss +34 -13
- package/src/stylesheets/5-components/_interrupt.scss +3 -10
- package/src/stylesheets/5-components/_loader.scss +1 -2
- package/src/stylesheets/5-components/_message-author.scss +2 -4
- package/src/stylesheets/5-components/_message-body.scss +33 -10
- package/src/stylesheets/5-components/_message-card.scss +2 -10
- package/src/stylesheets/5-components/_message-carousel.scss +4 -4
- package/src/stylesheets/5-components/_message-cta.scss +0 -6
- package/src/stylesheets/5-components/_message.scss +1 -0
- package/src/stylesheets/5-components/_modal.scss +2 -5
- package/src/stylesheets/5-components/_options.scss +17 -22
- package/src/stylesheets/5-components/_pre-chat-messages.scss +3 -1
- package/src/stylesheets/5-components/_prompt.scss +3 -7
- package/src/stylesheets/5-components/_skip-link.scss +2 -1
- package/src/stylesheets/5-components/_suggestions.scss +2 -2
- package/src/stylesheets/5-components/_translation-options.scss +5 -2
- package/src/stylesheets/5-components/_unread-messages.scss +33 -0
- package/src/stylesheets/5-components/_upload.scss +20 -27
- package/src/stylesheets/6-default-implementation/_hover.scss +14 -17
- package/src/stylesheets/7-deprecated/1-settings/_config.scss +17 -0
- package/src/stylesheets/7-deprecated/3-app/_app.scss +2 -1
- package/src/stylesheets/7-deprecated/5-components/_card.scss +1 -0
- package/src/stylesheets/7-deprecated/5-components/_chat-status.scss +66 -20
- package/src/stylesheets/7-deprecated/5-components/_conversation.scss +1 -4
- package/src/stylesheets/7-deprecated/5-components/_input.scss +6 -1
- package/src/stylesheets/7-deprecated/5-components/_interrupt.scss +1 -4
- package/src/stylesheets/7-deprecated/5-components/_message.scss +49 -12
- package/src/stylesheets/7-deprecated/5-components/_translation-options.scss +30 -37
- package/src/stylesheets/7-deprecated/5-components/_unread-messages.scss +38 -0
- package/src/stylesheets/deprecated-view.scss +1 -0
- package/src/stylesheets/styles.scss +2 -0
- package/webpack/config.common.js +6 -1
- package/webpack/config.package.js +18 -0
- package/webpack/defaults.js +1 -1
- package/src/javascripts/ui/components/core/seamly-file-upload.js +0 -86
- package/src/javascripts/ui/components/entry/text-entry/hooks.js +0 -46
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'preact/hooks'
|
|
2
|
+
import { useDispatch } from 'react-redux'
|
|
3
|
+
import { maxCharacterSrDebounceDelay, maxCharacterWarningLimit } from 'config'
|
|
4
|
+
import { useLiveRegion } from 'ui/hooks/live-region-hooks'
|
|
5
|
+
import {
|
|
6
|
+
useEntryTextLimit,
|
|
7
|
+
useSeamlyStateContext,
|
|
8
|
+
} from 'ui/hooks/seamly-state-hooks'
|
|
9
|
+
import { debounce } from 'ui/utils/general-utils'
|
|
10
|
+
import { ControlState } from 'domains/forms/forms.types'
|
|
11
|
+
import { useFormControl } from 'domains/forms/hooks'
|
|
12
|
+
import { useI18n } from 'domains/i18n/hooks'
|
|
13
|
+
import { clearAbortTransaction } from 'domains/store/slice'
|
|
14
|
+
|
|
15
|
+
export function useCharacterLimit(controlName) {
|
|
16
|
+
const { t } = useI18n()
|
|
17
|
+
const { sendAssertive } = useLiveRegion()
|
|
18
|
+
const { hasLimit, limit } = useEntryTextLimit()
|
|
19
|
+
|
|
20
|
+
const debouncedSendAssertive = useMemo(
|
|
21
|
+
() => debounce(sendAssertive, maxCharacterSrDebounceDelay),
|
|
22
|
+
[sendAssertive],
|
|
23
|
+
)
|
|
24
|
+
const validateLimit = useMemo(() => {
|
|
25
|
+
return debounce((_reachedCharacterWarning, _remainingChars) => {
|
|
26
|
+
if (_reachedCharacterWarning) {
|
|
27
|
+
debouncedSendAssertive(
|
|
28
|
+
t('input.srCharacterLimitText', { limit: _remainingChars }),
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
}, maxCharacterSrDebounceDelay)
|
|
32
|
+
}, [debouncedSendAssertive, t])
|
|
33
|
+
|
|
34
|
+
const [{ value }] = useFormControl(controlName)
|
|
35
|
+
const remainingChars = hasLimit && value ? limit - value.length : limit
|
|
36
|
+
const reachedCharacterWarning = hasLimit
|
|
37
|
+
? remainingChars <= maxCharacterWarningLimit
|
|
38
|
+
: false
|
|
39
|
+
const reachedCharacterLimit = hasLimit ? remainingChars < 0 : false
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
validateLimit(reachedCharacterWarning, remainingChars)
|
|
43
|
+
}, [reachedCharacterWarning, remainingChars, validateLimit])
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
hasCharacterLimit: hasLimit,
|
|
47
|
+
characterLimit: limit,
|
|
48
|
+
reachedCharacterWarning,
|
|
49
|
+
reachedCharacterLimit,
|
|
50
|
+
remainingChars,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const useEntryTextTranslation = (controlName: ControlState['name']) => {
|
|
55
|
+
const { hasCharacterLimit, characterLimit } = useCharacterLimit(controlName)
|
|
56
|
+
|
|
57
|
+
const {
|
|
58
|
+
entryMeta: {
|
|
59
|
+
optionsOverride: { text },
|
|
60
|
+
},
|
|
61
|
+
} = useSeamlyStateContext()
|
|
62
|
+
|
|
63
|
+
const { t } = useI18n()
|
|
64
|
+
|
|
65
|
+
const placeholder: string = useMemo(
|
|
66
|
+
() =>
|
|
67
|
+
t('input.inputPlaceholder', {
|
|
68
|
+
hasLimit: hasCharacterLimit,
|
|
69
|
+
text: text?.placeholder || t('input.inputPlaceholderText'),
|
|
70
|
+
limit: hasCharacterLimit ? characterLimit : null,
|
|
71
|
+
}),
|
|
72
|
+
[t, hasCharacterLimit, characterLimit, text?.placeholder],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const label: string = useMemo(
|
|
76
|
+
() =>
|
|
77
|
+
t('input.inputLabel', {
|
|
78
|
+
hasLimit: !text?.label ? hasCharacterLimit : false,
|
|
79
|
+
text: text?.label || t('input.inputLabelText'),
|
|
80
|
+
limit: !text?.label && hasCharacterLimit ? characterLimit : null,
|
|
81
|
+
}),
|
|
82
|
+
[t, hasCharacterLimit, characterLimit, text?.label],
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const labelClass: string = useMemo(
|
|
86
|
+
() => (text?.label ? 'input__label' : 'visually-hidden'),
|
|
87
|
+
[text?.label],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return { placeholder, label, labelClass }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const useEntryAbortTransaction = () => {
|
|
94
|
+
const dispatch = useDispatch()
|
|
95
|
+
|
|
96
|
+
const {
|
|
97
|
+
entryMeta: { actions },
|
|
98
|
+
} = useSeamlyStateContext()
|
|
99
|
+
|
|
100
|
+
const clearEntryAbortTransaction = () => {
|
|
101
|
+
dispatch(clearAbortTransaction())
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
abortTransaction: actions?.abortTransaction,
|
|
106
|
+
clearEntryAbortTransaction,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -9,7 +9,7 @@ import { visibilityStates } from 'domains/visibility/constants'
|
|
|
9
9
|
import { useVisibility } from 'domains/visibility/hooks'
|
|
10
10
|
import TextEntryForm from './text-entry-form'
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
export const textEntryControlName = 'textMessageEntry'
|
|
13
13
|
|
|
14
14
|
export default function TextEntry({ ...props }) {
|
|
15
15
|
const { isOpen, setVisibility } = useVisibility()
|
|
@@ -18,8 +18,8 @@ export default function TextEntry({ ...props }) {
|
|
|
18
18
|
const { sendMessage } = useSeamlyCommands()
|
|
19
19
|
const handleSubmit = useCallback(
|
|
20
20
|
(values, { updateControlValue }) => {
|
|
21
|
-
sendMessage({ body: values[
|
|
22
|
-
updateControlValue(
|
|
21
|
+
sendMessage({ body: values[textEntryControlName] })
|
|
22
|
+
updateControlValue(textEntryControlName, '')
|
|
23
23
|
focusSkipLinkTarget()
|
|
24
24
|
|
|
25
25
|
if (!isOpen) {
|
|
@@ -36,7 +36,10 @@ export default function TextEntry({ ...props }) {
|
|
|
36
36
|
persistData={true}
|
|
37
37
|
onSubmit={handleSubmit}
|
|
38
38
|
>
|
|
39
|
-
<TextEntryForm
|
|
39
|
+
<TextEntryForm
|
|
40
|
+
controlName={textEntryControlName}
|
|
41
|
+
skipLinkId={skipLinkId}
|
|
42
|
+
/>
|
|
40
43
|
</FormProvider>
|
|
41
44
|
)
|
|
42
45
|
}
|
package/src/javascripts/ui/components/entry/text-entry/{text-entry-form.js → text-entry-form.tsx}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useLayoutEffect
|
|
1
|
+
import { useCallback, useLayoutEffect } from 'preact/hooks'
|
|
2
2
|
import Form from 'ui/components/form-controls/form'
|
|
3
3
|
import Input from 'ui/components/form-controls/input'
|
|
4
4
|
import Icon from 'ui/components/layout/icon'
|
|
@@ -7,7 +7,7 @@ import { useLiveRegion, useSeamlyCommands } from 'ui/hooks/seamly-hooks'
|
|
|
7
7
|
import { useFormControl } from 'domains/forms/hooks'
|
|
8
8
|
import { useI18n } from 'domains/i18n/hooks'
|
|
9
9
|
import { className } from 'lib/css'
|
|
10
|
-
import { useCharacterLimit } from './hooks'
|
|
10
|
+
import { useCharacterLimit, useEntryTextTranslation } from './hooks'
|
|
11
11
|
|
|
12
12
|
export default function TextEntryForm({ controlName, skipLinkId }) {
|
|
13
13
|
const { t } = useI18n()
|
|
@@ -15,16 +15,17 @@ export default function TextEntryForm({ controlName, skipLinkId }) {
|
|
|
15
15
|
const { emitEvent } = useSeamlyCommands()
|
|
16
16
|
const handleKeyUp = useSeamlyTyping()
|
|
17
17
|
const { setBlockAutoEntrySwitch } = useSeamlyEntry()
|
|
18
|
-
|
|
18
|
+
const { placeholder, label, labelClass } =
|
|
19
|
+
useEntryTextTranslation(controlName)
|
|
19
20
|
// TODO: Standardize the validation on form fields
|
|
20
21
|
const {
|
|
21
22
|
hasCharacterLimit,
|
|
22
|
-
characterLimit,
|
|
23
23
|
reachedCharacterWarning,
|
|
24
24
|
reachedCharacterLimit,
|
|
25
25
|
remainingChars,
|
|
26
26
|
} = useCharacterLimit(controlName)
|
|
27
27
|
const [{ value }] = useFormControl(controlName)
|
|
28
|
+
|
|
28
29
|
const hasValue = !!value
|
|
29
30
|
|
|
30
31
|
const handleFocus = useCallback(() => {
|
|
@@ -34,22 +35,7 @@ export default function TextEntryForm({ controlName, skipLinkId }) {
|
|
|
34
35
|
|
|
35
36
|
emitEvent('ui.inputFocus')
|
|
36
37
|
}, [t, sendAssertive, reachedCharacterWarning, remainingChars, emitEvent])
|
|
37
|
-
|
|
38
|
-
() =>
|
|
39
|
-
t('input.inputPlaceholder', {
|
|
40
|
-
hasLimit: hasCharacterLimit,
|
|
41
|
-
limit: hasCharacterLimit ? characterLimit : null,
|
|
42
|
-
}),
|
|
43
|
-
[t, hasCharacterLimit, characterLimit],
|
|
44
|
-
)
|
|
45
|
-
const labelText = useMemo(
|
|
46
|
-
() =>
|
|
47
|
-
t('input.inputLabel', {
|
|
48
|
-
hasLimit: hasCharacterLimit,
|
|
49
|
-
limit: hasCharacterLimit ? characterLimit : null,
|
|
50
|
-
}),
|
|
51
|
-
[t, hasCharacterLimit, characterLimit],
|
|
52
|
-
)
|
|
38
|
+
|
|
53
39
|
// When the input holds a value, the component should be blocked from switching
|
|
54
40
|
// to file upload form.
|
|
55
41
|
useLayoutEffect(() => {
|
|
@@ -87,8 +73,8 @@ export default function TextEntryForm({ controlName, skipLinkId }) {
|
|
|
87
73
|
className={className('input__text')}
|
|
88
74
|
autocomplete="off"
|
|
89
75
|
placeholder={placeholder}
|
|
90
|
-
labelText={
|
|
91
|
-
labelClass={className(
|
|
76
|
+
labelText={label}
|
|
77
|
+
labelClass={className(labelClass)}
|
|
92
78
|
aria-invalid={hasCharacterLimit ? reachedCharacterLimit : null}
|
|
93
79
|
onKeyUp={handleKeyUp}
|
|
94
80
|
onFocus={handleFocus}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef } from 'preact/hooks'
|
|
2
|
+
import { useSelector } from 'react-redux'
|
|
2
3
|
import Icon from 'ui/components/layout/icon'
|
|
3
4
|
import InOutTransition, {
|
|
4
5
|
transitionStartStates,
|
|
@@ -17,7 +18,7 @@ import { runIfElementContainsOrHasFocus } from 'ui/utils/general-utils'
|
|
|
17
18
|
import { actionTypes } from 'ui/utils/seamly-utils'
|
|
18
19
|
import { useUserHasResponded } from 'domains/app/hooks'
|
|
19
20
|
import { useI18n } from 'domains/i18n/hooks'
|
|
20
|
-
import {
|
|
21
|
+
import { selectHasError } from 'domains/interrupt/selectors'
|
|
21
22
|
import { useTranslatedEventData } from 'domains/translations/hooks'
|
|
22
23
|
import { className } from 'lib/css'
|
|
23
24
|
|
|
@@ -27,7 +28,7 @@ const Faq = () => {
|
|
|
27
28
|
const sectionId = useGeneratedId()
|
|
28
29
|
const focusSkiplinkTarget = useSkiplinkTargetFocusing()
|
|
29
30
|
const { sendPolite } = useLiveRegion()
|
|
30
|
-
const
|
|
31
|
+
const hasError = useSelector(selectHasError)
|
|
31
32
|
const { hasCountdown, endCountdown } = useSeamlyIdleDetachCountdown()
|
|
32
33
|
const { hasPrompt, continueChat } = useSeamlyResumeConversationPrompt()
|
|
33
34
|
|
|
@@ -36,7 +37,7 @@ const Faq = () => {
|
|
|
36
37
|
payload: lastFaqEventPayload,
|
|
37
38
|
})
|
|
38
39
|
const faqs = useMemo(() => {
|
|
39
|
-
const newFaqs = lastFaqEventPayload && !
|
|
40
|
+
const newFaqs = lastFaqEventPayload && !hasError ? eventBody : []
|
|
40
41
|
const itemBaseClass = `faqs__item`
|
|
41
42
|
return newFaqs.map(({ categories = [], ...faqRest }) => ({
|
|
42
43
|
...faqRest,
|
|
@@ -51,7 +52,7 @@ const Faq = () => {
|
|
|
51
52
|
),
|
|
52
53
|
],
|
|
53
54
|
}))
|
|
54
|
-
}, [lastFaqEventPayload,
|
|
55
|
+
}, [lastFaqEventPayload, hasError, eventBody])
|
|
55
56
|
|
|
56
57
|
const prevFaqs = useRef(null)
|
|
57
58
|
const prevHasFaqs = useRef(false)
|
|
@@ -1,16 +1,27 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'preact/compat'
|
|
1
2
|
import { useFormContext, useFormControl } from 'domains/forms/hooks'
|
|
2
3
|
import FormControlWrapper from './wrapper'
|
|
3
4
|
|
|
5
|
+
type InputProps = HTMLAttributes<HTMLInputElement> & {
|
|
6
|
+
id: string
|
|
7
|
+
name: string
|
|
8
|
+
type: string
|
|
9
|
+
labelText: string
|
|
10
|
+
labelClass: string
|
|
11
|
+
contentHint?: string
|
|
12
|
+
'aria-describedby'?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
function Input({
|
|
5
16
|
id,
|
|
6
17
|
name,
|
|
7
18
|
type,
|
|
8
19
|
labelText,
|
|
9
20
|
labelClass,
|
|
10
|
-
contentHint,
|
|
21
|
+
contentHint = null,
|
|
11
22
|
'aria-describedby': ariaDescribedBy,
|
|
12
23
|
...props
|
|
13
|
-
}) {
|
|
24
|
+
}: InputProps) {
|
|
14
25
|
const { isSubmitted } = useFormContext()
|
|
15
26
|
const [field, { error }] = useFormControl(name)
|
|
16
27
|
const hasError = isSubmitted && error
|
|
@@ -12,9 +12,6 @@ const FormControlWrapper = ({
|
|
|
12
12
|
}) => {
|
|
13
13
|
return (
|
|
14
14
|
<>
|
|
15
|
-
<label htmlFor={id} className={labelClass}>
|
|
16
|
-
{labelText}
|
|
17
|
-
</label>
|
|
18
15
|
{contentHint && (
|
|
19
16
|
<span
|
|
20
17
|
id={`${id}-content-hint`}
|
|
@@ -23,8 +20,15 @@ const FormControlWrapper = ({
|
|
|
23
20
|
{contentHint}
|
|
24
21
|
</span>
|
|
25
22
|
)}
|
|
23
|
+
|
|
26
24
|
<Error id={`${id}-error`} error={!validity && errorText} />
|
|
27
|
-
|
|
25
|
+
|
|
26
|
+
<div className={className('form-control__wrapper')}>
|
|
27
|
+
<label htmlFor={id} className={labelClass}>
|
|
28
|
+
{labelText}
|
|
29
|
+
</label>
|
|
30
|
+
{children}
|
|
31
|
+
</div>
|
|
28
32
|
</>
|
|
29
33
|
)
|
|
30
34
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useSelector } from 'react-redux'
|
|
1
2
|
import {
|
|
2
3
|
useSeamlyCurrentAgent,
|
|
3
4
|
useSeamlyHeaderData,
|
|
@@ -5,7 +6,7 @@ import {
|
|
|
5
6
|
} from 'ui/hooks/seamly-hooks'
|
|
6
7
|
import { useStartChatIcon } from 'domains/config/hooks'
|
|
7
8
|
import { useI18n } from 'domains/i18n/hooks'
|
|
8
|
-
import {
|
|
9
|
+
import { selectHasError } from 'domains/interrupt/selectors'
|
|
9
10
|
import { useVisibility } from 'domains/visibility/hooks'
|
|
10
11
|
import { className } from 'lib/css'
|
|
11
12
|
import Icon from './icon'
|
|
@@ -16,10 +17,10 @@ const AgentInfo = () => {
|
|
|
16
17
|
const unreadMessageCount = useSeamlyUnreadCount()
|
|
17
18
|
const { isOpen } = useVisibility()
|
|
18
19
|
const currentAgent = useSeamlyCurrentAgent()
|
|
19
|
-
const
|
|
20
|
+
const hasError = useSelector(selectHasError)
|
|
20
21
|
const startChatIcon = useStartChatIcon()
|
|
21
22
|
const src = currentAgent?.avatar ?? startChatIcon
|
|
22
|
-
const displaySubtitle =
|
|
23
|
+
const displaySubtitle = hasError ? '' : subTitle
|
|
23
24
|
|
|
24
25
|
const classNames = ['message-count']
|
|
25
26
|
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
+
import { useSelector } from 'react-redux'
|
|
1
2
|
import AppOptions from 'ui/components/app-options'
|
|
2
3
|
import ChatScrollProvider from 'ui/components/conversation/event/chat-scroll/chat-scroll-provider'
|
|
3
4
|
import EntryContainer from 'ui/components/entry/entry-container'
|
|
4
5
|
import CollapseButton from 'ui/components/view/window-view/collapse-button'
|
|
5
|
-
import {
|
|
6
|
+
import { selectHasError } from 'domains/interrupt/selectors'
|
|
6
7
|
import TranslationStatus from 'domains/translations/components/translation-status'
|
|
7
8
|
import { useVisibility } from 'domains/visibility/hooks'
|
|
8
9
|
import { className } from 'lib/css'
|
|
10
|
+
import Interrupt from './interrupt'
|
|
9
11
|
|
|
10
|
-
function ChatFrame({ children
|
|
11
|
-
const
|
|
12
|
+
function ChatFrame({ children }) {
|
|
13
|
+
const hasError = useSelector(selectHasError)
|
|
12
14
|
const { isOpen } = useVisibility()
|
|
13
15
|
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
return <InterruptComponent {...meta} />
|
|
17
|
-
}
|
|
18
|
-
return null
|
|
16
|
+
if (hasError) {
|
|
17
|
+
return <Interrupt />
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
return (
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
+
import { useSelector } from 'react-redux'
|
|
1
2
|
import AppOptions from 'ui/components/app-options'
|
|
2
3
|
import ChatScrollProvider from 'ui/components/conversation/event/chat-scroll/chat-scroll-provider'
|
|
3
4
|
import EntryContainer from 'ui/components/entry/entry-container'
|
|
4
|
-
import {
|
|
5
|
+
import { selectHasError } from 'domains/interrupt/selectors'
|
|
5
6
|
import TranslationStatus from 'domains/translations/components/translation-status'
|
|
6
7
|
import { useVisibility } from 'domains/visibility/hooks'
|
|
7
8
|
import { className } from 'lib/css'
|
|
9
|
+
import Interrupt from './interrupt'
|
|
8
10
|
|
|
9
|
-
function ChatFrame({ children
|
|
10
|
-
const { hasInterrupt, meta } = useInterrupt()
|
|
11
|
+
function ChatFrame({ children }) {
|
|
11
12
|
const { isOpen } = useVisibility()
|
|
13
|
+
const hasError = useSelector(selectHasError)
|
|
12
14
|
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
return <InterruptComponent {...meta} />
|
|
16
|
-
}
|
|
17
|
-
return null
|
|
15
|
+
if (hasError) {
|
|
16
|
+
return <Interrupt />
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
return (
|
|
@@ -6,27 +6,18 @@ import {
|
|
|
6
6
|
useSeamlyCommands,
|
|
7
7
|
useSkiplinkTargetFocusing,
|
|
8
8
|
} from 'ui/hooks/seamly-hooks'
|
|
9
|
+
import { useInterrupt } from 'domains/interrupt/hooks'
|
|
9
10
|
import { className } from 'lib/css'
|
|
10
11
|
|
|
11
|
-
const Interrupt = ({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
buttonText,
|
|
16
|
-
action,
|
|
17
|
-
srText,
|
|
18
|
-
}) => {
|
|
12
|
+
const Interrupt = () => {
|
|
13
|
+
const {
|
|
14
|
+
meta: { originalError, title, message, buttonText, action, srText },
|
|
15
|
+
} = useInterrupt()
|
|
19
16
|
const seamlyCommands = useSeamlyCommands()
|
|
20
17
|
const headingId = useGeneratedId()
|
|
21
18
|
const { sendPolite } = useLiveRegion()
|
|
22
19
|
const focusSkiplinkTarget = useSkiplinkTargetFocusing()
|
|
23
|
-
const isExpiredError = originalError
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (isExpiredError && seamlyCommands[action]) {
|
|
27
|
-
seamlyCommands[action]()
|
|
28
|
-
}
|
|
29
|
-
}, [action, seamlyCommands, isExpiredError])
|
|
20
|
+
const isExpiredError = originalError?.name === 'SeamlySessionExpiredError'
|
|
30
21
|
|
|
31
22
|
useEffect(() => {
|
|
32
23
|
if (!isExpiredError && srText) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { useSelector } from 'react-redux'
|
|
1
2
|
import useEventComponentMapping from 'ui/hooks/use-event-component-mapping'
|
|
2
3
|
import { useConfig } from 'domains/config/hooks'
|
|
3
|
-
import {
|
|
4
|
+
import { selectHasError } from 'domains/interrupt/selectors'
|
|
4
5
|
import { useVisibility } from 'domains/visibility/hooks'
|
|
5
6
|
import { className } from 'lib/css'
|
|
6
7
|
|
|
@@ -11,10 +12,10 @@ export function PreChatMessageEvent({ event }) {
|
|
|
11
12
|
|
|
12
13
|
export default function PreChatMessages() {
|
|
13
14
|
const { preChatEvents, layoutMode } = useConfig()
|
|
14
|
-
const
|
|
15
|
+
const hasError = useSelector(selectHasError)
|
|
15
16
|
const { isOpen } = useVisibility()
|
|
16
17
|
|
|
17
|
-
const isVisible = !(
|
|
18
|
+
const isVisible = !(hasError || !preChatEvents?.length || isOpen)
|
|
18
19
|
|
|
19
20
|
return (
|
|
20
21
|
isVisible && (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef } from 'preact/hooks'
|
|
2
|
+
import { useSelector } from 'react-redux'
|
|
2
3
|
import SuggestionsList from 'ui/components/suggestions/suggestions-list'
|
|
3
4
|
import InOutTransition, {
|
|
4
5
|
transitionStartStates,
|
|
@@ -18,7 +19,7 @@ import { actionTypes } from 'ui/utils/seamly-utils'
|
|
|
18
19
|
import { useUserHasResponded } from 'domains/app/hooks'
|
|
19
20
|
import { useConfig } from 'domains/config/hooks'
|
|
20
21
|
import { useI18n } from 'domains/i18n/hooks'
|
|
21
|
-
import {
|
|
22
|
+
import { selectHasError } from 'domains/interrupt/selectors'
|
|
22
23
|
import { useTranslatedEventData } from 'domains/translations/hooks'
|
|
23
24
|
import { visibilityStates } from 'domains/visibility/constants'
|
|
24
25
|
import { useVisibility } from 'domains/visibility/hooks'
|
|
@@ -37,7 +38,7 @@ const Suggestions = ({ isAside = false }) => {
|
|
|
37
38
|
const containerRef = useRef(null)
|
|
38
39
|
const { sendPolite } = useLiveRegion()
|
|
39
40
|
// interrupt & countdown hooks
|
|
40
|
-
const
|
|
41
|
+
const hasError = useSelector(selectHasError)
|
|
41
42
|
const { hasCountdown, endCountdown } = useSeamlyIdleDetachCountdown()
|
|
42
43
|
const { hasPrompt, continueChat } = useSeamlyResumeConversationPrompt()
|
|
43
44
|
// data hooks
|
|
@@ -45,8 +46,8 @@ const Suggestions = ({ isAside = false }) => {
|
|
|
45
46
|
const payload = useSeamlyServiceData('suggestion')
|
|
46
47
|
const { body: eventBody } = useTranslatedEventData({ payload })
|
|
47
48
|
const suggestions = useMemo(
|
|
48
|
-
() => (payload && !
|
|
49
|
-
[payload,
|
|
49
|
+
() => (payload && !hasError ? eventBody : []),
|
|
50
|
+
[payload, hasError, eventBody],
|
|
50
51
|
)
|
|
51
52
|
|
|
52
53
|
const prevSuggestions = useRef(null)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useCallback } from 'preact/hooks'
|
|
2
|
+
import { useSelector } from 'react-redux'
|
|
2
3
|
import ChatStatus from 'ui/components/chat-status'
|
|
3
4
|
import { useSkiplinkTargetFocusing } from 'ui/hooks/focus-helper-hooks'
|
|
4
5
|
import { useI18n } from 'domains/i18n/hooks'
|
|
5
|
-
import {
|
|
6
|
+
import { selectHasError } from 'domains/interrupt/selectors'
|
|
6
7
|
import {
|
|
7
8
|
useLocaleNativeName,
|
|
8
9
|
useTranslations,
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
export default function TranslationChatStatus() {
|
|
13
14
|
const { t } = useI18n()
|
|
14
15
|
const { id } = useTranslationsContainer()
|
|
15
|
-
const
|
|
16
|
+
const hasError = useSelector(selectHasError)
|
|
16
17
|
const { disableTranslations, currentLocale } = useTranslations()
|
|
17
18
|
const localeNativeName = useLocaleNativeName(currentLocale)
|
|
18
19
|
const focusSkiplinkTarget = useSkiplinkTargetFocusing()
|
|
@@ -24,7 +25,7 @@ export default function TranslationChatStatus() {
|
|
|
24
25
|
focusSkiplinkTarget()
|
|
25
26
|
}, [disableTranslations, focusSkiplinkTarget])
|
|
26
27
|
|
|
27
|
-
if (
|
|
28
|
+
if (hasError) {
|
|
28
29
|
return null
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import Conversation from '../conversation/conversation'
|
|
2
2
|
import Chat from '../layout/chat'
|
|
3
3
|
import ChatFrame from '../layout/chat-frame'
|
|
4
|
-
import Interrupt from '../layout/interrupt'
|
|
5
4
|
|
|
6
5
|
const AppView = () => {
|
|
7
6
|
return (
|
|
8
7
|
<Chat>
|
|
9
|
-
<ChatFrame
|
|
8
|
+
<ChatFrame>
|
|
10
9
|
<Conversation />
|
|
11
10
|
</ChatFrame>
|
|
12
11
|
</Chat>
|
|
@@ -5,7 +5,6 @@ import DeprecatedToggleButton from '../entry/deprecated-toggle-button'
|
|
|
5
5
|
import AgentInfo from '../layout/agent-info'
|
|
6
6
|
import DeprecatedAppFrame from '../layout/deprecated-app-frame'
|
|
7
7
|
import Header from '../layout/header'
|
|
8
|
-
import Interrupt from '../layout/interrupt'
|
|
9
8
|
|
|
10
9
|
const ShowInlineView = ({ children }) => {
|
|
11
10
|
const { showInlineView, containerRef } = useShowInlineView()
|
|
@@ -24,7 +23,7 @@ const DeprecatedView = () => {
|
|
|
24
23
|
<Header onCloseChat={closeChat}>
|
|
25
24
|
<AgentInfo />
|
|
26
25
|
</Header>
|
|
27
|
-
<ChatFrame
|
|
26
|
+
<ChatFrame>
|
|
28
27
|
<Conversation />
|
|
29
28
|
</ChatFrame>
|
|
30
29
|
</DeprecatedAppFrame>
|
|
@@ -1,12 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTranslatedEventData } from 'package/hooks'
|
|
2
|
+
import { useCallback, useEffect, useMemo } from 'preact/hooks'
|
|
3
|
+
import { useSelector } from 'react-redux'
|
|
2
4
|
import AppView from 'ui/components/view/app-view'
|
|
3
5
|
import InlineView from 'ui/components/view/inline-view'
|
|
4
6
|
import WindowView from 'ui/components/view/window-view'
|
|
5
7
|
import { useSeamlyAppContainerClassNames } from 'ui/hooks/component-helper-hooks'
|
|
6
8
|
import { useSeamlyContainerElement } from 'ui/hooks/focus-helper-hooks'
|
|
9
|
+
import {
|
|
10
|
+
useSeamlyCurrentAgent,
|
|
11
|
+
useSeamlyServiceInfo,
|
|
12
|
+
useSeamlyUnreadCount,
|
|
13
|
+
} from 'ui/hooks/seamly-state-hooks'
|
|
14
|
+
import useNotification from 'ui/hooks/use-notifications'
|
|
7
15
|
import { useUserHasResponded } from 'domains/app/hooks'
|
|
8
16
|
import { useConfig } from 'domains/config/hooks'
|
|
9
17
|
import { useI18n } from 'domains/i18n/hooks'
|
|
18
|
+
import {
|
|
19
|
+
selectLastUnreadEvent,
|
|
20
|
+
selectShowNotifications,
|
|
21
|
+
} from 'domains/store/selectors'
|
|
10
22
|
import { useVisibility } from 'domains/visibility/hooks'
|
|
11
23
|
import { className } from 'lib/css'
|
|
12
24
|
|
|
@@ -16,17 +28,50 @@ const ViewComponentsMap = {
|
|
|
16
28
|
window: WindowView,
|
|
17
29
|
}
|
|
18
30
|
|
|
31
|
+
function stripHtml(html) {
|
|
32
|
+
const tmp = document.createElement('div')
|
|
33
|
+
tmp.innerHTML = html
|
|
34
|
+
return tmp.textContent || tmp.innerText || ''
|
|
35
|
+
}
|
|
36
|
+
|
|
19
37
|
const View = ({ children }) => {
|
|
38
|
+
const { sendNotification } = useNotification()
|
|
39
|
+
const unreadMessageCount = useSeamlyUnreadCount()
|
|
20
40
|
const [, setSeamlyContainerElement] = useSeamlyContainerElement()
|
|
21
41
|
const { namespace, layoutMode, zIndex } = useConfig()
|
|
42
|
+
const currentAgent = useSeamlyCurrentAgent()
|
|
43
|
+
const { proactiveMessages } = useSeamlyServiceInfo()
|
|
22
44
|
const { isOpen, isVisible } = useVisibility()
|
|
23
45
|
const appContainerClassNames = useSeamlyAppContainerClassNames()
|
|
24
46
|
const userHasResponded = useUserHasResponded()
|
|
47
|
+
const lastUnreadEvent = useSelector(selectLastUnreadEvent)
|
|
48
|
+
const { body } = useTranslatedEventData(lastUnreadEvent)
|
|
49
|
+
|
|
50
|
+
const showNotifications = useSelector(selectShowNotifications)
|
|
25
51
|
const { locale } = useI18n()
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (unreadMessageCount === 0 || !proactiveMessages || !showNotifications)
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
sendNotification(currentAgent?.name, {
|
|
58
|
+
// @ts-ignore
|
|
59
|
+
body: stripHtml(body?.text || body.prompt?.text),
|
|
60
|
+
icon: currentAgent?.avatar,
|
|
61
|
+
})
|
|
62
|
+
}, [
|
|
63
|
+
sendNotification,
|
|
64
|
+
unreadMessageCount,
|
|
65
|
+
body,
|
|
66
|
+
currentAgent,
|
|
67
|
+
proactiveMessages,
|
|
68
|
+
showNotifications,
|
|
69
|
+
])
|
|
26
70
|
const ViewComponent = ViewComponentsMap[layoutMode]
|
|
27
71
|
|
|
28
72
|
const containerElementRef = useCallback(
|
|
29
|
-
(container) => {
|
|
73
|
+
(container: HTMLElement) => {
|
|
74
|
+
if (typeof setSeamlyContainerElement !== 'function') return
|
|
30
75
|
setSeamlyContainerElement(container)
|
|
31
76
|
},
|
|
32
77
|
[setSeamlyContainerElement],
|
|
@@ -54,7 +99,11 @@ const View = ({ children }) => {
|
|
|
54
99
|
`namespace--${namespace}`,
|
|
55
100
|
]
|
|
56
101
|
|
|
57
|
-
const classNames = ['app', ...defaultClassNames
|
|
102
|
+
const classNames = ['app', ...defaultClassNames]
|
|
103
|
+
|
|
104
|
+
if (typeof appContainerClassNames !== 'function') {
|
|
105
|
+
classNames.push(...appContainerClassNames)
|
|
106
|
+
}
|
|
58
107
|
|
|
59
108
|
if (!isOpen && layoutMode !== 'app') {
|
|
60
109
|
classNames.push('app--collapsed')
|
|
@@ -69,7 +118,7 @@ const View = ({ children }) => {
|
|
|
69
118
|
<div
|
|
70
119
|
className={className(classNames)}
|
|
71
120
|
lang={blockLang}
|
|
72
|
-
tabIndex=
|
|
121
|
+
tabIndex={-1}
|
|
73
122
|
data-nosnippet
|
|
74
123
|
style={{ zIndex }}
|
|
75
124
|
ref={containerElementRef}
|