@seamly/web-ui 20.7.0 → 20.8.0-alpha.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 (222) hide show
  1. package/CHANGELOG.md +212 -16
  2. package/build/dist/lib/hooks.js +1 -1
  3. package/build/dist/lib/hooks.min.js +1 -1
  4. package/build/dist/lib/index.debug.js +939 -784
  5. package/build/dist/lib/index.debug.min.js +1 -1
  6. package/build/dist/lib/index.debug.min.js.LICENSE.txt +186 -130
  7. package/build/dist/lib/index.js +24806 -19703
  8. package/build/dist/lib/index.min.js +1 -1
  9. package/build/dist/lib/index.min.js.LICENSE.txt +38 -4
  10. package/build/dist/lib/standalone.js +32726 -26838
  11. package/build/dist/lib/standalone.min.js +1 -1
  12. package/build/dist/lib/standalone.min.js.LICENSE.txt +39 -0
  13. package/build/dist/lib/storage.js +2 -2
  14. package/build/dist/lib/storage.min.js +1 -1
  15. package/build/dist/lib/style-guide.js +8649 -7863
  16. package/build/dist/lib/style-guide.min.js +2 -1
  17. package/build/dist/lib/style-guide.min.js.LICENSE.txt +38 -0
  18. package/build/dist/lib/utils.js +1 -2
  19. package/build/dist/lib/utils.min.js +1 -1
  20. package/package.json +19 -9
  21. package/src/icons/avatar_agent-32.svg +7 -0
  22. package/src/icons/avatar_bot-32.svg +6 -1
  23. package/src/javascripts/api/index.js +1 -1
  24. package/src/javascripts/{config.js → config.ts} +3 -1
  25. package/src/javascripts/config.types.ts +95 -0
  26. package/src/javascripts/domains/app/actions.ts +83 -0
  27. package/src/javascripts/domains/app/app.types.ts +3 -0
  28. package/src/javascripts/domains/app/hooks.js +3 -5
  29. package/src/javascripts/domains/app/selectors.ts +6 -0
  30. package/src/javascripts/domains/app/slice.ts +30 -0
  31. package/src/javascripts/domains/config/actions.ts +45 -0
  32. package/src/javascripts/domains/config/hooks.ts +19 -0
  33. package/src/javascripts/domains/config/selectors.ts +24 -0
  34. package/src/javascripts/domains/config/slice.ts +111 -0
  35. package/src/javascripts/domains/errors/index.js +13 -9
  36. package/src/javascripts/domains/forms/context.ts +14 -0
  37. package/src/javascripts/domains/forms/forms.types.ts +24 -0
  38. package/src/javascripts/domains/forms/{hooks.js → hooks.ts} +23 -26
  39. package/src/javascripts/domains/forms/{provider.js → provider.tsx} +20 -14
  40. package/src/javascripts/domains/forms/{selectors.js → selectors.ts} +7 -8
  41. package/src/javascripts/domains/forms/slice.ts +84 -0
  42. package/src/javascripts/domains/forms/utils.ts +15 -0
  43. package/src/javascripts/domains/i18n/actions.ts +24 -0
  44. package/src/javascripts/domains/i18n/{hooks.js → hooks.ts} +2 -2
  45. package/src/javascripts/domains/i18n/i18n.types.ts +6 -0
  46. package/src/javascripts/domains/i18n/selectors.ts +16 -0
  47. package/src/javascripts/domains/i18n/{reducer.js → slice.ts} +43 -37
  48. package/src/javascripts/domains/interrupt/{hooks.js → hooks.ts} +2 -2
  49. package/src/javascripts/domains/interrupt/{middleware.js → middleware.ts} +11 -8
  50. package/src/javascripts/domains/interrupt/selectors.ts +6 -0
  51. package/src/javascripts/domains/interrupt/slice.ts +40 -0
  52. package/src/javascripts/domains/options/middleware.js +9 -6
  53. package/src/javascripts/domains/redux/redux.types.ts +11 -0
  54. package/src/javascripts/domains/store/index.ts +53 -0
  55. package/src/javascripts/domains/store/slice.ts +639 -0
  56. package/src/javascripts/domains/store/store.types.ts +135 -0
  57. package/src/javascripts/domains/translations/components/chat-status.js +2 -2
  58. package/src/javascripts/domains/translations/components/options-button.js +1 -1
  59. package/src/javascripts/domains/translations/components/options-dialog/form.js +5 -5
  60. package/src/javascripts/domains/translations/components/options-dialog/index.js +2 -2
  61. package/src/javascripts/domains/translations/{hooks.js → hooks.ts} +28 -23
  62. package/src/javascripts/domains/translations/middleware.js +29 -27
  63. package/src/javascripts/domains/translations/selectors.js +4 -9
  64. package/src/javascripts/domains/translations/slice.ts +67 -0
  65. package/src/javascripts/domains/translations/translations.types.ts +12 -0
  66. package/src/javascripts/domains/visibility/{actions.js → actions.ts} +25 -19
  67. package/src/javascripts/domains/visibility/{hooks.js → hooks.ts} +13 -10
  68. package/src/javascripts/domains/visibility/{selectors.js → selectors.ts} +3 -6
  69. package/src/javascripts/domains/visibility/slice.ts +38 -0
  70. package/src/javascripts/domains/visibility/utils.js +0 -9
  71. package/src/javascripts/domains/visibility/visibility.types.ts +6 -0
  72. package/src/javascripts/index.ts +92 -0
  73. package/src/javascripts/lib/engine/index.js +15 -11
  74. package/src/javascripts/lib/external-api/initialize-api.js +1 -1
  75. package/src/javascripts/lib/id.js +5 -8
  76. package/src/javascripts/lib/mutex.js +3 -1
  77. package/src/javascripts/lib/store/providers/cookie-storage.js +1 -1
  78. package/src/javascripts/lib/store/providers/session-storage.js +1 -1
  79. package/src/javascripts/package/hooks.js +2 -2
  80. package/src/javascripts/package/utils.js +0 -1
  81. package/src/javascripts/schema.ts +1455 -0
  82. package/src/javascripts/style-guide/components/app.js +4 -4
  83. package/src/javascripts/style-guide/components/static-core.js +87 -65
  84. package/src/javascripts/style-guide/components/view.js +4 -4
  85. package/src/javascripts/style-guide/state-helpers/index.js +5 -5
  86. package/src/javascripts/style-guide/states.js +6 -4
  87. package/src/javascripts/style-guide.ts +5 -0
  88. package/src/javascripts/ui/components/app-options/index.js +2 -4
  89. package/src/javascripts/ui/components/conversation/component-filter.js +1 -1
  90. package/src/javascripts/ui/components/conversation/conversation.js +5 -5
  91. package/src/javascripts/ui/components/conversation/event/card-message.js +1 -1
  92. package/src/javascripts/ui/components/conversation/event/carousel-component/components/controls.js +1 -1
  93. package/src/javascripts/ui/components/conversation/event/carousel-message/components/slide.js +1 -1
  94. package/src/javascripts/ui/components/conversation/event/carousel-message/index.js +1 -1
  95. package/src/javascripts/ui/components/conversation/event/choice-prompt.js +2 -2
  96. package/src/javascripts/ui/components/conversation/event/conversation-suggestions.js +11 -14
  97. package/src/javascripts/ui/components/conversation/event/cta.js +1 -1
  98. package/src/javascripts/ui/components/conversation/event/divider/variants/default.js +1 -1
  99. package/src/javascripts/ui/components/conversation/event/divider/variants/new-translation.js +5 -2
  100. package/src/javascripts/ui/components/conversation/event/event-participant.js +2 -2
  101. package/src/javascripts/ui/components/conversation/event/hooks/use-formatted-date.js +2 -2
  102. package/src/javascripts/ui/components/conversation/event/image-lightbox.js +1 -1
  103. package/src/javascripts/ui/components/conversation/event/image.js +5 -7
  104. package/src/javascripts/ui/components/conversation/event/participant.js +1 -1
  105. package/src/javascripts/ui/components/conversation/event/splash.js +3 -3
  106. package/src/javascripts/ui/components/conversation/event/text.js +1 -1
  107. package/src/javascripts/ui/components/conversation/event/translation.js +2 -2
  108. package/src/javascripts/ui/components/conversation/event/upload.js +2 -2
  109. package/src/javascripts/ui/components/conversation/event/video.js +1 -1
  110. package/src/javascripts/ui/components/conversation/message-container.js +4 -4
  111. package/src/javascripts/ui/components/core/seamly-api-context.js +1 -1
  112. package/src/javascripts/ui/components/core/seamly-core.js +15 -14
  113. package/src/javascripts/ui/components/core/seamly-event-subscriber.js +96 -91
  114. package/src/javascripts/ui/components/core/seamly-file-upload.js +20 -24
  115. package/src/javascripts/ui/components/core/seamly-initializer.js +1 -1
  116. package/src/javascripts/ui/components/core/seamly-instance-functions-loader.js +5 -4
  117. package/src/javascripts/ui/components/core/seamly-new-notifications.js +2 -2
  118. package/src/javascripts/ui/components/core/seamly-read-state.js +10 -17
  119. package/src/javascripts/ui/components/entry/deprecated-toggle-button.js +3 -3
  120. package/src/javascripts/ui/components/entry/entry-container.js +4 -6
  121. package/src/javascripts/ui/components/entry/text-entry/hooks.js +3 -3
  122. package/src/javascripts/ui/components/entry/text-entry/index.js +3 -2
  123. package/src/javascripts/ui/components/entry/text-entry/text-entry-form.js +6 -10
  124. package/src/javascripts/ui/components/entry/upload/file-upload-form.js +2 -2
  125. package/src/javascripts/ui/components/entry/upload/index.js +10 -9
  126. package/src/javascripts/ui/components/entry/upload-toggle.js +2 -2
  127. package/src/javascripts/ui/components/faq/faq.js +6 -6
  128. package/src/javascripts/ui/components/form-controls/file-input.js +1 -1
  129. package/src/javascripts/ui/components/form-controls/form.js +1 -1
  130. package/src/javascripts/ui/components/form-controls/input.js +1 -1
  131. package/src/javascripts/ui/components/form-controls/select.js +1 -1
  132. package/src/javascripts/ui/components/layout/agent-info.js +4 -4
  133. package/src/javascripts/ui/components/layout/chat-frame.js +3 -3
  134. package/src/javascripts/ui/components/layout/chat.js +11 -12
  135. package/src/javascripts/ui/components/layout/deprecated-app-frame.js +10 -9
  136. package/src/javascripts/ui/components/layout/header.js +1 -1
  137. package/src/javascripts/ui/components/layout/interrupt.js +23 -24
  138. package/src/javascripts/ui/components/layout/pre-chat-messages.js +11 -11
  139. package/src/javascripts/ui/components/layout/privacy-disclaimer.js +2 -2
  140. package/src/javascripts/ui/components/options/options-button.js +12 -10
  141. package/src/javascripts/ui/components/options/transcript/index.js +2 -2
  142. package/src/javascripts/ui/components/options/transcript/transcript-form.js +1 -1
  143. package/src/javascripts/ui/components/suggestions/index.js +9 -8
  144. package/src/javascripts/ui/components/view/deprecated-view.js +19 -16
  145. package/src/javascripts/ui/components/view/index.js +12 -12
  146. package/src/javascripts/ui/components/view/inline-view.js +2 -2
  147. package/src/javascripts/ui/components/view/window-view/collapse-button.js +3 -3
  148. package/src/javascripts/ui/components/view/window-view/index.js +13 -13
  149. package/src/javascripts/ui/components/view/window-view/window-open-button.js +13 -13
  150. package/src/javascripts/ui/components/warnings/idle-detach-warning.js +1 -1
  151. package/src/javascripts/ui/components/warnings/resume-conversation-prompt.js +1 -1
  152. package/src/javascripts/ui/components/widgets/lightbox.js +2 -2
  153. package/src/javascripts/ui/components/widgets/upload-progress.js +1 -1
  154. package/src/javascripts/ui/hooks/component-helper-hooks.js +1 -1
  155. package/src/javascripts/ui/hooks/file-upload-hooks.js +4 -6
  156. package/src/javascripts/ui/hooks/focus-helper-hooks.js +14 -12
  157. package/src/javascripts/ui/hooks/live-region-hooks.js +2 -0
  158. package/src/javascripts/ui/hooks/seamly-api-hooks.js +8 -3
  159. package/src/javascripts/ui/hooks/seamly-entry-hooks.js +28 -25
  160. package/src/javascripts/ui/hooks/seamly-hooks.js +25 -25
  161. package/src/javascripts/ui/hooks/seamly-option-hooks.js +17 -19
  162. package/src/javascripts/ui/hooks/seamly-state-hooks.js +14 -13
  163. package/src/javascripts/ui/hooks/use-seamly-chat.js +15 -25
  164. package/src/javascripts/ui/hooks/use-seamly-commands.js +46 -46
  165. package/src/javascripts/ui/hooks/use-seamly-idle-detach-countdown.js +22 -24
  166. package/src/javascripts/ui/hooks/use-seamly-resume-conversation-prompt.js +8 -9
  167. package/src/javascripts/ui/hooks/use-single-file-upload.js +4 -6
  168. package/src/javascripts/ui/hooks/utility-hooks.js +4 -4
  169. package/src/javascripts/ui/utils/form-utils.js +0 -145
  170. package/src/javascripts/ui/utils/general-utils.js +3 -4
  171. package/src/javascripts/ui/utils/seamly-utils.ts +73 -0
  172. package/webpack/config.common.js +16 -0
  173. package/webpack/config.dev.js +1 -0
  174. package/webpack/config.package.js +26 -5
  175. package/webpack/defaults.js +7 -2
  176. package/webpack/parts/babel-loader-browser-plugins.js +1 -0
  177. package/webpack/parts/dev-server.js +4 -3
  178. package/src/javascripts/domains/app/actions.js +0 -112
  179. package/src/javascripts/domains/app/index.js +0 -7
  180. package/src/javascripts/domains/app/reducer.js +0 -16
  181. package/src/javascripts/domains/app/selectors.js +0 -8
  182. package/src/javascripts/domains/app/utils.js +0 -4
  183. package/src/javascripts/domains/config/actions.js +0 -7
  184. package/src/javascripts/domains/config/hooks.js +0 -23
  185. package/src/javascripts/domains/config/index.js +0 -7
  186. package/src/javascripts/domains/config/reducer.js +0 -79
  187. package/src/javascripts/domains/config/selectors.js +0 -23
  188. package/src/javascripts/domains/config/utils.js +0 -4
  189. package/src/javascripts/domains/forms/actions.js +0 -21
  190. package/src/javascripts/domains/forms/context.js +0 -6
  191. package/src/javascripts/domains/forms/index.js +0 -8
  192. package/src/javascripts/domains/forms/reducer.js +0 -84
  193. package/src/javascripts/domains/forms/utils.js +0 -20
  194. package/src/javascripts/domains/i18n/actions.js +0 -20
  195. package/src/javascripts/domains/i18n/index.js +0 -7
  196. package/src/javascripts/domains/i18n/selectors.js +0 -15
  197. package/src/javascripts/domains/i18n/utils.js +0 -4
  198. package/src/javascripts/domains/interrupt/actions.js +0 -4
  199. package/src/javascripts/domains/interrupt/index.js +0 -9
  200. package/src/javascripts/domains/interrupt/reducer.js +0 -22
  201. package/src/javascripts/domains/interrupt/selectors.js +0 -6
  202. package/src/javascripts/domains/interrupt/utils.js +0 -4
  203. package/src/javascripts/domains/options/index.js +0 -1
  204. package/src/javascripts/domains/redux/context.js +0 -6
  205. package/src/javascripts/domains/redux/create-redux-store.js +0 -21
  206. package/src/javascripts/domains/redux/hooks.js +0 -80
  207. package/src/javascripts/domains/redux/index.js +0 -19
  208. package/src/javascripts/domains/redux/provider.js +0 -5
  209. package/src/javascripts/domains/redux/utils.js +0 -12
  210. package/src/javascripts/domains/store/index.js +0 -46
  211. package/src/javascripts/domains/store/state-reducer.js +0 -56
  212. package/src/javascripts/domains/translations/actions.js +0 -11
  213. package/src/javascripts/domains/translations/index.js +0 -10
  214. package/src/javascripts/domains/translations/reducer.js +0 -69
  215. package/src/javascripts/domains/translations/utils.js +0 -4
  216. package/src/javascripts/domains/visibility/index.js +0 -8
  217. package/src/javascripts/domains/visibility/reducer.js +0 -24
  218. package/src/javascripts/index.js +0 -153
  219. package/src/javascripts/lib/redux-helpers/index.js +0 -99
  220. package/src/javascripts/style-guide.js +0 -5
  221. package/src/javascripts/ui/hooks/use-seamly-dispatch.js +0 -3
  222. package/src/javascripts/ui/utils/seamly-utils.js +0 -832
@@ -0,0 +1,14 @@
1
+ import type { FormContextType } from 'domains/forms/forms.types'
2
+ import { createContext } from 'preact'
3
+
4
+ const FormContext = createContext<FormContextType>({
5
+ handleSubmit: () => undefined,
6
+ isSubmitted: false,
7
+ isValid: false,
8
+ updateControlValue: () => undefined,
9
+ updateControlTouched: () => undefined,
10
+ errors: undefined,
11
+ })
12
+
13
+ export default FormContext
14
+ export const { Provider, Consumer } = FormContext
@@ -0,0 +1,24 @@
1
+ export type FormContextType = {
2
+ handleSubmit: (e: Event) => void
3
+ isSubmitted: boolean
4
+ isValid: boolean
5
+ formId?: string
6
+ updateControlValue: (name: string, value: string) => void
7
+ updateControlTouched: (name: string, value: boolean) => void
8
+ errors?: Record<string, unknown>
9
+ validationSchema?: Record<string, unknown>
10
+ values?: Record<string, string>
11
+ }
12
+
13
+ export type ControlState = {
14
+ value?: string
15
+ touched?: boolean
16
+ name?: string
17
+ }
18
+
19
+ export interface FormState {
20
+ [key: string]: {
21
+ persistData?: boolean
22
+ controls: Record<string, ControlState>
23
+ }
24
+ }
@@ -1,22 +1,23 @@
1
+ import { deregisterControl, registerControl } from 'domains/forms/slice'
2
+ import {
3
+ getFormById,
4
+ getControlValueByName,
5
+ getControlTouchedByName,
6
+ } from 'domains/forms/selectors'
1
7
  import {
2
- useCallback,
3
8
  useContext,
9
+ useMemo,
4
10
  useEffect,
5
11
  useLayoutEffect,
6
- useMemo,
7
- useRef,
12
+ useCallback,
8
13
  } from 'preact/hooks'
9
- import { useSelectorWithProps, useStoreDispatch } from 'domains/redux'
14
+ import { useDispatch, useSelector } from 'react-redux'
10
15
  import FormContext from './context'
11
- import * as Actions from './actions'
12
- import {
13
- getControlValueByName,
14
- getFormById,
15
- getControlTouchedByName,
16
- } from './selectors'
17
16
  import { validate } from './utils'
17
+ import type { RootState } from 'domains/store'
18
+ import type { FormContextType } from 'domains/forms/forms.types'
18
19
 
19
- export function useFormContext() {
20
+ export function useFormContext(): FormContextType {
20
21
  return useContext(FormContext)
21
22
  }
22
23
 
@@ -42,21 +43,17 @@ export function useValidations(values, validationSchema) {
42
43
  }
43
44
 
44
45
  export function useFormControl(name) {
45
- const dispatch = useStoreDispatch()
46
+ const dispatch = useDispatch()
46
47
  const { formId, updateControlValue, updateControlTouched, errors } =
47
48
  useFormContext()
48
- const form = useSelectorWithProps(getFormById, { formId }, [formId])
49
+ const form = useSelector((state: RootState) => getFormById(state, { formId }))
49
50
  const isRegistered = !!form
50
- const isRegisteredRef = useRef()
51
- isRegisteredRef.current = isRegistered
52
- const value = useSelectorWithProps(getControlValueByName, { formId, name }, [
53
- formId,
54
- name,
55
- ])
56
- const touched = useSelectorWithProps(
57
- getControlTouchedByName,
58
- { formId, name },
59
- [formId, name],
51
+ const value = useSelector((state: RootState) => {
52
+ return getControlValueByName(state, { formId, name })
53
+ })
54
+
55
+ const touched = useSelector((state: RootState) =>
56
+ getControlTouchedByName(state, { formId, name }),
60
57
  )
61
58
  const error = errors?.[name]
62
59
  const isValid = !error
@@ -64,13 +61,13 @@ export function useFormControl(name) {
64
61
  useEffect(() => {
65
62
  // Make sure the form is registered
66
63
  // Since child useEffect runs before FormProvider useEffect
67
- if (isRegisteredRef.current) {
68
- dispatch(Actions.registerControl(formId, name))
64
+ if (isRegistered) {
65
+ dispatch(registerControl({ formId, name }))
69
66
  }
70
67
  }, [isRegistered, formId, name, dispatch])
71
68
  useLayoutEffect(() => {
72
69
  return () => {
73
- dispatch(Actions.deregisterControl(formId, name))
70
+ dispatch(deregisterControl({ formId, name }))
74
71
  }
75
72
  }, [isRegistered, formId, name, dispatch])
76
73
 
@@ -1,3 +1,14 @@
1
+ import { setHasResponded } from 'domains/app/slice'
2
+ import { Provider } from 'domains/forms/context'
3
+ import { useValidations } from 'domains/forms/hooks'
4
+ import { getFormValuesByFormId } from 'domains/forms/selectors'
5
+ import {
6
+ deregisterForm,
7
+ registerForm,
8
+ updateControlTouched as dispatchControlTouched,
9
+ updateControlValue as dispatchControlValue,
10
+ } from 'domains/forms/slice'
11
+ import type { RootState } from 'domains/store'
1
12
  import {
2
13
  useCallback,
3
14
  useEffect,
@@ -5,12 +16,7 @@ import {
5
16
  useMemo,
6
17
  useState,
7
18
  } from 'preact/hooks'
8
- import { useSelectorWithProps, useStoreDispatch } from 'domains/redux'
9
- import { setHasResponded } from 'domains/app/actions'
10
- import { Provider } from 'domains/forms/context'
11
- import * as Actions from 'domains/forms/actions'
12
- import { getFormValuesByFormId } from 'domains/forms/selectors'
13
- import { useValidations } from 'domains/forms/hooks'
19
+ import { useDispatch, useSelector } from 'react-redux'
14
20
 
15
21
  export default function FormProvider({
16
22
  children,
@@ -21,10 +27,10 @@ export default function FormProvider({
21
27
  validationSchema,
22
28
  ...props
23
29
  }) {
24
- const dispatch = useStoreDispatch()
25
- const values = useSelectorWithProps(getFormValuesByFormId, { formId }, [
26
- formId,
27
- ])
30
+ const dispatch = useDispatch()
31
+ const values = useSelector((store: RootState) =>
32
+ getFormValuesByFormId(store, { formId }),
33
+ )
28
34
  const [isSubmitted, setIsSubmitted] = useState(false)
29
35
  const [externalErrors, setExternalErrors] = useState({})
30
36
  const { isValid: validationIsValid, errors: validationErrors } =
@@ -40,27 +46,27 @@ export default function FormProvider({
40
46
  // register
41
47
  useLayoutEffect(() => {
42
48
  // register form in redux store
43
- dispatch(Actions.registerForm(formId, persistData))
49
+ dispatch(registerForm({ formId, persistData }))
44
50
  }, [formId, persistData, dispatch])
45
51
 
46
52
  // deregister
47
53
  useEffect(() => {
48
54
  return () => {
49
55
  // deregister form from redux store
50
- dispatch(Actions.deregisterForm(formId))
56
+ dispatch(deregisterForm({ formId }))
51
57
  }
52
58
  }, [formId, persistData, dispatch])
53
59
 
54
60
  const updateControlValue = useCallback(
55
61
  (name, value) => {
56
- dispatch(Actions.updateControlValue(formId, name, value))
62
+ dispatch(dispatchControlValue({ formId, name, value }))
57
63
  },
58
64
  [formId, dispatch],
59
65
  )
60
66
 
61
67
  const updateControlTouched = useCallback(
62
68
  (name, touched) => {
63
- dispatch(Actions.updateControlTouched(formId, name, touched))
69
+ dispatch(dispatchControlTouched({ formId, name, touched }))
64
70
  },
65
71
  [dispatch, formId],
66
72
  )
@@ -1,12 +1,10 @@
1
- import { createSelector } from 'reselect'
2
- import { getPropSelector } from 'domains/redux/utils'
3
- import { selectState } from './utils'
1
+ import { createSelector } from '@reduxjs/toolkit'
4
2
 
5
- export const getState = selectState
3
+ export const getState = ({ forms }) => forms
6
4
 
7
5
  export const getFormById = createSelector(
8
6
  getState,
9
- getPropSelector('formId'),
7
+ (_, { formId }) => formId,
10
8
  (forms, formId) => forms[formId],
11
9
  )
12
10
 
@@ -17,23 +15,24 @@ export const getFormControlsByFormId = createSelector(
17
15
 
18
16
  export const getFormValuesByFormId = createSelector(
19
17
  getFormControlsByFormId,
20
- (controls) => {
18
+ (controls: { [key: string]: { value: string } }) => {
21
19
  const valuesObj = {}
22
20
  Object.entries(controls).forEach(([key, { value }]) => {
23
21
  valuesObj[key] = value
24
22
  })
23
+
25
24
  return valuesObj
26
25
  },
27
26
  )
28
27
 
29
28
  export const getControlValueByName = createSelector(
30
29
  getFormControlsByFormId,
31
- getPropSelector('name'),
30
+ (_, { name }) => name,
32
31
  (controls, name) => controls[name]?.value,
33
32
  )
34
33
 
35
34
  export const getControlTouchedByName = createSelector(
36
35
  getFormControlsByFormId,
37
- getPropSelector('name'),
36
+ (_, { name }) => name,
38
37
  (controls, name) => controls[name]?.touched,
39
38
  )
@@ -0,0 +1,84 @@
1
+ import { createSlice } from '@reduxjs/toolkit'
2
+ import { resetApp } from 'domains/app/actions'
3
+ import { ControlState } from 'domains/forms/forms.types'
4
+
5
+ const initialFormState = {
6
+ controls: {},
7
+ }
8
+
9
+ const initialControlState: ControlState = {
10
+ value: '',
11
+ touched: false,
12
+ }
13
+
14
+ const initialState = {
15
+ controls: initialControlState,
16
+ }
17
+
18
+ export const formsSlice = createSlice({
19
+ name: 'forms',
20
+ initialState,
21
+ reducers: {
22
+ registerForm: (state, { payload: { persistData, formId } }) => {
23
+ const formState = persistData
24
+ ? state[formId] ?? { ...initialFormState, persistData }
25
+ : { ...initialFormState, persistData }
26
+
27
+ state[formId] = formState
28
+ },
29
+ deregisterForm: (state, { payload: { formId } }) => {
30
+ if (!state[formId]?.persistData) {
31
+ delete state[formId]
32
+ }
33
+
34
+ return state
35
+ },
36
+ // Form control handlers
37
+ registerControl: (state, { payload: { name, formId } }) => {
38
+ state[formId].controls = {
39
+ [name]: initialControlState,
40
+ }
41
+ },
42
+ deregisterControl: (state, { payload: { name, formId } }) => {
43
+ const form = state[formId]
44
+ if (!form) {
45
+ return state
46
+ }
47
+ if (form.persistData) {
48
+ return state
49
+ }
50
+ const controls = { ...form.controls }
51
+ delete controls[name]
52
+ return {
53
+ ...state,
54
+ [formId]: {
55
+ ...form,
56
+ controls,
57
+ },
58
+ }
59
+ },
60
+ updateControlValue: (state, { payload: { formId, name, value } }) => {
61
+ // return updateFormControl(state, formId, name, { value })
62
+ if (!state[formId]?.controls) return
63
+ state[formId].controls[name].value = value
64
+ },
65
+ updateControlTouched: (state, { payload: { formId, name, touched } }) => {
66
+ if (!state[formId]?.controls[name]) return
67
+ state[formId].controls[name].touched = touched
68
+ },
69
+ },
70
+ extraReducers: (builder) => {
71
+ builder.addCase(resetApp.pending, () => initialState)
72
+ },
73
+ })
74
+
75
+ export const {
76
+ registerForm,
77
+ deregisterForm,
78
+ registerControl,
79
+ deregisterControl,
80
+ updateControlValue,
81
+ updateControlTouched,
82
+ } = formsSlice.actions
83
+
84
+ export default formsSlice.reducer
@@ -0,0 +1,15 @@
1
+ export function validate(values, schema: Record<string, unknown> = {}) {
2
+ return Object.entries(schema).reduce((errors, [key, validations]) => {
3
+ const validationsArr = !Array.isArray(validations)
4
+ ? [validations]
5
+ : validations
6
+
7
+ for (let i = 0; i < validationsArr?.length ?? 0; i++) {
8
+ if (!validationsArr[i].fn(values[key], validationsArr[i].compareValue)) {
9
+ errors[key] = validationsArr[i].errorText
10
+ break
11
+ }
12
+ }
13
+ return errors
14
+ }, {})
15
+ }
@@ -0,0 +1,24 @@
1
+ import { createAsyncThunk } from '@reduxjs/toolkit'
2
+ import { selectLocale } from 'domains/i18n/selectors'
3
+ import { ThunkAPI } from 'domains/redux/redux.types'
4
+
5
+ export const setLocale = createAsyncThunk<
6
+ {
7
+ translations: Record<string, string>
8
+ locale: string
9
+ },
10
+ string,
11
+ ThunkAPI
12
+ >('setLocale', async (locale, { getState, extra: { api } }) => {
13
+ const stateLocale = selectLocale(getState())
14
+ if (locale === stateLocale) {
15
+ return undefined
16
+ }
17
+
18
+ const response = await api.getTranslations(locale)
19
+
20
+ return {
21
+ translations: response,
22
+ locale,
23
+ }
24
+ })
@@ -1,10 +1,10 @@
1
- import { useCallback } from 'preact/hooks'
2
1
  import {
3
2
  MessageFormatter,
4
3
  pluralTypeHandler,
5
4
  selectTypeHandler,
6
5
  } from '@ultraq/icu-message-formatter'
7
- import { useSelector } from 'domains/redux'
6
+ import { useCallback } from 'preact/hooks'
7
+ import { useSelector } from 'react-redux'
8
8
  import * as Selectors from './selectors'
9
9
 
10
10
  // The passed in locale (en-GB) is only used to call Intl.PluralRules.select() in
@@ -0,0 +1,6 @@
1
+ export interface I18nState {
2
+ translations: Record<string, string>
3
+ isLoading: boolean
4
+ initialLocale?: string
5
+ locale?: string
6
+ }
@@ -0,0 +1,16 @@
1
+ import { createSelector } from 'reselect'
2
+
3
+ export const selectTranslations = createSelector(
4
+ ({ i18n }) => i18n,
5
+ ({ translations }) => translations,
6
+ )
7
+
8
+ export const selectInitialLocale = createSelector(
9
+ ({ i18n }) => i18n,
10
+ ({ initialLocale }) => initialLocale,
11
+ )
12
+
13
+ export const selectLocale = createSelector(
14
+ ({ i18n }) => i18n,
15
+ ({ locale }) => locale,
16
+ )
@@ -1,8 +1,10 @@
1
- import * as AppActions from 'domains/app/actions'
2
- import * as Actions from './actions'
3
- import { createReducer } from './utils'
1
+ import { createSlice } from '@reduxjs/toolkit'
2
+ import { initializeApp } from 'domains/app/actions'
3
+ import { initializeConfig } from 'domains/config/actions'
4
+ import { setLocale } from 'domains/i18n/actions'
5
+ import { I18nState } from 'domains/i18n/i18n.types'
4
6
 
5
- const defaultState = {
7
+ const initialState: I18nState = {
6
8
  translations: {
7
9
  'errors.configError.message':
8
10
  'We are sorry this happened, please retry at a later time.',
@@ -28,45 +30,49 @@ const defaultState = {
28
30
  },
29
31
  isLoading: false,
30
32
  initialLocale: undefined,
33
+ locale: undefined,
31
34
  }
32
35
 
33
- export default createReducer(
34
- {
35
- [Actions.setInitialLocale]: (state, { locale }) => ({
36
- ...state,
37
- initialLocale: locale,
38
- }),
39
- [Actions.setLocale.pending]: (state) => ({
40
- ...state,
41
- isLoading: true,
42
- }),
43
- [Actions.setLocale.fulfilled]: (
44
- state,
45
- { payload: translations, meta: { arg: locale } },
46
- ) => {
47
- if (!translations) {
48
- return { ...state, isLoading: false }
49
- }
50
- return {
51
- ...state,
52
- isLoading: false,
53
- locale,
54
- translations: Object.keys(translations)
36
+ export const i18nSlice = createSlice({
37
+ name: 'app',
38
+ initialState,
39
+ reducers: {
40
+ setInitialLocale: (state, action) => {
41
+ state.initialLocale = action.payload
42
+ },
43
+ setTranslations: (state, { payload }) => {
44
+ state.translations = payload
45
+ },
46
+ },
47
+ extraReducers: (builder) => {
48
+ // Add reducers for additional action types here, and handle loading state as needed
49
+ builder
50
+ .addCase(initializeConfig.fulfilled, (state, { payload }) => {
51
+ state.initialLocale = payload.locale
52
+ })
53
+ .addCase(initializeApp.fulfilled, (state, { payload }) => {
54
+ state.locale = payload.locale
55
+ })
56
+ .addCase(setLocale.fulfilled, (state, { payload }) => {
57
+ if (!payload?.translations) {
58
+ state.isLoading = false
59
+ return
60
+ }
61
+
62
+ state.locale = payload.locale
63
+ state.translations = Object.keys(payload.translations)
55
64
  .sort()
56
65
  .reduce(
57
66
  (accum, key) => ({
58
67
  ...accum,
59
- [key]: translations[key],
68
+ [key]: payload.translations[key],
60
69
  }),
61
70
  {},
62
- ),
63
- }
64
- },
65
- [Actions.setLocale.rejected]: (state) => ({
66
- ...state,
67
- isLoading: false,
68
- }),
69
- [AppActions.initialize.pending]: () => defaultState,
71
+ )
72
+ })
70
73
  },
71
- defaultState,
72
- )
74
+ })
75
+
76
+ export const { setInitialLocale, setTranslations } = i18nSlice.actions
77
+
78
+ export default i18nSlice.reducer
@@ -1,6 +1,6 @@
1
+ import { useI18n } from 'domains/i18n/hooks'
1
2
  import { useMemo } from 'preact/hooks'
2
- import { useI18n } from 'domains/i18n'
3
- import { useSelector } from 'domains/redux'
3
+ import { useSelector } from 'react-redux'
4
4
  import * as Selectors from './selectors'
5
5
 
6
6
  export function useInterrupt() {
@@ -1,9 +1,10 @@
1
- import SeamlyGeneralError from 'api/errors/seamly-general-error'
2
1
  import SeamlyConfigurationError from 'api/errors/seamly-configuration-error'
3
- import SeamlySessionExpiredError from 'api/errors/seamly-session-expired-error'
2
+ import SeamlyGeneralError from 'api/errors/seamly-general-error'
4
3
  import SeamlyOfflineError from 'api/errors/seamly-offline-error'
4
+ import SeamlySessionExpiredError from 'api/errors/seamly-session-expired-error'
5
5
  import SeamlyUnauthorizedError from 'api/errors/seamly-unauthorized-error'
6
6
  import SeamlyUnavailableError from 'api/errors/seamly-unavailable-error'
7
+ import { setInterrupt } from 'domains/interrupt/slice'
7
8
 
8
9
  const handledErrorTypes = [
9
10
  SeamlyGeneralError,
@@ -14,13 +15,15 @@ const handledErrorTypes = [
14
15
  SeamlyUnavailableError,
15
16
  ]
16
17
 
17
- export default function createMiddleware({ api }) {
18
+ export default function createInterruptMiddleware({ api }) {
18
19
  return () => (next) => (action) => {
19
- const { error } = action
20
- if (error) {
21
- if (!handledErrorTypes.some((ErrorType) => error instanceof ErrorType)) {
22
- throw error
23
- } else if (error.action === 'reset') {
20
+ const { payload, type } = action
21
+ if (type === setInterrupt.type) {
22
+ if (
23
+ !handledErrorTypes.some((ErrorType) => payload.name === ErrorType.name)
24
+ ) {
25
+ throw new Error(payload)
26
+ } else if (payload.action === 'reset') {
24
27
  // [SMLY-942] We clear the store before a reset to force a new conversation if the page is refreshed before the conversation is reset
25
28
  api.disconnect().then(() => {
26
29
  api.clearStore()
@@ -0,0 +1,6 @@
1
+ import { createSelector } from '@reduxjs/toolkit'
2
+
3
+ export const selectError = createSelector(
4
+ ({ interrupt }) => interrupt,
5
+ ({ error }) => error,
6
+ )
@@ -0,0 +1,40 @@
1
+ import { createSlice, isAnyOf } from '@reduxjs/toolkit'
2
+ import { initializeApp, resetApp } from 'domains/app/actions'
3
+ import { initializeConfig } from 'domains/config/actions'
4
+ import { setLocale } from 'domains/i18n/actions'
5
+ import { initializeVisibility, setVisibility } from 'domains/visibility/actions'
6
+
7
+ const initialState = {
8
+ error: undefined,
9
+ }
10
+
11
+ export const interruptSlice = createSlice({
12
+ name: 'interrupt',
13
+ initialState,
14
+ reducers: {
15
+ setInterrupt: (state, action) => {
16
+ state.error = action.payload
17
+ },
18
+ clearInterrupt: () => initialState,
19
+ },
20
+ extraReducers: (builder) => {
21
+ builder
22
+ .addCase(resetApp.fulfilled, () => initialState)
23
+ .addMatcher(
24
+ isAnyOf(
25
+ initializeApp.rejected,
26
+ initializeConfig.rejected,
27
+ setLocale.rejected,
28
+ setVisibility.rejected,
29
+ initializeVisibility.rejected,
30
+ ),
31
+ (state, { payload }) => {
32
+ state.error = payload
33
+ },
34
+ )
35
+ },
36
+ })
37
+
38
+ export const { setInterrupt, clearInterrupt } = interruptSlice.actions
39
+
40
+ export default interruptSlice.reducer
@@ -1,16 +1,19 @@
1
- import { seamlyActions } from 'ui/utils/seamly-utils'
1
+ import {
2
+ setUserSelectedOption,
3
+ setUserSelectedOptions,
4
+ } from 'domains/store/slice'
2
5
 
3
- export default function createMiddleware({ api }) {
6
+ export default function createOptionsMiddleware({ api }) {
4
7
  return () => (next) => (action) => {
5
8
  const result = next(action)
6
9
  switch (action.type) {
7
- case seamlyActions.SET_USER_SELECTED_OPTIONS:
8
- api.store.set('options', action.options)
10
+ case setUserSelectedOptions.toString():
11
+ api.store.set('options', action.payload)
9
12
  break
10
- case seamlyActions.SET_USER_SELECTED_OPTION:
13
+ case setUserSelectedOption.toString():
11
14
  api.store.set('options', {
12
15
  ...(api.store.get('options') || {}),
13
- [action.option]: action.value,
16
+ [action.payload.option]: action.payload.value,
14
17
  })
15
18
  break
16
19
  }
@@ -0,0 +1,11 @@
1
+ import { Config } from 'config.types'
2
+ import type { RootState } from 'domains/store'
3
+
4
+ export type ThunkAPI = {
5
+ state: RootState
6
+ extra: {
7
+ api: any
8
+ config: Config
9
+ eventBus: any
10
+ }
11
+ }