@seamly/web-ui 21.0.7 → 22.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/build/dist/lib/components.js +9354 -7909
  2. package/build/dist/lib/components.js.map +1 -0
  3. package/build/dist/lib/components.min.js +2 -1
  4. package/build/dist/lib/components.min.js.LICENSE.txt +2 -2
  5. package/build/dist/lib/components.min.js.map +1 -0
  6. package/build/dist/lib/config.js +2 -1
  7. package/build/dist/lib/config.js.map +1 -0
  8. package/build/dist/lib/config.min.js +2 -1
  9. package/build/dist/lib/config.min.js.map +1 -0
  10. package/build/dist/lib/contexts.js +2 -1
  11. package/build/dist/lib/contexts.js.map +1 -0
  12. package/build/dist/lib/contexts.min.js +2 -1
  13. package/build/dist/lib/contexts.min.js.map +1 -0
  14. package/build/dist/lib/deprecated-view.css +1 -1
  15. package/build/dist/lib/deprecated-view.js +1 -1
  16. package/build/dist/lib/hooks.js +7006 -5903
  17. package/build/dist/lib/hooks.js.map +1 -0
  18. package/build/dist/lib/hooks.min.js +2 -1
  19. package/build/dist/lib/hooks.min.js.map +1 -0
  20. package/build/dist/lib/index.debug.js +965 -384
  21. package/build/dist/lib/index.debug.js.map +1 -0
  22. package/build/dist/lib/index.debug.min.js +2 -1
  23. package/build/dist/lib/index.debug.min.js.LICENSE.txt +336 -108
  24. package/build/dist/lib/index.debug.min.js.map +1 -0
  25. package/build/dist/lib/index.js +2991 -5664
  26. package/build/dist/lib/index.js.map +1 -0
  27. package/build/dist/lib/index.min.js +2 -1
  28. package/build/dist/lib/index.min.js.LICENSE.txt +2 -2
  29. package/build/dist/lib/index.min.js.map +1 -0
  30. package/build/dist/lib/sounds/beep.mp3 +0 -0
  31. package/build/dist/lib/standalone.js +9461 -12461
  32. package/build/dist/lib/standalone.js.map +1 -0
  33. package/build/dist/lib/standalone.min.js +2 -1
  34. package/build/dist/lib/standalone.min.js.LICENSE.txt +1 -1
  35. package/build/dist/lib/standalone.min.js.map +1 -0
  36. package/build/dist/lib/storage.js +2 -1
  37. package/build/dist/lib/storage.js.map +1 -0
  38. package/build/dist/lib/storage.min.js +2 -1
  39. package/build/dist/lib/storage.min.js.map +1 -0
  40. package/build/dist/lib/style-guide.js +1831 -6023
  41. package/build/dist/lib/style-guide.js.map +1 -0
  42. package/build/dist/lib/style-guide.min.js +2 -1
  43. package/build/dist/lib/style-guide.min.js.LICENSE.txt +2 -2
  44. package/build/dist/lib/style-guide.min.js.map +1 -0
  45. package/build/dist/lib/styles-default-implementation.css +1 -1
  46. package/build/dist/lib/styles-default-implementation.js +1 -1
  47. package/build/dist/lib/styles.css +1 -1
  48. package/build/dist/lib/styles.js +1 -1
  49. package/build/dist/lib/utils.js +11598 -14588
  50. package/build/dist/lib/utils.js.map +1 -0
  51. package/build/dist/lib/utils.min.js +2 -1
  52. package/build/dist/lib/utils.min.js.LICENSE.txt +1 -6
  53. package/build/dist/lib/utils.min.js.map +1 -0
  54. package/package.json +58 -48
  55. package/src/javascripts/api/conversation-connector.ts +2 -0
  56. package/src/javascripts/api/errors/seamly-api-error.ts +15 -0
  57. package/src/javascripts/api/index.ts +168 -94
  58. package/src/javascripts/config.ts +1 -1
  59. package/src/javascripts/config.types.ts +18 -11
  60. package/src/javascripts/domains/config/selectors.ts +1 -1
  61. package/src/javascripts/domains/config/slice.ts +12 -0
  62. package/src/javascripts/domains/forms/forms.types.ts +1 -0
  63. package/src/javascripts/domains/forms/hooks.ts +10 -2
  64. package/src/javascripts/domains/i18n/slice.ts +2 -0
  65. package/src/javascripts/domains/interrupt/hooks.ts +15 -7
  66. package/src/javascripts/domains/interrupt/middleware.ts +7 -14
  67. package/src/javascripts/domains/interrupt/selectors.ts +4 -0
  68. package/src/javascripts/domains/interrupt/slice.ts +2 -2
  69. package/src/javascripts/domains/store/selectors.ts +23 -10
  70. package/src/javascripts/domains/store/slice.ts +63 -11
  71. package/src/javascripts/domains/store/store.types.ts +39 -1
  72. package/src/javascripts/domains/translations/components/options-button.tsx +1 -4
  73. package/src/javascripts/domains/translations/components/translation-status.tsx +4 -3
  74. package/src/javascripts/domains/translations/hooks.ts +11 -4
  75. package/src/javascripts/domains/translations/slice.ts +2 -0
  76. package/src/javascripts/index.ts +2 -0
  77. package/src/javascripts/lib/url-helpers.ts +24 -0
  78. package/src/javascripts/schema.ts +10 -0
  79. package/src/javascripts/style-guide/states.js +65 -0
  80. package/src/javascripts/ui/components/app-options/index.js +4 -3
  81. package/src/javascripts/ui/components/conversation/conversation.tsx +2 -0
  82. package/src/javascripts/ui/components/conversation/event/chat-scroll/chat-scroll-provider.tsx +2 -0
  83. package/src/javascripts/ui/components/conversation/event/choice-prompt.js +1 -1
  84. package/src/javascripts/ui/components/conversation/event/text.js +1 -1
  85. package/src/javascripts/ui/components/conversation/event/upload.js +50 -9
  86. package/src/javascripts/ui/components/conversation/use-chat-scroll.ts +3 -2
  87. package/src/javascripts/ui/components/core/seamly-event-subscriber.ts +16 -14
  88. package/src/javascripts/ui/components/core/seamly-file-upload.tsx +156 -0
  89. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +5 -5
  90. package/src/javascripts/ui/components/entry/abort-transaction-button/abort-transaction-button.tsx +45 -0
  91. package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +4 -3
  92. package/src/javascripts/ui/components/entry/text-entry/hooks.ts +108 -0
  93. package/src/javascripts/ui/components/entry/text-entry/index.js +7 -4
  94. package/src/javascripts/ui/components/entry/text-entry/{text-entry-form.js → text-entry-form.tsx} +8 -22
  95. package/src/javascripts/ui/components/faq/faq.js +5 -4
  96. package/src/javascripts/ui/components/form-controls/{input.js → input.tsx} +13 -2
  97. package/src/javascripts/ui/components/form-controls/{wrapper.js → wrapper.tsx} +8 -4
  98. package/src/javascripts/ui/components/layout/agent-info.js +4 -3
  99. package/src/javascripts/ui/components/layout/chat-frame.js +7 -8
  100. package/src/javascripts/ui/components/layout/deprecated-chat-frame.js +7 -8
  101. package/src/javascripts/ui/components/layout/interrupt.js +6 -15
  102. package/src/javascripts/ui/components/layout/pre-chat-messages.js +4 -3
  103. package/src/javascripts/ui/components/suggestions/index.js +5 -4
  104. package/src/javascripts/ui/components/translation-chat-status/index.tsx +4 -3
  105. package/src/javascripts/ui/components/view/app-view.js +1 -2
  106. package/src/javascripts/ui/components/view/deprecated-view.js +1 -2
  107. package/src/javascripts/ui/components/view/{index.js → index.tsx} +53 -4
  108. package/src/javascripts/ui/components/view/inline-view.js +1 -11
  109. package/src/javascripts/ui/components/view/window-view/{index.js → index.tsx} +15 -11
  110. package/src/javascripts/ui/components/view/window-view/window-open-button.js +4 -3
  111. package/src/javascripts/ui/components/widgets/{in-out-transition.js → in-out-transition.tsx} +67 -28
  112. package/src/javascripts/ui/hooks/sounds/beep.mp3 +0 -0
  113. package/src/javascripts/ui/hooks/use-click-outside.ts +5 -3
  114. package/src/javascripts/ui/hooks/use-notifications.ts +114 -0
  115. package/src/javascripts/ui/hooks/{use-seamly-chat.js → use-seamly-chat.ts} +5 -1
  116. package/src/javascripts/ui/hooks/use-session-expired-command.ts +17 -0
  117. package/src/javascripts/ui/hooks/use-timeout.ts +20 -0
  118. package/src/stylesheets/3-chat/_chat.scss +3 -5
  119. package/src/stylesheets/4-base/_formelements.scss +0 -36
  120. package/src/stylesheets/5-components/_abort-transaction.scss +10 -0
  121. package/src/stylesheets/5-components/_buttons.scss +18 -3
  122. package/src/stylesheets/5-components/_character-limit.scss +2 -2
  123. package/src/stylesheets/5-components/_chat-status.scss +26 -37
  124. package/src/stylesheets/5-components/_choice-prompt.scss +9 -10
  125. package/src/stylesheets/5-components/_conversation.scss +9 -62
  126. package/src/stylesheets/5-components/_disclaimer.scss +11 -3
  127. package/src/stylesheets/5-components/_error.scss +3 -2
  128. package/src/stylesheets/5-components/_idle.scss +3 -8
  129. package/src/stylesheets/5-components/_input.scss +34 -13
  130. package/src/stylesheets/5-components/_interrupt.scss +3 -10
  131. package/src/stylesheets/5-components/_loader.scss +1 -2
  132. package/src/stylesheets/5-components/_message-author.scss +2 -4
  133. package/src/stylesheets/5-components/_message-body.scss +33 -10
  134. package/src/stylesheets/5-components/_message-card.scss +2 -10
  135. package/src/stylesheets/5-components/_message-carousel.scss +4 -4
  136. package/src/stylesheets/5-components/_message-cta.scss +0 -6
  137. package/src/stylesheets/5-components/_message.scss +1 -0
  138. package/src/stylesheets/5-components/_modal.scss +2 -5
  139. package/src/stylesheets/5-components/_options.scss +17 -22
  140. package/src/stylesheets/5-components/_pre-chat-messages.scss +3 -1
  141. package/src/stylesheets/5-components/_prompt.scss +3 -7
  142. package/src/stylesheets/5-components/_skip-link.scss +2 -1
  143. package/src/stylesheets/5-components/_suggestions.scss +2 -2
  144. package/src/stylesheets/5-components/_translation-options.scss +5 -2
  145. package/src/stylesheets/5-components/_unread-messages.scss +33 -0
  146. package/src/stylesheets/5-components/_upload.scss +20 -27
  147. package/src/stylesheets/6-default-implementation/_hover.scss +14 -17
  148. package/src/stylesheets/7-deprecated/1-settings/_config.scss +17 -0
  149. package/src/stylesheets/7-deprecated/3-app/_app.scss +2 -1
  150. package/src/stylesheets/7-deprecated/5-components/_card.scss +1 -0
  151. package/src/stylesheets/7-deprecated/5-components/_chat-status.scss +66 -20
  152. package/src/stylesheets/7-deprecated/5-components/_conversation.scss +1 -4
  153. package/src/stylesheets/7-deprecated/5-components/_input.scss +6 -1
  154. package/src/stylesheets/7-deprecated/5-components/_interrupt.scss +1 -4
  155. package/src/stylesheets/7-deprecated/5-components/_message.scss +49 -12
  156. package/src/stylesheets/7-deprecated/5-components/_translation-options.scss +30 -37
  157. package/src/stylesheets/7-deprecated/5-components/_unread-messages.scss +38 -0
  158. package/src/stylesheets/deprecated-view.scss +1 -0
  159. package/src/stylesheets/styles.scss +2 -0
  160. package/webpack/config.common.js +6 -1
  161. package/webpack/config.package.js +18 -0
  162. package/webpack/defaults.js +1 -1
  163. package/src/javascripts/ui/components/core/seamly-file-upload.js +0 -86
  164. package/src/javascripts/ui/components/entry/text-entry/hooks.js +0 -46
@@ -6,7 +6,7 @@ import {
6
6
  useMemo,
7
7
  } from 'preact/hooks'
8
8
  import { useDispatch, useSelector } from 'react-redux'
9
- import type { FormContextType } from 'domains/forms/forms.types'
9
+ import type { ControlState, FormContextType } from 'domains/forms/forms.types'
10
10
  import {
11
11
  getControlTouchedByName,
12
12
  getControlValueByName,
@@ -42,7 +42,15 @@ export function useValidations(values, validationSchema) {
42
42
  }
43
43
  }
44
44
 
45
- export function useFormControl(name) {
45
+ export function useFormControl(name): [
46
+ {
47
+ name: string
48
+ onInput: (_e: any) => void
49
+ onBlur: () => void
50
+ value: string
51
+ },
52
+ ControlState,
53
+ ] {
46
54
  const dispatch = useDispatch()
47
55
  const { formId, updateControlValue, updateControlTouched, errors } =
48
56
  useFormContext()
@@ -1,4 +1,5 @@
1
1
  import { createSlice } from '@reduxjs/toolkit'
2
+ import { resetApp } from 'domains/app/actions'
2
3
  import { initializeConfig } from 'domains/config/actions'
3
4
  import { setLocale } from 'domains/i18n/actions'
4
5
  import { I18nState } from 'domains/i18n/i18n.types'
@@ -46,6 +47,7 @@ export const i18nSlice = createSlice({
46
47
  extraReducers: (builder) => {
47
48
  // Add reducers for additional action types here, and handle loading state as needed
48
49
  builder
50
+ .addCase(resetApp.pending, () => initialState)
49
51
  .addCase(initializeConfig.fulfilled, (state, { payload }) => {
50
52
  state.initialLocale = payload.locale
51
53
  })
@@ -1,16 +1,24 @@
1
1
  import { useMemo } from 'preact/hooks'
2
2
  import { useSelector } from 'react-redux'
3
3
  import { useI18n } from 'domains/i18n/hooks'
4
- import * as Selectors from './selectors'
4
+ import { selectError, selectHasError } from './selectors'
5
5
 
6
6
  export function useInterrupt() {
7
7
  const { t } = useI18n()
8
-
9
- const error = useSelector(Selectors.selectError)
10
- const hasInterrupt = Boolean(error)
8
+ const error = useSelector(selectError)
9
+ const hasError = useSelector(selectHasError)
11
10
 
12
11
  const meta = useMemo(() => {
13
- if (!error) return {}
12
+ if (!hasError) {
13
+ return {
14
+ title: undefined,
15
+ message: undefined,
16
+ srText: undefined,
17
+ buttonText: undefined,
18
+ originalError: undefined,
19
+ }
20
+ }
21
+
14
22
  const { langKey, action } = error
15
23
  const title = t(`${langKey}.title`)
16
24
  const message = t(`${langKey}.message`)
@@ -23,7 +31,7 @@ export function useInterrupt() {
23
31
  ...(action && langKey ? { buttonText } : {}),
24
32
  originalError: error,
25
33
  }
26
- }, [t, error])
34
+ }, [hasError, error, t])
27
35
 
28
- return { hasInterrupt, meta, error }
36
+ return { hasError, meta, error }
29
37
  }
@@ -1,27 +1,20 @@
1
- import SeamlyConfigurationError from 'api/errors/seamly-configuration-error'
2
1
  import SeamlyGeneralError from 'api/errors/seamly-general-error'
3
- import SeamlyOfflineError from 'api/errors/seamly-offline-error'
4
- import SeamlySessionExpiredError from 'api/errors/seamly-session-expired-error'
5
- import SeamlyUnauthorizedError from 'api/errors/seamly-unauthorized-error'
6
- import SeamlyUnavailableError from 'api/errors/seamly-unavailable-error'
7
2
  import { setInterrupt } from 'domains/interrupt/slice'
8
3
 
9
4
  const handledErrorTypes = [
10
- SeamlyGeneralError,
11
- SeamlyConfigurationError,
12
- SeamlySessionExpiredError,
13
- SeamlyOfflineError,
14
- SeamlyUnauthorizedError,
15
- SeamlyUnavailableError,
5
+ 'SeamlyGeneralError',
6
+ 'SeamlyConfigurationError',
7
+ 'SeamlySessionExpiredError',
8
+ 'SeamlyOfflineError',
9
+ 'SeamlyUnauthorizedError',
10
+ 'SeamlyUnavailableError',
16
11
  ]
17
12
 
18
13
  export default function createInterruptMiddleware({ api }) {
19
14
  return () => (next) => (action) => {
20
15
  const { payload, type } = action
21
16
  if (type === setInterrupt.type) {
22
- if (
23
- !handledErrorTypes.some((ErrorType) => payload.name === ErrorType.name)
24
- ) {
17
+ if (!handledErrorTypes.includes(payload.name)) {
25
18
  throw new SeamlyGeneralError(payload)
26
19
  } else if (payload.action === 'reset') {
27
20
  // [SMLY-942] We clear the store before a reset to force a new conversation if the page is refreshed before the conversation is reset
@@ -4,3 +4,7 @@ export const selectError = createSelector(
4
4
  ({ interrupt }) => interrupt,
5
5
  ({ error }) => error,
6
6
  )
7
+
8
+ export const selectHasError = createSelector(selectError, (error) =>
9
+ Boolean(error),
10
+ )
@@ -1,5 +1,5 @@
1
1
  import { createSlice, isAnyOf } from '@reduxjs/toolkit'
2
- import { initializeApp, resetApp } from 'domains/app/actions'
2
+ import { initializeApp } from 'domains/app/actions'
3
3
  import { initializeConfig } from 'domains/config/actions'
4
4
  import { setLocale } from 'domains/i18n/actions'
5
5
  import { initializeVisibility, setVisibility } from 'domains/visibility/actions'
@@ -19,7 +19,7 @@ export const interruptSlice = createSlice({
19
19
  },
20
20
  extraReducers: (builder) => {
21
21
  builder
22
- .addCase(resetApp.fulfilled, () => initialState)
22
+ .addCase(initializeConfig.pending, () => initialState)
23
23
  .addMatcher(
24
24
  isAnyOf(
25
25
  initializeApp.rejected,
@@ -2,15 +2,28 @@ import { createSelector } from '@reduxjs/toolkit'
2
2
  import { selectEvents } from 'ui/hooks/seamly-state-hooks'
3
3
  import { readStates } from 'ui/utils/seamly-utils'
4
4
  import { isUnreadMessage } from 'domains/store/slice'
5
+ import { RootState } from '.'
5
6
 
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)
7
+ export const selectUnreadEvents = createSelector(selectEvents, (events) => {
8
+ return events.filter((event) => {
9
+ return (
10
+ isUnreadMessage(event) &&
11
+ event.type !== 'service_data' &&
12
+ event.payload?.messageStatus === readStates.received
13
+ )
14
+ })
16
15
  })
16
+ export const selectLastUnreadEvent = createSelector(
17
+ selectUnreadEvents,
18
+ (events) => events.at(-1),
19
+ )
20
+
21
+ export const selectUnreadEventIds = createSelector(
22
+ selectUnreadEvents,
23
+ (events) => events.map((event) => event.payload.id),
24
+ )
25
+
26
+ export const selectShowNotifications = createSelector(
27
+ ({ state }: RootState) => state.options.features?.webNotifications,
28
+ (webNotifications) => webNotifications?.enabled,
29
+ )
@@ -1,5 +1,5 @@
1
1
  import { PayloadAction, createSlice, isAnyOf } from '@reduxjs/toolkit'
2
- import { ConversationHistoryResponse } from 'api'
2
+ import type { ConversationHistoryResponse } from 'api'
3
3
  import { getTimeFromSeconds } from 'ui/utils/general-utils'
4
4
  import {
5
5
  entryTypes,
@@ -13,6 +13,7 @@ import { initializeConfig } from 'domains/config/actions'
13
13
  import type {
14
14
  AckEvent,
15
15
  ChannelEvent,
16
+ CurrentUploadPayload,
16
17
  EntryMeta,
17
18
  MessageParticipant,
18
19
  ServiceInfo,
@@ -21,6 +22,7 @@ import type {
21
22
  import { randomId } from 'lib/id'
22
23
 
23
24
  export const isUnreadMessage = ({ type, payload }: ChannelEvent) =>
25
+ document.visibilityState === 'hidden' ||
24
26
  (type === eventTypes.message && !payload.fromClient) ||
25
27
  (type === eventTypes.info && payload.type === payloadTypes.text)
26
28
 
@@ -104,12 +106,19 @@ const calculateNewEntryMeta = (
104
106
  channelEvent?: ChannelEvent,
105
107
  ) => {
106
108
  const entry =
107
- channelEvent?.type === 'message' ? channelEvent?.payload.entry : {}
109
+ channelEvent?.type === 'message'
110
+ ? channelEvent?.payload.translatedEntry || channelEvent?.payload.entry
111
+ : {}
108
112
 
109
113
  const { blockAutoEntrySwitch } = entryMeta
114
+ const actions = channelEvent?.payload?.actions || {}
110
115
 
111
116
  if (!entry) {
112
- return { ...entryMeta, optionsOverride: {} }
117
+ return {
118
+ ...entryMeta,
119
+ optionsOverride: {},
120
+ actions,
121
+ }
113
122
  }
114
123
 
115
124
  const { type, options } = entry
@@ -127,6 +136,7 @@ const calculateNewEntryMeta = (
127
136
  ...entryMeta.optionsOverride,
128
137
  [type]: options || {},
129
138
  },
139
+ actions,
130
140
  }
131
141
  }
132
142
 
@@ -151,6 +161,7 @@ export const initialStoreState: StoreState = {
151
161
  resumeConversationPrompt: false,
152
162
  serviceInfo: {
153
163
  activeServiceSessionId: '',
164
+ proactiveMessages: false,
154
165
  },
155
166
  participantInfo: {
156
167
  participants: {},
@@ -166,13 +177,18 @@ export const initialStoreState: StoreState = {
166
177
  headerCollapseButtonId: randomId(),
167
178
  serviceData: {},
168
179
  options: {
169
- features: {},
180
+ features: {
181
+ webNotifications: {
182
+ enabled: false,
183
+ },
184
+ },
170
185
  panelActive: false,
171
186
  optionActive: '',
172
187
  userSelectedOptions: {},
173
188
  },
174
189
  showFileUpload: false,
175
190
  currentUploads: [],
191
+ processingFileUploads: [],
176
192
  entryMeta: {
177
193
  default: entryTypes.text,
178
194
  active: entryTypes.text,
@@ -180,6 +196,7 @@ export const initialStoreState: StoreState = {
180
196
  blockAutoEntrySwitch: false,
181
197
  options: {},
182
198
  optionsOverride: {},
199
+ actions: {},
183
200
  },
184
201
  seamlyContainerElement: null,
185
202
  }
@@ -393,6 +410,8 @@ export const storeSlice = createSlice({
393
410
  state.serviceInfo = {
394
411
  ...state.serviceInfo,
395
412
  activeServiceSessionId,
413
+ proactiveMessages:
414
+ activeServiceSettings?.proactiveMessages?.enabled || false,
396
415
  }
397
416
 
398
417
  state.serviceData = {
@@ -402,7 +421,7 @@ export const storeSlice = createSlice({
402
421
 
403
422
  state.options = {
404
423
  ...state.options,
405
- features: newFeatures || {},
424
+ features: newFeatures,
406
425
  }
407
426
  state.entryMeta =
408
427
  !newFeaturesHasUpload &&
@@ -497,15 +516,15 @@ export const storeSlice = createSlice({
497
516
  state.options.features[payload.key].enabled = payload.enabled
498
517
  },
499
518
  clearFeatures: (state) => {
500
- // case CLEAR_FEATURES:
501
- state.options.features = {}
519
+ state.options.features = {
520
+ webNotifications: state.options.features.webNotifications,
521
+ }
502
522
  },
503
523
  showOption: (state, { payload }) => {
504
524
  state.options.panelActive = true
505
525
  state.options.optionActive = payload
506
526
  },
507
527
  hideOption: (state) => {
508
- // case HIDE_OPTION:
509
528
  state.options.panelActive = false
510
529
  state.options.optionActive = ''
511
530
  },
@@ -523,6 +542,7 @@ export const storeSlice = createSlice({
523
542
  state.entryMeta.active = payload.default
524
543
  state.entryMeta.options = payload.options || {}
525
544
  state.entryMeta.optionsOverride = {}
545
+ state.entryMeta.actions = {}
526
546
  },
527
547
  setActiveEntryType: (state, { payload }) => {
528
548
  state.entryMeta.active = payload
@@ -530,7 +550,13 @@ export const storeSlice = createSlice({
530
550
  setUserEntryType: (state, { payload }) => {
531
551
  state.entryMeta.userSelected = payload
532
552
  },
533
- registerUpload: (state, { payload }) => {
553
+ clearAbortTransaction: (state) => {
554
+ state.entryMeta.actions = {}
555
+ },
556
+ registerUpload: (
557
+ state,
558
+ { payload }: PayloadAction<Omit<CurrentUploadPayload, 'progress'>>,
559
+ ) => {
534
560
  state.currentUploads.push({
535
561
  id: payload.fileId,
536
562
  name: payload.fileName,
@@ -541,7 +567,12 @@ export const storeSlice = createSlice({
541
567
  uploadHandle: payload.uploadHandle,
542
568
  })
543
569
  },
544
- setUploadProgress: (state, { payload }) => {
570
+ setUploadProgress: (
571
+ state,
572
+ {
573
+ payload,
574
+ }: PayloadAction<Omit<CurrentUploadPayload, 'uploadHandle' | 'fileName'>>,
575
+ ) => {
545
576
  state.currentUploads = state.currentUploads.map((fileUpload) => {
546
577
  if (fileUpload.id === payload.fileId) {
547
578
  return {
@@ -555,6 +586,14 @@ export const storeSlice = createSlice({
555
586
  return fileUpload
556
587
  })
557
588
  },
589
+ startProcessingImage: (state, { payload }: PayloadAction<string>) => {
590
+ state.processingFileUploads.push(payload)
591
+ },
592
+ doneProcessingImage: (state, { payload }: PayloadAction<string>) => {
593
+ state.processingFileUploads = state.processingFileUploads.filter(
594
+ (fileId) => fileId !== payload,
595
+ )
596
+ },
558
597
  setUploadError: (state, { payload }) => {
559
598
  state.currentUploads = state.currentUploads.map((fileUpload) => {
560
599
  if (fileUpload.id === payload.fileId) {
@@ -569,7 +608,10 @@ export const storeSlice = createSlice({
569
608
  return fileUpload
570
609
  })
571
610
  },
572
- setUploadComplete: (state, { payload }) => {
611
+ setUploadComplete: (
612
+ state,
613
+ { payload }: PayloadAction<CurrentUploadPayload['fileId']>,
614
+ ) => {
573
615
  state.currentUploads = state.currentUploads.map((fileUpload) => {
574
616
  if (fileUpload.id === payload) {
575
617
  return {
@@ -586,6 +628,12 @@ export const storeSlice = createSlice({
586
628
  setSeamlyContainerElement: (state, { payload }) => {
587
629
  state.seamlyContainerElement = payload
588
630
  },
631
+ setProactiveMessages: (
632
+ state,
633
+ { payload }: PayloadAction<ServiceInfo['proactiveMessages']>,
634
+ ) => {
635
+ state.serviceInfo.proactiveMessages = payload
636
+ },
589
637
  },
590
638
  extraReducers: (builder) => {
591
639
  builder
@@ -640,14 +688,18 @@ export const {
640
688
  setSeamlyContainerElement,
641
689
  setServiceDataItem,
642
690
  setServiceEntryMetadata,
691
+ clearAbortTransaction,
643
692
  setUploadComplete,
644
693
  setUploadError,
645
694
  setUploadProgress,
695
+ startProcessingImage,
696
+ doneProcessingImage,
646
697
  setUserEntryType,
647
698
  setUserSelectedOption,
648
699
  setUserSelectedOptions,
649
700
  showOption,
650
701
  stopIdleDetachCountdownCounter,
702
+ setProactiveMessages,
651
703
  } = storeSlice.actions
652
704
 
653
705
  export default storeSlice.reducer
@@ -20,6 +20,14 @@ export type AckEvent = {
20
20
  type DefaultEventProps = {
21
21
  timeIndicator?: number
22
22
  key?: string
23
+ translatedEntry?: components['schemas']['MessageMessage']['entry']
24
+ actions?: {
25
+ abortTransaction?: {
26
+ label: string
27
+ topicFallbackMessage: string
28
+ topicName: string
29
+ }
30
+ }
23
31
  }
24
32
 
25
33
  export type MessageInfo = components['schemas']['MessageInfo'] &
@@ -32,6 +40,12 @@ export interface InfoEvent {
32
40
  export type MessageMessage = components['schemas']['MessageMessage'] & {
33
41
  key?: string
34
42
  }
43
+
44
+ export type MessageUpload = components['schemas']['MessageMessage'] & {
45
+ body: components['schemas']['MessageBodyUpload'] &
46
+ components['schemas']['MessageBodyVideo']
47
+ }
48
+
35
49
  export interface MessageEvent {
36
50
  type: 'message'
37
51
  payload: MessageMessage & DefaultEventProps
@@ -71,7 +85,12 @@ export type HistoryResponse = {
71
85
  export type HistoryEvents = HistoryResponse['events']
72
86
 
73
87
  export type EntryMetaOptions =
74
- History['activeServiceSettings']['entry']['options']
88
+ History['activeServiceSettings']['entry']['options'] & {
89
+ text?: History['activeServiceSettings']['entry']['options']['text'] & {
90
+ placeholder?: string
91
+ label?: string
92
+ }
93
+ }
75
94
 
76
95
  export type EntryMeta = {
77
96
  default: typeof entryTypes.text
@@ -80,6 +99,13 @@ export type EntryMeta = {
80
99
  blockAutoEntrySwitch: boolean
81
100
  options: EntryMetaOptions
82
101
  optionsOverride: EntryMetaOptions
102
+ actions?: {
103
+ abortTransaction?: {
104
+ label: string
105
+ topicFallbackMessage: string
106
+ topicName: string
107
+ }
108
+ }
83
109
  }
84
110
 
85
111
  export type ParticipantInfo = {
@@ -96,8 +122,16 @@ export interface CurrentUpload {
96
122
  complete: boolean
97
123
  }
98
124
 
125
+ export interface CurrentUploadPayload {
126
+ fileId: CurrentUpload['id']
127
+ progress: CurrentUpload['progress']
128
+ fileName: CurrentUpload['name']
129
+ uploadHandle: CurrentUpload['uploadHandle']
130
+ }
131
+
99
132
  export type ServiceInfo = {
100
133
  activeServiceSessionId: string
134
+ proactiveMessages: boolean
101
135
  }
102
136
 
103
137
  export interface StoreState {
@@ -144,6 +178,9 @@ export interface StoreState {
144
178
  enabledFromEntry: boolean
145
179
  enabled: boolean
146
180
  }
181
+ webNotifications: {
182
+ enabled: boolean
183
+ }
147
184
  }
148
185
  panelActive: boolean
149
186
  optionActive: string
@@ -151,6 +188,7 @@ export interface StoreState {
151
188
  }
152
189
  showFileUpload: boolean
153
190
  currentUploads: CurrentUpload[]
191
+ processingFileUploads: string[]
154
192
  entryMeta: EntryMeta
155
193
  seamlyContainerElement: null | HTMLElement
156
194
  }
@@ -58,9 +58,6 @@ export default function TranslationsOptionsButton({
58
58
  onKeyDown={onMainKeyDownHandler}
59
59
  >
60
60
  <InOutTransition
61
- onOutTransitionComplete={() => undefined}
62
- onInTransitionComplete={() => undefined}
63
- timeout={0}
64
61
  transitionStartState={transitionStartStates.notRendered}
65
62
  isActive={menuIsOpen}
66
63
  >
@@ -83,7 +80,7 @@ export default function TranslationsOptionsButton({
83
80
  onKeyDown={handleToggleKeyDown}
84
81
  ref={toggleButton}
85
82
  aria-haspopup="dialog"
86
- aria-expanded={menuIsOpen.toString()}
83
+ aria-expanded={menuIsOpen}
87
84
  >
88
85
  {children}
89
86
  </button>
@@ -1,13 +1,14 @@
1
+ import { useSelector } from 'react-redux'
1
2
  import TranslationChatStatus from 'ui/components/translation-chat-status'
2
3
  import TranslationProposal from 'ui/components/translation-proposal'
3
- import { useInterrupt } from 'domains/interrupt/hooks'
4
+ import { selectHasError } from 'domains/interrupt/selectors'
4
5
  import { useTranslations } from 'domains/translations/hooks'
5
6
 
6
7
  export default function TranslationStatus() {
7
- const { hasInterrupt } = useInterrupt()
8
+ const hasError = useSelector(selectHasError)
8
9
 
9
10
  const { isActive } = useTranslations()
10
- if (hasInterrupt) {
11
+ if (hasError) {
11
12
  return null
12
13
  }
13
14
 
@@ -85,32 +85,39 @@ export function useTranslatedEventData(channelEvent: ChannelEvent): {
85
85
  const getTranslations = (): {
86
86
  body: EventDataBody
87
87
  translatedBody: TranslatedEventDataBody
88
+ translation?: any
88
89
  } => {
89
- if (!channelEvent.payload)
90
+ if (!channelEvent?.payload)
90
91
  return { body: undefined, translatedBody: undefined }
91
92
  if (channelEvent.type === 'participant') {
92
93
  return {
93
94
  body: channelEvent.payload?.participant.introduction,
94
95
  translatedBody:
95
96
  channelEvent.payload?.participant?.translatedIntroduction,
97
+ translation:
98
+ // @ts-ignore
99
+ channelEvent.payload?.participant?.translation,
96
100
  }
97
101
  }
98
102
 
99
103
  return {
100
104
  body: channelEvent.payload.body,
101
105
  translatedBody: channelEvent.payload.translatedBody,
106
+ // @ts-ignore
107
+ translation: channelEvent?.payload?.translation,
102
108
  }
103
109
  }
104
110
 
105
- const { translatedBody, body } = getTranslations()
111
+ const { translatedBody, translation, body } = getTranslations()
106
112
  const hasTranslation = !!translatedBody
107
113
  const isTranslated = useSelector(selectIsTranslated(channelEvent))
108
114
 
109
115
  return {
110
- body: hasTranslation && isTranslated ? translatedBody?.data : body,
116
+ // @ts-ignore
117
+ body: hasTranslation && isTranslated ? translatedBody : body,
111
118
  hasTranslation,
112
119
  isTranslated: isTranslated && hasTranslation,
113
- locale: translatedBody?.locale,
120
+ locale: translation?.locale,
114
121
  }
115
122
  }
116
123
 
@@ -1,4 +1,5 @@
1
1
  import { PayloadAction, createSlice, nanoid } from '@reduxjs/toolkit'
2
+ import { resetApp } from 'domains/app/actions'
2
3
  import { initializeConfig } from 'domains/config/actions'
3
4
  import { addEvent, setHistory } from 'domains/store/slice'
4
5
  import type { ChannelEvent, HistoryResponse } from 'domains/store/store.types'
@@ -93,6 +94,7 @@ export const translationSlice = createSlice({
93
94
  },
94
95
  extraReducers: (builder) => {
95
96
  builder
97
+ .addCase(resetApp.pending, () => translationsInitialState)
96
98
  .addCase(initializeConfig.fulfilled, (state, { payload }) => {
97
99
  const feature = payload?.features?.translation
98
100
  if (!feature) return
@@ -1,3 +1,5 @@
1
+ // Polyfills
2
+ import 'core-js/es/array/at'
1
3
  // Used by: Client
2
4
  import initializeExternalApi from './lib/external-api/initialize-api'
3
5
 
@@ -40,6 +40,30 @@ export const setSearchParam = (key: string, value: string): void => {
40
40
  window.location.href = url.toString()
41
41
  }
42
42
 
43
+ // Adds search parameters to the url
44
+ export const setSearchParams = (params: Record<string, string>): void => {
45
+ const url = new URL(window.location.href)
46
+ const { searchParams } = url
47
+
48
+ Object.entries(params).forEach(([key, value]) => {
49
+ searchParams.set(key, value)
50
+ })
51
+
52
+ url.search = searchParams.toString()
53
+ window.location.href = url.toString()
54
+ }
55
+
56
+ // Removes a search parameter from the url
57
+ export const removeSearchParam = (...keys: string[]): void => {
58
+ const url = new URL(window.location.href)
59
+ const { searchParams } = url
60
+
61
+ keys.forEach((key) => searchParams.delete(key))
62
+
63
+ url.search = searchParams.toString()
64
+ window.history.replaceState({}, null, url)
65
+ }
66
+
43
67
  // Replace search parameters with those found in URL search parameters or sessionStorage
44
68
  export const replaceSearchParams = <T extends string>(
45
69
  ...keys: T[]
@@ -251,6 +251,11 @@ export interface components {
251
251
  EntryText: {
252
252
  /** @description Settings of the text entry */
253
253
  options?: {
254
+ /**
255
+ * @description A custom label to show above the input field
256
+ * @example Your name:
257
+ */
258
+ label?: string | null
254
259
  /**
255
260
  * @description Whether or not the expected input is language neutral. A postal code or a last name for example are independent of language.
256
261
  * @example false
@@ -261,6 +266,11 @@ export interface components {
261
266
  * @example 100
262
267
  */
263
268
  limit?: number | null
269
+ /**
270
+ * @description A custom placeholder to show in the input field
271
+ * @example Your message (max. 100 characters)
272
+ */
273
+ placeholder?: string | null
264
274
  }
265
275
  /**
266
276
  * @description Entry type