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.
- package/CHANGELOG.md +9 -0
- package/config/vitest/__mocks__/sparkie.tsx +6 -1
- package/config/vitest/vitest.config.mts +0 -1
- package/package.json +1 -1
- package/src/index.tsx +15 -6
- package/src/main/main.tsx +19 -1
- package/src/modules/cursor/service.ts +20 -2
- package/src/modules/messages/events.ts +25 -0
- package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.spec.tsx +21 -0
- package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.tsx +2 -0
- package/src/modules/messages/hooks/use-subscribe-message-received-event/use-subscribe-message-received-event.tsx +16 -1
- package/src/modules/messages/store/index.ts +1 -0
- package/src/modules/messages/store/unread-messages-set.atom.ts +21 -0
- package/src/modules/messages/types.ts +4 -0
- package/src/modules/sparkie/service.ts +13 -3
- package/src/modules/thread/hooks/index.ts +1 -0
- package/src/modules/thread/hooks/use-subscribe-thread-closed-event/index.ts +2 -0
- package/src/modules/thread/hooks/use-subscribe-thread-closed-event/use-subscribe-thread-closed-event.tsx +22 -0
- package/src/modules/thread/index.ts +1 -0
- package/src/modules/widget/components/container/container.tsx +2 -0
- package/src/modules/widget/events.ts +34 -18
- package/src/modules/widget/hooks/index.ts +1 -1
- package/src/modules/widget/hooks/use-listen-to-visibility-events/index.ts +1 -0
- package/src/modules/widget/hooks/use-listen-to-visibility-events/use-listen-to-visibility-events.tsx +20 -0
- package/src/modules/widget/store/widget-tabs.atom.ts +2 -2
- package/src/modules/widget/types.ts +1 -1
- package/src/types.ts +6 -0
- package/config/vitest/__mocks__/use-init-sparkie.tsx +0 -14
- package/src/modules/widget/hooks/use-init-sparkie/index.ts +0 -1
- 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 {
|
|
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
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
|
|
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.
|
|
62
|
+
TutorWidgetEvents['tutor-app-widget-loaded'].dispatch()
|
|
59
63
|
} catch (error) {
|
|
64
|
+
initSparkieError = error
|
|
60
65
|
console.error(error)
|
|
61
|
-
TutorWidgetEvents.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
}, [
|
|
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)
|
|
@@ -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
|
|
238
|
+
if (this.sparkie) {
|
|
229
239
|
await this.sparkieInstance.destroy({ skipSignOut: true })
|
|
230
240
|
}
|
|
231
|
-
} catch
|
|
232
|
-
console.
|
|
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,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
|
|
10
|
-
{
|
|
9
|
+
const TutorWidgetEventsObject = {
|
|
10
|
+
[TutorWidgetEventTypes.OPEN]: {
|
|
11
11
|
name: TutorWidgetEventTypes.OPEN,
|
|
12
|
-
handler: () =>
|
|
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 = (
|
|
28
|
-
|
|
31
|
+
const listener: EventListener = () => {
|
|
32
|
+
void callback()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
window.addEventListener(TutorWidgetEventTypes.CLOSE, listener)
|
|
29
36
|
|
|
30
|
-
|
|
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
|
-
|
|
60
|
+
} as const
|
|
45
61
|
|
|
46
|
-
export const TutorWidgetEvents =
|
|
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-
|
|
1
|
+
export * from './use-listen-to-visibility-events'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useListenToVisibilityEvents } from './use-listen-to-visibility-events'
|
package/src/modules/widget/hooks/use-listen-to-visibility-events/use-listen-to-visibility-events.tsx
ADDED
|
@@ -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: '
|
|
12
|
-
history: new Set(['
|
|
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
|
-
}
|