@seamly/web-ui 20.7.0 → 20.8.0-alpha.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.
Files changed (222) hide show
  1. package/CHANGELOG.md +212 -16
  2. package/build/dist/lib/hooks.js +1 -1
  3. package/build/dist/lib/hooks.min.js +1 -1
  4. package/build/dist/lib/index.debug.js +939 -784
  5. package/build/dist/lib/index.debug.min.js +1 -1
  6. package/build/dist/lib/index.debug.min.js.LICENSE.txt +186 -130
  7. package/build/dist/lib/index.js +24806 -19703
  8. package/build/dist/lib/index.min.js +1 -1
  9. package/build/dist/lib/index.min.js.LICENSE.txt +38 -4
  10. package/build/dist/lib/standalone.js +32726 -26838
  11. package/build/dist/lib/standalone.min.js +1 -1
  12. package/build/dist/lib/standalone.min.js.LICENSE.txt +39 -0
  13. package/build/dist/lib/storage.js +2 -2
  14. package/build/dist/lib/storage.min.js +1 -1
  15. package/build/dist/lib/style-guide.js +8649 -7863
  16. package/build/dist/lib/style-guide.min.js +2 -1
  17. package/build/dist/lib/style-guide.min.js.LICENSE.txt +38 -0
  18. package/build/dist/lib/utils.js +1 -2
  19. package/build/dist/lib/utils.min.js +1 -1
  20. package/package.json +19 -9
  21. package/src/icons/avatar_agent-32.svg +7 -0
  22. package/src/icons/avatar_bot-32.svg +6 -1
  23. package/src/javascripts/api/index.js +1 -1
  24. package/src/javascripts/{config.js → config.ts} +3 -1
  25. package/src/javascripts/config.types.ts +95 -0
  26. package/src/javascripts/domains/app/actions.ts +83 -0
  27. package/src/javascripts/domains/app/app.types.ts +3 -0
  28. package/src/javascripts/domains/app/hooks.js +3 -5
  29. package/src/javascripts/domains/app/selectors.ts +6 -0
  30. package/src/javascripts/domains/app/slice.ts +30 -0
  31. package/src/javascripts/domains/config/actions.ts +45 -0
  32. package/src/javascripts/domains/config/hooks.ts +19 -0
  33. package/src/javascripts/domains/config/selectors.ts +24 -0
  34. package/src/javascripts/domains/config/slice.ts +111 -0
  35. package/src/javascripts/domains/errors/index.js +13 -9
  36. package/src/javascripts/domains/forms/context.ts +14 -0
  37. package/src/javascripts/domains/forms/forms.types.ts +24 -0
  38. package/src/javascripts/domains/forms/{hooks.js → hooks.ts} +23 -26
  39. package/src/javascripts/domains/forms/{provider.js → provider.tsx} +20 -14
  40. package/src/javascripts/domains/forms/{selectors.js → selectors.ts} +7 -8
  41. package/src/javascripts/domains/forms/slice.ts +84 -0
  42. package/src/javascripts/domains/forms/utils.ts +15 -0
  43. package/src/javascripts/domains/i18n/actions.ts +24 -0
  44. package/src/javascripts/domains/i18n/{hooks.js → hooks.ts} +2 -2
  45. package/src/javascripts/domains/i18n/i18n.types.ts +6 -0
  46. package/src/javascripts/domains/i18n/selectors.ts +16 -0
  47. package/src/javascripts/domains/i18n/{reducer.js → slice.ts} +43 -37
  48. package/src/javascripts/domains/interrupt/{hooks.js → hooks.ts} +2 -2
  49. package/src/javascripts/domains/interrupt/{middleware.js → middleware.ts} +11 -8
  50. package/src/javascripts/domains/interrupt/selectors.ts +6 -0
  51. package/src/javascripts/domains/interrupt/slice.ts +40 -0
  52. package/src/javascripts/domains/options/middleware.js +9 -6
  53. package/src/javascripts/domains/redux/redux.types.ts +11 -0
  54. package/src/javascripts/domains/store/index.ts +53 -0
  55. package/src/javascripts/domains/store/slice.ts +639 -0
  56. package/src/javascripts/domains/store/store.types.ts +135 -0
  57. package/src/javascripts/domains/translations/components/chat-status.js +2 -2
  58. package/src/javascripts/domains/translations/components/options-button.js +1 -1
  59. package/src/javascripts/domains/translations/components/options-dialog/form.js +5 -5
  60. package/src/javascripts/domains/translations/components/options-dialog/index.js +2 -2
  61. package/src/javascripts/domains/translations/{hooks.js → hooks.ts} +28 -23
  62. package/src/javascripts/domains/translations/middleware.js +29 -27
  63. package/src/javascripts/domains/translations/selectors.js +4 -9
  64. package/src/javascripts/domains/translations/slice.ts +67 -0
  65. package/src/javascripts/domains/translations/translations.types.ts +12 -0
  66. package/src/javascripts/domains/visibility/{actions.js → actions.ts} +25 -19
  67. package/src/javascripts/domains/visibility/{hooks.js → hooks.ts} +13 -10
  68. package/src/javascripts/domains/visibility/{selectors.js → selectors.ts} +3 -6
  69. package/src/javascripts/domains/visibility/slice.ts +38 -0
  70. package/src/javascripts/domains/visibility/utils.js +0 -9
  71. package/src/javascripts/domains/visibility/visibility.types.ts +6 -0
  72. package/src/javascripts/index.ts +92 -0
  73. package/src/javascripts/lib/engine/index.js +15 -11
  74. package/src/javascripts/lib/external-api/initialize-api.js +1 -1
  75. package/src/javascripts/lib/id.js +5 -8
  76. package/src/javascripts/lib/mutex.js +3 -1
  77. package/src/javascripts/lib/store/providers/cookie-storage.js +1 -1
  78. package/src/javascripts/lib/store/providers/session-storage.js +1 -1
  79. package/src/javascripts/package/hooks.js +2 -2
  80. package/src/javascripts/package/utils.js +0 -1
  81. package/src/javascripts/schema.ts +1455 -0
  82. package/src/javascripts/style-guide/components/app.js +4 -4
  83. package/src/javascripts/style-guide/components/static-core.js +87 -65
  84. package/src/javascripts/style-guide/components/view.js +4 -4
  85. package/src/javascripts/style-guide/state-helpers/index.js +5 -5
  86. package/src/javascripts/style-guide/states.js +6 -4
  87. package/src/javascripts/style-guide.ts +5 -0
  88. package/src/javascripts/ui/components/app-options/index.js +2 -4
  89. package/src/javascripts/ui/components/conversation/component-filter.js +1 -1
  90. package/src/javascripts/ui/components/conversation/conversation.js +5 -5
  91. package/src/javascripts/ui/components/conversation/event/card-message.js +1 -1
  92. package/src/javascripts/ui/components/conversation/event/carousel-component/components/controls.js +1 -1
  93. package/src/javascripts/ui/components/conversation/event/carousel-message/components/slide.js +1 -1
  94. package/src/javascripts/ui/components/conversation/event/carousel-message/index.js +1 -1
  95. package/src/javascripts/ui/components/conversation/event/choice-prompt.js +2 -2
  96. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +11 -14
  97. package/src/javascripts/ui/components/conversation/event/cta.js +1 -1
  98. package/src/javascripts/ui/components/conversation/event/divider/variants/default.js +1 -1
  99. package/src/javascripts/ui/components/conversation/event/divider/variants/new-translation.js +5 -2
  100. package/src/javascripts/ui/components/conversation/event/event-participant.js +2 -2
  101. package/src/javascripts/ui/components/conversation/event/hooks/use-formatted-date.js +2 -2
  102. package/src/javascripts/ui/components/conversation/event/image-lightbox.js +1 -1
  103. package/src/javascripts/ui/components/conversation/event/image.js +5 -7
  104. package/src/javascripts/ui/components/conversation/event/participant.js +1 -1
  105. package/src/javascripts/ui/components/conversation/event/splash.js +3 -3
  106. package/src/javascripts/ui/components/conversation/event/text.js +1 -1
  107. package/src/javascripts/ui/components/conversation/event/translation.js +2 -2
  108. package/src/javascripts/ui/components/conversation/event/upload.js +2 -2
  109. package/src/javascripts/ui/components/conversation/event/video.js +1 -1
  110. package/src/javascripts/ui/components/conversation/message-container.js +4 -4
  111. package/src/javascripts/ui/components/core/seamly-api-context.js +1 -1
  112. package/src/javascripts/ui/components/core/seamly-core.js +15 -14
  113. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +96 -91
  114. package/src/javascripts/ui/components/core/seamly-file-upload.js +20 -24
  115. package/src/javascripts/ui/components/core/seamly-initializer.js +1 -1
  116. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +5 -4
  117. package/src/javascripts/ui/components/core/seamly-new-notifications.js +2 -2
  118. package/src/javascripts/ui/components/core/seamly-read-state.js +10 -17
  119. package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +3 -3
  120. package/src/javascripts/ui/components/entry/entry-container.js +4 -6
  121. package/src/javascripts/ui/components/entry/text-entry/hooks.js +3 -3
  122. package/src/javascripts/ui/components/entry/text-entry/index.js +3 -2
  123. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +6 -10
  124. package/src/javascripts/ui/components/entry/upload/file-upload-form.js +2 -2
  125. package/src/javascripts/ui/components/entry/upload/index.js +10 -9
  126. package/src/javascripts/ui/components/entry/upload-toggle.js +2 -2
  127. package/src/javascripts/ui/components/faq/faq.js +6 -6
  128. package/src/javascripts/ui/components/form-controls/file-input.js +1 -1
  129. package/src/javascripts/ui/components/form-controls/form.js +1 -1
  130. package/src/javascripts/ui/components/form-controls/input.js +1 -1
  131. package/src/javascripts/ui/components/form-controls/select.js +1 -1
  132. package/src/javascripts/ui/components/layout/agent-info.js +4 -4
  133. package/src/javascripts/ui/components/layout/chat-frame.js +3 -3
  134. package/src/javascripts/ui/components/layout/chat.js +11 -12
  135. package/src/javascripts/ui/components/layout/deprecated-app-frame.js +10 -9
  136. package/src/javascripts/ui/components/layout/header.js +1 -1
  137. package/src/javascripts/ui/components/layout/interrupt.js +23 -24
  138. package/src/javascripts/ui/components/layout/pre-chat-messages.js +11 -11
  139. package/src/javascripts/ui/components/layout/privacy-disclaimer.js +2 -2
  140. package/src/javascripts/ui/components/options/options-button.js +12 -10
  141. package/src/javascripts/ui/components/options/transcript/index.js +2 -2
  142. package/src/javascripts/ui/components/options/transcript/transcript-form.js +1 -1
  143. package/src/javascripts/ui/components/suggestions/index.js +9 -8
  144. package/src/javascripts/ui/components/view/deprecated-view.js +19 -16
  145. package/src/javascripts/ui/components/view/index.js +12 -12
  146. package/src/javascripts/ui/components/view/inline-view.js +2 -2
  147. package/src/javascripts/ui/components/view/window-view/collapse-button.js +3 -3
  148. package/src/javascripts/ui/components/view/window-view/index.js +13 -13
  149. package/src/javascripts/ui/components/view/window-view/window-open-button.js +13 -13
  150. package/src/javascripts/ui/components/warnings/idle-detach-warning.js +1 -1
  151. package/src/javascripts/ui/components/warnings/resume-conversation-prompt.js +1 -1
  152. package/src/javascripts/ui/components/widgets/lightbox.js +2 -2
  153. package/src/javascripts/ui/components/widgets/upload-progress.js +1 -1
  154. package/src/javascripts/ui/hooks/component-helper-hooks.js +1 -1
  155. package/src/javascripts/ui/hooks/file-upload-hooks.js +4 -6
  156. package/src/javascripts/ui/hooks/focus-helper-hooks.js +14 -12
  157. package/src/javascripts/ui/hooks/live-region-hooks.js +2 -0
  158. package/src/javascripts/ui/hooks/seamly-api-hooks.js +8 -3
  159. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +28 -25
  160. package/src/javascripts/ui/hooks/seamly-hooks.js +25 -25
  161. package/src/javascripts/ui/hooks/seamly-option-hooks.js +17 -19
  162. package/src/javascripts/ui/hooks/seamly-state-hooks.js +14 -13
  163. package/src/javascripts/ui/hooks/use-seamly-chat.js +15 -25
  164. package/src/javascripts/ui/hooks/use-seamly-commands.js +46 -46
  165. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +22 -24
  166. package/src/javascripts/ui/hooks/use-seamly-resume-conversation-prompt.js +8 -9
  167. package/src/javascripts/ui/hooks/use-single-file-upload.js +4 -6
  168. package/src/javascripts/ui/hooks/utility-hooks.js +4 -4
  169. package/src/javascripts/ui/utils/form-utils.js +0 -145
  170. package/src/javascripts/ui/utils/general-utils.js +3 -4
  171. package/src/javascripts/ui/utils/seamly-utils.ts +73 -0
  172. package/webpack/config.common.js +16 -0
  173. package/webpack/config.dev.js +1 -0
  174. package/webpack/config.package.js +26 -5
  175. package/webpack/defaults.js +7 -2
  176. package/webpack/parts/babel-loader-browser-plugins.js +1 -0
  177. package/webpack/parts/dev-server.js +4 -3
  178. package/src/javascripts/domains/app/actions.js +0 -112
  179. package/src/javascripts/domains/app/index.js +0 -7
  180. package/src/javascripts/domains/app/reducer.js +0 -16
  181. package/src/javascripts/domains/app/selectors.js +0 -8
  182. package/src/javascripts/domains/app/utils.js +0 -4
  183. package/src/javascripts/domains/config/actions.js +0 -7
  184. package/src/javascripts/domains/config/hooks.js +0 -23
  185. package/src/javascripts/domains/config/index.js +0 -7
  186. package/src/javascripts/domains/config/reducer.js +0 -79
  187. package/src/javascripts/domains/config/selectors.js +0 -23
  188. package/src/javascripts/domains/config/utils.js +0 -4
  189. package/src/javascripts/domains/forms/actions.js +0 -21
  190. package/src/javascripts/domains/forms/context.js +0 -6
  191. package/src/javascripts/domains/forms/index.js +0 -8
  192. package/src/javascripts/domains/forms/reducer.js +0 -84
  193. package/src/javascripts/domains/forms/utils.js +0 -20
  194. package/src/javascripts/domains/i18n/actions.js +0 -20
  195. package/src/javascripts/domains/i18n/index.js +0 -7
  196. package/src/javascripts/domains/i18n/selectors.js +0 -15
  197. package/src/javascripts/domains/i18n/utils.js +0 -4
  198. package/src/javascripts/domains/interrupt/actions.js +0 -4
  199. package/src/javascripts/domains/interrupt/index.js +0 -9
  200. package/src/javascripts/domains/interrupt/reducer.js +0 -22
  201. package/src/javascripts/domains/interrupt/selectors.js +0 -6
  202. package/src/javascripts/domains/interrupt/utils.js +0 -4
  203. package/src/javascripts/domains/options/index.js +0 -1
  204. package/src/javascripts/domains/redux/context.js +0 -6
  205. package/src/javascripts/domains/redux/create-redux-store.js +0 -21
  206. package/src/javascripts/domains/redux/hooks.js +0 -80
  207. package/src/javascripts/domains/redux/index.js +0 -19
  208. package/src/javascripts/domains/redux/provider.js +0 -5
  209. package/src/javascripts/domains/redux/utils.js +0 -12
  210. package/src/javascripts/domains/store/index.js +0 -46
  211. package/src/javascripts/domains/store/state-reducer.js +0 -56
  212. package/src/javascripts/domains/translations/actions.js +0 -11
  213. package/src/javascripts/domains/translations/index.js +0 -10
  214. package/src/javascripts/domains/translations/reducer.js +0 -69
  215. package/src/javascripts/domains/translations/utils.js +0 -4
  216. package/src/javascripts/domains/visibility/index.js +0 -8
  217. package/src/javascripts/domains/visibility/reducer.js +0 -24
  218. package/src/javascripts/index.js +0 -153
  219. package/src/javascripts/lib/redux-helpers/index.js +0 -99
  220. package/src/javascripts/style-guide.js +0 -5
  221. package/src/javascripts/ui/hooks/use-seamly-dispatch.js +0 -3
  222. package/src/javascripts/ui/utils/seamly-utils.js +0 -832
@@ -0,0 +1,53 @@
1
+ import { configureStore } from '@reduxjs/toolkit'
2
+ import appReducer from 'domains/app/slice'
3
+ import configReducer from 'domains/config/slice'
4
+ import { createErrorsMiddleware } from 'domains/errors'
5
+ import formsReducer from 'domains/forms/slice'
6
+ import i18nReducer from 'domains/i18n/slice'
7
+ import createInterruptMiddleware from 'domains/interrupt/middleware'
8
+ import interruptReducer from 'domains/interrupt/slice'
9
+ import createOptionsMiddleware from 'domains/options/middleware'
10
+ import stateReducer from 'domains/store/slice'
11
+ import createI18nMiddleware from 'domains/translations/middleware'
12
+ import translationReducer from 'domains/translations/slice'
13
+ import visibilityReducer from 'domains/visibility/slice'
14
+ import { useDispatch } from 'react-redux'
15
+
16
+ export function createStore({ initialState, api, eventBus, config }) {
17
+ const store = configureStore({
18
+ reducer: {
19
+ state: stateReducer,
20
+ app: appReducer,
21
+ config: configReducer,
22
+ i18n: i18nReducer,
23
+ translations: translationReducer,
24
+ visibility: visibilityReducer,
25
+ forms: formsReducer,
26
+ interrupt: interruptReducer,
27
+ },
28
+ preloadedState: initialState,
29
+ middleware: (getDefaultMiddleWare) =>
30
+ getDefaultMiddleWare({
31
+ thunk: {
32
+ extraArgument: {
33
+ api,
34
+ eventBus,
35
+ config,
36
+ },
37
+ },
38
+ serializableCheck: false,
39
+ }).concat([
40
+ createErrorsMiddleware({ api }),
41
+ createInterruptMiddleware({ api }),
42
+ createOptionsMiddleware({ api }),
43
+ createI18nMiddleware,
44
+ ]),
45
+ })
46
+
47
+ return store
48
+ }
49
+ export type Store = ReturnType<typeof createStore>
50
+ export type RootState = ReturnType<Store['getState']>
51
+ export type AppDispatch = Store['dispatch']
52
+
53
+ export const useAppDispatch: () => AppDispatch = useDispatch
@@ -0,0 +1,639 @@
1
+ import { createSlice, PayloadAction } from '@reduxjs/toolkit'
2
+ import { initializeApp, resetApp } from 'domains/app/actions'
3
+ import { initializeConfig } from 'domains/config/actions'
4
+ import {
5
+ ChannelEvent,
6
+ EntryMeta,
7
+ HistoryEvent,
8
+ MessageParticipant,
9
+ ServiceInfo,
10
+ StoreState,
11
+ } from 'domains/store/store.types'
12
+ import { randomId } from 'lib/id'
13
+ import { getTimeFromSeconds } from 'ui/utils/general-utils'
14
+ import {
15
+ entryTypes,
16
+ eventTypes,
17
+ featureKeys,
18
+ payloadTypes,
19
+ readStates,
20
+ } from 'ui/utils/seamly-utils'
21
+
22
+ export const isUnreadMessage = ({ type, payload }: ChannelEvent) =>
23
+ (type === eventTypes.message && !payload.fromClient) ||
24
+ (type === eventTypes.info && payload.type === payloadTypes.text)
25
+
26
+ const orderHistory = (events): ChannelEvent[] => {
27
+ return events.sort(
28
+ (
29
+ { payload: { occurredAt: occurredAtA } },
30
+ { payload: { occurredAt: occurredAtB } },
31
+ ) => occurredAtA - occurredAtB,
32
+ )
33
+ }
34
+
35
+ export const mergeHistory = (
36
+ stateEvents: ChannelEvent[],
37
+ historyEvents: HistoryEvent[],
38
+ ) => {
39
+ const newStateEvents = stateEvents.filter(
40
+ (stateEvent) =>
41
+ // Deduplicate the event streams, giving events in historyEvents
42
+ // precedence so the server is able to push changes to events.
43
+ !historyEvents.some(
44
+ (historyEvent) => historyEvent.payload.id === stateEvent.payload.id,
45
+ ),
46
+ )
47
+
48
+ const newHistoryEvents = historyEvents
49
+ .filter(
50
+ (historyEvent) =>
51
+ // Remove all non displayable participant messages
52
+ !(
53
+ historyEvent.type === 'participant' &&
54
+ !historyEvent.payload.participant.introduction
55
+ ),
56
+ )
57
+ // Reverse is done here because the server sends the history in the order
58
+ // newest to oldest. In the case of exactly the same occurredAt timestamps
59
+ // these messages will be shown in the wrong order if not reversed. For
60
+ // the normal merging logic there is no added effect.
61
+ .reverse()
62
+
63
+ return orderHistory([...newHistoryEvents, ...newStateEvents])
64
+ }
65
+
66
+ const participantReducer = (participantInfo, action) => {
67
+ // TODO: a) Styleguide only! b) Should be removed after styleguide overhaul.
68
+ if (!participantInfo) {
69
+ return {
70
+ participants: {},
71
+ currentAgent: '',
72
+ }
73
+ }
74
+
75
+ const { participants } = participantInfo || { participants: {} }
76
+ const { id, avatar, name, introduction } = action.participant
77
+ const oldParticipant = participants[id]
78
+
79
+ const newParticipants = {
80
+ ...participants,
81
+ [id]: oldParticipant
82
+ ? {
83
+ ...oldParticipant,
84
+ ...(avatar ? { avatar } : {}),
85
+ ...(name ? { name } : {}),
86
+ ...(introduction ? { introduction } : {}),
87
+ }
88
+ : action.participant,
89
+ }
90
+
91
+ return {
92
+ ...participantInfo,
93
+ participants: newParticipants,
94
+ currentAgent:
95
+ participantInfo.currentAgent !== id && !action.fromClient
96
+ ? id
97
+ : participantInfo.currentAgent,
98
+ }
99
+ }
100
+
101
+ const calculateNewEntryMeta = (
102
+ entryMeta: EntryMeta,
103
+ channelEvent: ChannelEvent,
104
+ ) => {
105
+ const entry =
106
+ channelEvent.type === 'message' ? channelEvent?.payload.entry : {}
107
+
108
+ const { blockAutoEntrySwitch } = entryMeta
109
+
110
+ if (!entry) {
111
+ return { ...entryMeta, optionsOverride: {} }
112
+ }
113
+
114
+ const { type, options } = entry
115
+
116
+ let newActive = entryMeta.active
117
+
118
+ if (!blockAutoEntrySwitch && type !== entryMeta.userSelected) {
119
+ newActive = type
120
+ }
121
+
122
+ return {
123
+ ...entryMeta,
124
+ active: newActive,
125
+ optionsOverride: {
126
+ ...entryMeta.optionsOverride,
127
+ [type]: options || {},
128
+ },
129
+ }
130
+ }
131
+
132
+ export const initialStoreState: StoreState = {
133
+ events: [],
134
+ initialState: {
135
+ userResponded: false,
136
+ },
137
+ unreadEvents: 0,
138
+ userHasResponded: false,
139
+ loadedImageEventIds: [],
140
+ isLoading: false,
141
+ idleDetachCountdown: {
142
+ hasCountdown: false,
143
+ isActive: false,
144
+ remaining: undefined,
145
+ wasStopped: undefined,
146
+ count: undefined,
147
+ timer: undefined,
148
+ },
149
+ resumeConversationPrompt: false,
150
+ serviceInfo: {
151
+ activeServiceSessionId: '',
152
+ },
153
+ participantInfo: {
154
+ participants: {},
155
+ currentAgent: '',
156
+ },
157
+ headerTitles: {
158
+ title: null,
159
+ subTitle: '',
160
+ },
161
+ historyLoaded: false,
162
+ skiplinkTargetId: randomId(),
163
+ optionsButtonId: randomId(),
164
+ headerCollapseButtonId: randomId(),
165
+ serviceData: {},
166
+ options: {
167
+ features: {},
168
+ panelActive: false,
169
+ optionActive: '',
170
+ userSelectedOptions: {},
171
+ },
172
+ showFileUpload: false,
173
+ currentUploads: [],
174
+ entryMeta: {
175
+ default: entryTypes.text,
176
+ active: entryTypes.text,
177
+ userSelected: null,
178
+ blockAutoEntrySwitch: false,
179
+ options: {},
180
+ optionsOverride: {},
181
+ },
182
+ seamlyContainerElement: null,
183
+ }
184
+
185
+ export const storeSlice = createSlice({
186
+ name: 'store',
187
+ initialState: initialStoreState,
188
+ reducers: {
189
+ addEvent: (state, action: PayloadAction<ChannelEvent>) => {
190
+ const { type: eventType, payload } = action.payload
191
+
192
+ const accountHasUploads = state.options.features.hasOwnProperty(
193
+ featureKeys.uploads,
194
+ )
195
+
196
+ let newOptions = { ...state.options }
197
+
198
+ // This enabled override of the service enabled value for uploads.
199
+ // If a message is sent with entry of type upload it will temporarily
200
+ // override service value and enable uploads.
201
+ if (
202
+ accountHasUploads &&
203
+ (eventType === eventTypes.message ||
204
+ eventType === eventTypes.participant) &&
205
+ !payload.fromClient
206
+ ) {
207
+ const entryType = eventType === 'message' ? payload.entry?.type : {}
208
+ newOptions = {
209
+ ...newOptions,
210
+ features: {
211
+ ...newOptions.features,
212
+ uploads: {
213
+ ...newOptions.features?.uploads,
214
+ enabledFromEntry: entryType === entryTypes.upload,
215
+ },
216
+ },
217
+ }
218
+ }
219
+
220
+ const incrementUnread = isUnreadMessage(action.payload)
221
+
222
+ // We check for duplicated and ignore them as in some error of the websocket
223
+ // a duplicate join can be active for a while until the server connection
224
+ // times out.
225
+ const eventExists = state.events.find((e) => e.payload.id === payload.id)
226
+ if (eventExists) {
227
+ return
228
+ }
229
+
230
+ const newEntryMeta = calculateNewEntryMeta(
231
+ state.entryMeta,
232
+ action.payload,
233
+ )
234
+
235
+ state.entryMeta =
236
+ !accountHasUploads && newEntryMeta.active === entryTypes.upload
237
+ ? { ...state.entryMeta }
238
+ : newEntryMeta
239
+
240
+ state.options = newOptions
241
+
242
+ if (incrementUnread) {
243
+ state.unreadEvents += 1
244
+ action.payload.payload.messageStatus = payload.fromClient
245
+ ? readStates.read
246
+ : readStates.received
247
+ }
248
+
249
+ action.payload.payload.key = randomId()
250
+ state.events.push(action.payload)
251
+ },
252
+ ackEvent: (state, { payload }) => {
253
+ // If any ACKs are sent without transactionID the conversation crashes.
254
+ // Ensure that this edge case is handled gracefully.
255
+ if (!payload.transactionId) {
256
+ console.warn('ACK received without transaction ID.')
257
+ return
258
+ }
259
+
260
+ const matchedEvent = state.events.find(
261
+ (m) => m.payload.transactionId === payload.transactionId,
262
+ )
263
+
264
+ const { id, occurredAt } = payload
265
+ if (matchedEvent) {
266
+ state.events = orderHistory(
267
+ state.events.map((m) =>
268
+ m.payload.id === matchedEvent.payload.id
269
+ ? {
270
+ ...m,
271
+ payload: { ...m.payload, id, occurredAt },
272
+ }
273
+ : m,
274
+ ),
275
+ )
276
+ }
277
+ },
278
+ clearEvents: (state) => {
279
+ state.unreadEvents = 0
280
+ state.loadedImageEventIds = []
281
+ state.events = []
282
+ },
283
+ setEventsRead: (state, { payload }) => {
284
+ state.unreadEvents = 0
285
+ state.events.forEach((event) => {
286
+ if (payload.indexOf(event.payload.id) !== -1) {
287
+ event.payload = {
288
+ ...event.payload,
289
+ ...(event.payload.messageStatus === readStates.received && {
290
+ messageStatus: readStates.read,
291
+ }),
292
+ }
293
+ }
294
+ return event
295
+ })
296
+ },
297
+ setLoadedImageEventIds: (state, { payload }) => {
298
+ state.loadedImageEventIds.push(payload)
299
+ },
300
+ setHistory: (
301
+ state,
302
+ {
303
+ payload: {
304
+ events: history,
305
+ participants,
306
+ activeServiceSessionId,
307
+ activeServiceSettings = {},
308
+ serviceData,
309
+ resumeConversationPrompt,
310
+ },
311
+ },
312
+ ) => {
313
+ const events = mergeHistory(state.events, history)
314
+
315
+ const mergedParticipants = {
316
+ ...state.participantInfo.participants,
317
+ ...participants,
318
+ }
319
+
320
+ const lastParticipantEvent = events
321
+ .slice()
322
+ .reverse()
323
+ .find(
324
+ (m) =>
325
+ (m.type === 'message' || m.type === 'participant') &&
326
+ !m.payload.fromClient,
327
+ )
328
+
329
+ let lastParticipantId = null
330
+
331
+ if (lastParticipantEvent) {
332
+ if (lastParticipantEvent.type === 'message') {
333
+ lastParticipantId = lastParticipantEvent.payload.participant
334
+ }
335
+ if (lastParticipantEvent.type === 'participant') {
336
+ lastParticipantId = lastParticipantEvent.payload.participant.id
337
+ }
338
+ }
339
+
340
+ const { entry, uploads } = activeServiceSettings
341
+
342
+ const historyNewEntryMeta = calculateNewEntryMeta(
343
+ {
344
+ ...state.entryMeta,
345
+ ...entry,
346
+ active: entry.default || payloadTypes.text,
347
+ options: { ...(entry && entry.options ? entry.options : {}) },
348
+ },
349
+ events[events.length - 1],
350
+ )
351
+
352
+ let newFeatures = { ...state.options.features }
353
+
354
+ const newFeaturesHasUpload = newFeatures.hasOwnProperty(
355
+ featureKeys.uploads,
356
+ )
357
+
358
+ // Only set uploads if it was initialised by the account config.
359
+ if (newFeaturesHasUpload && lastParticipantEvent.type === 'message') {
360
+ const { payload: lastParticipantEventPayload } = lastParticipantEvent
361
+ const entryType = lastParticipantEventPayload?.entry?.type || {}
362
+ newFeatures = {
363
+ ...newFeatures,
364
+ uploads: {
365
+ enabled: !!(uploads && uploads.enabled),
366
+ enabledFromEntry: entryType === entryTypes.upload,
367
+ },
368
+ }
369
+ }
370
+
371
+ state.unreadEvents = events.filter(
372
+ (event) =>
373
+ event.type === 'message' &&
374
+ event.payload.messageStatus === readStates.received,
375
+ ).length
376
+ state.events = events.filter(
377
+ (e) => e.type !== 'participant' || !!e.payload.participant.introduction,
378
+ )
379
+ state.participantInfo = {
380
+ ...state.participantInfo,
381
+ ...(lastParticipantId
382
+ ? participantReducer(state.participantInfo, {
383
+ participant: mergedParticipants[lastParticipantId],
384
+ })
385
+ : {}),
386
+ participants: mergedParticipants,
387
+ }
388
+ state.historyLoaded = true
389
+ state.serviceInfo = {
390
+ ...state.serviceInfo,
391
+ activeServiceSessionId,
392
+ }
393
+ state.serviceData = serviceData || {}
394
+ state.options = {
395
+ ...state.options,
396
+ features: newFeatures || {},
397
+ }
398
+ state.entryMeta =
399
+ !newFeaturesHasUpload &&
400
+ historyNewEntryMeta.active === entryTypes.upload
401
+ ? { ...state.entryMeta }
402
+ : historyNewEntryMeta
403
+ state.resumeConversationPrompt = resumeConversationPrompt || false
404
+
405
+ if (lastParticipantId) {
406
+ state.headerTitles.subTitle = mergedParticipants[lastParticipantId].name
407
+ }
408
+ },
409
+ resetHistoryLoadedFlag: (state) => {
410
+ state.historyLoaded = false
411
+ },
412
+ setIsLoading: (state, { payload }) => {
413
+ state.isLoading = payload
414
+ },
415
+ initIdleDetachCountdown: (state, { payload }) => {
416
+ const { delaySeconds, delayTime } = payload
417
+
418
+ state.idleDetachCountdown = {
419
+ hasCountdown: true,
420
+ isActive: true,
421
+ wasStopped: false,
422
+ count: delaySeconds,
423
+ remaining: delaySeconds,
424
+ timer: delayTime,
425
+ }
426
+ },
427
+ decrementIdleDetachCountdownCounter: (state) => {
428
+ const { idleDetachCountdown } = state
429
+
430
+ const { remaining: prevRemaining } = idleDetachCountdown
431
+
432
+ const remaining = prevRemaining - 1
433
+
434
+ state.idleDetachCountdown.remaining = remaining
435
+ state.idleDetachCountdown.timer = getTimeFromSeconds(remaining)
436
+ },
437
+ stopIdleDetachCountdownCounter: (state) => {
438
+ state.idleDetachCountdown.isActive = false
439
+ state.idleDetachCountdown.wasStopped = true
440
+ },
441
+ clearIdleDetachCountdown: (state) => {
442
+ state.idleDetachCountdown.hasCountdown = false
443
+ state.idleDetachCountdown.isActive = false
444
+ },
445
+ initResumeConversationPrompt: (state) => {
446
+ state.resumeConversationPrompt = true
447
+ },
448
+ clearResumeConversationPrompt: (state) => {
449
+ state.resumeConversationPrompt = false
450
+ },
451
+ setParticipant: (state, { payload }: PayloadAction<MessageParticipant>) => {
452
+ state.participantInfo = participantReducer(state.participantInfo, {
453
+ participant: payload.participant,
454
+ fromClient: payload.fromClient,
455
+ })
456
+ },
457
+ setActiveService: (state, { payload }: PayloadAction<ServiceInfo>) => {
458
+ if (
459
+ state.serviceInfo.activeServiceSessionId !==
460
+ payload.activeServiceSessionId
461
+ ) {
462
+ state.serviceInfo.activeServiceSessionId =
463
+ payload.activeServiceSessionId
464
+ }
465
+ },
466
+ setHeaderTitle: (state, { payload }) => {
467
+ state.headerTitles.title = payload
468
+ },
469
+ setHeaderSubTitle: (state, { payload }) => {
470
+ state.headerTitles.subTitle = payload
471
+ },
472
+ setInitialState: (state, { payload }) => {
473
+ state.initialState = payload.initialState
474
+ state.unreadEvents = initialStoreState.unreadEvents
475
+ },
476
+ setServiceDataItem: (state, { payload }) => {
477
+ state.serviceData[payload.type] = payload
478
+ },
479
+ setFeatures: (state, { payload }) => {
480
+ if (!payload.features) {
481
+ return
482
+ }
483
+ state.options.features = payload.features
484
+ },
485
+ setFeatureEnabledState: (state, { payload }) => {
486
+ if (!state.options.features.hasOwnProperty(payload.key)) {
487
+ return
488
+ }
489
+ state.options.features[payload.key].enabled = payload.enabled
490
+ },
491
+ clearFeatures: (state) => {
492
+ // case CLEAR_FEATURES:
493
+ state.options.features = {}
494
+ },
495
+ showOption: (state, { payload }) => {
496
+ state.options.panelActive = true
497
+ state.options.optionActive = payload
498
+ },
499
+ hideOption: (state) => {
500
+ // case HIDE_OPTION:
501
+ state.options.panelActive = false
502
+ state.options.optionActive = ''
503
+ },
504
+ setUserSelectedOptions: (state, { payload }) => {
505
+ state.options.userSelectedOptions = payload
506
+ },
507
+ setUserSelectedOption: (state, { payload }) => {
508
+ const { option, value } = payload
509
+ state.options.userSelectedOptions[option] = value
510
+ },
511
+ setBlockAutoEntrySwitch: (state, { payload }) => {
512
+ state.entryMeta.blockAutoEntrySwitch = payload
513
+ },
514
+ setServiceEntryMetadata: (state, { payload }) => {
515
+ state.entryMeta.active = payload.default
516
+ state.entryMeta.options = payload.options || {}
517
+ state.entryMeta.optionsOverride = {}
518
+ },
519
+ setActiveEntryType: (state, { payload }) => {
520
+ state.entryMeta.active = payload
521
+ },
522
+ setUserEntryType: (state, { payload }) => {
523
+ state.entryMeta.userSelected = payload
524
+ },
525
+ registerUpload: (state, { payload }) => {
526
+ state.currentUploads.push({
527
+ id: payload.fileId,
528
+ name: payload.fileName,
529
+ progress: 1,
530
+ uploading: true,
531
+ complete: false,
532
+ error: '',
533
+ uploadHandle: payload.uploadHandle,
534
+ })
535
+ },
536
+ setUploadProgress: (state, { payload }) => {
537
+ state.currentUploads = state.currentUploads.map((fileUpload) => {
538
+ if (fileUpload.id === payload.fileId) {
539
+ return {
540
+ ...fileUpload,
541
+ progress: payload.progress,
542
+ uploading: payload.progress !== 100,
543
+ uploadHandle:
544
+ payload.progress === 100 ? null : fileUpload.uploadHandle,
545
+ }
546
+ }
547
+ return fileUpload
548
+ })
549
+ },
550
+ setUploadError: (state, { payload }) => {
551
+ state.currentUploads = state.currentUploads.map((fileUpload) => {
552
+ if (fileUpload.id === payload.fileId) {
553
+ return {
554
+ ...fileUpload,
555
+ error: payload.errorText,
556
+ progress: 0,
557
+ uploading: false,
558
+ uploadHandle: null,
559
+ }
560
+ }
561
+ return fileUpload
562
+ })
563
+ },
564
+ setUploadComplete: (state, { payload }) => {
565
+ state.currentUploads = state.currentUploads.map((fileUpload) => {
566
+ if (fileUpload.id === payload) {
567
+ return {
568
+ ...fileUpload,
569
+ complete: true,
570
+ }
571
+ }
572
+ return fileUpload
573
+ })
574
+ },
575
+ clearAllUploads: (state) => {
576
+ state.currentUploads = []
577
+ },
578
+ setSeamlyContainerElement: (state, { payload }) => {
579
+ state.seamlyContainerElement = payload
580
+ },
581
+ },
582
+ extraReducers: (builder) => {
583
+ builder
584
+ .addCase(resetApp.pending, () => initialStoreState)
585
+ .addCase(initializeConfig.fulfilled, (state, { payload }) => {
586
+ state.headerTitles.subTitle = payload.agentParticipant?.name
587
+
588
+ if (!payload.features) return
589
+
590
+ state.options.features = payload.features
591
+ })
592
+ .addCase(initializeApp.fulfilled, (state, { payload }) => {
593
+ if (!payload.initialState) return
594
+ state.initialState = payload.initialState
595
+ })
596
+ },
597
+ })
598
+
599
+ export const {
600
+ ackEvent,
601
+ addEvent,
602
+ clearAllUploads,
603
+ clearEvents,
604
+ clearFeatures,
605
+ clearIdleDetachCountdown,
606
+ clearResumeConversationPrompt,
607
+ decrementIdleDetachCountdownCounter,
608
+ hideOption,
609
+ initIdleDetachCountdown,
610
+ initResumeConversationPrompt,
611
+ registerUpload,
612
+ resetHistoryLoadedFlag,
613
+ setActiveEntryType,
614
+ setActiveService,
615
+ setBlockAutoEntrySwitch,
616
+ setEventsRead,
617
+ setFeatureEnabledState,
618
+ setFeatures,
619
+ setHeaderSubTitle,
620
+ setHeaderTitle,
621
+ setHistory,
622
+ setInitialState,
623
+ setIsLoading,
624
+ setLoadedImageEventIds,
625
+ setParticipant,
626
+ setSeamlyContainerElement,
627
+ setServiceDataItem,
628
+ setServiceEntryMetadata,
629
+ setUploadComplete,
630
+ setUploadError,
631
+ setUploadProgress,
632
+ setUserEntryType,
633
+ setUserSelectedOption,
634
+ setUserSelectedOptions,
635
+ showOption,
636
+ stopIdleDetachCountdownCounter,
637
+ } = storeSlice.actions
638
+
639
+ export default storeSlice.reducer