app-tutor-ai-consumer 1.8.2 → 1.9.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 (30) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/config/vitest/__mocks__/sparkie.tsx +6 -1
  3. package/config/vitest/vitest.config.mts +0 -1
  4. package/package.json +1 -1
  5. package/src/index.tsx +15 -6
  6. package/src/main/main.tsx +19 -1
  7. package/src/modules/cursor/service.ts +20 -2
  8. package/src/modules/messages/events.ts +25 -0
  9. package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.spec.tsx +21 -0
  10. package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.tsx +2 -0
  11. package/src/modules/messages/hooks/use-subscribe-message-received-event/use-subscribe-message-received-event.tsx +16 -1
  12. package/src/modules/messages/store/index.ts +1 -0
  13. package/src/modules/messages/store/unread-messages-set.atom.ts +21 -0
  14. package/src/modules/messages/types.ts +4 -0
  15. package/src/modules/sparkie/service.ts +13 -3
  16. package/src/modules/thread/hooks/index.ts +1 -0
  17. package/src/modules/thread/hooks/use-subscribe-thread-closed-event/index.ts +2 -0
  18. package/src/modules/thread/hooks/use-subscribe-thread-closed-event/use-subscribe-thread-closed-event.tsx +22 -0
  19. package/src/modules/thread/index.ts +1 -0
  20. package/src/modules/widget/components/container/container.tsx +2 -0
  21. package/src/modules/widget/events.ts +34 -18
  22. package/src/modules/widget/hooks/index.ts +1 -1
  23. package/src/modules/widget/hooks/use-listen-to-visibility-events/index.ts +1 -0
  24. package/src/modules/widget/hooks/use-listen-to-visibility-events/use-listen-to-visibility-events.tsx +20 -0
  25. package/src/modules/widget/store/widget-tabs.atom.ts +2 -2
  26. package/src/modules/widget/types.ts +1 -1
  27. package/src/types.ts +6 -0
  28. package/config/vitest/__mocks__/use-init-sparkie.tsx +0 -14
  29. package/src/modules/widget/hooks/use-init-sparkie/index.ts +0 -1
  30. package/src/modules/widget/hooks/use-init-sparkie/use-init-sparkie.tsx +0 -20
package/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ # [1.9.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.8.2...v1.9.0) (2025-07-17)
2
+
3
+ ### Features
4
+
5
+ - add on close listener ([c41e700](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/c41e700241ac7a3f31d8091eb4e03141fd75c83e))
6
+ - add thread event listner ([904b78d](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/904b78dd3271552d2167d467814aa4add1550201))
7
+ - add useSubscribeThreadClosed event listener ([edfa1ee](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/edfa1ee8cd7296292ad485d283f3675d6a626ac1))
8
+ - add useSubscribeThreadClosed to container.tsx ([53aa939](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/53aa939561a1ad3415ba7735344632390015764b))
9
+
1
10
  ## [1.8.2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.8.1...v1.8.2) (2025-07-16)
2
11
 
3
12
  ## [1.8.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.8.0...v1.8.1) (2025-07-15)
@@ -1,6 +1,9 @@
1
1
  import SparkieMock from '@/src/modules/sparkie/__tests__/sparkie.mock'
2
2
  import { SparkieService } from '@/src/modules/sparkie'
3
- import { SparkieMessageServiceMock } from '@/src/modules/sparkie/__tests__/sparkie.mock'
3
+ import {
4
+ SparkieMessageServiceMock,
5
+ SparkieCursorServiceMock
6
+ } from '@/src/modules/sparkie/__tests__/sparkie.mock'
4
7
  import MessageService from '@hotmart/sparkie/dist/MessageService'
5
8
 
6
9
  vi.mock('@hotmart/sparkie', () => ({ default: SparkieMock }))
@@ -9,4 +12,6 @@ beforeEach(() => {
9
12
  vi.spyOn(SparkieService, 'getMessageService').mockResolvedValue(
10
13
  SparkieMessageServiceMock as unknown as MessageService
11
14
  )
15
+
16
+ vi.spyOn(SparkieService, 'getCursorService').mockResolvedValue(SparkieCursorServiceMock as never)
12
17
  })
@@ -14,7 +14,6 @@ export default defineConfig({
14
14
  './config/vitest/__mocks__/sparkie.tsx',
15
15
  './config/vitest/__mocks__/icons.tsx',
16
16
  './config/vitest/__mocks__/intersection-observer.ts',
17
- './config/vitest/__mocks__/use-init-sparkie.tsx',
18
17
  './config/vitest/polyfills/global.js'
19
18
  ],
20
19
  coverage: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-tutor-ai-consumer",
3
- "version": "1.8.2",
3
+ "version": "1.9.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
package/src/index.tsx CHANGED
@@ -10,7 +10,7 @@ import { initAxios } from './config/request/api'
10
10
  import { devMode, productionMode } from './lib/utils'
11
11
  import { Main } from './main'
12
12
  import { SparkieService } from './modules/sparkie'
13
- import { TutorWidgetEvents, TutorWidgetEventTypes } from './modules/widget'
13
+ import { TutorWidgetEvents } from './modules/widget'
14
14
  import type { WidgetSettingProps } from './types'
15
15
 
16
16
  const loadMainStyles = () => {
@@ -44,7 +44,11 @@ window.startChatWidget = async (
44
44
  await initLanguage(settings.locale)
45
45
  await initDayjs(settings.locale)
46
46
 
47
+ let isLoadingSparkie: boolean = false
48
+ let initSparkieError: unknown = null
49
+
47
50
  try {
51
+ isLoadingSparkie = true
48
52
  await SparkieService.initSparkie({
49
53
  token: settings?.hotmartToken,
50
54
  skipPresenceSetup: true,
@@ -55,18 +59,23 @@ window.startChatWidget = async (
55
59
  }
56
60
  })
57
61
  await SparkieService.ensureInitialized()
58
- TutorWidgetEvents.get(TutorWidgetEventTypes.LOADED)?.dispatch()
62
+ TutorWidgetEvents['tutor-app-widget-loaded'].dispatch()
59
63
  } catch (error) {
64
+ initSparkieError = error
60
65
  console.error(error)
61
- TutorWidgetEvents.get(TutorWidgetEventTypes.LOADED)?.dispatch({ detail: { isSuccess: false } })
66
+ TutorWidgetEvents['tutor-app-widget-loaded'].dispatch({ detail: { isSuccess: false } })
67
+ } finally {
68
+ isLoadingSparkie = false
62
69
  }
63
70
 
64
- if (root)
71
+ if (root) {
72
+ TutorWidgetEvents['c3po-app-widget-open'].dispatch()
65
73
  root.render(
66
74
  <StrictMode>
67
- <Main settings={settings} />
75
+ <Main settings={settings} metadata={{ initSparkieError, isLoadingSparkie }} />
68
76
  </StrictMode>
69
77
  )
78
+ }
70
79
  }
71
80
 
72
- window.closeChatWidget = () => TutorWidgetEvents.get(TutorWidgetEventTypes.CLOSE)?.dispatch()
81
+ window.closeChatWidget = () => TutorWidgetEvents['c3po-app-widget-close'].dispatch()
package/src/main/main.tsx CHANGED
@@ -3,13 +3,31 @@ import '@/config/styles/index.css'
3
3
  import { ErrorBoundary, GenericError } from '@/src/lib/components/errors'
4
4
  import { useDefaultId } from '@/src/lib/hooks'
5
5
  import { useAppLang } from '../config/i18n'
6
+ import { Spinner } from '../lib/components'
6
7
  import { GlobalProviders } from '../modules/global-providers'
7
8
  import { WidgetContainer } from '../modules/widget'
9
+ import { useListenToVisibilityEvents } from '../modules/widget/hooks'
8
10
  import type { WidgetSettingProps } from '../types'
9
11
 
10
- function Main({ settings }: { settings: WidgetSettingProps }) {
12
+ export type MainProps = {
13
+ settings: WidgetSettingProps
14
+ metadata?: { isLoadingSparkie: boolean; initSparkieError: unknown }
15
+ }
16
+ function Main({
17
+ settings,
18
+ metadata = { initSparkieError: null, isLoadingSparkie: false }
19
+ }: MainProps) {
11
20
  useDefaultId()
12
21
  useAppLang(settings.locale)
22
+ useListenToVisibilityEvents()
23
+
24
+ if (metadata.isLoadingSparkie) {
25
+ return (
26
+ <div className='flex h-full w-full flex-col items-center justify-center'>
27
+ <Spinner className='h-10 w-10 text-neutral-500' />
28
+ </div>
29
+ )
30
+ }
13
31
 
14
32
  return (
15
33
  <ErrorBoundary fallback={<GenericError />}>
@@ -1,12 +1,30 @@
1
+ import { ApiError } from '@/src/config/request'
2
+ import { HttpCodes } from '@/src/lib/utils'
1
3
  import { SparkieService } from '../sparkie'
2
4
 
3
5
  import type { ICursorUpdate } from './types'
4
6
 
5
7
  class CursorService {
6
- constructor(private sparkie = SparkieService.sparkieInstance) {}
8
+ async getSparkieCursorService() {
9
+ try {
10
+ const messageService = await SparkieService.getCursorService()
11
+
12
+ if (!messageService) throw new Error()
13
+
14
+ return messageService
15
+ } catch (error) {
16
+ throw new ApiError({
17
+ statusCode: HttpCodes.UNPROCESSABLE_ENTITY,
18
+ message: 'sparkie.cursorService not defined',
19
+ extra: { error }
20
+ })
21
+ }
22
+ }
7
23
 
8
24
  async updateCursor(conversationId: string): Promise<ICursorUpdate | null> {
9
- const data = await this.sparkie.cursorService?.update(conversationId)
25
+ const cursorService = await this.getSparkieCursorService()
26
+
27
+ const data = await cursorService?.update(conversationId)
10
28
 
11
29
  return data ?? null
12
30
  }
@@ -0,0 +1,25 @@
1
+ import type { ICustomEvent } from '@/src/types'
2
+
3
+ import type { SubmitQuestionEventDetail } from './types'
4
+
5
+ export const MessagesEventTypes = {
6
+ SUBMIT_QUESTION: 'c3po-chat:questionSubmitted'
7
+ } as const
8
+
9
+ const MessagesEventsList: Array<ICustomEvent<typeof MessagesEventTypes>> = [
10
+ {
11
+ name: MessagesEventTypes.SUBMIT_QUESTION,
12
+ handler: () => () => undefined,
13
+ dispatch: () => {
14
+ const event: CustomEventInit<SubmitQuestionEventDetail> = {
15
+ detail: {
16
+ timestamp: Date.now()
17
+ }
18
+ }
19
+
20
+ window.dispatchEvent(new CustomEvent(MessagesEventTypes.SUBMIT_QUESTION, event))
21
+ }
22
+ }
23
+ ] as const
24
+
25
+ export const MessagesEvents = new Map(MessagesEventsList.map((e) => [e.name, e]))
@@ -3,6 +3,7 @@ import { MessagesService } from '@/src/modules/messages'
3
3
  import { useGetProfile } from '@/src/modules/profile'
4
4
  import * as Store from '@/src/modules/widget'
5
5
  import WidgetSettingPropsBuilder from '@/src/modules/widget/__tests__/widget-settings-props.builder'
6
+ import { MessagesEventTypes } from '../../events'
6
7
 
7
8
  import useSendTextMessage from './use-send-text-message'
8
9
 
@@ -83,4 +84,24 @@ describe('useSendTextMessage', () => {
83
84
  }
84
85
  })
85
86
  })
87
+
88
+ it('should dispatch window custom event when mutating', async () => {
89
+ const num = chance.integer()
90
+ vi.spyOn(globalThis.window, 'dispatchEvent')
91
+ vi.spyOn(Date, 'now').mockReturnValueOnce(num)
92
+
93
+ const { result } = render()
94
+
95
+ await waitFor(() => result.current.mutateAsync(message))
96
+
97
+ expect(globalThis.window.dispatchEvent).toHaveBeenCalledTimes(1)
98
+ expect(globalThis.window.dispatchEvent).toHaveBeenNthCalledWith(
99
+ 1,
100
+ new CustomEvent(MessagesEventTypes.SUBMIT_QUESTION, {
101
+ detail: {
102
+ timestamp: num
103
+ }
104
+ })
105
+ )
106
+ })
86
107
  })
@@ -4,6 +4,7 @@ import { v4 } from 'uuid'
4
4
  import { MessagesService } from '@/src/modules/messages'
5
5
  import { useGetProfile } from '@/src/modules/profile'
6
6
  import { useWidgetLoadingAtom, useWidgetSettingsAtomValue } from '@/src/modules/widget'
7
+ import { MessagesEvents } from '../../events'
7
8
 
8
9
  function useSendTextMessage() {
9
10
  const settings = useWidgetSettingsAtomValue()
@@ -53,6 +54,7 @@ function useSendTextMessage() {
53
54
  },
54
55
  onMutate: () => {
55
56
  setWidgetLoading(true)
57
+ MessagesEvents.get('c3po-chat:questionSubmitted')?.dispatch()
56
58
  }
57
59
  })
58
60
  }
@@ -3,9 +3,11 @@ import type { InfiniteData } from '@tanstack/react-query'
3
3
  import { useQueryClient } from '@tanstack/react-query'
4
4
  import { produce } from 'immer'
5
5
 
6
+ import { useUpdateCursor } from '@/src/modules/cursor/hooks'
6
7
  import { useGetProfile } from '@/src/modules/profile'
7
8
  import { SparkieService } from '@/src/modules/sparkie'
8
9
  import { useWidgetLoadingAtom, useWidgetSettingsAtom } from '@/src/modules/widget'
10
+ import { useUnreadMessagesSetAtom } from '../../store'
9
11
  import type { FetchMessagesResponse, IMessageWithSenderData } from '../../types'
10
12
  import { getMessagesInfiniteQuery } from '../use-infinite-get-messages'
11
13
 
@@ -14,6 +16,8 @@ const useSubscribeMessageReceivedEvent = () => {
14
16
  const profileQuery = useGetProfile()
15
17
  const queryClient = useQueryClient()
16
18
  const [, setWidgetLoading] = useWidgetLoadingAtom()
19
+ const [, addUnreadMessagesToSet] = useUnreadMessagesSetAtom()
20
+ const useUpdateCursorMutation = useUpdateCursor()
17
21
 
18
22
  const conversationId = useMemo(() => String(settings?.conversationId), [settings?.conversationId])
19
23
  const profileId = useMemo(() => String(profileQuery?.data?.id), [profileQuery?.data?.id])
@@ -61,7 +65,11 @@ const useSubscribeMessageReceivedEvent = () => {
61
65
  const isMine = data.contactId === profileId
62
66
 
63
67
  if (!isMine) {
68
+ addUnreadMessagesToSet({ itemId: data.id })
64
69
  setTimeout(() => setWidgetLoading(false), 100)
70
+ } else {
71
+ // The cursor should update only with my messages
72
+ useUpdateCursorMutation.mutate(data.conversationId)
65
73
  }
66
74
  }
67
75
 
@@ -74,7 +82,14 @@ const useSubscribeMessageReceivedEvent = () => {
74
82
  messageReceived
75
83
  })
76
84
  }
77
- }, [profileId, query.queryKey, queryClient, setWidgetLoading])
85
+ }, [
86
+ addUnreadMessagesToSet,
87
+ profileId,
88
+ query.queryKey,
89
+ queryClient,
90
+ setWidgetLoading,
91
+ useUpdateCursorMutation
92
+ ])
78
93
  }
79
94
 
80
95
  export default useSubscribeMessageReceivedEvent
@@ -0,0 +1 @@
1
+ export * from './unread-messages-set.atom'
@@ -0,0 +1,21 @@
1
+ import { atom, useAtom, useAtomValue } from 'jotai'
2
+
3
+ const unreadMessagesSetAtom = atom(new Set<string>())
4
+
5
+ const setUnreadMessagesSetAtom = atom(
6
+ (get) => get(unreadMessagesSetAtom),
7
+ (_, set, { itemId = '', clear = false }: { itemId?: string; clear?: boolean }) =>
8
+ set(unreadMessagesSetAtom, (p) => {
9
+ if (clear) return new Set<string>()
10
+
11
+ const previousItems = Array.from(p)
12
+
13
+ return new Set([...previousItems, itemId])
14
+ })
15
+ )
16
+
17
+ const unreadMessagesAtom = atom((get) => get(unreadMessagesSetAtom).size)
18
+
19
+ export const useUnreadMessagesSetAtom = () => useAtom(setUnreadMessagesSetAtom)
20
+ export const useUnreadMessagesSetAtomValue = () => useAtomValue(setUnreadMessagesSetAtom)
21
+ export const useUnreadMessagesCount = () => useAtomValue(unreadMessagesAtom)
@@ -76,3 +76,7 @@ export type FetchMessagesResponse = {
76
76
  messages: IMessageWithSenderData[]
77
77
  hasMore: boolean
78
78
  }
79
+
80
+ export type SubmitQuestionEventDetail = {
81
+ timestamp: number
82
+ }
@@ -178,6 +178,16 @@ class SparkieService {
178
178
  return messageService
179
179
  }
180
180
 
181
+ async getCursorService() {
182
+ await this.ensureInitialized()
183
+
184
+ const cursorService = this.sparkieInstance.cursorService
185
+
186
+ if (!cursorService) throw new Error('CursorService not available after initialization')
187
+
188
+ return cursorService
189
+ }
190
+
181
191
  async updateToken(token: string, reinitialize = true): Promise<boolean> {
182
192
  this.sparkieInstance.setAPIToken(token)
183
193
 
@@ -225,11 +235,11 @@ class SparkieService {
225
235
 
226
236
  async destroySparkie(): Promise<void> {
227
237
  try {
228
- if (this.sparkie && this.initializationState !== 'idle') {
238
+ if (this.sparkie) {
229
239
  await this.sparkieInstance.destroy({ skipSignOut: true })
230
240
  }
231
- } catch (error) {
232
- console.error('Error destroying Sparkie:', error)
241
+ } catch {
242
+ console.warn('Sparkie instance not available for destruction')
233
243
  } finally {
234
244
  this.initializationState = 'idle'
235
245
  this.initializationPromise = null
@@ -0,0 +1 @@
1
+ export * from './use-subscribe-thread-closed-event'
@@ -0,0 +1,2 @@
1
+ export * from './use-subscribe-thread-closed-event'
2
+ export { default as useSubscribeThreadClosedEvent } from './use-subscribe-thread-closed-event'
@@ -0,0 +1,22 @@
1
+ import { useCallback, useEffect } from 'react'
2
+
3
+ import { useUnreadMessagesSetAtom } from '@/src/modules/messages/store'
4
+ import { SparkieService } from '@/src/modules/sparkie'
5
+
6
+ function useSubscribeThreadClosedEvent() {
7
+ const [, setUnreadMessagesSet] = useUnreadMessagesSetAtom()
8
+
9
+ const threadClosed = useCallback(() => {
10
+ setUnreadMessagesSet({ clear: true })
11
+ }, [setUnreadMessagesSet])
12
+
13
+ useEffect(() => {
14
+ SparkieService.subscribeEvents({ threadClosed })
15
+
16
+ return () => {
17
+ SparkieService.removeEventSubscription({ threadClosed })
18
+ }
19
+ }, [threadClosed])
20
+ }
21
+
22
+ export default useSubscribeThreadClosedEvent
@@ -0,0 +1 @@
1
+ export * from './hooks'
@@ -1,10 +1,12 @@
1
1
  import { useSubscribeMessageReceivedEvent } from '@/src/modules/messages/hooks'
2
+ import { useSubscribeThreadClosedEvent } from '@/src/modules/thread/hooks'
2
3
  import { useWidgetTabsValueAtom } from '../../store'
3
4
  import { WIDGET_TABS } from '../constants'
4
5
 
5
6
  function WidgetContainer() {
6
7
  const widgetTabs = useWidgetTabsValueAtom()
7
8
  useSubscribeMessageReceivedEvent()
9
+ useSubscribeThreadClosedEvent()
8
10
 
9
11
  return (
10
12
  <div className='flex h-full flex-col items-center justify-stretch overflow-hidden'>
@@ -6,33 +6,49 @@ export const TutorWidgetEventTypes = {
6
6
  LOADED: 'tutor-app-widget-loaded'
7
7
  } as const
8
8
 
9
- const TutorWidgetEventsList = [
10
- {
9
+ const TutorWidgetEventsObject = {
10
+ [TutorWidgetEventTypes.OPEN]: {
11
11
  name: TutorWidgetEventTypes.OPEN,
12
- handler: () => () => undefined,
12
+ handler: (callback) => {
13
+ const listener: EventListener = () => {
14
+ void callback()
15
+ }
16
+
17
+ window.addEventListener(TutorWidgetEventTypes.OPEN, listener)
18
+
19
+ return () => {
20
+ window.removeEventListener(TutorWidgetEventTypes.OPEN, listener)
21
+ }
22
+ },
13
23
  dispatch: () => {
14
24
  window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.OPEN))
15
25
  }
16
- },
17
- {
26
+ } as ITutorWidgetEvent<void>,
27
+
28
+ [TutorWidgetEventTypes.CLOSE]: {
18
29
  name: TutorWidgetEventTypes.CLOSE,
19
- handler: () => () => undefined,
20
- dispatch: () => {
21
- window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.CLOSE))
22
- }
23
- },
24
- {
25
- name: TutorWidgetEventTypes.LOADED,
26
30
  handler: (callback) => {
27
- const listener: EventListener = (e) => {
28
- const evt = e as CustomEvent<{ isSuccess: boolean }>
31
+ const listener: EventListener = () => {
32
+ void callback()
33
+ }
34
+
35
+ window.addEventListener(TutorWidgetEventTypes.CLOSE, listener)
29
36
 
30
- console.log(evt.detail)
37
+ return () => {
38
+ window.removeEventListener(TutorWidgetEventTypes.CLOSE, listener)
39
+ }
40
+ },
41
+ dispatch: () => window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.CLOSE))
42
+ } as ITutorWidgetEvent<void>,
31
43
 
44
+ [TutorWidgetEventTypes.LOADED]: {
45
+ name: TutorWidgetEventTypes.LOADED,
46
+ handler: (callback: (payload: { isSuccess: boolean }) => void) => {
47
+ const listener: EventListener = (e) => {
48
+ const evt = e as CustomEvent<{ isSuccess: boolean }>
32
49
  callback(evt.detail)
33
50
  }
34
51
  window.addEventListener(TutorWidgetEventTypes.LOADED, listener)
35
-
36
52
  return () => {
37
53
  window.removeEventListener(TutorWidgetEventTypes.LOADED, listener)
38
54
  }
@@ -41,9 +57,9 @@ const TutorWidgetEventsList = [
41
57
  window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.LOADED, payload))
42
58
  }
43
59
  } as ITutorWidgetEvent<{ isSuccess: boolean }>
44
- ] as const
60
+ } as const
45
61
 
46
- export const TutorWidgetEvents = new Map(TutorWidgetEventsList.map((e) => [e.name, e]))
62
+ export const TutorWidgetEvents = TutorWidgetEventsObject
47
63
 
48
64
  export const ACTION_EVENTS = {
49
65
  SCROLL: 'c3po-app-widget-scroll-to-bottom'
@@ -1 +1 @@
1
- export * from './use-init-sparkie'
1
+ export * from './use-listen-to-visibility-events'
@@ -0,0 +1 @@
1
+ export { default as useListenToVisibilityEvents } from './use-listen-to-visibility-events'
@@ -0,0 +1,20 @@
1
+ import { useEffect } from 'react'
2
+
3
+ import { SparkieService } from '@/src/modules/sparkie'
4
+ import { TutorWidgetEvents } from '../../events'
5
+
6
+ function useListenToVisibilityEvents() {
7
+ useEffect(() => {
8
+ const listener = async () => {
9
+ await SparkieService.destroySparkie()
10
+ }
11
+
12
+ const clear = TutorWidgetEvents['c3po-app-widget-close'].handler(listener)
13
+
14
+ return () => {
15
+ clear?.()
16
+ }
17
+ }, [])
18
+ }
19
+
20
+ export default useListenToVisibilityEvents
@@ -8,8 +8,8 @@ export type WidgetTabsProps = {
8
8
  }
9
9
 
10
10
  const INITIAL_PROPS: WidgetTabsProps = {
11
- currentTab: 'chat',
12
- history: new Set(['chat'])
11
+ currentTab: 'starter',
12
+ history: new Set(['starter'])
13
13
  }
14
14
 
15
15
  export const widgetTabsAtom = atom<WidgetTabsProps>(INITIAL_PROPS)
@@ -2,6 +2,6 @@ import type { TutorWidgetEventTypes } from './events'
2
2
 
3
3
  export type ITutorWidgetEvent<T = unknown> = {
4
4
  name: (typeof TutorWidgetEventTypes)[keyof typeof TutorWidgetEventTypes]
5
- handler: (callback: (payload: T) => void) => () => void
5
+ handler: (callback: (payload: T) => void | Promise<void>) => () => void
6
6
  dispatch: (payload?: CustomEventInit<T>) => void
7
7
  }
package/src/types.ts CHANGED
@@ -36,3 +36,9 @@ export type WidgetSettingProps = {
36
36
  owner_id?: string
37
37
  current_media_codes?: string
38
38
  }
39
+
40
+ export interface ICustomEvent<T = object> {
41
+ name: T[keyof T]
42
+ handler: (listener: EventListenerOrEventListenerObject) => () => void | Promise<void>
43
+ dispatch: <D = unknown>(detail?: D) => void
44
+ }
@@ -1,14 +0,0 @@
1
- import { useInitSparkie } from '@/src/modules/widget/hooks/use-init-sparkie'
2
-
3
- vi.mock('@/src/modules/widget/hooks/use-init-sparkie/use-init-sparkie', () => ({
4
- useInitSparkie: vi.fn()
5
- }))
6
-
7
- beforeEach(() => {
8
- vi.mocked(useInitSparkie).mockReturnValue({
9
- data: true,
10
- isError: false,
11
- isLoading: false,
12
- refetch: vi.fn()
13
- } as unknown as ReturnType<typeof useInitSparkie>)
14
- })
@@ -1 +0,0 @@
1
- export * from './use-init-sparkie'
@@ -1,20 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
-
3
- import { SparkieService } from '@/src/modules/sparkie'
4
- import type { WidgetSettingProps } from '@/src/types'
5
-
6
- export const getInitSparkieQuery = (settings: WidgetSettingProps) => ({
7
- queryKey: ['SparkieService:initializeSparkie', settings?.hotmartToken ?? ''],
8
- queryFn: () =>
9
- SparkieService.initSparkie({
10
- token: settings?.hotmartToken,
11
- skipPresenceSetup: true
12
- })
13
- })
14
-
15
- export function useInitSparkie(settings: WidgetSettingProps | null) {
16
- return useQuery({
17
- ...getInitSparkieQuery(settings as WidgetSettingProps),
18
- enabled: Boolean(settings?.hotmartToken?.trim())
19
- })
20
- }