@seamly/web-ui 19.0.0 → 19.1.3

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 (66) hide show
  1. package/build/dist/lib/index.debug.js +150 -62
  2. package/build/dist/lib/index.debug.min.js +1 -1
  3. package/build/dist/lib/index.debug.min.js.LICENSE.txt +40 -8
  4. package/build/dist/lib/index.js +493 -413
  5. package/build/dist/lib/index.min.js +1 -1
  6. package/build/dist/lib/standalone.js +581 -432
  7. package/build/dist/lib/standalone.min.js +1 -1
  8. package/build/dist/lib/style-guide.js +1875 -1791
  9. package/build/dist/lib/style-guide.min.js +1 -1
  10. package/package.json +1 -1
  11. package/src/javascripts/api/errors/seamly-base-error.js +17 -0
  12. package/src/javascripts/api/errors/seamly-configuration-error.js +4 -6
  13. package/src/javascripts/api/errors/seamly-general-error.js +4 -6
  14. package/src/javascripts/api/errors/seamly-offline-error.js +4 -6
  15. package/src/javascripts/api/errors/seamly-session-expired-error.js +4 -6
  16. package/src/javascripts/api/errors/seamly-unauthorized-error.js +4 -6
  17. package/src/javascripts/api/errors/seamly-unavailable-error.js +17 -0
  18. package/src/javascripts/api/index.js +10 -11
  19. package/src/javascripts/api/producer.js +5 -1
  20. package/src/javascripts/domains/app/actions.js +27 -17
  21. package/src/javascripts/domains/app/index.js +2 -1
  22. package/src/javascripts/domains/config/reducer.js +1 -0
  23. package/src/javascripts/domains/config/selectors.js +1 -1
  24. package/src/javascripts/domains/errors/index.js +33 -0
  25. package/src/javascripts/domains/i18n/reducer.js +6 -0
  26. package/src/javascripts/domains/interrupt/middleware.js +2 -0
  27. package/src/javascripts/domains/interrupt/reducer.js +10 -9
  28. package/src/javascripts/domains/store/index.js +6 -1
  29. package/src/javascripts/domains/store/state-reducer.js +10 -6
  30. package/src/javascripts/domains/visibility/actions.js +73 -0
  31. package/src/javascripts/domains/visibility/constants.js +8 -0
  32. package/src/javascripts/domains/visibility/hooks.js +24 -0
  33. package/src/javascripts/domains/visibility/index.js +8 -0
  34. package/src/javascripts/domains/visibility/reducer.js +19 -0
  35. package/src/javascripts/domains/visibility/selectors.js +9 -0
  36. package/src/javascripts/domains/visibility/utils.js +42 -0
  37. package/src/javascripts/index.js +3 -12
  38. package/src/javascripts/lib/engine/index.js +1 -0
  39. package/src/javascripts/lib/redux-helpers/index.js +1 -5
  40. package/src/javascripts/style-guide/components/app.js +1 -1
  41. package/src/javascripts/style-guide/components/static-core.js +13 -0
  42. package/src/javascripts/style-guide/states.js +48 -21
  43. package/src/javascripts/ui/components/conversation/conversation.js +2 -2
  44. package/src/javascripts/ui/components/conversation/event/hooks/use-text-rendering.js +1 -8
  45. package/src/javascripts/ui/components/conversation/event/text.js +19 -13
  46. package/src/javascripts/ui/components/core/seamly-core.js +3 -0
  47. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +3 -3
  48. package/src/javascripts/ui/components/core/seamly-initializer.js +2 -6
  49. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +2 -3
  50. package/src/javascripts/ui/components/core/seamly-new-notifications.js +2 -2
  51. package/src/javascripts/ui/components/core/seamly-read-state.js +2 -2
  52. package/src/javascripts/ui/components/entry/toggle-button.js +2 -2
  53. package/src/javascripts/ui/components/layout/agent-info.js +2 -2
  54. package/src/javascripts/ui/components/layout/app-frame.js +2 -3
  55. package/src/javascripts/ui/components/layout/chat-frame.js +2 -2
  56. package/src/javascripts/ui/components/layout/modal-wrapper.js +3 -6
  57. package/src/javascripts/ui/components/layout/view.js +3 -6
  58. package/src/javascripts/ui/hooks/seamly-hooks.js +0 -2
  59. package/src/javascripts/ui/hooks/use-seamly-chat.js +3 -5
  60. package/src/javascripts/ui/hooks/use-seamly-commands.js +7 -29
  61. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +2 -2
  62. package/src/javascripts/ui/utils/general-utils.js +0 -9
  63. package/src/javascripts/ui/utils/seamly-utils.js +0 -66
  64. package/src/.DS_Store +0 -0
  65. package/src/javascripts/ui/hooks/use-seamly-stored-visibility.js +0 -31
  66. package/src/javascripts/ui/hooks/use-seamly-visibility.js +0 -98
@@ -195,7 +195,6 @@ export class API {
195
195
  // cookies in CORS requests.
196
196
  .withCredentials()
197
197
  .send({
198
- authorizationRequired: true,
199
198
  externalId: this.externalId || undefined,
200
199
  })
201
200
 
@@ -211,7 +210,7 @@ export class API {
211
210
  return initialState
212
211
  } catch (error) {
213
212
  if (error.status >= 500) {
214
- throw new SeamlyGeneralError()
213
+ throw new SeamlyGeneralError(error)
215
214
  }
216
215
  throw error
217
216
  }
@@ -260,13 +259,13 @@ export class API {
260
259
  }
261
260
  } catch (error) {
262
261
  if (error.status === 401) {
263
- throw new SeamlyUnauthorizedError()
262
+ throw new SeamlyUnauthorizedError(error)
264
263
  }
265
264
  if (error.status === 404) {
266
- throw new SeamlySessionExpiredError()
265
+ throw new SeamlySessionExpiredError(error)
267
266
  }
268
267
  if (error.status >= 500) {
269
- throw new SeamlyGeneralError()
268
+ throw new SeamlyGeneralError(error)
270
269
  }
271
270
  throw error
272
271
  }
@@ -353,10 +352,10 @@ export class API {
353
352
  })
354
353
  .catch((error) => {
355
354
  if (error.status === 404) {
356
- throw new SeamlyConfigurationError()
355
+ throw new SeamlyConfigurationError(error)
357
356
  }
358
357
  if (error.status >= 500) {
359
- throw new SeamlyGeneralError()
358
+ throw new SeamlyGeneralError(error)
360
359
  }
361
360
  throw error
362
361
  })
@@ -373,13 +372,13 @@ export class API {
373
372
  })
374
373
  .catch((error) => {
375
374
  if (error.status === 401) {
376
- throw new SeamlyUnauthorizedError()
375
+ throw new SeamlyUnauthorizedError(error)
377
376
  }
378
377
  if (error.status === 404) {
379
- throw new SeamlySessionExpiredError()
378
+ throw new SeamlySessionExpiredError(error)
380
379
  }
381
380
  if (error.status >= 500) {
382
- throw new SeamlyGeneralError()
381
+ throw new SeamlyGeneralError(error)
383
382
  }
384
383
  throw error
385
384
  })
@@ -402,7 +401,7 @@ export class API {
402
401
  return body.translations
403
402
  } catch (error) {
404
403
  if (error.status >= 500) {
405
- throw new SeamlyGeneralError()
404
+ throw new SeamlyGeneralError(error)
406
405
  }
407
406
  throw error
408
407
  }
@@ -20,6 +20,7 @@ export default class ConversationProducer {
20
20
  type: 'error',
21
21
  payload: {
22
22
  type: 'seamly_offline',
23
+ error: err,
23
24
  },
24
25
  })
25
26
  })
@@ -77,7 +78,10 @@ export default class ConversationProducer {
77
78
  .receive('error', (err) => {
78
79
  log('[CHANNEL][JOIN] ERROR', err)
79
80
 
80
- this.emit({ type: 'error', payload: { type: 'join_channel_erred' } })
81
+ this.emit({
82
+ type: 'error',
83
+ payload: { type: 'join_channel_erred', error: err },
84
+ })
81
85
  this.emit({ type: 'connection', connected: false, ready: false })
82
86
  this.channel.socket.disconnect()
83
87
  })
@@ -1,8 +1,10 @@
1
1
  import { seamlyActions } from '../../ui/utils/seamly-utils'
2
2
  import { Actions as ConfigActions } from '../config'
3
- import { Actions as InterruptActions } from '../interrupt'
4
3
  import { Actions as I18nActions } from '../i18n'
4
+ import * as VisibilityActions from '../visibility/actions'
5
5
  import { createAction, createThunk } from './utils'
6
+ import SeamlyUnavailableError from '../../api/errors/seamly-unavailable-error'
7
+ import SeamlySessionExpiredError from '../../api/errors/seamly-session-expired-error'
6
8
 
7
9
  export const setHasResponded = createAction(
8
10
  'setHasResponded',
@@ -12,31 +14,39 @@ export const setHasResponded = createAction(
12
14
  export const initialize = createThunk(
13
15
  'initialize',
14
16
  async (config, { dispatch, extra: { api } }) => {
15
- try {
16
- dispatch(ConfigActions.initialize(config))
17
+ dispatch(ConfigActions.initialize(config))
18
+ let locale = config?.context?.locale
17
19
 
20
+ try {
18
21
  const { features, defaultLocale } = await api.getConfig()
19
22
  dispatch({ type: seamlyActions.SET_FEATURES, features })
20
-
21
- let locale = config?.context?.locale || defaultLocale
23
+ locale = locale || defaultLocale
22
24
  dispatch(I18nActions.setInitialLocale(locale))
23
- try {
24
- if (api.hasConversation()) {
25
- const initialState = await api.getConversationIntitialState()
26
- dispatch({ type: seamlyActions.SET_INITIAL_STATE, initialState })
25
+ } catch (e) {
26
+ throw new SeamlyUnavailableError()
27
+ }
27
28
 
28
- locale = initialState.translation?.locale || locale
29
+ try {
30
+ if (api.hasConversation()) {
31
+ const initialState = await api.getConversationIntitialState()
32
+ dispatch({ type: seamlyActions.SET_INITIAL_STATE, initialState })
33
+
34
+ locale = initialState.translation?.locale || locale
29
35
 
30
- if ('userResponded' in initialState) {
31
- dispatch(setHasResponded(initialState.userResponded))
32
- }
36
+ if ('userResponded' in initialState) {
37
+ dispatch(setHasResponded(initialState.userResponded))
33
38
  }
34
- } catch (error) {
35
- dispatch(InterruptActions.set(error))
36
39
  }
40
+ } catch (e) {
41
+ if (e instanceof SeamlySessionExpiredError) {
42
+ throw e
43
+ }
44
+ throw new SeamlyUnavailableError()
45
+ } finally {
37
46
  await dispatch(I18nActions.setLocale(locale))
38
- } catch (error) {
39
- dispatch(InterruptActions.set(error))
47
+ dispatch(VisibilityActions.initialize())
40
48
  }
41
49
  },
42
50
  )
51
+
52
+ export const reset = createAction('reset', () => {})
@@ -1,6 +1,7 @@
1
1
  import * as Actions from './actions'
2
+ import * as Selectors from './selectors'
2
3
 
3
4
  export * from './hooks'
4
5
  export { default as Reducer } from './reducer'
5
6
 
6
- export { Actions }
7
+ export { Actions, Selectors }
@@ -27,6 +27,7 @@ const configKeys = [
27
27
  'messages',
28
28
  'visible',
29
29
  'visibilityCallback',
30
+ 'errorCallback',
30
31
  ]
31
32
  const updateState = (state, { config }) => {
32
33
  const { messages, ...partialConfig } = pick(config, configKeys)
@@ -1,5 +1,5 @@
1
1
  import { createSelector } from 'reselect'
2
- import { visibilityStates } from '../../ui/utils/seamly-utils'
2
+ import { visibilityStates } from '../visibility/constants'
3
3
  import { selectState } from './utils'
4
4
 
5
5
  export const selectConfig = createSelector(selectState, (config) => {
@@ -0,0 +1,33 @@
1
+ import { createDomain } from '../../lib/redux-helpers'
2
+ import { Selectors as ConfigSelectors } from '../config'
3
+
4
+ const { createAction } = createDomain('errors')
5
+
6
+ export const catchError = createAction('catch-error', (error) => ({ error }))
7
+
8
+ export function createMiddleware({ api: seamlyApi }) {
9
+ return ({ getState }) => {
10
+ const handleError = (action) => {
11
+ const { errorCallback, namespace, api, layoutMode } =
12
+ ConfigSelectors.selectConfig(getState())
13
+ errorCallback?.(action.error, {
14
+ namespace,
15
+ api,
16
+ layoutMode,
17
+ conversationUrl: seamlyApi.getConversationUrl(),
18
+ action: action.type ? action : undefined,
19
+ })
20
+ }
21
+ return (next) => (action) => {
22
+ try {
23
+ if (action.error) {
24
+ handleError(action)
25
+ }
26
+ return next(action)
27
+ } catch (error) {
28
+ handleError({ error })
29
+ throw error
30
+ }
31
+ }
32
+ }
33
+ }
@@ -18,6 +18,12 @@ const defaultState = {
18
18
  'errors.seamlyOffline.srText':
19
19
  'The chat has connection issues. There might be a problem with your or our network connection. The chat session should resume as soon as the connection is available again.',
20
20
  'errors.seamlyOffline.title': 'Connection issues',
21
+ 'errors.seamlyUnavailable.buttonText': 'Try again',
22
+ 'errors.seamlyUnavailable.message':
23
+ 'The server could not be reached. Try again in a little while.',
24
+ 'errors.seamlyUnavailable.srText':
25
+ 'The chat server could not be reached. Try again in a little while.',
26
+ 'errors.seamlyUnavailable.title': 'Server unavailable',
21
27
  },
22
28
  isLoading: false,
23
29
  initialLocale: undefined,
@@ -3,6 +3,7 @@ import SeamlyConfigurationError from '../../api/errors/seamly-configuration-erro
3
3
  import SeamlySessionExpiredError from '../../api/errors/seamly-session-expired-error'
4
4
  import SeamlyOfflineError from '../../api/errors/seamly-offline-error'
5
5
  import SeamlyUnauthorizedError from '../../api/errors/seamly-unauthorized-error'
6
+ import SeamlyUnavailableError from '../../api/errors/seamly-unavailable-error'
6
7
 
7
8
  const handledErrorTypes = [
8
9
  SeamlyGeneralError,
@@ -10,6 +11,7 @@ const handledErrorTypes = [
10
11
  SeamlySessionExpiredError,
11
12
  SeamlyOfflineError,
12
13
  SeamlyUnauthorizedError,
14
+ SeamlyUnavailableError,
13
15
  ]
14
16
 
15
17
  export default function createMiddleware({ api }) {
@@ -1,21 +1,22 @@
1
1
  import { createReducer } from './utils'
2
2
  import * as Actions from './actions'
3
+ import { Actions as AppActions } from '../app'
3
4
 
4
5
  const initialState = {
5
6
  error: undefined,
6
7
  }
7
8
 
9
+ const handleError = (state, { error }) => ({
10
+ ...state,
11
+ error,
12
+ })
13
+
8
14
  export default createReducer(
9
15
  {
10
- [Actions.set]: (state, { error }) => {
11
- return {
12
- ...state,
13
- error,
14
- }
15
- },
16
- [Actions.clear]: () => {
17
- return initialState
18
- },
16
+ [Actions.set]: handleError,
17
+ [AppActions.initialize.rejected]: handleError,
18
+ [Actions.clear]: () => initialState,
19
+ [AppActions.reset]: () => initialState,
19
20
  },
20
21
  initialState,
21
22
  )
@@ -11,14 +11,16 @@ import {
11
11
  createMiddleware as createI18nMiddleware,
12
12
  } from '../translations'
13
13
  import { Reducer as i18nReducer } from '../i18n'
14
+ import { Reducer as visibilityReducer } from '../visibility'
14
15
  import {
15
16
  Reducer as interruptReducer,
16
17
  createMiddleware as createInterruptMiddleware,
17
18
  } from '../interrupt'
18
19
  import { createMiddleware as createOptionsMiddleware } from '../options'
20
+ import { createMiddleware as createErrorsMiddleware } from '../errors'
19
21
  import stateReducer from './state-reducer'
20
22
 
21
- export function createStore({ initialState, api } = {}) {
23
+ export function createStore({ initialState, api, eventBus } = {}) {
22
24
  const store = createReduxStore({
23
25
  reducers: {
24
26
  state: stateReducer,
@@ -28,11 +30,14 @@ export function createStore({ initialState, api } = {}) {
28
30
  [String(translationsReducer)]: translationsReducer,
29
31
  [String(i18nReducer)]: i18nReducer,
30
32
  [String(interruptReducer)]: interruptReducer,
33
+ [String(visibilityReducer)]: visibilityReducer,
31
34
  },
32
35
  initialState,
33
36
  middlewares: [
37
+ createErrorsMiddleware({ api }),
34
38
  thunkMiddleware.withExtraArgument({
35
39
  api,
40
+ eventBus,
36
41
  }),
37
42
  createConfigMiddleware(),
38
43
  createInterruptMiddleware({ api }),
@@ -1,11 +1,8 @@
1
1
  // Legacy state reducer. Do not add new features here but extract/create new reducers as needed
2
2
 
3
3
  import { randomId } from '../../lib/id'
4
- import {
5
- entryTypes,
6
- seamlyStateReducer,
7
- visibilityStates,
8
- } from '../../ui/utils/seamly-utils'
4
+ import { entryTypes, seamlyStateReducer } from '../../ui/utils/seamly-utils'
5
+ import { Actions as AppActions } from '../app'
9
6
 
10
7
  const initialState = {
11
8
  events: [],
@@ -14,7 +11,6 @@ const initialState = {
14
11
  isLoading: false,
15
12
  idleDetachCountdown: { hasCountdown: false, isActive: false },
16
13
  resumeConversationPrompt: false,
17
- visible: visibilityStates.hidden,
18
14
  serviceInfo: {
19
15
  activeServiceSessionId: '',
20
16
  },
@@ -52,5 +48,13 @@ const initialState = {
52
48
  }
53
49
 
54
50
  export default function stateReducer(state = initialState, action) {
51
+ if (action.type === String(AppActions.reset)) {
52
+ const { visible } = state
53
+ return {
54
+ ...initialState,
55
+ visible,
56
+ }
57
+ }
58
+
55
59
  return seamlyStateReducer(state, action)
56
60
  }
@@ -0,0 +1,73 @@
1
+ import { Selectors as ConfigSelectors } from '../config'
2
+ import * as AppSelectors from '../app/selectors'
3
+
4
+ import { visibilityStates, StoreKey } from './constants'
5
+ import { createAction, createThunk, calculateVisibility } from './utils'
6
+ import * as Selectors from './selectors'
7
+ import { selectState } from '../../ui/hooks/seamly-state-hooks'
8
+
9
+ export const setFromStorage = createAction('setFromStorage', (visibility) => ({
10
+ visibility,
11
+ }))
12
+
13
+ const validVisibilityStates = [
14
+ visibilityStates.open,
15
+ visibilityStates.minimized,
16
+ visibilityStates.hidden,
17
+ ]
18
+ export const setVisibility = createThunk(
19
+ 'set',
20
+ (requestedVisibility, { getState, extra: { api, eventBus } }) => {
21
+ const state = getState()
22
+ const previousVisibility = Selectors.selectVisibility(state)
23
+ const hasResponded = AppSelectors.selectUserHasResponded(state)
24
+ const hasConversation = api.hasConversation()
25
+ const config = ConfigSelectors.selectConfig(state)
26
+ const { visibilityCallback = calculateVisibility, layoutMode } = config
27
+ const { unreadEvents: unreadMessageCount } = selectState(state)
28
+
29
+ const calculatedVisibility = visibilityCallback({
30
+ hasConversation,
31
+ hasResponded,
32
+ previousVisibility,
33
+ requestedVisibility,
34
+ config,
35
+ })
36
+ if (!validVisibilityStates.includes(calculatedVisibility)) {
37
+ console.error(
38
+ 'The visibilityCallback function should return "open", "minimized" or "hidden".',
39
+ )
40
+ return undefined
41
+ }
42
+ if (previousVisibility === calculatedVisibility) {
43
+ return undefined
44
+ }
45
+ // Store the user-requested visibility in order to reinitialize after refresh
46
+ api.store.set(StoreKey, {
47
+ ...(api.store.get(StoreKey) || {}),
48
+ [layoutMode]: requestedVisibility,
49
+ })
50
+ if (requestedVisibility) {
51
+ eventBus.emit('ui.visible', requestedVisibility, {
52
+ visibility: requestedVisibility,
53
+ hasConversation,
54
+ hasResponded,
55
+ unreadMessageCount,
56
+ })
57
+ }
58
+ return calculatedVisibility
59
+ },
60
+ )
61
+
62
+ export const initialize = createThunk(
63
+ 'initialize',
64
+ async (locale, { dispatch, getState, extra: { api } }) => {
65
+ // initialize stored visibility
66
+ const { layoutMode } = ConfigSelectors.selectConfig(getState())
67
+ const storedVisibility = api.store.get(StoreKey)?.[layoutMode]
68
+ if (storedVisibility) {
69
+ dispatch(setFromStorage(storedVisibility))
70
+ }
71
+ dispatch(setVisibility(visibilityStates.initialize))
72
+ },
73
+ )
@@ -0,0 +1,8 @@
1
+ export const StoreKey = 'visibility'
2
+
3
+ export const visibilityStates = {
4
+ hidden: 'hidden',
5
+ minimized: 'minimized',
6
+ open: 'open',
7
+ initialize: null,
8
+ }
@@ -0,0 +1,24 @@
1
+ import { useCallback } from 'preact/hooks'
2
+ import { useConfig } from '../config'
3
+ import { useSelector, useStoreDispatch } from '../redux'
4
+ import * as Actions from './actions'
5
+ import * as Selectors from './selectors'
6
+ import { visibilityStates } from './constants'
7
+
8
+ export const useVisibility = () => {
9
+ const dispatch = useStoreDispatch()
10
+ const visible = useSelector(Selectors.selectVisibility)
11
+ const isVisible = visible ? visible !== visibilityStates.hidden : false
12
+ const { layoutMode } = useConfig()
13
+ const isOpen =
14
+ visible && layoutMode
15
+ ? visible === visibilityStates.open ||
16
+ (layoutMode === 'inline' && visible !== visibilityStates.hidden)
17
+ : false
18
+ const setVisibility = useCallback(
19
+ (visibility) => dispatch(Actions.setVisibility(visibility)),
20
+ [dispatch],
21
+ )
22
+
23
+ return { isVisible, isOpen, visible, setVisibility }
24
+ }
@@ -0,0 +1,8 @@
1
+ import * as Actions from './actions'
2
+ import * as Selectors from './selectors'
3
+
4
+ export * from './hooks'
5
+ export { visibilityStates } from './constants'
6
+ export { default as Reducer } from './reducer'
7
+ export { calculateVisibility } from './utils'
8
+ export { Actions, Selectors }
@@ -0,0 +1,19 @@
1
+ import { createReducer } from './utils'
2
+ import * as Actions from './actions'
3
+ import { visibilityStates } from './constants'
4
+
5
+ const initialState = {
6
+ visibility: visibilityStates.initialize,
7
+ }
8
+
9
+ export default createReducer(
10
+ {
11
+ [Actions.setFromStorage]: (state, { visibility }) => ({
12
+ ...state,
13
+ visibility,
14
+ }),
15
+ [Actions.setVisibility.fulfilled]: (state, { payload: visibility }) =>
16
+ visibility ? { ...state, visibility } : state,
17
+ },
18
+ initialState,
19
+ )
@@ -0,0 +1,9 @@
1
+ import { createSelector } from 'reselect'
2
+ import { selectState } from './utils'
3
+
4
+ export const selectVisibility = createSelector(
5
+ selectState,
6
+ (state) => state.visibility,
7
+ )
8
+
9
+ export { selectState }
@@ -0,0 +1,42 @@
1
+ import { createDomain } from '../../lib/redux-helpers'
2
+ import { visibilityStates } from './constants'
3
+
4
+ export const {
5
+ createAction,
6
+ createActions,
7
+ createThunk,
8
+ createReducer,
9
+ selectState,
10
+ } = createDomain('visibility')
11
+
12
+ export const calculateVisibility = ({
13
+ hasResponded,
14
+ previousVisibility,
15
+ requestedVisibility,
16
+ config,
17
+ }) => {
18
+ const { defaults, layoutMode, hideOnNoUserResponse } = config
19
+
20
+ const { visible: defaultVisibility } = defaults || {}
21
+
22
+ // Requesting open should override the responded check.
23
+ if (
24
+ layoutMode === 'window' &&
25
+ hideOnNoUserResponse &&
26
+ requestedVisibility !== visibilityStates.open
27
+ ) {
28
+ return hasResponded
29
+ ? requestedVisibility || previousVisibility || visibilityStates.open
30
+ : visibilityStates.hidden
31
+ }
32
+
33
+ const baseVisibility =
34
+ layoutMode === 'inline' ? visibilityStates.open : visibilityStates.minimized
35
+
36
+ return (
37
+ requestedVisibility ||
38
+ previousVisibility ||
39
+ defaultVisibility ||
40
+ baseVisibility
41
+ )
42
+ }
@@ -12,7 +12,7 @@ export { default as AgentInfo } from './ui/components/layout/agent-info'
12
12
  export { default as AppFrame } from './ui/components/layout/app-frame'
13
13
 
14
14
  // Used by: Client
15
- export { calculateVisibility } from './ui/utils/seamly-utils'
15
+ export { calculateVisibility } from './domains/visibility'
16
16
 
17
17
  // Used by: Client
18
18
  export { default as ChatFrame } from './ui/components/layout/chat-frame'
@@ -114,7 +114,7 @@ export { useSeamlyMessageContainerClassNames } from './ui/hooks/seamly-hooks'
114
114
  export { useSeamlyOptions } from './ui/hooks/seamly-hooks'
115
115
 
116
116
  // Used by: Client
117
- export { useSeamlyVisibility } from './ui/hooks/seamly-hooks'
117
+ export { useVisibility as useSeamlyVisibility } from './domains/visibility'
118
118
 
119
119
  // Used by: Client
120
120
  export {
@@ -135,7 +135,7 @@ export { default as View } from './ui/components/layout/view'
135
135
 
136
136
  // Used by: Client
137
137
  // Used by: StyleGuide
138
- export { visibilityStates } from './ui/utils/seamly-utils'
138
+ export { visibilityStates } from './domains/visibility'
139
139
 
140
140
  // Used by: StyleGuide
141
141
  export { API } from './api'
@@ -143,14 +143,5 @@ export { API } from './api'
143
143
  // Used by: StyleGuide
144
144
  export { default as SeamlyGeneralError } from './api/errors/seamly-general-error'
145
145
 
146
- // Used by: StyleGuide
147
- export { default as SeamlyConfigurationError } from './api/errors/seamly-configuration-error'
148
-
149
- // Used by: StyleGuide
150
- export { default as SeamlySessionExpiredError } from './api/errors/seamly-session-expired-error'
151
-
152
146
  // Used by: StyleGuide
153
147
  export { default as SeamlyOfflineError } from './api/errors/seamly-offline-error'
154
-
155
- // Used by: StyleGuide
156
- export { default as SeamlyUnauthorizedError } from './api/errors/seamly-unauthorized-error'
@@ -51,6 +51,7 @@ export default class Engine {
51
51
 
52
52
  const store = createStore({
53
53
  api: this.api,
54
+ eventBus: this.eventBus,
54
55
  })
55
56
 
56
57
  await store.dispatch(AppActions.initialize(renderConfig))
@@ -79,11 +79,7 @@ export function createThunk(type, payloadCreator) {
79
79
  }
80
80
 
81
81
  export function createReducer(domain, handlers = {}, defaultState) {
82
- const reducer = (state, action) => {
83
- if (state === undefined) {
84
- // eslint-disable-next-line no-param-reassign
85
- state = defaultState
86
- }
82
+ const reducer = (state = defaultState, action) => {
87
83
  const typeReducer = handlers?.[action?.type]
88
84
  return typeReducer ? typeReducer(state, action) : state
89
85
  }
@@ -82,7 +82,7 @@ const StyleGuideApp = ({
82
82
  overlay.addEventListener('click', () => {
83
83
  setStaticState((s) => ({
84
84
  ...s,
85
- visible: 'minimized',
85
+ visibility: { ...s.visibility, visible: 'minimized' },
86
86
  }))
87
87
  })
88
88
  }
@@ -1,4 +1,6 @@
1
1
  import { useMemo, useRef } from 'preact/hooks'
2
+ import thunkMiddleware from 'redux-thunk'
3
+
2
4
  import {
3
5
  SeamlyEventBusContext,
4
6
  SeamlyApiContext,
@@ -19,6 +21,7 @@ import {
19
21
  Reducer as configReducer,
20
22
  Actions as ConfigActions,
21
23
  } from '../../domains/config'
24
+ import { Reducer as visibilityReducer } from '../../domains/visibility'
22
25
 
23
26
  const bareApi = {
24
27
  send: () => {},
@@ -26,6 +29,7 @@ const bareApi = {
26
29
  return Promise.resolve({})
27
30
  },
28
31
  store: { get: () => {}, set: () => {} },
32
+ hasConversation: () => false,
29
33
  }
30
34
 
31
35
  const SeamlyTestCore = ({ state, translations, children }) => {
@@ -37,6 +41,7 @@ const SeamlyTestCore = ({ state, translations, children }) => {
37
41
  translations: translationsSlice,
38
42
  interrupt: interruptSlice,
39
43
  config: configSlice,
44
+ visibility: visibilitySlice,
40
45
  ...restState
41
46
  } = state || {}
42
47
  const newStore = createReduxStore({
@@ -48,12 +53,20 @@ const SeamlyTestCore = ({ state, translations, children }) => {
48
53
  [String(translationsReducer)]: translationsReducer,
49
54
  [String(i18nReducer)]: i18nReducer,
50
55
  [String(interruptReducer)]: interruptReducer,
56
+ [String(visibilityReducer)]: visibilityReducer,
51
57
  },
52
58
  initialState: {
53
59
  state: restState,
54
60
  [String(translationsReducer)]: translationsSlice,
55
61
  [String(interruptReducer)]: interruptSlice,
62
+ [String(visibilityReducer)]: visibilitySlice,
56
63
  },
64
+ middlewares: [
65
+ thunkMiddleware.withExtraArgument({
66
+ api: bareApi,
67
+ eventBus: eventBusRef.current,
68
+ }),
69
+ ],
57
70
  })
58
71
  newStore.dispatch(ConfigActions.initialize(configSlice || {}))
59
72
  newStore.dispatch(I18nActions.setLocale.fulfilled('en-GB', translations))