@seamly/web-ui 20.8.1 → 21.0.0

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 (208) hide show
  1. package/build/dist/lib/deprecated-view.js +1 -1
  2. package/build/dist/lib/index.debug.js +585 -584
  3. package/build/dist/lib/index.debug.min.js +1 -1
  4. package/build/dist/lib/index.debug.min.js.LICENSE.txt +110 -110
  5. package/build/dist/lib/index.js +20269 -26441
  6. package/build/dist/lib/index.min.js +1 -1
  7. package/build/dist/lib/index.min.js.LICENSE.txt +6 -1
  8. package/build/dist/lib/standalone.js +27728 -34583
  9. package/build/dist/lib/standalone.min.js +1 -1
  10. package/build/dist/lib/standalone.min.js.LICENSE.txt +1 -1
  11. package/build/dist/lib/storage.js +6 -15
  12. package/build/dist/lib/style-guide.js +9660 -8970
  13. package/build/dist/lib/style-guide.min.js +1 -1
  14. package/build/dist/lib/styles-default-implementation.js +1 -1
  15. package/build/dist/lib/styles.js +1 -1
  16. package/build/dist/lib/utils.js +85 -3
  17. package/build/dist/lib/utils.min.js +1 -1
  18. package/package.json +54 -52
  19. package/src/icons/icon_check-16.svg +14 -0
  20. package/src/icons/icon_check-32.svg +14 -0
  21. package/src/javascripts/api/conversation-connector.ts +149 -0
  22. package/src/javascripts/api/errors/seamly-base-error.js +19 -0
  23. package/src/javascripts/api/errors/seamly-unavailable-error.js +5 -7
  24. package/src/javascripts/api/{index.js → index.ts} +163 -116
  25. package/src/javascripts/config.types.ts +5 -4
  26. package/src/javascripts/domains/app/actions.ts +47 -46
  27. package/src/javascripts/domains/app/hooks.js +1 -1
  28. package/src/javascripts/domains/config/actions.ts +2 -8
  29. package/src/javascripts/domains/config/hooks.ts +1 -1
  30. package/src/javascripts/domains/config/selectors.ts +6 -6
  31. package/src/javascripts/domains/config/slice.ts +3 -3
  32. package/src/javascripts/domains/errors/index.ts +66 -0
  33. package/src/javascripts/domains/forms/context.ts +1 -1
  34. package/src/javascripts/domains/forms/forms.types.ts +3 -3
  35. package/src/javascripts/domains/forms/hooks.ts +10 -10
  36. package/src/javascripts/domains/forms/provider.tsx +9 -9
  37. package/src/javascripts/domains/i18n/actions.ts +11 -5
  38. package/src/javascripts/domains/i18n/hooks.ts +11 -8
  39. package/src/javascripts/domains/i18n/selectors.ts +10 -4
  40. package/src/javascripts/domains/i18n/slice.ts +0 -1
  41. package/src/javascripts/domains/interrupt/hooks.ts +1 -1
  42. package/src/javascripts/domains/interrupt/middleware.ts +1 -1
  43. package/src/javascripts/domains/store/index.ts +1 -1
  44. package/src/javascripts/domains/store/selectors.ts +16 -0
  45. package/src/javascripts/domains/store/slice.ts +47 -41
  46. package/src/javascripts/domains/store/store.types.ts +38 -10
  47. package/src/javascripts/domains/translations/components/{options-button.js → options-button.tsx} +30 -20
  48. package/src/javascripts/domains/translations/components/options-dialog/index.tsx +33 -0
  49. package/src/javascripts/domains/translations/components/options-dialog/translation-option.tsx +37 -0
  50. package/src/javascripts/domains/translations/components/options-dialog/translation-options.tsx +85 -0
  51. package/src/javascripts/domains/translations/components/translation-status.tsx +15 -0
  52. package/src/javascripts/domains/translations/hooks.ts +77 -11
  53. package/src/javascripts/domains/translations/slice.ts +20 -9
  54. package/src/javascripts/domains/translations/translations.types.ts +4 -2
  55. package/src/javascripts/domains/visibility/actions.ts +6 -10
  56. package/src/javascripts/domains/visibility/hooks.ts +33 -14
  57. package/src/javascripts/domains/visibility/selectors.ts +3 -2
  58. package/src/javascripts/domains/visibility/slice.ts +2 -6
  59. package/src/javascripts/index.ts +19 -21
  60. package/src/javascripts/lib/engine/{index.js → index.tsx} +25 -7
  61. package/src/javascripts/lib/url-helpers.ts +112 -0
  62. package/src/javascripts/package/utils.js +5 -2
  63. package/src/javascripts/schema.ts +28 -0
  64. package/src/javascripts/style-guide/components/app.js +16 -12
  65. package/src/javascripts/style-guide/components/links.js +6 -6
  66. package/src/javascripts/style-guide/components/static-core.js +6 -3
  67. package/src/javascripts/style-guide/components/view.js +1 -1
  68. package/src/javascripts/style-guide/states.js +129 -31
  69. package/src/javascripts/style-guide/style-guide-engine.js +1 -1
  70. package/src/javascripts/ui/components/app-options/index.js +25 -6
  71. package/src/javascripts/ui/components/chat-app.js +1 -1
  72. package/src/javascripts/ui/components/chat-status/chat-status-action.tsx +30 -0
  73. package/src/javascripts/ui/components/chat-status/index.tsx +61 -0
  74. package/src/javascripts/ui/components/conversation/component-filter.js +9 -9
  75. package/src/javascripts/ui/components/conversation/{conversation.js → conversation.tsx} +32 -41
  76. package/src/javascripts/ui/components/conversation/event/card-component.js +2 -2
  77. package/src/javascripts/ui/components/conversation/event/card-message.js +1 -1
  78. package/src/javascripts/ui/components/conversation/event/carousel-component/components/controls.js +2 -2
  79. package/src/javascripts/ui/components/conversation/event/carousel-component/index.js +4 -4
  80. package/src/javascripts/ui/components/conversation/event/carousel-message/components/slide.js +2 -2
  81. package/src/javascripts/ui/components/conversation/event/carousel-message/index.js +1 -1
  82. package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-context.ts +12 -0
  83. package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-provider.tsx +46 -0
  84. package/src/javascripts/ui/components/conversation/event/chat-scroll/unread-messages-button.tsx +27 -0
  85. package/src/javascripts/ui/components/conversation/event/choice-prompt.js +12 -8
  86. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +6 -6
  87. package/src/javascripts/ui/components/conversation/event/cta.js +2 -2
  88. package/src/javascripts/ui/components/conversation/event/divider/index.js +0 -1
  89. package/src/javascripts/ui/components/conversation/event/divider/variants/default.js +1 -1
  90. package/src/javascripts/ui/components/conversation/event/divider/variants/new-translation.js +17 -22
  91. package/src/javascripts/ui/components/conversation/event/divider/variants/time-indicator.js +2 -2
  92. package/src/javascripts/ui/components/conversation/event/event-participant.js +1 -1
  93. package/src/javascripts/ui/components/conversation/event/event.tsx +66 -0
  94. package/src/javascripts/ui/components/conversation/event/hooks/use-event-link-click-handler.js +1 -1
  95. package/src/javascripts/ui/components/conversation/event/hooks/use-formatted-date.js +1 -1
  96. package/src/javascripts/ui/components/conversation/event/image-lightbox.js +1 -1
  97. package/src/javascripts/ui/components/conversation/event/image.js +2 -2
  98. package/src/javascripts/ui/components/conversation/event/splash.js +1 -1
  99. package/src/javascripts/ui/components/conversation/event/translation.js +1 -1
  100. package/src/javascripts/ui/components/conversation/event/upload.js +2 -2
  101. package/src/javascripts/ui/components/conversation/event/video.js +2 -2
  102. package/src/javascripts/ui/components/conversation/event-divider.js +1 -1
  103. package/src/javascripts/ui/components/conversation/message-container.js +1 -1
  104. package/src/javascripts/ui/components/conversation/use-chat-scroll.ts +108 -0
  105. package/src/javascripts/ui/components/core/{seamly-activity-monitor.js → seamly-activity-monitor.tsx} +12 -5
  106. package/src/javascripts/ui/components/core/seamly-api-context.ts +7 -0
  107. package/src/javascripts/ui/components/core/seamly-chat.tsx +8 -0
  108. package/src/javascripts/ui/components/core/{seamly-core.js → seamly-core.tsx} +27 -14
  109. package/src/javascripts/ui/components/core/seamly-event-subscriber.ts +340 -0
  110. package/src/javascripts/ui/components/core/seamly-file-upload.js +2 -2
  111. package/src/javascripts/ui/components/core/seamly-idle-detach-counter.js +1 -1
  112. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +24 -11
  113. package/src/javascripts/ui/components/core/seamly-live-region.js +4 -4
  114. package/src/javascripts/ui/components/core/seamly-new-notifications.js +3 -3
  115. package/src/javascripts/ui/components/core/seamly-read-state.js +2 -33
  116. package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +4 -4
  117. package/src/javascripts/ui/components/entry/entry-container.js +8 -8
  118. package/src/javascripts/ui/components/entry/text-entry/hooks.js +3 -3
  119. package/src/javascripts/ui/components/entry/text-entry/index.js +3 -3
  120. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +4 -4
  121. package/src/javascripts/ui/components/entry/upload/file-upload-form.js +3 -3
  122. package/src/javascripts/ui/components/entry/upload/index.js +5 -5
  123. package/src/javascripts/ui/components/entry/upload-toggle.js +6 -6
  124. package/src/javascripts/ui/components/faq/faq.js +14 -14
  125. package/src/javascripts/ui/components/form-controls/error.js +2 -2
  126. package/src/javascripts/ui/components/form-controls/file-input.js +3 -3
  127. package/src/javascripts/ui/components/layout/agent-info.js +3 -3
  128. package/src/javascripts/ui/components/layout/chat-frame.js +20 -12
  129. package/src/javascripts/ui/components/layout/chat.js +5 -5
  130. package/src/javascripts/ui/components/layout/deprecated-app-frame.js +6 -6
  131. package/src/javascripts/ui/components/layout/deprecated-chat-frame.js +34 -0
  132. package/src/javascripts/ui/components/layout/header.js +2 -2
  133. package/src/javascripts/ui/components/layout/icon.js +11 -9
  134. package/src/javascripts/ui/components/layout/interrupt.js +7 -5
  135. package/src/javascripts/ui/components/layout/pre-chat-messages.js +1 -1
  136. package/src/javascripts/ui/components/layout/privacy-disclaimer.js +2 -2
  137. package/src/javascripts/ui/components/options/options-button.js +5 -5
  138. package/src/javascripts/ui/components/options/{options-frame.js → options-frame.tsx} +52 -18
  139. package/src/javascripts/ui/components/options/transcript/index.js +9 -10
  140. package/src/javascripts/ui/components/options/transcript/transcript-form.js +2 -2
  141. package/src/javascripts/ui/components/suggestions/index.js +8 -8
  142. package/src/javascripts/ui/components/suggestions/suggestions-item.js +1 -1
  143. package/src/javascripts/{domains/translations/components/chat-status.js → ui/components/translation-chat-status/index.tsx} +13 -14
  144. package/src/javascripts/ui/components/translation-proposal/index.tsx +36 -0
  145. package/src/javascripts/ui/components/view/app-view.js +2 -7
  146. package/src/javascripts/ui/components/view/deprecated-view.js +8 -10
  147. package/src/javascripts/ui/components/view/index.js +6 -6
  148. package/src/javascripts/ui/components/view/inline-view.js +4 -8
  149. package/src/javascripts/ui/components/view/window-view/collapse-button.js +2 -2
  150. package/src/javascripts/ui/components/view/window-view/index.js +11 -17
  151. package/src/javascripts/ui/components/view/window-view/window-open-button.js +6 -6
  152. package/src/javascripts/ui/components/warnings/idle-detach-warning.js +3 -3
  153. package/src/javascripts/ui/components/warnings/prompt.js +1 -1
  154. package/src/javascripts/ui/components/warnings/resume-conversation-prompt.js +4 -4
  155. package/src/javascripts/ui/components/widgets/in-out-transition.js +20 -18
  156. package/src/javascripts/ui/components/widgets/lightbox.js +3 -3
  157. package/src/javascripts/ui/components/widgets/modal.js +2 -2
  158. package/src/javascripts/ui/components/widgets/upload-progress.js +2 -2
  159. package/src/javascripts/ui/hooks/file-upload-hooks.js +1 -1
  160. package/src/javascripts/ui/hooks/focus-helper-hooks.js +1 -1
  161. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +6 -6
  162. package/src/javascripts/ui/hooks/seamly-hooks.js +11 -10
  163. package/src/javascripts/ui/hooks/seamly-option-hooks.js +6 -6
  164. package/src/javascripts/ui/hooks/{seamly-state-hooks.js → seamly-state-hooks.ts} +9 -6
  165. package/src/javascripts/ui/hooks/use-click-outside.ts +29 -0
  166. package/src/javascripts/ui/hooks/use-event-component-mapping.js +11 -10
  167. package/src/javascripts/ui/hooks/use-interval.js +1 -1
  168. package/src/javascripts/ui/hooks/use-seamly-actions.ts +29 -29
  169. package/src/javascripts/ui/hooks/use-seamly-chat.js +13 -23
  170. package/src/javascripts/ui/hooks/use-seamly-commands.js +20 -15
  171. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +8 -8
  172. package/src/javascripts/ui/hooks/use-seamly-resume-conversation-prompt.js +2 -2
  173. package/src/javascripts/ui/hooks/use-single-file-upload.js +1 -1
  174. package/src/javascripts/ui/hooks/utility-hooks.js +1 -1
  175. package/src/javascripts/ui/utils/general-utils.js +0 -23
  176. package/src/javascripts/ui/utils/seamly-utils.ts +10 -1
  177. package/src/javascripts/ui/utils/seamly-utils.types.ts +9 -0
  178. package/src/stylesheets/1-settings/_config.scss +1 -1
  179. package/src/stylesheets/3-chat/_chat.scss +23 -5
  180. package/src/stylesheets/5-components/_chat-status.scss +72 -16
  181. package/src/stylesheets/5-components/_conversation.scss +35 -1
  182. package/src/stylesheets/5-components/_disclaimer.scss +0 -5
  183. package/src/stylesheets/5-components/_options.scss +16 -2
  184. package/src/stylesheets/5-components/_translation-options.scss +39 -0
  185. package/src/stylesheets/6-default-implementation/_scrollbar.scss +1 -1
  186. package/src/stylesheets/7-deprecated/3-app/_app.scss +19 -4
  187. package/src/stylesheets/7-deprecated/5-components/_chat-status.scss +5 -0
  188. package/src/stylesheets/7-deprecated/5-components/_options.scss +1 -0
  189. package/src/stylesheets/7-deprecated/5-components/_translation-options.scss +39 -0
  190. package/src/stylesheets/deprecated-view.scss +1 -0
  191. package/src/stylesheets/styles.scss +1 -0
  192. package/webpack/config.common.js +4 -4
  193. package/webpack/config.package.js +10 -16
  194. package/webpack/config.site.js +4 -1
  195. package/webpack/config.test.js +2 -1
  196. package/build/dist/lib/deprecated-view.css +0 -1
  197. package/build/dist/lib/styles-default-implementation.css +0 -1
  198. package/build/dist/lib/styles.css +0 -1
  199. package/src/.DS_Store +0 -0
  200. package/src/javascripts/api/event-producer.js +0 -20
  201. package/src/javascripts/api/producer.js +0 -136
  202. package/src/javascripts/domains/errors/index.js +0 -37
  203. package/src/javascripts/domains/translations/components/options-dialog/form.js +0 -70
  204. package/src/javascripts/domains/translations/components/options-dialog/index.js +0 -87
  205. package/src/javascripts/ui/components/chat-status/index.js +0 -38
  206. package/src/javascripts/ui/components/conversation/event/event.js +0 -36
  207. package/src/javascripts/ui/components/core/seamly-api-context.js +0 -5
  208. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +0 -279
@@ -1,13 +1,13 @@
1
- import { createAsyncThunk, unwrapResult } from '@reduxjs/toolkit'
1
+ import { createAsyncThunk } from '@reduxjs/toolkit'
2
+ import type { Config } from 'config.types'
2
3
  import SeamlySessionExpiredError from 'api/errors/seamly-session-expired-error'
3
4
  import SeamlyUnavailableError from 'api/errors/seamly-unavailable-error'
4
- import { Config } from 'config.types'
5
+ import { actionTypes } from 'ui/utils/seamly-utils'
5
6
  import { initializeConfig, resetConfig } from 'domains/config/actions'
6
7
  import { setLocale } from 'domains/i18n/actions'
7
8
  import { ThunkAPI } from 'domains/redux/redux.types'
8
9
  import type { RootState } from 'domains/store'
9
10
  import { initializeVisibility } from 'domains/visibility/actions'
10
- import { actionTypes } from 'ui/utils/seamly-utils'
11
11
 
12
12
  export const initializeApp = createAsyncThunk<
13
13
  {
@@ -17,60 +17,61 @@ export const initializeApp = createAsyncThunk<
17
17
  },
18
18
  void,
19
19
  ThunkAPI
20
- >(
21
- 'initializeApp',
22
- async (_, { extra: { api, config }, rejectWithValue, dispatch }) => {
23
- let locale = config?.context?.locale
24
- try {
25
- if (api.hasConversation()) {
26
- const initialState = await api.getConversationIntitialState()
20
+ >('initializeApp', async (_, { extra: { api, config }, rejectWithValue }) => {
21
+ let locale = config?.context?.locale
22
+ try {
23
+ if (api.hasConversation()) {
24
+ const initialState = await api.getConversationIntitialState()
27
25
 
28
- locale = initialState.translation?.locale || locale
26
+ locale = initialState.translation?.locale || locale
29
27
 
30
- return { initialState, locale, config }
31
- } else {
32
- if (config?.context?.topic) {
33
- api.send('action', {
34
- type: actionTypes.setTopic,
35
- body: {
36
- name: config.context.topic,
37
- // Separate fallback message is not needed here. Only an attached service will use this, but none will
38
- // be attached before the conversation has started (meaning the fallback message will never be shown).
39
- fallbackMessage: config.context.topic,
40
- },
41
- })
42
- }
43
- if (config?.context?.translationLocale) {
44
- locale = config.context.translationLocale
45
- api.send('action', {
28
+ return { initialState, locale, config }
29
+ } else {
30
+ if (config?.context?.topic) {
31
+ api.send('action', {
32
+ type: actionTypes.setTopic,
33
+ body: {
34
+ name: config.context.topic,
35
+ // Separate fallback message is not needed here. Only an attached service will use this, but none will
36
+ // be attached before the conversation has started (meaning the fallback message will never be shown).
37
+ fallbackMessage: config.context.topic,
38
+ },
39
+ })
40
+ }
41
+ if (config?.context?.translationLocale) {
42
+ locale = config.context.translationLocale
43
+ api.send(
44
+ 'action',
45
+ {
46
46
  type: actionTypes.setTranslation,
47
47
  body: { enabled: true, locale },
48
- })
49
- }
50
- return { initialState: undefined, locale, config }
51
- }
52
- } catch (e) {
53
- if (e instanceof SeamlySessionExpiredError) {
54
- const err = new SeamlySessionExpiredError()
55
-
56
- return rejectWithValue({
57
- name: err.name,
58
- message: err.message,
59
- originalEvent: err.originalEvent,
60
- originalError: err.originalError,
61
- action: err.action,
62
- })
48
+ },
49
+ false,
50
+ )
63
51
  }
52
+ return { initialState: undefined, locale, config }
53
+ }
54
+ } catch (e) {
55
+ if (e instanceof SeamlySessionExpiredError) {
56
+ const err = new SeamlySessionExpiredError()
64
57
 
65
- const err = new SeamlyUnavailableError()
66
58
  return rejectWithValue({
67
59
  name: err.name,
68
60
  message: err.message,
69
- langKey: err.langKey,
61
+ originalEvent: err.originalEvent,
62
+ originalError: err.originalError,
63
+ action: err.action,
70
64
  })
71
65
  }
72
- },
73
- )
66
+
67
+ const err = new SeamlyUnavailableError()
68
+ return rejectWithValue({
69
+ name: err.name,
70
+ message: err.message,
71
+ langKey: err.langKey,
72
+ })
73
+ }
74
+ })
74
75
 
75
76
  export const resetApp = createAsyncThunk<unknown, void, ThunkAPI>(
76
77
  'resetApp',
@@ -1,4 +1,4 @@
1
- import { selectUserHasResponded } from 'domains/app/selectors'
2
1
  import { useSelector } from 'react-redux'
2
+ import { selectUserHasResponded } from 'domains/app/selectors'
3
3
 
4
4
  export const useUserHasResponded = () => useSelector(selectUserHasResponded)
@@ -1,5 +1,4 @@
1
1
  import { createAsyncThunk } from '@reduxjs/toolkit'
2
- import SeamlyUnavailableError from 'api/errors/seamly-unavailable-error'
3
2
  import { ThunkAPI } from 'domains/redux/redux.types'
4
3
 
5
4
  export const initializeConfig = createAsyncThunk<any, void, ThunkAPI>(
@@ -28,13 +27,8 @@ export const initializeConfig = createAsyncThunk<any, void, ThunkAPI>(
28
27
  locale,
29
28
  connectWhenInView,
30
29
  }
31
- } catch (e) {
32
- const err = new SeamlyUnavailableError()
33
- return rejectWithValue({
34
- name: err.name,
35
- langKey: err.langKey,
36
- message: err.message,
37
- })
30
+ } catch (error) {
31
+ return rejectWithValue(error)
38
32
  }
39
33
  },
40
34
  )
@@ -1,5 +1,5 @@
1
- import { selectConfig } from 'domains/config/selectors'
2
1
  import { useSelector } from 'react-redux'
2
+ import { selectConfig } from 'domains/config/selectors'
3
3
 
4
4
  export const useConfig = () => useSelector(selectConfig)
5
5
 
@@ -1,15 +1,15 @@
1
1
  import { createSelector } from '@reduxjs/toolkit'
2
- import { Config } from 'config.types'
2
+ import type { VisibilityOptions } from 'config.types'
3
+ import type { RootState } from 'domains/store'
3
4
  import { visibilityStates } from 'domains/visibility/constants'
4
5
 
5
6
  export const selectConfig = createSelector(
6
- ({ config }): Config => config,
7
+ ({ config }: RootState) => config,
7
8
  (config) => {
8
9
  let newConfig = {
9
- visible:
10
- config?.layoutMode === 'inline'
11
- ? visibilityStates.open
12
- : visibilityStates.minimized,
10
+ visible: (config?.layoutMode === 'inline'
11
+ ? visibilityStates.open
12
+ : visibilityStates.minimized) as VisibilityOptions,
13
13
  appContainerClassNames: config.appContainerClassNames || [],
14
14
  ...config,
15
15
  }
@@ -1,9 +1,9 @@
1
- import { createSlice, PayloadAction } from '@reduxjs/toolkit'
2
- import { defaultConfig } from 'config'
1
+ import { PayloadAction, createSlice } from '@reduxjs/toolkit'
3
2
  import { Config } from 'config.types'
3
+ import { defaultConfig } from 'config'
4
+ import { pick } from 'ui/utils/general-utils'
4
5
  import { initializeConfig, resetConfig } from 'domains/config/actions'
5
6
  import type { ChannelEvent } from 'domains/store/store.types'
6
- import { pick } from 'ui/utils/general-utils'
7
7
 
8
8
  export const initialConfigState: Config = {
9
9
  ...defaultConfig,
@@ -0,0 +1,66 @@
1
+ import { createAction } from '@reduxjs/toolkit'
2
+ import SeamlyBaseError from 'api/errors/seamly-base-error'
3
+ import SeamlyConfigurationError from 'api/errors/seamly-configuration-error'
4
+ import SeamlyGeneralError from 'api/errors/seamly-general-error'
5
+ import SeamlyOfflineError from 'api/errors/seamly-offline-error'
6
+ import SeamlySessionExpiredError from 'api/errors/seamly-session-expired-error'
7
+ import SeamlyUnauthorizedError from 'api/errors/seamly-unauthorized-error'
8
+ import SeamlyUnavailableError from 'api/errors/seamly-unavailable-error'
9
+ import { selectConfig } from 'domains/config/selectors'
10
+
11
+ const ErrorTypes = {
12
+ SeamlyGeneralError,
13
+ SeamlyConfigurationError,
14
+ SeamlySessionExpiredError,
15
+ SeamlyOfflineError,
16
+ SeamlyUnauthorizedError,
17
+ SeamlyUnavailableError,
18
+ }
19
+
20
+ export const catchError = createAction('catch-error', (error) => ({
21
+ payload: error,
22
+ }))
23
+
24
+ export function createErrorsMiddleware({ api: seamlyApi }) {
25
+ return ({ getState }) => {
26
+ const handleError = (action) => {
27
+ const { errorCallback, namespace, api, layoutMode } = selectConfig(
28
+ getState(),
29
+ )
30
+
31
+ const { error, type } = action
32
+
33
+ const errorType = ErrorTypes[error?.name]
34
+ ? new ErrorTypes[error.name](error)
35
+ : new SeamlyBaseError(error)
36
+
37
+ errorCallback?.(errorType, {
38
+ namespace,
39
+ api,
40
+ layoutMode,
41
+ conversationUrl: seamlyApi.getConversationUrl(),
42
+ action: type ? action : undefined,
43
+ })
44
+ }
45
+
46
+ return (next) => (action) => {
47
+ try {
48
+ if (action.payload?.originalEvent?.payload) {
49
+ handleError({
50
+ error: action.payload,
51
+ type: action.payload?.originalEvent?.payload?.type,
52
+ })
53
+ } else if (action.payload?.originalError) {
54
+ handleError({
55
+ error: action.payload,
56
+ type: undefined,
57
+ })
58
+ }
59
+ return next(action)
60
+ } catch (error) {
61
+ handleError({ error })
62
+ throw error
63
+ }
64
+ }
65
+ }
66
+ }
@@ -1,5 +1,5 @@
1
- import type { FormContextType } from 'domains/forms/forms.types'
2
1
  import { createContext } from 'preact'
2
+ import type { FormContextType } from 'domains/forms/forms.types'
3
3
 
4
4
  const FormContext = createContext<FormContextType>({
5
5
  handleSubmit: () => undefined,
@@ -1,10 +1,10 @@
1
1
  export type FormContextType = {
2
- handleSubmit: (e: Event) => void
2
+ handleSubmit: (_e: Event) => void
3
3
  isSubmitted: boolean
4
4
  isValid: boolean
5
5
  formId?: string
6
- updateControlValue: (name: string, value: string) => void
7
- updateControlTouched: (name: string, value: boolean) => void
6
+ updateControlValue: (_name: string, _value: string) => void
7
+ updateControlTouched: (_name: string, _value: boolean) => void
8
8
  errors?: Record<string, unknown>
9
9
  validationSchema?: Record<string, unknown>
10
10
  values?: Record<string, string>
@@ -1,21 +1,21 @@
1
- import { deregisterControl, registerControl } from 'domains/forms/slice'
2
- import {
3
- getFormById,
4
- getControlValueByName,
5
- getControlTouchedByName,
6
- } from 'domains/forms/selectors'
7
1
  import {
2
+ useCallback,
8
3
  useContext,
9
- useMemo,
10
4
  useEffect,
11
5
  useLayoutEffect,
12
- useCallback,
6
+ useMemo,
13
7
  } from 'preact/hooks'
14
8
  import { useDispatch, useSelector } from 'react-redux'
9
+ import type { FormContextType } from 'domains/forms/forms.types'
10
+ import {
11
+ getControlTouchedByName,
12
+ getControlValueByName,
13
+ getFormById,
14
+ } from 'domains/forms/selectors'
15
+ import { deregisterControl, registerControl } from 'domains/forms/slice'
16
+ import type { RootState } from 'domains/store'
15
17
  import FormContext from './context'
16
18
  import { validate } from './utils'
17
- import type { RootState } from 'domains/store'
18
- import type { FormContextType } from 'domains/forms/forms.types'
19
19
 
20
20
  export function useFormContext(): FormContextType {
21
21
  return useContext(FormContext)
@@ -1,22 +1,22 @@
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useLayoutEffect,
5
+ useMemo,
6
+ useState,
7
+ } from 'preact/hooks'
8
+ import { useDispatch, useSelector } from 'react-redux'
1
9
  import { setHasResponded } from 'domains/app/slice'
2
10
  import { Provider } from 'domains/forms/context'
3
11
  import { useValidations } from 'domains/forms/hooks'
4
12
  import { getFormValuesByFormId } from 'domains/forms/selectors'
5
13
  import {
6
14
  deregisterForm,
7
- registerForm,
8
15
  updateControlTouched as dispatchControlTouched,
9
16
  updateControlValue as dispatchControlValue,
17
+ registerForm,
10
18
  } from 'domains/forms/slice'
11
19
  import type { RootState } from 'domains/store'
12
- import {
13
- useCallback,
14
- useEffect,
15
- useLayoutEffect,
16
- useMemo,
17
- useState,
18
- } from 'preact/hooks'
19
- import { useDispatch, useSelector } from 'react-redux'
20
20
 
21
21
  export default function FormProvider({
22
22
  children,
@@ -11,12 +11,16 @@ export const setLocale = createAsyncThunk<
11
11
  ThunkAPI
12
12
  >(
13
13
  'setLocale',
14
- async (locale, { extra: { api } }) => {
15
- const response = await api.getTranslations(locale)
14
+ async (locale, { extra: { api }, rejectWithValue }) => {
15
+ try {
16
+ const response = await api.getTranslations(locale)
16
17
 
17
- return {
18
- translations: response,
19
- locale,
18
+ return {
19
+ translations: response,
20
+ locale,
21
+ }
22
+ } catch (error) {
23
+ return rejectWithValue(error)
20
24
  }
21
25
  },
22
26
  {
@@ -29,6 +33,8 @@ export const setLocale = createAsyncThunk<
29
33
  // Already fetched or in progress, don't need to re-fetch
30
34
  return false
31
35
  }
36
+
37
+ return true
32
38
  },
33
39
  },
34
40
  )
@@ -3,12 +3,15 @@ import {
3
3
  pluralTypeHandler,
4
4
  selectTypeHandler,
5
5
  } from '@ultraq/icu-message-formatter'
6
- import { RootState } from 'domains/store'
7
6
  import { useCallback } from 'preact/hooks'
8
7
  import { useSelector } from 'react-redux'
9
- import * as Selectors from './selectors'
8
+ import {
9
+ selectInitialLocale,
10
+ selectIsLoading,
11
+ selectLocale,
12
+ selectTranslations,
13
+ } from 'domains/i18n/selectors'
10
14
 
11
- // The passed in locale (en-GB) is only used to call Intl.PluralRules.select() in
12
15
  // pluralTypeHandler. Since we only use exact plural matches (=0, =1 etc) we can
13
16
  // safely use en-GB all the time.
14
17
  const formatter = new MessageFormatter('en-GB', {
@@ -17,13 +20,13 @@ const formatter = new MessageFormatter('en-GB', {
17
20
  })
18
21
 
19
22
  export function useI18n() {
20
- const translations = useSelector(Selectors.selectTranslations)
21
- const locale = useSelector(Selectors.selectLocale)
22
- const initialLocale = useSelector(Selectors.selectInitialLocale)
23
- const isLoading = useSelector((state: RootState) => state.i18n.isLoading)
23
+ const translations = useSelector(selectTranslations)
24
+ const locale = useSelector(selectLocale)
25
+ const initialLocale = useSelector(selectInitialLocale)
26
+ const isLoading = useSelector(selectIsLoading)
24
27
 
25
28
  const t = useCallback(
26
- (key, values = {}) => {
29
+ (key: string, values = {}) => {
27
30
  const translation = translations[key]
28
31
  if (!translation) {
29
32
  if (isLoading) return null
@@ -1,16 +1,22 @@
1
- import { createSelector } from 'reselect'
1
+ import { createSelector } from '@reduxjs/toolkit'
2
+ import type { RootState } from 'domains/store'
2
3
 
3
4
  export const selectTranslations = createSelector(
4
- ({ i18n }) => i18n,
5
+ ({ i18n }: RootState) => i18n,
5
6
  ({ translations }) => translations,
6
7
  )
7
8
 
8
9
  export const selectInitialLocale = createSelector(
9
- ({ i18n }) => i18n,
10
+ ({ i18n }: RootState) => i18n,
10
11
  ({ initialLocale }) => initialLocale,
11
12
  )
12
13
 
13
14
  export const selectLocale = createSelector(
14
- ({ i18n }) => i18n,
15
+ ({ i18n }: RootState) => i18n,
15
16
  ({ locale }) => locale,
16
17
  )
18
+
19
+ export const selectIsLoading = createSelector(
20
+ ({ i18n }: RootState) => i18n,
21
+ ({ isLoading }) => isLoading,
22
+ )
@@ -1,5 +1,4 @@
1
1
  import { createSlice } from '@reduxjs/toolkit'
2
- import { initializeApp } from 'domains/app/actions'
3
2
  import { initializeConfig } from 'domains/config/actions'
4
3
  import { setLocale } from 'domains/i18n/actions'
5
4
  import { I18nState } from 'domains/i18n/i18n.types'
@@ -1,6 +1,6 @@
1
- import { useI18n } from 'domains/i18n/hooks'
2
1
  import { useMemo } from 'preact/hooks'
3
2
  import { useSelector } from 'react-redux'
3
+ import { useI18n } from 'domains/i18n/hooks'
4
4
  import * as Selectors from './selectors'
5
5
 
6
6
  export function useInterrupt() {
@@ -22,7 +22,7 @@ export default function createInterruptMiddleware({ api }) {
22
22
  if (
23
23
  !handledErrorTypes.some((ErrorType) => payload.name === ErrorType.name)
24
24
  ) {
25
- throw new Error(payload)
25
+ throw new SeamlyGeneralError(payload)
26
26
  } else if (payload.action === 'reset') {
27
27
  // [SMLY-942] We clear the store before a reset to force a new conversation if the page is refreshed before the conversation is reset
28
28
  api.disconnect().then(() => {
@@ -1,4 +1,5 @@
1
1
  import { configureStore } from '@reduxjs/toolkit'
2
+ import { useDispatch } from 'react-redux'
2
3
  import appReducer from 'domains/app/slice'
3
4
  import configReducer from 'domains/config/slice'
4
5
  import { createErrorsMiddleware } from 'domains/errors'
@@ -11,7 +12,6 @@ import stateReducer from 'domains/store/slice'
11
12
  import createI18nMiddleware from 'domains/translations/middleware'
12
13
  import translationReducer from 'domains/translations/slice'
13
14
  import visibilityReducer from 'domains/visibility/slice'
14
- import { useDispatch } from 'react-redux'
15
15
 
16
16
  export function createStore({ initialState, api, eventBus, config }) {
17
17
  const store = configureStore({
@@ -0,0 +1,16 @@
1
+ import { createSelector } from '@reduxjs/toolkit'
2
+ import { selectEvents } from 'ui/hooks/seamly-state-hooks'
3
+ import { readStates } from 'ui/utils/seamly-utils'
4
+ import { isUnreadMessage } from 'domains/store/slice'
5
+
6
+ export const selectUnreadEventIds = createSelector(selectEvents, (events) => {
7
+ return events
8
+ .filter((event) => {
9
+ return (
10
+ isUnreadMessage(event) &&
11
+ event.type !== 'service_data' &&
12
+ event.payload?.messageStatus === readStates.received
13
+ )
14
+ })
15
+ .map((event) => event.payload.id)
16
+ })