@seamly/web-ui 18.2.0 → 19.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 (194) hide show
  1. package/build/dist/lib/index.debug.js +598 -136
  2. package/build/dist/lib/index.debug.min.js +1 -1
  3. package/build/dist/lib/index.debug.min.js.LICENSE.txt +190 -22
  4. package/build/dist/lib/index.js +4745 -4468
  5. package/build/dist/lib/index.min.js +1 -1
  6. package/build/dist/lib/index.min.js.LICENSE.txt +1 -1
  7. package/build/dist/lib/standalone.js +4839 -4465
  8. package/build/dist/lib/standalone.min.js +1 -1
  9. package/build/dist/lib/standalone.min.js.LICENSE.txt +1 -1
  10. package/build/dist/lib/style-guide.js +1770 -980
  11. package/build/dist/lib/style-guide.min.js +1 -1
  12. package/build/dist/lib/styles.css +1 -1
  13. package/build/dist/lib/utils.js +0 -1
  14. package/build/dist/lib/utils.min.js +1 -1
  15. package/package.json +29 -29
  16. package/src/javascripts/api/index.js +33 -48
  17. package/src/javascripts/api/producer.js +9 -12
  18. package/src/javascripts/config.js +9 -11
  19. package/src/javascripts/domains/app/actions.js +43 -0
  20. package/src/javascripts/domains/app/hooks.js +6 -0
  21. package/src/javascripts/domains/app/index.js +6 -0
  22. package/src/javascripts/domains/app/reducer.js +16 -0
  23. package/src/javascripts/domains/app/selectors.js +8 -0
  24. package/src/javascripts/domains/app/utils.js +4 -0
  25. package/src/javascripts/domains/config/actions.js +4 -0
  26. package/src/javascripts/domains/config/hooks.js +6 -0
  27. package/src/javascripts/domains/config/index.js +8 -0
  28. package/src/javascripts/domains/config/middleware.js +22 -0
  29. package/src/javascripts/domains/config/reducer.js +63 -0
  30. package/src/javascripts/domains/config/selectors.js +23 -0
  31. package/src/javascripts/domains/config/utils.js +4 -0
  32. package/src/javascripts/domains/forms/actions.js +2 -4
  33. package/src/javascripts/domains/forms/hooks.js +10 -14
  34. package/src/javascripts/domains/forms/provider.js +4 -6
  35. package/src/javascripts/domains/forms/reducer.js +1 -2
  36. package/src/javascripts/domains/forms/selectors.js +4 -4
  37. package/src/javascripts/domains/forms/utils.js +5 -0
  38. package/src/javascripts/domains/i18n/actions.js +35 -0
  39. package/src/javascripts/domains/i18n/hooks.js +38 -0
  40. package/src/javascripts/domains/i18n/index.js +5 -80
  41. package/src/javascripts/domains/i18n/reducer.js +58 -0
  42. package/src/javascripts/domains/i18n/selectors.js +15 -0
  43. package/src/javascripts/domains/i18n/utils.js +9 -0
  44. package/src/javascripts/domains/interrupt/actions.js +4 -0
  45. package/src/javascripts/domains/interrupt/hooks.js +29 -0
  46. package/src/javascripts/domains/interrupt/index.js +9 -0
  47. package/src/javascripts/domains/interrupt/middleware.js +30 -0
  48. package/src/javascripts/domains/interrupt/reducer.js +21 -0
  49. package/src/javascripts/domains/interrupt/selectors.js +6 -0
  50. package/src/javascripts/domains/interrupt/utils.js +4 -0
  51. package/src/javascripts/domains/options/index.js +1 -0
  52. package/src/javascripts/domains/options/middleware.js +35 -0
  53. package/src/javascripts/domains/redux/create-redux-store.js +14 -6
  54. package/src/javascripts/domains/redux/hooks.js +3 -2
  55. package/src/javascripts/domains/redux/index.js +2 -1
  56. package/src/javascripts/domains/redux/provider.js +5 -0
  57. package/src/javascripts/domains/store/index.js +44 -0
  58. package/src/javascripts/{ui → domains}/store/state-reducer.js +4 -7
  59. package/src/javascripts/domains/translations/actions.js +4 -6
  60. package/src/javascripts/domains/translations/components/chat-status.js +7 -13
  61. package/src/javascripts/domains/translations/components/options-button.js +3 -3
  62. package/src/javascripts/domains/translations/components/options-dialog/form.js +12 -7
  63. package/src/javascripts/domains/translations/components/options-dialog/index.js +2 -5
  64. package/src/javascripts/domains/translations/hooks.js +1 -1
  65. package/src/javascripts/domains/translations/index.js +1 -0
  66. package/src/javascripts/domains/translations/middleware.js +43 -0
  67. package/src/javascripts/domains/translations/reducer.js +4 -11
  68. package/src/javascripts/domains/translations/selectors.js +3 -3
  69. package/src/javascripts/domains/translations/utils.js +4 -0
  70. package/src/javascripts/index.js +20 -5
  71. package/src/javascripts/lib/css.js +5 -5
  72. package/src/javascripts/lib/engine/index.js +39 -11
  73. package/src/javascripts/lib/external-api/index.js +6 -6
  74. package/src/javascripts/lib/mutex.js +30 -0
  75. package/src/javascripts/lib/parse-body.js +1 -1
  76. package/src/javascripts/lib/redux-helpers/index.js +25 -8
  77. package/src/javascripts/lib/split-url-params.js +2 -2
  78. package/src/javascripts/lib/store/providers/app-storage.js +1 -1
  79. package/src/javascripts/lib/store/providers/cookie-storage.js +1 -1
  80. package/src/javascripts/package/utils.js +0 -1
  81. package/src/javascripts/style-guide/components/app.js +12 -14
  82. package/src/javascripts/style-guide/components/links.js +6 -6
  83. package/src/javascripts/style-guide/components/static-core.js +32 -10
  84. package/src/javascripts/style-guide/state-helpers/index.js +1 -1
  85. package/src/javascripts/style-guide/states.js +29 -71
  86. package/src/javascripts/style-guide/style-guide-engine.js +13 -12
  87. package/src/javascripts/ui/components/chat-app.js +2 -2
  88. package/src/javascripts/ui/components/conversation/component-filter.js +2 -2
  89. package/src/javascripts/ui/components/conversation/conversation.js +2 -2
  90. package/src/javascripts/ui/components/conversation/event/card-component.js +24 -3
  91. package/src/javascripts/ui/components/conversation/event/carousel-component/components/pagination.js +2 -2
  92. package/src/javascripts/ui/components/conversation/event/carousel-component/index.js +4 -3
  93. package/src/javascripts/ui/components/conversation/event/carousel-message/components/slide.js +2 -1
  94. package/src/javascripts/ui/components/conversation/event/carousel-message/index.js +2 -2
  95. package/src/javascripts/ui/components/conversation/event/choice-prompt.js +5 -5
  96. package/src/javascripts/ui/components/conversation/event/divider/variants/new-translation.js +2 -2
  97. package/src/javascripts/ui/components/conversation/event/event-participant.js +3 -5
  98. package/src/javascripts/ui/components/conversation/event/hooks/use-event-link-click-handler.js +2 -2
  99. package/src/javascripts/ui/components/conversation/event/hooks/use-formatted-date.js +3 -3
  100. package/src/javascripts/ui/components/conversation/event/hooks/use-text-rendering.js +3 -3
  101. package/src/javascripts/ui/components/conversation/event/participant.js +2 -2
  102. package/src/javascripts/ui/components/conversation/event/upload.js +12 -27
  103. package/src/javascripts/ui/components/conversation/message-container.js +4 -6
  104. package/src/javascripts/ui/components/core/seamly-activity-monitor.js +4 -5
  105. package/src/javascripts/ui/components/core/seamly-core.js +6 -7
  106. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +18 -17
  107. package/src/javascripts/ui/components/core/seamly-file-upload.js +5 -6
  108. package/src/javascripts/ui/components/core/seamly-idle-detach-counter.js +2 -6
  109. package/src/javascripts/ui/components/core/seamly-initializer.js +7 -60
  110. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +10 -16
  111. package/src/javascripts/ui/components/core/seamly-live-region.js +1 -1
  112. package/src/javascripts/ui/components/core/seamly-new-notifications.js +5 -6
  113. package/src/javascripts/ui/components/core/seamly-read-state.js +8 -6
  114. package/src/javascripts/ui/components/entry/entry-container.js +7 -10
  115. package/src/javascripts/ui/components/entry/text-entry/hooks.js +6 -4
  116. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +10 -3
  117. package/src/javascripts/ui/components/entry/toggle-button.js +24 -10
  118. package/src/javascripts/ui/components/entry/upload/file-upload-form.js +6 -3
  119. package/src/javascripts/ui/components/entry/upload/index.js +11 -13
  120. package/src/javascripts/ui/components/faq/faq.js +6 -6
  121. package/src/javascripts/ui/components/form-controls/error.js +22 -0
  122. package/src/javascripts/ui/components/form-controls/file-input.js +3 -9
  123. package/src/javascripts/ui/components/form-controls/select.js +1 -1
  124. package/src/javascripts/ui/components/form-controls/wrapper.js +2 -9
  125. package/src/javascripts/ui/components/layout/agent-info.js +4 -4
  126. package/src/javascripts/ui/components/layout/app-frame.js +15 -12
  127. package/src/javascripts/ui/components/layout/chat-frame.js +3 -5
  128. package/src/javascripts/ui/components/layout/header.js +4 -18
  129. package/src/javascripts/ui/components/layout/interrupt.js +6 -2
  130. package/src/javascripts/ui/components/layout/privacy-disclaimer.js +2 -2
  131. package/src/javascripts/ui/components/options/cobrowsing.js +3 -7
  132. package/src/javascripts/ui/components/options/options-button.js +9 -13
  133. package/src/javascripts/ui/components/options/options-frame.js +1 -1
  134. package/src/javascripts/ui/components/options/transcript/index.js +2 -2
  135. package/src/javascripts/ui/components/options/transcript/transcript-form.js +1 -1
  136. package/src/javascripts/ui/components/warnings/cobrowsing-active-frame.js +3 -6
  137. package/src/javascripts/ui/components/warnings/idle-detach-warning.js +2 -6
  138. package/src/javascripts/ui/components/warnings/resume-conversation-prompt.js +1 -1
  139. package/src/javascripts/ui/components/widgets/in-out-transition.js +2 -2
  140. package/src/javascripts/ui/components/widgets/lightbox.js +4 -4
  141. package/src/javascripts/ui/components/widgets/modal.js +3 -3
  142. package/src/javascripts/ui/components/widgets/upload-progress.js +3 -14
  143. package/src/javascripts/ui/hooks/component-helper-hooks.js +4 -15
  144. package/src/javascripts/ui/hooks/file-upload-hooks.js +3 -3
  145. package/src/javascripts/ui/hooks/focus-helper-hooks.js +4 -4
  146. package/src/javascripts/ui/hooks/live-region-hooks.js +2 -2
  147. package/src/javascripts/ui/hooks/seamly-api-hooks.js +0 -6
  148. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +22 -25
  149. package/src/javascripts/ui/hooks/seamly-hooks.js +3 -10
  150. package/src/javascripts/ui/hooks/seamly-option-hooks.js +4 -4
  151. package/src/javascripts/ui/hooks/seamly-state-hooks.js +8 -16
  152. package/src/javascripts/ui/hooks/use-event-component-mapping.js +1 -1
  153. package/src/javascripts/ui/hooks/use-seamly-chat.js +1 -0
  154. package/src/javascripts/ui/hooks/use-seamly-commands.js +31 -54
  155. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +3 -3
  156. package/src/javascripts/ui/hooks/use-seamly-stored-visibility.js +3 -3
  157. package/src/javascripts/ui/hooks/use-seamly-visibility.js +6 -8
  158. package/src/javascripts/ui/hooks/use-single-file-upload.js +4 -1
  159. package/src/javascripts/ui/hooks/utility-hooks.js +2 -2
  160. package/src/javascripts/ui/utils/form-utils.js +3 -3
  161. package/src/javascripts/ui/utils/general-utils.js +21 -22
  162. package/src/javascripts/ui/utils/seamly-utils.js +15 -83
  163. package/src/javascripts/ui/utils/validations.js +10 -7
  164. package/src/stylesheets/1-settings/_config.scss +2 -1
  165. package/src/stylesheets/3-app/_app.scss +3 -4
  166. package/src/stylesheets/5-components/_card.scss +0 -1
  167. package/src/stylesheets/5-components/_faq.scss +3 -8
  168. package/src/stylesheets/5-components/_message.scss +10 -0
  169. package/src/stylesheets/5-components/_modal.scss +3 -3
  170. package/src/stylesheets/5-components/_options.scss +3 -2
  171. package/webpack/config.common.js +3 -3
  172. package/webpack/config.package.js +4 -22
  173. package/webpack/config.site.js +8 -6
  174. package/webpack/defaults.js +0 -3
  175. package/CHANGELOG.md +0 -561
  176. package/build/dist/translations/de-informal.js +0 -275
  177. package/build/dist/translations/de-informal.min.js +0 -1
  178. package/build/dist/translations/en.js +0 -275
  179. package/build/dist/translations/en.min.js +0 -1
  180. package/build/dist/translations/es-informal.js +0 -281
  181. package/build/dist/translations/es-informal.min.js +0 -1
  182. package/build/dist/translations/nl-formal.js +0 -275
  183. package/build/dist/translations/nl-formal.min.js +0 -1
  184. package/build/dist/translations/nl-informal.js +0 -275
  185. package/build/dist/translations/nl-informal.min.js +0 -1
  186. package/src/javascripts/lib/i18n.js +0 -46
  187. package/src/javascripts/ui/components/core/seamly-api.js +0 -44
  188. package/src/javascripts/ui/hooks/use-seamly-interrupt.js +0 -62
  189. package/src/javascripts/ui/store/index.js +0 -37
  190. package/translations/de-informal.js +0 -237
  191. package/translations/en.js +0 -234
  192. package/translations/es-informal.js +0 -243
  193. package/translations/nl-formal.js +0 -230
  194. package/translations/nl-informal.js +0 -230
@@ -0,0 +1,43 @@
1
+ import { seamlyActions } from '../../ui/utils/seamly-utils'
2
+ import { Actions as ConfigActions } from '../config'
3
+ import { Actions as InterruptActions } from '../interrupt'
4
+ import { Actions as I18nActions } from '../i18n'
5
+ import { createAction, createThunk } from './utils'
6
+
7
+ export const setHasResponded = createAction(
8
+ 'setHasResponded',
9
+ (hasResponded) => ({ hasResponded }),
10
+ )
11
+
12
+ export const initialize = createThunk(
13
+ 'initialize',
14
+ (config) =>
15
+ async (dispatch, getState, { api }) => {
16
+ try {
17
+ dispatch(ConfigActions.initialize(config))
18
+
19
+ const { features, defaultLocale } = await api.getConfig()
20
+ dispatch({ type: seamlyActions.SET_FEATURES, features })
21
+
22
+ let locale = config?.context?.locale || defaultLocale
23
+ dispatch(I18nActions.setInitialLocale(locale))
24
+ try {
25
+ if (api.hasConversation()) {
26
+ const initialState = await api.getConversationIntitialState()
27
+ dispatch({ type: seamlyActions.SET_INITIAL_STATE, initialState })
28
+
29
+ locale = initialState.translation?.locale || locale
30
+
31
+ if ('userResponded' in initialState) {
32
+ dispatch(setHasResponded(initialState.userResponded))
33
+ }
34
+ }
35
+ } catch (error) {
36
+ dispatch(InterruptActions.set(error))
37
+ }
38
+ await dispatch(I18nActions.setLocale(locale))
39
+ } catch (error) {
40
+ dispatch(InterruptActions.set(error))
41
+ }
42
+ },
43
+ )
@@ -0,0 +1,6 @@
1
+ import { useSelector } from '../redux'
2
+ import * as Selectors from './selectors'
3
+
4
+ export function useUserHasResponded() {
5
+ return useSelector(Selectors.selectUserHasResponded)
6
+ }
@@ -0,0 +1,6 @@
1
+ import * as Actions from './actions'
2
+
3
+ export * from './hooks'
4
+ export { default as Reducer } from './reducer'
5
+
6
+ export { Actions }
@@ -0,0 +1,16 @@
1
+ import { createReducer } from './utils'
2
+ import * as Actions from './actions'
3
+
4
+ const initialState = {
5
+ userHasResponded: false,
6
+ }
7
+
8
+ export default createReducer(
9
+ {
10
+ [Actions.setHasResponded]: (state, { hasResponded }) => ({
11
+ ...state,
12
+ userHasResponded: hasResponded,
13
+ }),
14
+ },
15
+ initialState,
16
+ )
@@ -0,0 +1,8 @@
1
+ import { createSelector } from 'reselect'
2
+ import { selectState } from './utils'
3
+
4
+ export const selectUserHasResponded = createSelector(
5
+ selectState,
6
+ (state) => state.userHasResponded,
7
+ )
8
+ export { selectState }
@@ -0,0 +1,4 @@
1
+ import { createDomain } from '../../lib/redux-helpers'
2
+
3
+ export const { createAction, createThunk, createReducer, selectState } =
4
+ createDomain('app')
@@ -0,0 +1,4 @@
1
+ import { createAction } from './utils'
2
+
3
+ export const initialize = createAction('initialize', (config) => ({ config }))
4
+ export const update = createAction('update', (config) => ({ config }))
@@ -0,0 +1,6 @@
1
+ import { useSelector } from '../redux'
2
+ import * as Selectors from './selectors'
3
+
4
+ export function useConfig() {
5
+ return useSelector(Selectors.selectConfig)
6
+ }
@@ -0,0 +1,8 @@
1
+ import * as Actions from './actions'
2
+ import * as Selectors from './selectors'
3
+
4
+ export * from './hooks'
5
+ export { default as createMiddleware } from './middleware'
6
+ export { default as Reducer } from './reducer'
7
+
8
+ export { Actions, Selectors }
@@ -0,0 +1,22 @@
1
+ import * as Actions from './actions'
2
+ import { seamlyActions } from '../../ui/utils/seamly-utils'
3
+
4
+ export default function createMiddleware() {
5
+ return ({ dispatch }) =>
6
+ (next) =>
7
+ (action) => {
8
+ const result = next(action)
9
+
10
+ switch (action.type) {
11
+ case String(Actions.initialize):
12
+ case String(Actions.update):
13
+ if (action?.config?.defaults?.agentName) {
14
+ dispatch({
15
+ type: seamlyActions.SET_HEADER_SUB_TITLE,
16
+ title: action?.config?.defaults?.agentName,
17
+ })
18
+ }
19
+ }
20
+ return result
21
+ }
22
+ }
@@ -0,0 +1,63 @@
1
+ import { defaultConfig } from '../../config'
2
+ import { createReducer } from './utils'
3
+ import * as Actions from './actions'
4
+ import { pick } from '../../ui/utils/general-utils'
5
+
6
+ const initialState = {
7
+ ...defaultConfig,
8
+ hideOnNoUserResponse: false,
9
+ showDisclaimer: false,
10
+ showFaq: false,
11
+ customComponents: {},
12
+ defaults: {},
13
+ }
14
+
15
+ const configKeys = [
16
+ 'hideOnNoUserResponse',
17
+ 'showDisclaimer',
18
+ 'showFaq',
19
+ 'namespace',
20
+ 'customComponents',
21
+ 'defaults',
22
+ 'layoutMode',
23
+ 'api',
24
+ 'zIndex',
25
+ 'context',
26
+ 'appContainerClassNames',
27
+ 'messages',
28
+ 'visible',
29
+ 'visibilityCallback',
30
+ ]
31
+ const updateState = (state, { config }) => {
32
+ const { messages, ...partialConfig } = pick(config, configKeys)
33
+ let newState = state
34
+ if (Object.keys(partialConfig).length > 0) {
35
+ newState = {
36
+ ...newState,
37
+ ...partialConfig,
38
+ }
39
+ }
40
+ if (messages) {
41
+ newState = {
42
+ ...newState,
43
+ messages: {
44
+ ...newState.messages,
45
+ ...messages,
46
+ },
47
+ }
48
+ }
49
+
50
+ return newState
51
+ }
52
+
53
+ export default createReducer(
54
+ {
55
+ [Actions.initialize]: (state, action) => {
56
+ return updateState(state, action)
57
+ },
58
+ [Actions.update]: (state, action) => {
59
+ return updateState(state, action)
60
+ },
61
+ },
62
+ initialState,
63
+ )
@@ -0,0 +1,23 @@
1
+ import { createSelector } from 'reselect'
2
+ import { visibilityStates } from '../../ui/utils/seamly-utils'
3
+ import { selectState } from './utils'
4
+
5
+ export const selectConfig = createSelector(selectState, (config) => {
6
+ let newConfig = {
7
+ visible:
8
+ config?.layoutMode === 'inline'
9
+ ? visibilityStates.open
10
+ : visibilityStates.minimized,
11
+ appContainerClassNames: config.appContainerClassNames || [],
12
+ ...config,
13
+ }
14
+ if (typeof newConfig.appContainerClassNames === 'function') {
15
+ newConfig = {
16
+ ...newConfig,
17
+ appContainerClassNames: newConfig.appContainerClassNames(newConfig),
18
+ }
19
+ }
20
+ return newConfig
21
+ })
22
+
23
+ export { selectState }
@@ -0,0 +1,4 @@
1
+ import { createDomain } from '../../lib/redux-helpers'
2
+
3
+ export const { createAction, createThunk, createReducer, selectState } =
4
+ createDomain('config')
@@ -1,10 +1,8 @@
1
- import { createDomain } from '../../lib/redux-helpers'
2
-
3
- const { createActions } = createDomain('forms')
1
+ import { createActions } from './utils'
4
2
 
5
3
  export const [registerForm, deregisterForm] = createActions('form', {
6
4
  register: (formId, persistData) => ({ formId, persistData }),
7
- deregister: formId => ({ formId }),
5
+ deregister: (formId) => ({ formId }),
8
6
  })
9
7
 
10
8
  export const [
@@ -29,10 +29,10 @@ export function useForm() {
29
29
  }
30
30
 
31
31
  export function useValidations(values, validationSchema) {
32
- const errors = useMemo(() => validate(values, validationSchema), [
33
- values,
34
- validationSchema,
35
- ])
32
+ const errors = useMemo(
33
+ () => validate(values, validationSchema),
34
+ [values, validationSchema],
35
+ )
36
36
  return {
37
37
  isValid: Object.keys(errors).length === 0,
38
38
  errors,
@@ -41,12 +41,8 @@ export function useValidations(values, validationSchema) {
41
41
 
42
42
  export function useFormControl(name) {
43
43
  const dispatch = useStoreDispatch()
44
- const {
45
- formId,
46
- updateControlValue,
47
- updateControlTouched,
48
- errors,
49
- } = useFormContext()
44
+ const { formId, updateControlValue, updateControlTouched, errors } =
45
+ useFormContext()
50
46
  const form = useSelectorWithProps(getFormById, { formId }, [formId])
51
47
  const isRegistered = !!form
52
48
  const isRegisteredRef = useRef()
@@ -77,10 +73,10 @@ export function useFormControl(name) {
77
73
  }, [isRegistered, formId, name, dispatch])
78
74
 
79
75
  // preact uses onInput instead of onChange
80
- const onInput = useCallback(e => updateControlValue(name, e.target.value), [
81
- name,
82
- updateControlValue,
83
- ])
76
+ const onInput = useCallback(
77
+ (e) => updateControlValue(name, e.target.value),
78
+ [name, updateControlValue],
79
+ )
84
80
 
85
81
  const onBlur = useCallback(() => {
86
82
  updateControlTouched(name, true)
@@ -25,10 +25,8 @@ export default function FormProvider({
25
25
  ])
26
26
  const [isSubmitted, setIsSubmitted] = useState(false)
27
27
  const [externalErrors, setExternalErrors] = useState({})
28
- const {
29
- isValid: validationIsValid,
30
- errors: validationErrors,
31
- } = useValidations(values, validationSchema)
28
+ const { isValid: validationIsValid, errors: validationErrors } =
29
+ useValidations(values, validationSchema)
32
30
  const errors = useMemo(
33
31
  () => ({
34
32
  ...validationErrors,
@@ -68,7 +66,7 @@ export default function FormProvider({
68
66
  // Function to manually set an error
69
67
  const setError = useCallback(
70
68
  (name, error) => {
71
- setExternalErrors(val => {
69
+ setExternalErrors((val) => {
72
70
  return {
73
71
  ...val,
74
72
  [name]: error,
@@ -79,7 +77,7 @@ export default function FormProvider({
79
77
  )
80
78
 
81
79
  const handleSubmit = useCallback(
82
- e => {
80
+ (e) => {
83
81
  e.preventDefault()
84
82
  setIsSubmitted(true)
85
83
  if (validationIsValid) {
@@ -1,4 +1,4 @@
1
- import { createReducer } from '../../lib/redux-helpers'
1
+ import { createReducer } from './utils'
2
2
  import * as Actions from './actions'
3
3
 
4
4
  const initialState = {}
@@ -31,7 +31,6 @@ function updateFormControl(state, formId, name, controlState) {
31
31
  }
32
32
 
33
33
  export default createReducer(
34
- 'form',
35
34
  {
36
35
  // Form handlers
37
36
  [Actions.registerForm]: (state, { formId, persistData }) => {
@@ -1,8 +1,8 @@
1
1
  import { createSelector } from 'reselect'
2
2
  import { getPropSelector } from '../redux/utils'
3
- import Reducer from './reducer'
3
+ import { selectState } from './utils'
4
4
 
5
- export const getState = state => state[String(Reducer)]
5
+ export const getState = selectState
6
6
 
7
7
  export const getFormById = createSelector(
8
8
  getState,
@@ -12,12 +12,12 @@ export const getFormById = createSelector(
12
12
 
13
13
  export const getFormControlsByFormId = createSelector(
14
14
  getFormById,
15
- form => form?.controls || {},
15
+ (form) => form?.controls || {},
16
16
  )
17
17
 
18
18
  export const getFormValuesByFormId = createSelector(
19
19
  getFormControlsByFormId,
20
- controls => {
20
+ (controls) => {
21
21
  const valuesObj = {}
22
22
  Object.entries(controls).forEach(([key, { value }]) => {
23
23
  valuesObj[key] = value
@@ -1,3 +1,8 @@
1
+ import { createDomain } from '../../lib/redux-helpers'
2
+
3
+ export const { createActions, createReducer, selectState } =
4
+ createDomain('forms')
5
+
1
6
  export function validate(values, schema = {}) {
2
7
  return Object.entries(schema).reduce((errors, [key, validations]) => {
3
8
  if (validations && !Array.isArray(validations)) {
@@ -0,0 +1,35 @@
1
+ import createMutex from '../../lib/mutex'
2
+ import { selectLocale } from './selectors'
3
+ import { createAction, createActions, createThunk } from './utils'
4
+
5
+ export const setInitialLocale = createAction('setInitialLocale', (locale) => ({
6
+ locale,
7
+ }))
8
+
9
+ export const [setLocaleStart, setLocaleResolve, setLocaleReject] =
10
+ createActions('setLocale', {
11
+ start: (locale) => ({ locale }),
12
+ resolve: (locale, translations) => ({ locale, translations }),
13
+ reject: (locale, error) => ({ locale, error }),
14
+ })
15
+
16
+ const mutex = createMutex()
17
+ export const setLocale = createThunk(
18
+ 'setLocale',
19
+ (locale) =>
20
+ async (dispatch, getState, { api }) => {
21
+ await mutex.runExclusively(async () => {
22
+ const currentLocale = selectLocale(getState())
23
+ if (currentLocale === locale) {
24
+ return
25
+ }
26
+ dispatch(setLocaleStart(locale))
27
+ try {
28
+ const translations = await api.getTranslations(locale)
29
+ dispatch(setLocaleResolve(locale, translations))
30
+ } catch (error) {
31
+ dispatch(setLocaleReject(locale, error))
32
+ }
33
+ })
34
+ },
35
+ )
@@ -0,0 +1,38 @@
1
+ import { useCallback } from 'preact/hooks'
2
+ import {
3
+ MessageFormatter,
4
+ pluralTypeHandler,
5
+ selectTypeHandler,
6
+ } from '@ultraq/icu-message-formatter'
7
+ import { useSelector } from '../redux'
8
+ import * as Selectors from './selectors'
9
+
10
+ // The passed in locale (en-GB) is only used to call Intl.PluralRules.select() in
11
+ // pluralTypeHandler. Since we only use exact plural matches (=0, =1 etc) we can
12
+ // safely use en-GB all the time.
13
+ const formatter = new MessageFormatter('en-GB', {
14
+ plural: pluralTypeHandler,
15
+ select: selectTypeHandler,
16
+ })
17
+
18
+ export function useI18n() {
19
+ const translations = useSelector(Selectors.selectTranslations)
20
+ const locale = useSelector(Selectors.selectLocale)
21
+ const initialLocale = useSelector(Selectors.selectInitialLocale)
22
+ const t = useCallback(
23
+ (key, values = {}) => {
24
+ const translation = translations[key]
25
+ if (!translation) {
26
+ return null
27
+ }
28
+ return formatter.format(translation, values)
29
+ },
30
+ [translations],
31
+ )
32
+
33
+ return {
34
+ t,
35
+ locale,
36
+ initialLocale,
37
+ }
38
+ }
@@ -1,82 +1,7 @@
1
- import { useCallback } from 'preact/hooks'
2
- import { createSelector } from 'reselect'
3
- import { createDomain, createReducer } from '../../lib/redux-helpers'
4
- import { useSelector } from '../redux'
5
- import { flattenObject } from '../../ui/utils/general-utils'
6
- import en from '../../../../translations/en'
1
+ import * as Actions from './actions'
2
+ import * as Selectors from './selectors'
7
3
 
8
- // Actions
9
- const { createAction } = createDomain('i18n')
4
+ export * from './hooks'
5
+ export { default as Reducer } from './reducer'
10
6
 
11
- export const initI18n = createAction('init', overrides => ({
12
- overrides,
13
- }))
14
-
15
- // Reducer
16
- const defaultState = { translations: flattenObject(en), overrides: {} }
17
- export const Reducer = createReducer(
18
- 'i18n',
19
- {
20
- [initI18n]: (state, { overrides }) => {
21
- return {
22
- ...state,
23
- overrides: overrides ? flattenObject(overrides) : undefined,
24
- }
25
- },
26
- },
27
- defaultState,
28
- )
29
-
30
- // Selectors
31
- export const getState = state => state[String(Reducer)]
32
- export const getTranslations = createSelector(
33
- getState,
34
- state => state.translations,
35
- )
36
- export const getOverrides = createSelector(
37
- getState,
38
- state => state.overrides || {},
39
- )
40
-
41
- export const getCombinedTranslations = createSelector(
42
- getTranslations,
43
- getOverrides,
44
- (translations, overrides) => {
45
- const overrideKeys = Object.keys(overrides)
46
- if (overrideKeys.length === 0) {
47
- return translations
48
- }
49
-
50
- const defaultKeys = Object.keys(translations)
51
- defaultKeys.forEach(key => {
52
- if (overrideKeys.indexOf(key) === -1) {
53
- console.error('Seamly: Missing translation key:', key)
54
- }
55
- })
56
-
57
- return {
58
- ...translations,
59
- ...overrides,
60
- }
61
- },
62
- )
63
-
64
- // Hooks
65
- export function useI18n() {
66
- const translations = useSelector(getCombinedTranslations)
67
- const t = useCallback(
68
- (key, values = {}) => {
69
- const translation = translations[key]
70
- if (typeof translation === 'function') {
71
- return translation(values)
72
- } else {
73
- return translation
74
- }
75
- },
76
- [translations],
77
- )
78
-
79
- return {
80
- t,
81
- }
82
- }
7
+ export { Actions, Selectors }
@@ -0,0 +1,58 @@
1
+ import * as Actions from './actions'
2
+ import { createReducer } from './utils'
3
+
4
+ const defaultState = {
5
+ translations: {
6
+ 'errors.configError.message':
7
+ 'We are sorry this happened, please retry at a later time.',
8
+ 'errors.configError.srText':
9
+ 'A chat configuration error occurred. Our apologies, please retry at a later time.',
10
+ 'errors.configError.title': 'Chat configuration error.',
11
+ 'errors.general.buttonText': 'Restart chat',
12
+ 'errors.general.message': 'Do you want to start a new chat session?',
13
+ 'errors.general.srText':
14
+ 'Something went wrong with the chat session. You can restart the chat.',
15
+ 'errors.general.title': 'Something went wrong',
16
+ 'errors.seamlyOffline.message':
17
+ 'There might be a problem with your or our network connection. The chat session should resume as soon the connection is available again.',
18
+ 'errors.seamlyOffline.srText':
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
+ 'errors.seamlyOffline.title': 'Connection issues',
21
+ },
22
+ isLoading: false,
23
+ initialLocale: undefined,
24
+ }
25
+
26
+ export default createReducer(
27
+ {
28
+ [Actions.setInitialLocale]: (state, { locale }) => ({
29
+ ...state,
30
+ initialLocale: locale,
31
+ }),
32
+ [Actions.setLocaleStart]: (state) => ({
33
+ ...state,
34
+ isLoading: true,
35
+ }),
36
+ [Actions.setLocaleResolve]: (state, { locale, translations }) => {
37
+ return {
38
+ ...state,
39
+ isLoading: false,
40
+ locale,
41
+ translations: Object.keys(translations)
42
+ .sort()
43
+ .reduce(
44
+ (accum, key) => ({
45
+ ...accum,
46
+ [key]: translations[key],
47
+ }),
48
+ {},
49
+ ),
50
+ }
51
+ },
52
+ [Actions.setLocaleReject]: (state) => ({
53
+ ...state,
54
+ isLoading: false,
55
+ }),
56
+ },
57
+ defaultState,
58
+ )
@@ -0,0 +1,15 @@
1
+ import { createSelector } from 'reselect'
2
+ import { selectState } from './utils'
3
+
4
+ export const selectTranslations = createSelector(
5
+ selectState,
6
+ (state) => state.translations,
7
+ )
8
+
9
+ export const selectInitialLocale = createSelector(
10
+ selectState,
11
+ (state) => state.initialLocale,
12
+ )
13
+ export const selectLocale = createSelector(selectState, (state) => state.locale)
14
+
15
+ export { selectState }
@@ -0,0 +1,9 @@
1
+ import { createDomain } from '../../lib/redux-helpers'
2
+
3
+ export const {
4
+ createAction,
5
+ createActions,
6
+ createThunk,
7
+ createReducer,
8
+ selectState,
9
+ } = createDomain('i18n')
@@ -0,0 +1,4 @@
1
+ import { createAction } from './utils'
2
+
3
+ export const set = createAction('set', (error) => ({ error }))
4
+ export const clear = createAction('clear')
@@ -0,0 +1,29 @@
1
+ import { useMemo } from 'preact/hooks'
2
+ import { useI18n } from '../i18n'
3
+ import { useSelector } from '../redux'
4
+ import * as Selectors from './selectors'
5
+
6
+ export function useInterrupt() {
7
+ const { t } = useI18n()
8
+
9
+ const error = useSelector(Selectors.selectError)
10
+ const hasInterrupt = Boolean(error)
11
+
12
+ const meta = useMemo(() => {
13
+ if (!error) return {}
14
+ const { langKey, action } = error
15
+ const title = t(`${langKey}.title`)
16
+ const message = t(`${langKey}.message`)
17
+ const srText = t(`${langKey}.srText`)
18
+ const buttonText = t(`${langKey}.buttonText`)
19
+
20
+ return {
21
+ ...(langKey ? { title, message, srText } : {}),
22
+ ...(action ? { action } : {}),
23
+ ...(action && langKey ? { buttonText } : {}),
24
+ originalError: error,
25
+ }
26
+ }, [t, error])
27
+
28
+ return { hasInterrupt, meta, error }
29
+ }
@@ -0,0 +1,9 @@
1
+ import * as Actions from './actions'
2
+ import * as Selectors from './selectors'
3
+
4
+ export * from './hooks'
5
+
6
+ export { default as createMiddleware } from './middleware'
7
+ export { default as Reducer } from './reducer'
8
+
9
+ export { Actions, Selectors }