@seamly/web-ui 20.8.1 → 21.0.1-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.
Files changed (225) hide show
  1. package/build/dist/lib/components.js +14855 -20
  2. package/build/dist/lib/components.min.js +2 -1
  3. package/build/dist/lib/components.min.js.LICENSE.txt +48 -0
  4. package/build/dist/lib/config.js +9 -3
  5. package/build/dist/lib/config.min.js +1 -1
  6. package/build/dist/lib/contexts.js +14 -5
  7. package/build/dist/lib/contexts.min.js +1 -1
  8. package/build/dist/lib/deprecated-view.js +1 -1
  9. package/build/dist/lib/hooks.js +8446 -20
  10. package/build/dist/lib/hooks.min.js +2 -1
  11. package/build/dist/lib/hooks.min.js.LICENSE.txt +38 -0
  12. package/build/dist/lib/index.debug.js +585 -584
  13. package/build/dist/lib/index.debug.min.js +1 -1
  14. package/build/dist/lib/index.debug.min.js.LICENSE.txt +110 -110
  15. package/build/dist/lib/index.js +20279 -26454
  16. package/build/dist/lib/index.min.js +1 -1
  17. package/build/dist/lib/index.min.js.LICENSE.txt +6 -1
  18. package/build/dist/lib/standalone.js +27823 -34681
  19. package/build/dist/lib/standalone.min.js +1 -1
  20. package/build/dist/lib/standalone.min.js.LICENSE.txt +1 -1
  21. package/build/dist/lib/storage.js +6 -15
  22. package/build/dist/lib/style-guide.js +23181 -7921
  23. package/build/dist/lib/style-guide.min.js +1 -1
  24. package/build/dist/lib/style-guide.min.js.LICENSE.txt +10 -0
  25. package/build/dist/lib/styles-default-implementation.js +1 -1
  26. package/build/dist/lib/styles.js +1 -1
  27. package/build/dist/lib/utils.js +22149 -17
  28. package/build/dist/lib/utils.min.js +2 -1
  29. package/build/dist/lib/utils.min.js.LICENSE.txt +48 -0
  30. package/package.json +54 -52
  31. package/src/icons/icon_check-16.svg +14 -0
  32. package/src/icons/icon_check-32.svg +14 -0
  33. package/src/javascripts/api/conversation-connector.ts +149 -0
  34. package/src/javascripts/api/errors/seamly-base-error.js +19 -0
  35. package/src/javascripts/api/errors/seamly-unavailable-error.js +5 -7
  36. package/src/javascripts/api/{index.js → index.ts} +163 -116
  37. package/src/javascripts/config.types.ts +5 -4
  38. package/src/javascripts/domains/app/actions.ts +47 -46
  39. package/src/javascripts/domains/app/hooks.js +1 -1
  40. package/src/javascripts/domains/config/actions.ts +2 -8
  41. package/src/javascripts/domains/config/hooks.ts +1 -1
  42. package/src/javascripts/domains/config/selectors.ts +6 -6
  43. package/src/javascripts/domains/config/slice.ts +3 -3
  44. package/src/javascripts/domains/errors/index.ts +66 -0
  45. package/src/javascripts/domains/forms/context.ts +1 -1
  46. package/src/javascripts/domains/forms/forms.types.ts +3 -3
  47. package/src/javascripts/domains/forms/hooks.ts +10 -10
  48. package/src/javascripts/domains/forms/provider.tsx +9 -9
  49. package/src/javascripts/domains/i18n/actions.ts +11 -5
  50. package/src/javascripts/domains/i18n/hooks.ts +11 -8
  51. package/src/javascripts/domains/i18n/selectors.ts +10 -4
  52. package/src/javascripts/domains/i18n/slice.ts +0 -1
  53. package/src/javascripts/domains/interrupt/hooks.ts +1 -1
  54. package/src/javascripts/domains/interrupt/middleware.ts +1 -1
  55. package/src/javascripts/domains/store/index.ts +1 -1
  56. package/src/javascripts/domains/store/selectors.ts +16 -0
  57. package/src/javascripts/domains/store/slice.ts +47 -41
  58. package/src/javascripts/domains/store/store.types.ts +38 -10
  59. package/src/javascripts/domains/translations/components/{options-button.js → options-button.tsx} +30 -20
  60. package/src/javascripts/domains/translations/components/options-dialog/index.tsx +33 -0
  61. package/src/javascripts/domains/translations/components/options-dialog/translation-option.tsx +42 -0
  62. package/src/javascripts/domains/translations/components/options-dialog/translation-options.tsx +81 -0
  63. package/src/javascripts/domains/translations/components/translation-status.tsx +15 -0
  64. package/src/javascripts/domains/translations/hooks.ts +77 -11
  65. package/src/javascripts/domains/translations/slice.ts +20 -9
  66. package/src/javascripts/domains/translations/translations.types.ts +4 -2
  67. package/src/javascripts/domains/visibility/actions.ts +6 -10
  68. package/src/javascripts/domains/visibility/hooks.ts +33 -14
  69. package/src/javascripts/domains/visibility/selectors.ts +3 -2
  70. package/src/javascripts/domains/visibility/slice.ts +2 -6
  71. package/src/javascripts/index.ts +18 -22
  72. package/src/javascripts/lib/engine/{index.js → index.tsx} +25 -7
  73. package/src/javascripts/lib/url-helpers.ts +112 -0
  74. package/src/javascripts/package/components.js +15 -1
  75. package/src/javascripts/package/config.js +1 -1
  76. package/src/javascripts/package/contexts.js +5 -3
  77. package/src/javascripts/package/hooks.js +19 -1
  78. package/src/javascripts/package/utils.js +13 -3
  79. package/src/javascripts/schema.ts +28 -0
  80. package/src/javascripts/style-guide/components/app.js +16 -12
  81. package/src/javascripts/style-guide/components/links.js +6 -6
  82. package/src/javascripts/style-guide/components/static-core.js +14 -9
  83. package/src/javascripts/style-guide/components/view.js +2 -2
  84. package/src/javascripts/style-guide/states.js +132 -36
  85. package/src/javascripts/style-guide/style-guide-engine.js +2 -1
  86. package/src/javascripts/style-guide/style-guide-external-api.js +1 -1
  87. package/src/javascripts/ui/components/app-options/index.js +25 -6
  88. package/src/javascripts/ui/components/chat-app.js +1 -1
  89. package/src/javascripts/ui/components/chat-status/chat-status-action.tsx +30 -0
  90. package/src/javascripts/ui/components/chat-status/index.tsx +61 -0
  91. package/src/javascripts/ui/components/conversation/component-filter.js +9 -9
  92. package/src/javascripts/ui/components/conversation/{conversation.js → conversation.tsx} +32 -41
  93. package/src/javascripts/ui/components/conversation/event/card-component.js +2 -2
  94. package/src/javascripts/ui/components/conversation/event/card-message.js +1 -1
  95. package/src/javascripts/ui/components/conversation/event/carousel-component/components/controls.js +2 -2
  96. package/src/javascripts/ui/components/conversation/event/carousel-component/index.js +4 -4
  97. package/src/javascripts/ui/components/conversation/event/carousel-message/components/slide.js +2 -2
  98. package/src/javascripts/ui/components/conversation/event/carousel-message/index.js +1 -1
  99. package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-context.ts +12 -0
  100. package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-provider.tsx +46 -0
  101. package/src/javascripts/ui/components/conversation/event/chat-scroll/unread-messages-button.tsx +30 -0
  102. package/src/javascripts/ui/components/conversation/event/choice-prompt.js +12 -8
  103. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +6 -6
  104. package/src/javascripts/ui/components/conversation/event/cta.js +2 -2
  105. package/src/javascripts/ui/components/conversation/event/divider/index.js +0 -1
  106. package/src/javascripts/ui/components/conversation/event/divider/variants/default.js +1 -1
  107. package/src/javascripts/ui/components/conversation/event/divider/variants/new-translation.js +17 -22
  108. package/src/javascripts/ui/components/conversation/event/divider/variants/time-indicator.js +2 -2
  109. package/src/javascripts/ui/components/conversation/event/event-participant.js +1 -1
  110. package/src/javascripts/ui/components/conversation/event/event.tsx +66 -0
  111. package/src/javascripts/ui/components/conversation/event/hooks/use-event-link-click-handler.js +1 -1
  112. package/src/javascripts/ui/components/conversation/event/hooks/use-formatted-date.js +1 -1
  113. package/src/javascripts/ui/components/conversation/event/image-lightbox.js +1 -1
  114. package/src/javascripts/ui/components/conversation/event/image.js +2 -2
  115. package/src/javascripts/ui/components/conversation/event/splash.js +1 -1
  116. package/src/javascripts/ui/components/conversation/event/translation.js +1 -1
  117. package/src/javascripts/ui/components/conversation/event/upload.js +2 -2
  118. package/src/javascripts/ui/components/conversation/event/video.js +2 -2
  119. package/src/javascripts/ui/components/conversation/event-divider.js +1 -1
  120. package/src/javascripts/ui/components/conversation/message-container.js +1 -1
  121. package/src/javascripts/ui/components/conversation/use-chat-scroll.ts +108 -0
  122. package/src/javascripts/ui/components/core/{seamly-activity-monitor.js → seamly-activity-monitor.tsx} +12 -5
  123. package/src/javascripts/ui/components/core/seamly-api-context.ts +7 -0
  124. package/src/javascripts/ui/components/core/seamly-chat.tsx +8 -0
  125. package/src/javascripts/ui/components/core/{seamly-core.js → seamly-core.tsx} +27 -14
  126. package/src/javascripts/ui/components/core/seamly-event-subscriber.ts +340 -0
  127. package/src/javascripts/ui/components/core/seamly-file-upload.js +2 -2
  128. package/src/javascripts/ui/components/core/seamly-idle-detach-counter.js +1 -1
  129. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +24 -11
  130. package/src/javascripts/ui/components/core/seamly-live-region.js +4 -4
  131. package/src/javascripts/ui/components/core/seamly-new-notifications.js +3 -3
  132. package/src/javascripts/ui/components/core/seamly-read-state.js +2 -33
  133. package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +4 -4
  134. package/src/javascripts/ui/components/entry/entry-container.js +8 -8
  135. package/src/javascripts/ui/components/entry/text-entry/hooks.js +3 -3
  136. package/src/javascripts/ui/components/entry/text-entry/index.js +3 -3
  137. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +4 -4
  138. package/src/javascripts/ui/components/entry/upload/file-upload-form.js +3 -3
  139. package/src/javascripts/ui/components/entry/upload/index.js +5 -5
  140. package/src/javascripts/ui/components/entry/upload-toggle.js +6 -6
  141. package/src/javascripts/ui/components/faq/faq.js +14 -14
  142. package/src/javascripts/ui/components/form-controls/error.js +2 -2
  143. package/src/javascripts/ui/components/form-controls/file-input.js +3 -3
  144. package/src/javascripts/ui/components/layout/agent-info.js +3 -3
  145. package/src/javascripts/ui/components/layout/chat-frame.js +20 -12
  146. package/src/javascripts/ui/components/layout/chat.js +5 -5
  147. package/src/javascripts/ui/components/layout/deprecated-app-frame.js +6 -6
  148. package/src/javascripts/ui/components/layout/deprecated-chat-frame.js +34 -0
  149. package/src/javascripts/ui/components/layout/header.js +2 -2
  150. package/src/javascripts/ui/components/layout/icon.js +11 -9
  151. package/src/javascripts/ui/components/layout/interrupt.js +7 -5
  152. package/src/javascripts/ui/components/layout/pre-chat-messages.js +1 -1
  153. package/src/javascripts/ui/components/layout/privacy-disclaimer.js +2 -2
  154. package/src/javascripts/ui/components/options/options-button.js +5 -5
  155. package/src/javascripts/ui/components/options/{options-frame.js → options-frame.tsx} +52 -18
  156. package/src/javascripts/ui/components/options/transcript/index.js +9 -10
  157. package/src/javascripts/ui/components/options/transcript/transcript-form.js +2 -2
  158. package/src/javascripts/ui/components/suggestions/index.js +8 -8
  159. package/src/javascripts/ui/components/suggestions/suggestions-item.js +1 -1
  160. package/src/javascripts/{domains/translations/components/chat-status.js → ui/components/translation-chat-status/index.tsx} +13 -14
  161. package/src/javascripts/ui/components/translation-proposal/index.tsx +36 -0
  162. package/src/javascripts/ui/components/view/app-view.js +2 -7
  163. package/src/javascripts/ui/components/view/deprecated-view.js +8 -10
  164. package/src/javascripts/ui/components/view/index.js +6 -6
  165. package/src/javascripts/ui/components/view/inline-view.js +4 -8
  166. package/src/javascripts/ui/components/view/window-view/collapse-button.js +2 -2
  167. package/src/javascripts/ui/components/view/window-view/index.js +11 -17
  168. package/src/javascripts/ui/components/view/window-view/window-open-button.js +6 -6
  169. package/src/javascripts/ui/components/warnings/idle-detach-warning.js +3 -3
  170. package/src/javascripts/ui/components/warnings/prompt.js +1 -1
  171. package/src/javascripts/ui/components/warnings/resume-conversation-prompt.js +4 -4
  172. package/src/javascripts/ui/components/widgets/in-out-transition.js +20 -18
  173. package/src/javascripts/ui/components/widgets/lightbox.js +3 -3
  174. package/src/javascripts/ui/components/widgets/modal.js +2 -2
  175. package/src/javascripts/ui/components/widgets/upload-progress.js +2 -2
  176. package/src/javascripts/ui/hooks/file-upload-hooks.js +1 -1
  177. package/src/javascripts/ui/hooks/focus-helper-hooks.js +1 -1
  178. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +6 -6
  179. package/src/javascripts/ui/hooks/seamly-hooks.js +11 -10
  180. package/src/javascripts/ui/hooks/seamly-option-hooks.js +6 -6
  181. package/src/javascripts/ui/hooks/{seamly-state-hooks.js → seamly-state-hooks.ts} +9 -6
  182. package/src/javascripts/ui/hooks/use-click-outside.ts +29 -0
  183. package/src/javascripts/ui/hooks/use-event-component-mapping.js +11 -10
  184. package/src/javascripts/ui/hooks/use-interval.js +1 -1
  185. package/src/javascripts/ui/hooks/use-seamly-actions.ts +29 -29
  186. package/src/javascripts/ui/hooks/use-seamly-chat.js +14 -24
  187. package/src/javascripts/ui/hooks/use-seamly-commands.js +20 -15
  188. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +8 -8
  189. package/src/javascripts/ui/hooks/use-seamly-resume-conversation-prompt.js +2 -2
  190. package/src/javascripts/ui/hooks/use-single-file-upload.js +1 -1
  191. package/src/javascripts/ui/hooks/utility-hooks.js +1 -1
  192. package/src/javascripts/ui/utils/general-utils.js +0 -23
  193. package/src/javascripts/ui/utils/seamly-utils.ts +10 -1
  194. package/src/javascripts/ui/utils/seamly-utils.types.ts +9 -0
  195. package/src/stylesheets/1-settings/_config.scss +1 -1
  196. package/src/stylesheets/3-chat/_chat.scss +24 -9
  197. package/src/stylesheets/5-components/_chat-status.scss +72 -16
  198. package/src/stylesheets/5-components/_conversation.scss +35 -1
  199. package/src/stylesheets/5-components/_disclaimer.scss +0 -5
  200. package/src/stylesheets/5-components/_options.scss +16 -6
  201. package/src/stylesheets/5-components/_translation-options.scss +51 -0
  202. package/src/stylesheets/6-default-implementation/_scrollbar.scss +1 -1
  203. package/src/stylesheets/7-deprecated/3-app/_app.scss +19 -4
  204. package/src/stylesheets/7-deprecated/5-components/_chat-status.scss +5 -0
  205. package/src/stylesheets/7-deprecated/5-components/_options.scss +1 -4
  206. package/src/stylesheets/7-deprecated/5-components/_translation-options.scss +49 -0
  207. package/src/stylesheets/deprecated-view.scss +1 -0
  208. package/src/stylesheets/styles.scss +1 -0
  209. package/webpack/config.common.js +4 -4
  210. package/webpack/config.package.js +10 -16
  211. package/webpack/config.site.js +4 -1
  212. package/webpack/config.test.js +2 -1
  213. package/build/dist/lib/deprecated-view.css +0 -1
  214. package/build/dist/lib/styles-default-implementation.css +0 -1
  215. package/build/dist/lib/styles.css +0 -1
  216. package/src/.DS_Store +0 -0
  217. package/src/javascripts/api/event-producer.js +0 -20
  218. package/src/javascripts/api/producer.js +0 -136
  219. package/src/javascripts/domains/errors/index.js +0 -37
  220. package/src/javascripts/domains/translations/components/options-dialog/form.js +0 -70
  221. package/src/javascripts/domains/translations/components/options-dialog/index.js +0 -87
  222. package/src/javascripts/ui/components/chat-status/index.js +0 -38
  223. package/src/javascripts/ui/components/conversation/event/event.js +0 -36
  224. package/src/javascripts/ui/components/core/seamly-api-context.js +0 -5
  225. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +0 -279
@@ -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
+ })
@@ -1,25 +1,24 @@
1
- import { createSlice, PayloadAction } from '@reduxjs/toolkit'
1
+ import { PayloadAction, createSlice, isAnyOf } from '@reduxjs/toolkit'
2
+ import { ConversationHistoryResponse } from 'api'
3
+ import { getTimeFromSeconds } from 'ui/utils/general-utils'
4
+ import {
5
+ entryTypes,
6
+ eventTypes,
7
+ featureKeys,
8
+ payloadTypes,
9
+ readStates,
10
+ } from 'ui/utils/seamly-utils'
2
11
  import { initializeApp, resetApp } from 'domains/app/actions'
3
12
  import { initializeConfig } from 'domains/config/actions'
4
- import {
13
+ import type {
14
+ AckEvent,
5
15
  ChannelEvent,
6
16
  EntryMeta,
7
- HistoryEvents,
8
- HistoryResponse,
9
17
  MessageParticipant,
10
- ParticipantInfo,
11
18
  ServiceInfo,
12
19
  StoreState,
13
20
  } from 'domains/store/store.types'
14
21
  import { randomId } from 'lib/id'
15
- import { getTimeFromSeconds } from 'ui/utils/general-utils'
16
- import {
17
- entryTypes,
18
- eventTypes,
19
- featureKeys,
20
- payloadTypes,
21
- readStates,
22
- } from 'ui/utils/seamly-utils'
23
22
 
24
23
  export const isUnreadMessage = ({ type, payload }: ChannelEvent) =>
25
24
  (type === eventTypes.message && !payload.fromClient) ||
@@ -36,7 +35,7 @@ export const orderHistory = (events: ChannelEvent[]) => {
36
35
 
37
36
  export const mergeHistory = (
38
37
  stateEvents: ChannelEvent[],
39
- historyEvents: HistoryEvents,
38
+ historyEvents: ConversationHistoryResponse['events'],
40
39
  ) => {
41
40
  const newStateEvents = stateEvents.filter(
42
41
  (stateEvent) =>
@@ -102,10 +101,10 @@ const participantReducer = (participantInfo, action) => {
102
101
 
103
102
  const calculateNewEntryMeta = (
104
103
  entryMeta: EntryMeta,
105
- channelEvent: ChannelEvent,
104
+ channelEvent?: ChannelEvent,
106
105
  ) => {
107
106
  const entry =
108
- channelEvent.type === 'message' ? channelEvent?.payload.entry : {}
107
+ channelEvent?.type === 'message' ? channelEvent?.payload.entry : {}
109
108
 
110
109
  const { blockAutoEntrySwitch } = entryMeta
111
110
 
@@ -133,6 +132,7 @@ const calculateNewEntryMeta = (
133
132
 
134
133
  export const initialStoreState: StoreState = {
135
134
  events: [],
135
+ isLastEventFromClient: false,
136
136
  initialState: {
137
137
  userResponded: false,
138
138
  },
@@ -243,15 +243,19 @@ export const storeSlice = createSlice({
243
243
 
244
244
  if (incrementUnread) {
245
245
  state.unreadEvents += 1
246
- action.payload.payload.messageStatus = payload.fromClient
247
- ? readStates.read
248
- : readStates.received
246
+ if (eventType !== 'service_data') {
247
+ action.payload.payload.messageStatus = payload.fromClient
248
+ ? readStates.read
249
+ : readStates.received
250
+ }
249
251
  }
250
252
 
251
253
  action.payload.payload.key = randomId()
252
254
  state.events.push(action.payload)
255
+
256
+ state.isLastEventFromClient = payload.fromClient
253
257
  },
254
- ackEvent: (state, { payload }) => {
258
+ ackEvent: (state, { payload: { payload } }: PayloadAction<AckEvent>) => {
255
259
  // If any ACKs are sent without transactionID the conversation crashes.
256
260
  // Ensure that this edge case is handled gracefully.
257
261
  if (!payload.transactionId) {
@@ -260,22 +264,17 @@ export const storeSlice = createSlice({
260
264
  }
261
265
 
262
266
  const matchedEvent = state.events.find(
263
- (m) => m.payload.transactionId === payload.transactionId,
267
+ (m) =>
268
+ m.payload.transactionId === payload.transactionId &&
269
+ (!payload.type || m.type === payload.type),
264
270
  )
265
271
 
266
272
  const { id, occurredAt } = payload
267
273
  if (matchedEvent) {
268
- state.events = orderHistory(
269
- //@ts-ignore
270
- state.events.map((m) =>
271
- m.payload.id === matchedEvent.payload.id
272
- ? {
273
- ...m,
274
- payload: { ...m.payload, id, occurredAt },
275
- }
276
- : m,
277
- ),
278
- )
274
+ matchedEvent.payload.id = id
275
+ matchedEvent.payload.occurredAt = occurredAt
276
+
277
+ state.events = orderHistory(state.events)
279
278
  }
280
279
  },
281
280
  clearEvents: (state) => {
@@ -283,15 +282,16 @@ export const storeSlice = createSlice({
283
282
  state.loadedImageEventIds = []
284
283
  state.events = []
285
284
  },
286
- setEventsRead: (state, { payload }) => {
285
+ setEventsRead: (state, { payload }: PayloadAction<string[]>) => {
287
286
  state.unreadEvents = 0
288
287
  state.events.forEach((event) => {
289
288
  if (payload.indexOf(event.payload.id) !== -1) {
290
289
  event.payload = {
291
290
  ...event.payload,
292
- ...(event.payload.messageStatus === readStates.received && {
293
- messageStatus: readStates.read,
294
- }),
291
+ ...(event.type !== 'service_data' &&
292
+ event.payload.messageStatus === readStates.received && {
293
+ messageStatus: readStates.read,
294
+ }),
295
295
  }
296
296
  }
297
297
  return event
@@ -311,7 +311,7 @@ export const storeSlice = createSlice({
311
311
  serviceData,
312
312
  resumeConversationPrompt,
313
313
  },
314
- }: PayloadAction<HistoryResponse>,
314
+ }: PayloadAction<ConversationHistoryResponse>,
315
315
  ) => {
316
316
  const events = mergeHistory(state.events, history)
317
317
 
@@ -341,14 +341,14 @@ export const storeSlice = createSlice({
341
341
  }
342
342
 
343
343
  const { entry } = activeServiceSettings
344
- const { upload } = entry.options
344
+ const upload = entry?.options?.upload
345
345
 
346
346
  const historyNewEntryMeta = calculateNewEntryMeta(
347
347
  {
348
348
  ...state.entryMeta,
349
349
  ...entry,
350
- active: entry.default || payloadTypes.text,
351
- options: { ...(entry && entry.options ? entry.options : {}) },
350
+ active: entry?.default || payloadTypes.text,
351
+ options: { ...(entry?.options ? entry.options : {}) },
352
352
  },
353
353
  events[events.length - 1],
354
354
  )
@@ -366,7 +366,7 @@ export const storeSlice = createSlice({
366
366
  newFeatures = {
367
367
  ...newFeatures,
368
368
  uploads: {
369
- enabled: !!(upload && upload.enabled),
369
+ enabled: !!(upload && upload?.enabled),
370
370
  enabledFromEntry: entryType === entryTypes.upload,
371
371
  },
372
372
  }
@@ -601,6 +601,12 @@ export const storeSlice = createSlice({
601
601
  if (!payload.initialState) return
602
602
  state.initialState = payload.initialState
603
603
  })
604
+ .addMatcher(
605
+ isAnyOf(initIdleDetachCountdown, initResumeConversationPrompt),
606
+ (state) => {
607
+ state.isLastEventFromClient = false
608
+ },
609
+ )
604
610
  },
605
611
  })
606
612