app-tutor-ai-consumer 1.33.0 → 1.34.0

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 (122) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/config/vitest/__mocks__/sparkie.tsx +1 -1
  3. package/package.json +1 -1
  4. package/src/bootstrap.ts +40 -0
  5. package/src/config/tests/handlers.ts +5 -4
  6. package/src/config/theme/init-theme.ts +11 -5
  7. package/src/index.tsx +22 -12
  8. package/src/lib/components/dropdown-actions/dropdown-actions.tsx +87 -0
  9. package/src/lib/components/dropdown-actions/dropdownActions.builder.ts +58 -0
  10. package/src/lib/components/dropdown-actions/dropdownActions.spec.tsx +76 -0
  11. package/src/lib/components/dropdown-actions/index.ts +1 -0
  12. package/src/lib/components/dropdown-actions/types.ts +16 -0
  13. package/src/lib/components/errors/generic/generic-error.tsx +11 -8
  14. package/src/lib/components/icons/document.svg +3 -0
  15. package/src/lib/components/icons/file.svg +3 -0
  16. package/src/lib/components/icons/icon-names.d.ts +8 -0
  17. package/src/lib/components/icons/image.svg +3 -0
  18. package/src/lib/components/icons/pdf.svg +3 -0
  19. package/src/lib/components/icons/plus.svg +3 -0
  20. package/src/lib/components/icons/retry.svg +3 -0
  21. package/src/lib/components/icons/spreadsheet.svg +3 -0
  22. package/src/lib/components/icons/tutor-logo.svg +9 -0
  23. package/src/lib/components/index.ts +1 -0
  24. package/src/lib/components/markdownrenderer/markdownrenderer.tsx +1 -3
  25. package/src/lib/hooks/index.ts +1 -0
  26. package/src/lib/hooks/use-chat-file-upload/constants.ts +11 -0
  27. package/src/lib/hooks/use-chat-file-upload/index.ts +1 -0
  28. package/src/lib/hooks/use-chat-file-upload/types.ts +14 -0
  29. package/src/lib/hooks/use-chat-file-upload/use-chat-file-upload.spec.ts +59 -0
  30. package/src/lib/hooks/use-chat-file-upload/use-chat-file-upload.ts +28 -0
  31. package/src/lib/hooks/use-click-outside/index.ts +1 -0
  32. package/src/lib/hooks/use-click-outside/use-click-outside.tsx +23 -0
  33. package/src/lib/hooks/use-click-outside/useClickOutside.spec.ts +102 -0
  34. package/src/lib/utils/index.ts +1 -0
  35. package/src/lib/utils/is-theme-dark.ts +21 -0
  36. package/src/main/hooks/use-initial-store/index.ts +1 -0
  37. package/src/main/hooks/use-initial-store/use-initial-store.tsx +64 -0
  38. package/src/main/hooks/use-initial-tab/index.ts +1 -0
  39. package/src/main/hooks/use-initial-tab/use-initial-tab.tsx +84 -0
  40. package/src/main/index.ts +1 -0
  41. package/src/main/main-content.tsx +14 -0
  42. package/src/main/main-wrapper.tsx +16 -0
  43. package/src/main/main.spec.tsx +5 -3
  44. package/src/main/main.tsx +7 -16
  45. package/src/main/types.ts +5 -0
  46. package/src/modules/global-providers/global-providers.tsx +1 -15
  47. package/src/modules/messages/__tests__/signed-urls.builder.ts +42 -0
  48. package/src/modules/messages/components/chat-file-preview/chat-file-preview.builder.ts +121 -0
  49. package/src/modules/messages/components/chat-file-preview/chat-file-preview.spec.tsx +107 -0
  50. package/src/modules/messages/components/chat-file-preview/chat-file-preview.tsx +45 -0
  51. package/src/modules/messages/components/chat-file-preview/components/close-button/close-button.tsx +31 -0
  52. package/src/modules/messages/components/chat-file-preview/components/close-button/index.ts +1 -0
  53. package/src/modules/messages/components/chat-file-preview/components/close-button/types.ts +4 -0
  54. package/src/modules/messages/components/chat-file-preview/components/document-preview/document-preview.tsx +63 -0
  55. package/src/modules/messages/components/chat-file-preview/components/document-preview/index.ts +1 -0
  56. package/src/modules/messages/components/chat-file-preview/components/document-preview/types.ts +10 -0
  57. package/src/modules/messages/components/chat-file-preview/components/error-preview/error-preview.tsx +37 -0
  58. package/src/modules/messages/components/chat-file-preview/components/error-preview/index.ts +1 -0
  59. package/src/modules/messages/components/chat-file-preview/components/error-preview/types.ts +4 -0
  60. package/src/modules/messages/components/chat-file-preview/components/image-preview/image-preview.tsx +32 -0
  61. package/src/modules/messages/components/chat-file-preview/components/image-preview/index.ts +1 -0
  62. package/src/modules/messages/components/chat-file-preview/components/image-preview/types.ts +6 -0
  63. package/src/modules/messages/components/chat-file-preview/constants.ts +22 -0
  64. package/src/modules/messages/components/chat-file-preview/index.ts +1 -0
  65. package/src/modules/messages/components/chat-file-preview/types.ts +13 -0
  66. package/src/modules/messages/components/chat-file-preview/utils.spec.ts +38 -0
  67. package/src/modules/messages/components/chat-file-preview/utils.ts +13 -0
  68. package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.builder.ts +19 -0
  69. package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.spec.tsx +58 -0
  70. package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.tsx +80 -0
  71. package/src/modules/messages/components/chat-file-uploader/index.ts +1 -0
  72. package/src/modules/messages/components/chat-file-uploader/types.ts +4 -0
  73. package/src/modules/messages/components/chat-input/chat-input.tsx +1 -1
  74. package/src/modules/messages/components/index.ts +1 -0
  75. package/src/modules/messages/components/message-item/message-item.tsx +1 -2
  76. package/src/modules/messages/constants.ts +2 -1
  77. package/src/modules/messages/hooks/use-get-signed-urls/index.ts +1 -0
  78. package/src/modules/messages/hooks/use-get-signed-urls/use-get-signed-urls.spec.tsx +27 -0
  79. package/src/modules/messages/hooks/use-get-signed-urls/use-get-signed-urls.tsx +38 -0
  80. package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.spec.tsx +1 -2
  81. package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.tsx +3 -8
  82. package/src/modules/messages/hooks/use-prefetch-messages/index.ts +1 -0
  83. package/src/modules/messages/hooks/use-prefetch-messages/use-prefetch-messages.tsx +28 -0
  84. package/src/modules/messages/hooks/use-subscribe-message-received-event/use-subscribe-message-received-event.tsx +54 -119
  85. package/src/modules/messages/hooks/use-suspense-messages/index.ts +2 -0
  86. package/src/modules/messages/hooks/use-suspense-messages/types.ts +4 -0
  87. package/src/modules/messages/hooks/use-suspense-messages/use-suspense-messages.tsx +21 -0
  88. package/src/modules/messages/service.direct.ts +18 -0
  89. package/src/modules/messages/service.ts +1 -2
  90. package/src/modules/messages/store/messages-max-count.atom.ts +2 -2
  91. package/src/modules/messages/types.ts +14 -0
  92. package/src/modules/profile/hooks/use-get-profile/use-get-profile.tsx +7 -6
  93. package/src/modules/sparkie/__tests__/sparkie.mock.ts +1 -4
  94. package/src/modules/sparkie/hooks/use-init-sparkie/use-init-sparkie.tsx +30 -38
  95. package/src/modules/sparkie/service.ts +2 -1
  96. package/src/modules/widget/__tests__/widget-settings-props.builder.ts +20 -1
  97. package/src/modules/widget/components/ai-disclaimer/ai-disclaimer.tsx +19 -0
  98. package/src/modules/widget/components/ai-disclaimer/index.ts +1 -0
  99. package/src/modules/widget/components/chat-page/chat-page.tsx +30 -71
  100. package/src/modules/widget/components/container/container.tsx +14 -0
  101. package/src/modules/widget/components/greetings-card/greetings-card.tsx +9 -2
  102. package/src/modules/widget/components/loading-page/loading-page.tsx +9 -15
  103. package/src/modules/widget/components/starter-page/starter-page-actions/starter-page-actions.spec.tsx +4 -4
  104. package/src/modules/widget/components/starter-page/starter-page-actions/starter-page-actions.tsx +3 -2
  105. package/src/modules/widget/components/starter-page/starter-page-content/starter-page-content.spec.tsx +0 -46
  106. package/src/modules/widget/components/starter-page/starter-page-content/starter-page-content.tsx +1 -30
  107. package/src/modules/widget/components/starter-page/starter-page-header/starter-page-header.spec.tsx +8 -4
  108. package/src/modules/widget/components/starter-page/starter-page-header/starter-page-header.tsx +15 -13
  109. package/src/modules/widget/components/starter-page/starter-page.spec.tsx +13 -3
  110. package/src/modules/widget/components/starter-page/starter-page.tsx +22 -87
  111. package/src/modules/widget/hooks/index.ts +0 -1
  112. package/src/modules/widget/hooks/use-listen-to-theme-change-event/use-listen-to-theme-change-event.tsx +8 -6
  113. package/src/modules/widget/store/create-store.ts +7 -0
  114. package/src/modules/widget/store/index.ts +1 -0
  115. package/src/modules/widget/store/widget-settings-config.atom.ts +1 -6
  116. package/src/modules/widget/store/widget-tabs.atom.ts +18 -37
  117. package/src/types.ts +1 -0
  118. package/src/wrapper.tsx +39 -19
  119. package/src/lib/hooks/use-response-timeout/index.ts +0 -1
  120. package/src/lib/hooks/use-response-timeout/use-response-timeout.tsx +0 -42
  121. package/src/modules/widget/hooks/use-init-widget/index.ts +0 -1
  122. package/src/modules/widget/hooks/use-init-widget/use-init-widget.tsx +0 -56
@@ -2,6 +2,8 @@ import { useDecision } from '@optimizely/react-sdk'
2
2
 
3
3
  import { render, screen } from '@/src/config/tests'
4
4
  import { useSendTextMessage } from '@/src/modules/messages/hooks'
5
+ import { useSparkieStateAtomValue } from '@/src/modules/sparkie/store'
6
+ import { useIsAgentParentAtomValue } from '../../store'
5
7
 
6
8
  import WidgetStarterPage from './starter-page'
7
9
 
@@ -10,11 +12,17 @@ vi.mock('@/src/modules/messages/hooks', () => ({
10
12
  useSendTextMessage: vi.fn()
11
13
  }))
12
14
 
13
- vi.mock('@/src/modules/sparkie/hooks/use-init-sparkie', () => ({
14
- useInitSparkie: vi.fn(() => true)
15
+ vi.mock('@optimizely/react-sdk')
16
+
17
+ vi.mock('@/src/modules/sparkie/store', async (actual) => ({
18
+ ...(await actual()),
19
+ useSparkieStateAtomValue: vi.fn()
15
20
  }))
16
21
 
17
- vi.mock('@optimizely/react-sdk')
22
+ vi.mock('../../store', async (actual) => ({
23
+ ...(await actual()),
24
+ useIsAgentParentAtomValue: vi.fn()
25
+ }))
18
26
 
19
27
  describe('WidgetStarterPage', () => {
20
28
  const useSendTextMessageMock = { mutate: vi.fn() }
@@ -25,6 +33,8 @@ describe('WidgetStarterPage', () => {
25
33
  beforeEach(() => {
26
34
  vi.mocked(useSendTextMessage).mockReturnValue(useSendTextMessageMock as never)
27
35
  vi.mocked(useDecision).mockReturnValue(useDecisionMock as never)
36
+ vi.mocked(useIsAgentParentAtomValue).mockReturnValue(false)
37
+ vi.mocked(useSparkieStateAtomValue).mockReturnValue('initialized')
28
38
  })
29
39
 
30
40
  it('should render without errors', () => {
@@ -1,16 +1,10 @@
1
- import { useCallback, useEffect, useMemo, useRef } from 'react'
2
- import { useDecision } from '@optimizely/react-sdk'
3
- import { useQueryClient } from '@tanstack/react-query'
1
+ import { useCallback, useRef } from 'react'
4
2
 
5
3
  import { useRefEventListener } from '@/src/lib/hooks'
6
4
  import { ChatInput, useChatInputValueAtom } from '@/src/modules/messages/components'
7
- import { getAllMessagesQuery, useSendTextMessage } from '@/src/modules/messages/hooks'
8
- import { useMessagesMaxCount } from '@/src/modules/messages/store'
9
- import { useGetProfile } from '@/src/modules/profile'
10
- import { useInitSparkie } from '@/src/modules/sparkie/hooks/use-init-sparkie'
11
- import { TutorWidgetEvents } from '../../events'
12
- import { useWidgetLoadingAtomValue, useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
13
- import { testQuestionRegex } from '../../utils'
5
+ import { useSendTextMessage } from '@/src/modules/messages/hooks'
6
+ import { useWidgetLoadingAtomValue, useWidgetTabsAtom } from '../../store'
7
+ import { AIDisclaimer } from '../ai-disclaimer'
14
8
  import { PageLayout } from '../page-layout'
15
9
 
16
10
  import { WidgetStarterPageActions } from './starter-page-actions'
@@ -19,33 +13,12 @@ import { WidgetStarterPageHeader } from './starter-page-header'
19
13
 
20
14
  function WidgetStarterPage() {
21
15
  const chatInputRef = useRef<HTMLTextAreaElement>(null)
22
- const hasSentInitialMessage = useRef(false)
23
16
 
24
- const [settings, setWidgetSettings] = useWidgetSettingsAtom()
25
17
  const [chatInputValue, setChatInputValue] = useChatInputValueAtom()
26
18
  const [, setWidgetTabs] = useWidgetTabsAtom()
27
19
  const sendTextMessageMutation = useSendTextMessage()
28
- const profileQuery = useGetProfile()
29
- const limit = useMessagesMaxCount()
30
- const queryClient = useQueryClient()
31
- const isSparkieReady = useInitSparkie()
32
20
 
33
21
  const widgetLoading = useWidgetLoadingAtomValue()
34
- const [lexTutorInitialMessageFF] = useDecision('lex_tutor_new_widget_initial_message')
35
-
36
- const conversationId = useMemo(() => settings?.conversationId, [settings?.conversationId])
37
-
38
- const profileId = useMemo(() => profileQuery.data?.id, [profileQuery.data?.id])
39
-
40
- const messagesQueryConfig = useMemo(
41
- () =>
42
- getAllMessagesQuery({
43
- conversationId,
44
- profileId,
45
- limit
46
- }),
47
- [conversationId, limit, profileId]
48
- )
49
22
 
50
23
  useRefEventListener<HTMLTextAreaElement>({
51
24
  config: {
@@ -77,68 +50,30 @@ function WidgetStarterPage() {
77
50
  sendText(chatInputRef.current?.value)
78
51
  }
79
52
 
80
- useEffect(() => {
81
- if (!conversationId || !profileId) return
82
-
83
- void queryClient.prefetchInfiniteQuery(messagesQueryConfig)
84
- }, [conversationId, messagesQueryConfig, profileId, queryClient])
85
-
86
- useEffect(() => {
87
- if (!isSparkieReady || hasSentInitialMessage.current || !lexTutorInitialMessageFF.enabled)
88
- return
89
-
90
- const clear = TutorWidgetEvents['tutor-initial-message'].handler(({ message }) => {
91
- if (!message) return
92
-
93
- setChatInputValue(testQuestionRegex(message))
94
- sendText(message)
95
- hasSentInitialMessage.current = true
96
- })
97
-
98
- return () => {
99
- clear?.()
100
- hasSentInitialMessage.current = false
101
- }
102
- }, [isSparkieReady, lexTutorInitialMessageFF.enabled, sendText, setChatInputValue])
103
-
104
- useEffect(() => {
105
- if (!isSparkieReady || hasSentInitialMessage.current || !lexTutorInitialMessageFF.enabled)
106
- return
107
-
108
- const initialMessage = settings?.initialMessage
109
-
110
- if (initialMessage) {
111
- setChatInputValue(testQuestionRegex(initialMessage))
112
- sendText(initialMessage)
113
- setWidgetSettings({ ...settings, initialMessage: '' })
114
- hasSentInitialMessage.current = true
115
- }
116
- }, [
117
- settings,
118
- isSparkieReady,
119
- sendText,
120
- setChatInputValue,
121
- lexTutorInitialMessageFF.enabled,
122
- setWidgetSettings
123
- ])
124
-
125
53
  return (
126
54
  <PageLayout
127
55
  asideChild={
128
- <ChatInput
129
- name='new-chat-msg-input'
130
- ref={chatInputRef}
131
- onSend={handleSend}
132
- buttonDisabled={widgetLoading || !chatInputValue.trim()}
133
- loading={!isSparkieReady}
134
- />
56
+ <>
57
+ <ChatInput
58
+ name='new-chat-msg-input'
59
+ ref={chatInputRef}
60
+ onSend={handleSend}
61
+ buttonDisabled={widgetLoading || !chatInputValue.trim()}
62
+ />
63
+
64
+ <div className='mx-auto w-fit'>
65
+ <AIDisclaimer />
66
+ </div>
67
+ </>
135
68
  }>
136
69
  <div className='grid-areas-[a_b] grid h-full grid-cols-1 grid-rows-[1fr_auto]'>
137
- <div className='grid-area-[a] flex min-h-0 flex-col max-md:px-[1.125rem] max-md:pt-[1.125rem] md:px-5 md:pt-5'>
138
- <WidgetStarterPageHeader />
139
- <WidgetStarterPageContent />
70
+ <div className='grid-areas-[a_b] grid h-full grid-cols-1 grid-rows-[1fr_auto]'>
71
+ <div className='grid-area-[a] flex min-h-0 flex-col max-md:px-[1.125rem] max-md:pt-[1.125rem] md:px-5 md:pt-5'>
72
+ <WidgetStarterPageHeader />
73
+ <WidgetStarterPageContent />
74
+ </div>
75
+ <WidgetStarterPageActions send={sendText} />
140
76
  </div>
141
- <WidgetStarterPageActions send={sendText} />
142
77
  </div>
143
78
  </PageLayout>
144
79
  )
@@ -1,4 +1,3 @@
1
- export * from './use-init-widget'
2
1
  export * from './use-listen-to-theme-change-event'
3
2
  export * from './use-listen-to-visibility-events'
4
3
  export * from './use-send-view-tutor-event'
@@ -1,20 +1,22 @@
1
1
  import { useLayoutEffect } from 'react'
2
2
  import { produce } from 'immer'
3
+ import type { useStore } from 'jotai'
3
4
 
4
5
  import { initTheme } from '@/src/config/theme'
5
6
  import { TutorWidgetEvents } from '../../events'
6
- import { useWidgetSettingsAtom } from '../../store'
7
-
8
- function useListenToThemeChangeEvent() {
9
- const [widgetSettings, setWidgetSettings] = useWidgetSettingsAtom()
7
+ import { widgetSettingsAtom } from '../../store'
10
8
 
9
+ function useListenToThemeChangeEvent(store?: ReturnType<typeof useStore>) {
11
10
  useLayoutEffect(() => {
12
11
  const clear = TutorWidgetEvents['c3po-app-widget-theme-change'].handler(({ theme }) => {
12
+ const widgetSettings = store?.get(widgetSettingsAtom)
13
+
13
14
  initTheme(theme)
14
15
 
15
16
  if (!widgetSettings || theme === widgetSettings?.config?.theme) return
16
17
 
17
- setWidgetSettings(
18
+ store?.set(
19
+ widgetSettingsAtom,
18
20
  produce(widgetSettings, (draft) => {
19
21
  draft.config = { ...draft.config, theme }
20
22
 
@@ -24,7 +26,7 @@ function useListenToThemeChangeEvent() {
24
26
  })
25
27
 
26
28
  return () => clear?.()
27
- }, [setWidgetSettings, widgetSettings])
29
+ }, [store])
28
30
  }
29
31
 
30
32
  export default useListenToThemeChangeEvent
@@ -0,0 +1,7 @@
1
+ import { createStore as jotaiCreateStore } from 'jotai'
2
+
3
+ export const createStore = () => {
4
+ const store = jotaiCreateStore()
5
+
6
+ return store
7
+ }
@@ -1,3 +1,4 @@
1
+ export * from './create-store'
1
2
  export * from './widget-container-intrinsic-height.atom'
2
3
  export * from './widget-loading.atom'
3
4
  export * from './widget-scrolling.atom'
@@ -1,10 +1,5 @@
1
1
  import { atom, useAtomValue } from 'jotai'
2
2
 
3
- import { widgetSettingsAtom } from './widget-settings.atom'
4
-
5
- export const widgetSettingsConfigAgentParentAtom = atom((get) => {
6
- const settings = get(widgetSettingsAtom)
7
- return settings?.config?.metadata?.parent === 'AGENT'
8
- })
3
+ export const widgetSettingsConfigAgentParentAtom = atom(false)
9
4
 
10
5
  export const useIsAgentParentAtomValue = () => useAtomValue(widgetSettingsConfigAgentParentAtom)
@@ -1,48 +1,21 @@
1
1
  import { atom, useAtom, useAtomValue } from 'jotai'
2
- import { atomWithDefault } from 'jotai/utils'
3
2
 
4
- import { sparkieStateAtom } from '../../sparkie/store'
5
3
  import type { CurrentTabKey } from '../components'
6
4
 
7
- import { widgetSettingsConfigAgentParentAtom } from './widget-settings-config.atom'
8
-
9
5
  export type WidgetTabsProps = {
10
6
  currentTab: CurrentTabKey
11
7
  history: Set<CurrentTabKey>
12
8
  }
13
9
 
10
+ // Prevent memory issues
11
+ const MAX_HISTORY_SIZE = 10
12
+
14
13
  const INITIAL_PROPS: WidgetTabsProps = {
15
- currentTab: 'starter',
16
- history: new Set(['starter'])
14
+ currentTab: 'loading',
15
+ history: new Set(['loading'])
17
16
  }
18
17
 
19
- export const widgetTabsAtom = atomWithDefault<WidgetTabsProps>((get) => {
20
- const sparkieState = get(sparkieStateAtom)
21
- const isAgentMode = get(widgetSettingsConfigAgentParentAtom)
22
-
23
- if (!isAgentMode) return INITIAL_PROPS
24
-
25
- switch (sparkieState) {
26
- case 'idle':
27
- case 'initializing':
28
- return {
29
- currentTab: 'loading',
30
- history: new Set(['loading'])
31
- }
32
- case 'initialized':
33
- return {
34
- currentTab: 'chat',
35
- history: new Set(['chat'])
36
- }
37
- case 'failed':
38
- return {
39
- currentTab: 'error',
40
- history: new Set(['error'])
41
- }
42
- default:
43
- return INITIAL_PROPS
44
- }
45
- })
18
+ export const widgetTabsAtom = atom<WidgetTabsProps>(INITIAL_PROPS)
46
19
 
47
20
  export const setWidgetTabsAtom = atom(
48
21
  (get) => get(widgetTabsAtom),
@@ -51,11 +24,15 @@ export const setWidgetTabsAtom = atom(
51
24
 
52
25
  if (currentValue.currentTab === currentTab) return
53
26
 
54
- const history = new Set([...currentValue.history, currentTab])
27
+ const historyArray = [...currentValue.history, currentTab]
28
+
29
+ if (historyArray.length > MAX_HISTORY_SIZE) {
30
+ historyArray.shift()
31
+ }
55
32
 
56
33
  const config: WidgetTabsProps = {
57
34
  currentTab,
58
- history
35
+ history: new Set(historyArray)
59
36
  }
60
37
 
61
38
  set(widgetTabsAtom, config)
@@ -70,8 +47,12 @@ export const goBackTabAtom = atom(null, (get, set) => {
70
47
 
71
48
  historyArray.pop()
72
49
 
50
+ const previousTab = historyArray[historyArray.length - 1]
51
+
52
+ if (!previousTab) return
53
+
73
54
  const config: WidgetTabsProps = {
74
- currentTab: historyArray[historyArray.length - 1],
55
+ currentTab: previousTab,
75
56
  history: new Set(historyArray)
76
57
  }
77
58
 
@@ -80,5 +61,5 @@ export const goBackTabAtom = atom(null, (get, set) => {
80
61
 
81
62
  export const useGetWidgetTabsAtom = () => useAtom(widgetTabsAtom)
82
63
  export const useWidgetTabsAtom = () => useAtom(setWidgetTabsAtom)
83
- export const useWidgetTabsValueAtom = () => useAtomValue(setWidgetTabsAtom)
64
+ export const useWidgetTabsValueAtom = () => useAtomValue(widgetTabsAtom)
84
65
  export const useWidgetGoBackTabAtom = () => useAtom(goBackTabAtom)
package/src/types.ts CHANGED
@@ -62,6 +62,7 @@ export type WidgetSettingProps = {
62
62
  courseName?: string
63
63
  source?: string
64
64
  promptId?: string
65
+ showFileUpload?: boolean
65
66
  }
66
67
  }
67
68
  }
package/src/wrapper.tsx CHANGED
@@ -1,32 +1,52 @@
1
- import { useEffect } from 'react'
1
+ import { StrictMode } from 'react'
2
+ import type { QueryClient } from '@tanstack/react-query'
3
+ import { Provider as JotaiProvider } from 'jotai'
2
4
 
5
+ import { QueryProvider } from './config/tanstack'
3
6
  import { Main } from './main'
4
- import { SparkieService } from './modules/sparkie'
5
- import { useSparkieStateAtom } from './modules/sparkie/store'
7
+ import { usePrefetchMessages } from './modules/messages/hooks/use-prefetch-messages'
8
+ import { WidgetLoadingPage } from './modules/widget'
9
+ import { WidgetErrorPage } from './modules/widget/components/error-page'
10
+ import { useListenToThemeChangeEvent } from './modules/widget/hooks'
11
+ import type { createStore } from './modules/widget/store'
6
12
  import type { WidgetSettingProps } from './types'
7
13
 
8
14
  export type WrapperProps = {
9
15
  settings: WidgetSettingProps
16
+ queryClient: QueryClient
17
+ state: 'ERROR' | 'LOADING' | 'READY'
18
+ store?: ReturnType<typeof createStore>
10
19
  }
11
20
 
12
- function Wrapper({ settings }: WrapperProps) {
13
- const [, setSparkieState] = useSparkieStateAtom()
14
-
15
- useEffect(() => {
16
- SparkieService.initSparkie({
17
- token: settings?.hotmartToken,
18
- skipPresenceSetup: true,
19
- retryOptions: {
20
- maxRetries: 5,
21
- retryDelay: 2000,
22
- backoffMultiplier: 1.5
23
- }
24
- })
25
- .then((result) => setSparkieState(result ? 'initialized' : 'failed'))
26
- .catch(() => setSparkieState('failed'))
27
- }, [setSparkieState, settings?.hotmartToken])
21
+ function ContentWrapper({ settings }: Pick<WrapperProps, 'settings'>) {
22
+ usePrefetchMessages(settings)
28
23
 
29
24
  return <Main settings={settings} />
30
25
  }
31
26
 
27
+ function WrapperItem({ state, settings }: Omit<WrapperProps, 'queryClient' | 'store'>) {
28
+ switch (state) {
29
+ case 'READY':
30
+ return <ContentWrapper settings={settings} />
31
+ case 'LOADING':
32
+ return <WidgetLoadingPage />
33
+ default:
34
+ return <WidgetErrorPage />
35
+ }
36
+ }
37
+
38
+ function Wrapper({ queryClient, settings, state, store }: WrapperProps) {
39
+ useListenToThemeChangeEvent(store)
40
+
41
+ return (
42
+ <StrictMode>
43
+ <JotaiProvider store={store}>
44
+ <QueryProvider queryClient={queryClient} showDevTools={false}>
45
+ <WrapperItem settings={settings} state={state} />
46
+ </QueryProvider>
47
+ </JotaiProvider>
48
+ </StrictMode>
49
+ )
50
+ }
51
+
32
52
  export default Wrapper
@@ -1 +0,0 @@
1
- export { default as useResponseTimeout } from './use-response-timeout'
@@ -1,42 +0,0 @@
1
- import { useCallback, useRef } from 'react'
2
-
3
- const FIVE_MINUTES = 5 * 60 * 1000
4
-
5
- function useResponseTimeout() {
6
- const timeoutRef = useRef<NodeJS.Timeout>(null)
7
- const isWaitingRef = useRef(false)
8
-
9
- const startTimeout = useCallback((callback: () => void) => {
10
- if (timeoutRef.current) clearTimeout(timeoutRef.current)
11
-
12
- isWaitingRef.current = true
13
-
14
- timeoutRef.current = setTimeout(() => {
15
- callback()
16
- isWaitingRef.current = false
17
- }, FIVE_MINUTES)
18
- }, [])
19
-
20
- const reset = useCallback(() => {
21
- if (timeoutRef.current) {
22
- clearTimeout(timeoutRef.current)
23
- timeoutRef.current = null
24
- }
25
- isWaitingRef.current = false
26
- }, [])
27
-
28
- const cleanup = useCallback(() => {
29
- if (timeoutRef.current) {
30
- clearTimeout(timeoutRef.current)
31
- }
32
- }, [])
33
-
34
- return {
35
- startTimeout,
36
- cleanup,
37
- reset,
38
- isWaiting: isWaitingRef.current
39
- }
40
- }
41
-
42
- export default useResponseTimeout
@@ -1 +0,0 @@
1
- export { default as useInitWidget } from './use-init-widget'
@@ -1,56 +0,0 @@
1
- import { useEffect, useState } from 'react'
2
-
3
- import { DataHubStore } from '@/src/config/datahub'
4
- import { initDayjs } from '@/src/config/dayjs'
5
- import { initAxios } from '@/src/config/request/api'
6
- import type { WidgetSettingProps } from '@/src/types'
7
- import { TutorWidgetEvents } from '../../events'
8
-
9
- const init = async (settings: WidgetSettingProps) => {
10
- try {
11
- initAxios(settings.hotmartToken)
12
- await initDayjs(settings.locale)
13
- DataHubStore.initData({
14
- ucode: settings.user?.ucode ?? '',
15
- membershipId: settings.membershipId ?? '',
16
- membershipSlug: settings.membershipSlug ?? '',
17
- sessionId: settings.sessionId,
18
- userId: Number(settings.userId),
19
- product: {
20
- id: Number(settings.productId) || 0,
21
- category: settings.productType ?? ''
22
- }
23
- })
24
- TutorWidgetEvents['tutor-app-widget-loaded'].dispatch()
25
- } catch (error) {
26
- console.error(error)
27
- TutorWidgetEvents['tutor-app-widget-loaded'].dispatch({ detail: { isSuccess: false } })
28
- }
29
- }
30
-
31
- function useInitWidget(settings: WidgetSettingProps) {
32
- const [completeSetup, setCompleteSetup] = useState(false)
33
- const [error, setError] = useState<unknown>(null)
34
-
35
- useEffect(() => {
36
- let isMounted = true
37
-
38
- if (completeSetup) return
39
-
40
- init(settings)
41
- .then(() => {
42
- if (isMounted) setCompleteSetup(true)
43
- })
44
- .catch((e) => {
45
- if (isMounted) setError(e)
46
- })
47
-
48
- return () => {
49
- isMounted = false
50
- }
51
- }, [completeSetup, settings])
52
-
53
- return { completeSetup, error }
54
- }
55
-
56
- export default useInitWidget