app-tutor-ai-consumer 1.33.1 → 1.35.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 +37 -0
- package/config/vitest/__mocks__/sparkie.tsx +2 -2
- package/package.json +2 -2
- package/src/@types/index.d.ts +3 -2
- package/src/bootstrap.ts +40 -0
- package/src/config/tanstack/query-provider.tsx +15 -4
- package/src/config/tests/handlers.ts +5 -4
- package/src/config/theme/init-theme.ts +11 -5
- package/src/index.backup.tsx +61 -0
- package/src/index.tsx +80 -17
- package/src/lib/components/dropdown-actions/dropdown-actions.tsx +87 -0
- package/src/lib/components/dropdown-actions/dropdownActions.builder.ts +58 -0
- package/src/lib/components/dropdown-actions/dropdownActions.spec.tsx +76 -0
- package/src/lib/components/dropdown-actions/index.ts +1 -0
- package/src/lib/components/dropdown-actions/types.ts +16 -0
- package/src/lib/components/errors/generic/generic-error.tsx +19 -10
- package/src/lib/components/icons/document.svg +3 -0
- package/src/lib/components/icons/file.svg +3 -0
- package/src/lib/components/icons/icon-names.d.ts +7 -0
- package/src/lib/components/icons/image.svg +3 -0
- package/src/lib/components/icons/pdf.svg +3 -0
- package/src/lib/components/icons/plus.svg +3 -0
- package/src/lib/components/icons/retry.svg +3 -0
- package/src/lib/components/icons/spreadsheet.svg +3 -0
- package/src/lib/components/index.ts +1 -0
- package/src/lib/components/markdownrenderer/markdownrenderer.tsx +1 -3
- package/src/lib/hooks/index.ts +1 -0
- package/src/lib/hooks/use-chat-file-upload/constants.ts +11 -0
- package/src/lib/hooks/use-chat-file-upload/index.ts +1 -0
- package/src/lib/hooks/use-chat-file-upload/types.ts +14 -0
- package/src/lib/hooks/use-chat-file-upload/use-chat-file-upload.spec.ts +59 -0
- package/src/lib/hooks/use-chat-file-upload/use-chat-file-upload.ts +28 -0
- package/src/lib/hooks/use-click-outside/index.ts +1 -0
- package/src/lib/hooks/use-click-outside/use-click-outside.tsx +23 -0
- package/src/lib/hooks/use-click-outside/useClickOutside.spec.ts +102 -0
- package/src/lib/utils/index.ts +1 -0
- package/src/lib/utils/is-theme-dark.ts +21 -0
- package/src/main/hooks/use-initial-store/index.ts +1 -0
- package/src/main/hooks/use-initial-store/use-initial-store.tsx +64 -0
- package/src/main/hooks/use-initial-tab/index.ts +1 -0
- package/src/main/hooks/use-initial-tab/use-initial-tab.tsx +48 -0
- package/src/main/index.ts +1 -0
- package/src/main/main-content.tsx +14 -0
- package/src/main/main-wrapper.tsx +16 -0
- package/src/main/main.spec.tsx +5 -3
- package/src/main/main.tsx +7 -16
- package/src/main/types.ts +5 -0
- package/src/modules/global-providers/global-providers.tsx +2 -21
- package/src/modules/messages/__tests__/imessage-with-sender-data.builder.ts +1 -1
- package/src/modules/messages/__tests__/signed-urls.builder.ts +42 -0
- package/src/modules/messages/components/chat-file-preview/chat-file-preview.builder.ts +121 -0
- package/src/modules/messages/components/chat-file-preview/chat-file-preview.spec.tsx +107 -0
- package/src/modules/messages/components/chat-file-preview/chat-file-preview.tsx +45 -0
- package/src/modules/messages/components/chat-file-preview/components/close-button/close-button.tsx +31 -0
- package/src/modules/messages/components/chat-file-preview/components/close-button/index.ts +1 -0
- package/src/modules/messages/components/chat-file-preview/components/close-button/types.ts +4 -0
- package/src/modules/messages/components/chat-file-preview/components/document-preview/document-preview.tsx +63 -0
- package/src/modules/messages/components/chat-file-preview/components/document-preview/index.ts +1 -0
- package/src/modules/messages/components/chat-file-preview/components/document-preview/types.ts +10 -0
- package/src/modules/messages/components/chat-file-preview/components/error-preview/error-preview.tsx +37 -0
- package/src/modules/messages/components/chat-file-preview/components/error-preview/index.ts +1 -0
- package/src/modules/messages/components/chat-file-preview/components/error-preview/types.ts +4 -0
- package/src/modules/messages/components/chat-file-preview/components/image-preview/image-preview.tsx +32 -0
- package/src/modules/messages/components/chat-file-preview/components/image-preview/index.ts +1 -0
- package/src/modules/messages/components/chat-file-preview/components/image-preview/types.ts +6 -0
- package/src/modules/messages/components/chat-file-preview/constants.ts +22 -0
- package/src/modules/messages/components/chat-file-preview/index.ts +1 -0
- package/src/modules/messages/components/chat-file-preview/types.ts +13 -0
- package/src/modules/messages/components/chat-file-preview/utils.spec.ts +38 -0
- package/src/modules/messages/components/chat-file-preview/utils.ts +13 -0
- package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.builder.ts +19 -0
- package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.spec.tsx +58 -0
- package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.tsx +80 -0
- package/src/modules/messages/components/chat-file-uploader/index.ts +1 -0
- package/src/modules/messages/components/chat-file-uploader/types.ts +4 -0
- package/src/modules/messages/components/index.ts +1 -0
- package/src/modules/messages/constants.ts +2 -1
- package/src/modules/messages/hooks/use-get-signed-urls/index.ts +1 -0
- package/src/modules/messages/hooks/use-get-signed-urls/use-get-signed-urls.spec.tsx +27 -0
- package/src/modules/messages/hooks/use-get-signed-urls/use-get-signed-urls.tsx +38 -0
- package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.spec.tsx +1 -2
- package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.tsx +6 -8
- package/src/modules/messages/hooks/use-prefetch-messages/index.ts +1 -0
- package/src/modules/messages/hooks/use-prefetch-messages/use-prefetch-messages.tsx +28 -0
- package/src/modules/messages/hooks/use-suspense-messages/index.ts +2 -0
- package/src/modules/messages/hooks/use-suspense-messages/types.ts +4 -0
- package/src/modules/messages/hooks/use-suspense-messages/use-suspense-messages.tsx +21 -0
- package/src/modules/messages/service.direct.ts +19 -1
- package/src/modules/messages/service.ts +1 -1
- package/src/modules/messages/store/messages-max-count.atom.ts +2 -2
- package/src/modules/messages/types.ts +15 -1
- package/src/modules/messages/utils/set-messages-cache/utils.ts +1 -1
- package/src/modules/profile/hooks/use-get-profile/use-get-profile.tsx +7 -6
- package/src/modules/sparkie/__tests__/sparkie.mock.ts +1 -4
- package/src/modules/sparkie/hooks/use-init-sparkie/use-init-sparkie.tsx +34 -28
- package/src/modules/sparkie/service.ts +1 -1
- package/src/modules/sparkie/store/index.ts +1 -0
- package/src/modules/sparkie/store/sparkie-state.atom.ts +13 -0
- package/src/modules/widget/__tests__/widget-settings-props.builder.ts +20 -1
- package/src/modules/widget/components/chat-page/chat-page.tsx +58 -0
- package/src/modules/widget/components/constants.tsx +3 -1
- package/src/modules/widget/components/error-page/error-page.spec.tsx +17 -0
- package/src/modules/widget/components/error-page/error-page.tsx +12 -0
- package/src/modules/widget/components/error-page/index.ts +1 -0
- package/src/modules/widget/components/loading-page/loading-page.tsx +9 -15
- package/src/modules/widget/components/starter-page/starter-page-actions/index.ts +1 -0
- package/src/modules/widget/components/starter-page/starter-page-actions/starter-page-actions.spec.tsx +68 -0
- package/src/modules/widget/components/starter-page/starter-page-actions/starter-page-actions.tsx +34 -0
- package/src/modules/widget/components/starter-page/starter-page-content/index.ts +1 -0
- package/src/modules/widget/components/starter-page/starter-page-content/starter-page-content.spec.tsx +16 -0
- package/src/modules/widget/components/starter-page/starter-page-content/starter-page-content.tsx +28 -0
- package/src/modules/widget/components/starter-page/starter-page-header/index.ts +1 -0
- package/src/modules/widget/components/starter-page/starter-page-header/starter-page-header.spec.tsx +45 -0
- package/src/modules/widget/components/starter-page/starter-page-header/starter-page-header.tsx +36 -0
- package/src/modules/widget/components/starter-page/starter-page.spec.tsx +13 -3
- package/src/modules/widget/components/starter-page/starter-page.tsx +15 -109
- package/src/modules/widget/hooks/index.ts +1 -1
- package/src/modules/widget/hooks/use-listen-to-theme-change-event/use-listen-to-theme-change-event.tsx +8 -6
- package/src/modules/widget/hooks/use-retry-last-message/index.ts +1 -0
- package/src/modules/widget/hooks/use-retry-last-message/use-retry-last-message.tsx +37 -0
- package/src/modules/widget/store/create-store.ts +7 -0
- package/src/modules/widget/store/index.ts +2 -0
- package/src/modules/widget/store/widget-last-user-message.atom.ts +12 -0
- package/src/modules/widget/store/widget-settings-config.atom.ts +1 -6
- package/src/modules/widget/store/widget-tabs.atom.ts +17 -6
- package/src/types.ts +10 -0
- package/src/wrapper.tsx +52 -0
- package/src/modules/widget/hooks/use-init-widget/index.ts +0 -1
- package/src/modules/widget/hooks/use-init-widget/use-init-widget.tsx +0 -56
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,40 @@
|
|
|
1
|
+
# [1.35.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.34.0...v1.35.0) (2025-11-05)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- add retry question ([7b75aba](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/7b75aba715ade6d5a7fec04237f8f13e643d47f5))
|
|
6
|
+
|
|
7
|
+
# [1.34.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.33.1...v1.34.0) (2025-11-04)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- fix aria label ([5aa714d](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/5aa714da552e93349c1c030c026ecb4a14c132ba))
|
|
12
|
+
- fix base branch ([96db93a](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/96db93a53c4a93d6e0800af9547ea81d118a6bb5))
|
|
13
|
+
- fix chat input ([d686bc5](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/d686bc530cecb84fc80cbf2a8b9ff029a3572e53))
|
|
14
|
+
- fix file types ([ee12680](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/ee12680db8c36307c4b067cb6460009c0509018e))
|
|
15
|
+
- fix file uploader ([1110f11](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/1110f116a0276f3d34ebec6eea81aaa59e50e993))
|
|
16
|
+
- fix pr request ([6ad4870](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/6ad4870cd2bd03f788eb10f00a65616d48c1087f))
|
|
17
|
+
- fix pr request ([1556154](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/155615476be3fb61994a790dd4464b9f53968f78))
|
|
18
|
+
- fix pr request ([17cd695](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/17cd69506c21b793ea05f634a07f987a125e3102))
|
|
19
|
+
- fix svg ([c9165f7](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/c9165f74d2f592e0fccf27176768e2ae6a782fd0))
|
|
20
|
+
- pr issues ([3f5b8ad](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/3f5b8ad54a657a4d95c66728c12559cd321b088d))
|
|
21
|
+
- refactor code ([1b7b5cf](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/1b7b5cfafe6c8f902bf3514fd4e36996f2e705c8))
|
|
22
|
+
- refactor component name ([a6c2a16](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/a6c2a16ab29733dd831bc88e3a6452c9e225af70))
|
|
23
|
+
- remove console.log ([66645dc](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/66645dc72dac1bd667c5440cf59e45aecc9b321a))
|
|
24
|
+
- update file uploader ([c12f347](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/c12f3472450a4afa214ebf87234e7eb3464c3f7d))
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
- add chat loading if is user message without response ([8ffe2a7](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/8ffe2a7bbf1d814f341855ec1efebd6e00f5cb65))
|
|
29
|
+
- add default store ([75a07c4](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/75a07c451c99b526aea896b6f94b7deb1f0f1888))
|
|
30
|
+
- change sparkie call to messages history page ([f5f3e73](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/f5f3e73d7ec147b7f5a4a5e76293cf29b2d0dd93))
|
|
31
|
+
- create chat file preview ([885b744](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/885b744d76b62a7e387d2e820e8e8030f6e17f9a))
|
|
32
|
+
- implement signed URLs functionality with hooks and service integration ([ff845fa](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/ff845fa34be2c28f55ec04b9a70522f3100a7bec))
|
|
33
|
+
- refactor dropdown ([88bba02](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/88bba02ec9021f5294450eecfd6d6c92097d6690))
|
|
34
|
+
- release branch for changing widget to support product agent ([ab39962](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/ab39962a5e2a35ee29056c329fe8a4038291ceaa))
|
|
35
|
+
- update feature flag file upload ([bd3313b](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/bd3313b634b53ac5dfda2737155e718d570fb099))
|
|
36
|
+
- update icon size ([2d77927](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/2d7792724ed1a1166b012da118b5ce3c44d9ad74))
|
|
37
|
+
|
|
1
38
|
## [1.33.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.33.0...v1.33.1) (2025-10-22)
|
|
2
39
|
|
|
3
40
|
### Bug Fixes
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
SparkieMessageServiceMock,
|
|
5
5
|
SparkieCursorServiceMock
|
|
6
6
|
} from '@/src/modules/sparkie/__tests__/sparkie.mock'
|
|
7
|
-
import MessageService from '@hotmart/sparkie/dist/MessageService'
|
|
7
|
+
import MessageService from '@hotmart-org-ca/sparkie/dist/MessageService'
|
|
8
8
|
|
|
9
|
-
vi.mock('@hotmart/sparkie', () => ({ default: SparkieMock }))
|
|
9
|
+
vi.mock('@hotmart-org-ca/sparkie', () => ({ default: SparkieMock }))
|
|
10
10
|
|
|
11
11
|
beforeEach(() => {
|
|
12
12
|
vi.spyOn(SparkieService, 'getMessageService').mockResolvedValue(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "app-tutor-ai-consumer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
|
|
@@ -108,8 +108,8 @@
|
|
|
108
108
|
"dependencies": {
|
|
109
109
|
"@hot-observability-js/react": "~1.1.0",
|
|
110
110
|
"@hotmart-org-ca/hot-observability-js": "~1.1.0",
|
|
111
|
+
"@hotmart-org-ca/sparkie": "~5.1.4",
|
|
111
112
|
"@hotmart/event-agent-js": "~1.1.2",
|
|
112
|
-
"@hotmart/sparkie": "~5.1.0",
|
|
113
113
|
"@optimizely/react-sdk": "~3.2.4",
|
|
114
114
|
"@tanstack/query-sync-storage-persister": "~5.80.7",
|
|
115
115
|
"@tanstack/react-query": "~5.80.6",
|
package/src/@types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { StartTutorWidgetProps } from '@/src/types'
|
|
1
|
+
import type { StartTutorWidgetProps, WidgetInstance } from '@/src/types'
|
|
2
2
|
|
|
3
3
|
export {}
|
|
4
4
|
|
|
@@ -13,7 +13,8 @@ declare global {
|
|
|
13
13
|
elementId: StartTutorWidgetProps['elementId'],
|
|
14
14
|
settings: StartTutorWidgetProps['settings']
|
|
15
15
|
) => Promise<void>
|
|
16
|
-
closeChatWidget: () => void
|
|
16
|
+
closeChatWidget: () => Promise<void>
|
|
17
|
+
__CHAT_WIDGET_INSTANCE__?: WidgetInstance
|
|
17
18
|
TOKEN: string
|
|
18
19
|
}
|
|
19
20
|
}
|
package/src/bootstrap.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { QueryClient } from '@tanstack/react-query'
|
|
2
|
+
|
|
3
|
+
import { initTheme } from '@/src/config/theme'
|
|
4
|
+
|
|
5
|
+
import { DataHubStore } from './config/datahub'
|
|
6
|
+
import { initDayjs } from './config/dayjs'
|
|
7
|
+
import { initLanguage } from './config/i18n'
|
|
8
|
+
import { initAxios } from './config/request/api'
|
|
9
|
+
import { getProfileQuery } from './modules/profile'
|
|
10
|
+
import type { Theme, WidgetSettingProps } from './types'
|
|
11
|
+
|
|
12
|
+
type BootstrapProps = {
|
|
13
|
+
settings: WidgetSettingProps
|
|
14
|
+
queryClient: QueryClient
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function bootstrap({ queryClient, settings }: BootstrapProps) {
|
|
18
|
+
initAxios(settings.hotmartToken)
|
|
19
|
+
initTheme(settings.config?.theme as Theme)
|
|
20
|
+
|
|
21
|
+
const promises = [
|
|
22
|
+
initLanguage(settings.locale),
|
|
23
|
+
queryClient.prefetchQuery(getProfileQuery()),
|
|
24
|
+
initDayjs(settings.locale)
|
|
25
|
+
] as const
|
|
26
|
+
|
|
27
|
+
await Promise.all(promises)
|
|
28
|
+
|
|
29
|
+
DataHubStore.initData({
|
|
30
|
+
ucode: settings.user?.ucode ?? '',
|
|
31
|
+
membershipId: settings.membershipId ?? '',
|
|
32
|
+
membershipSlug: settings.membershipSlug ?? '',
|
|
33
|
+
sessionId: settings.sessionId,
|
|
34
|
+
userId: Number(settings.userId),
|
|
35
|
+
product: {
|
|
36
|
+
id: Number(settings.productId) || 0,
|
|
37
|
+
category: settings.productType ?? ''
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
}
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
+
import type { Query, QueryClient } from '@tanstack/react-query'
|
|
1
2
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
|
2
3
|
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
|
|
3
4
|
import type { PropsWithChildren } from 'react'
|
|
4
5
|
|
|
5
|
-
import { persister
|
|
6
|
+
import { persister } from './query-client'
|
|
6
7
|
|
|
7
|
-
export type QueryProviderProps = PropsWithChildren<{
|
|
8
|
+
export type QueryProviderProps = PropsWithChildren<{
|
|
9
|
+
showDevTools?: boolean
|
|
10
|
+
queryClient: QueryClient
|
|
11
|
+
}>
|
|
8
12
|
|
|
9
|
-
function QueryProvider({ children, showDevTools = true }: QueryProviderProps) {
|
|
13
|
+
function QueryProvider({ children, queryClient, showDevTools = true }: QueryProviderProps) {
|
|
10
14
|
return (
|
|
11
|
-
<PersistQueryClientProvider
|
|
15
|
+
<PersistQueryClientProvider
|
|
16
|
+
client={queryClient}
|
|
17
|
+
persistOptions={
|
|
18
|
+
{
|
|
19
|
+
persister,
|
|
20
|
+
shouldPersistQuery: (query: Query) => query.meta?.persist !== false
|
|
21
|
+
} as Parameters<typeof PersistQueryClientProvider>[0]['persistOptions']
|
|
22
|
+
}>
|
|
12
23
|
{children}
|
|
13
24
|
{showDevTools && <ReactQueryDevtools buttonPosition='top-right' />}
|
|
14
25
|
</PersistQueryClientProvider>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { http, HttpResponse } from 'msw'
|
|
2
2
|
|
|
3
|
-
import type { IMessageWithSenderData } from '@/src/modules/messages'
|
|
4
3
|
import { MessagesEndpoints, MSG_MAX_COUNT } from '@/src/modules/messages'
|
|
5
4
|
import IMessageWithSenderDataMock from '@/src/modules/messages/__tests__/imessage-with-sender-data.mock'
|
|
5
|
+
import SignedUrlsResponseBuilder from '@/src/modules/messages/__tests__/signed-urls.builder'
|
|
6
6
|
import { ProfileEndpoints } from '@/src/modules/profile'
|
|
7
7
|
import ProfileAPIPropsBuilder from '@/src/modules/profile/__tests__/profile-api-props.builder'
|
|
8
8
|
|
|
@@ -19,13 +19,14 @@ export const handlers = [
|
|
|
19
19
|
http.all(ProfileEndpoints.getProfile(), () => {
|
|
20
20
|
return HttpResponse.json(new ProfileAPIPropsBuilder())
|
|
21
21
|
}),
|
|
22
|
+
http.all(MessagesEndpoints.getSignedUrls(), () => {
|
|
23
|
+
return HttpResponse.json(new SignedUrlsResponseBuilder())
|
|
24
|
+
}),
|
|
22
25
|
http.all(MessagesEndpoints.getAll(':conversationId'), ({ request }) => {
|
|
23
26
|
const limit = Number(new URL(request.url)?.searchParams?.get?.('limit'))
|
|
24
27
|
|
|
25
28
|
return HttpResponse.json(
|
|
26
|
-
new IMessageWithSenderDataMock().getMany(
|
|
27
|
-
isNaN(limit) ? MSG_MAX_COUNT : limit
|
|
28
|
-
) as IMessageWithSenderData[]
|
|
29
|
+
new IMessageWithSenderDataMock().getMany(isNaN(limit) ? MSG_MAX_COUNT : limit)
|
|
29
30
|
)
|
|
30
31
|
})
|
|
31
32
|
]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getTheme } from '@/src/lib/utils'
|
|
1
2
|
import type { Theme } from '@/src/types'
|
|
2
3
|
|
|
3
4
|
export function initTheme(theme: Theme) {
|
|
@@ -5,11 +6,16 @@ export function initTheme(theme: Theme) {
|
|
|
5
6
|
|
|
6
7
|
if (!rootElement) return
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const currentTheme = getTheme(theme)
|
|
10
|
+
|
|
11
|
+
const darkClasses = ['dark', 'bg-ai-dark']
|
|
12
|
+
const lightClasses = ['bg-neutral-100']
|
|
13
|
+
|
|
14
|
+
rootElement.classList.remove(...darkClasses, ...lightClasses)
|
|
15
|
+
|
|
16
|
+
if (currentTheme === 'dark') {
|
|
17
|
+
return rootElement.classList.add(...darkClasses)
|
|
11
18
|
}
|
|
12
19
|
|
|
13
|
-
rootElement.classList.
|
|
14
|
-
return rootElement.classList.add('bg-neutral-100')
|
|
20
|
+
return rootElement.classList.add(...lightClasses)
|
|
15
21
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import './config/styles/global.css'
|
|
2
|
+
import './config/styles/index.css'
|
|
3
|
+
|
|
4
|
+
import { StrictMode } from 'react'
|
|
5
|
+
import { createRoot } from 'react-dom/client'
|
|
6
|
+
|
|
7
|
+
import { initTheme } from '@/src/config/theme'
|
|
8
|
+
import { version } from '../package.json'
|
|
9
|
+
|
|
10
|
+
import { initLanguage } from './config/i18n'
|
|
11
|
+
import { devMode, productionMode } from './lib/utils'
|
|
12
|
+
import { Main } from './main'
|
|
13
|
+
import { TutorWidgetEvents } from './modules/widget'
|
|
14
|
+
import type { Theme, WidgetSettingProps } from './types'
|
|
15
|
+
|
|
16
|
+
const loadMainStyles = () => {
|
|
17
|
+
const isProduction = productionMode
|
|
18
|
+
const bundlePath = !isProduction
|
|
19
|
+
? `${process.env.BUNDLE_PATH}/`
|
|
20
|
+
: `${process.env.BUNDLE_PATH}/${process.env.APP_NAME}/_current/`
|
|
21
|
+
|
|
22
|
+
const cssPath = `${bundlePath}app-tutor-ai-consumer.css?v=${version}`
|
|
23
|
+
|
|
24
|
+
if (!document.querySelector(`link[href="${cssPath}"]`)) {
|
|
25
|
+
const linkElement = document.createElement('link')
|
|
26
|
+
linkElement.rel = 'stylesheet'
|
|
27
|
+
linkElement.href = cssPath
|
|
28
|
+
document.head.appendChild(linkElement)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
window.startChatWidget = async (
|
|
33
|
+
elementId = 'tutor-chat-app-widget',
|
|
34
|
+
settings: WidgetSettingProps
|
|
35
|
+
) => {
|
|
36
|
+
if (!devMode) {
|
|
37
|
+
loadMainStyles()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const rootElement = document.getElementById(elementId) as HTMLElement
|
|
41
|
+
|
|
42
|
+
if (!rootElement) return
|
|
43
|
+
|
|
44
|
+
rootElement.setAttribute('id', 'hotmart-app-tutor-ai-consumer-root')
|
|
45
|
+
const theme = (rootElement.getAttribute('data-theme') ?? 'dark') as Theme
|
|
46
|
+
const root = createRoot(rootElement)
|
|
47
|
+
|
|
48
|
+
await initLanguage(settings.locale)
|
|
49
|
+
initTheme(theme)
|
|
50
|
+
|
|
51
|
+
if (root) {
|
|
52
|
+
root.render(
|
|
53
|
+
<StrictMode>
|
|
54
|
+
<Main settings={{ ...settings, config: { ...settings.config, theme } }} />
|
|
55
|
+
</StrictMode>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
window.closeChatWidget = () =>
|
|
61
|
+
Promise.resolve(TutorWidgetEvents['c3po-app-widget-close'].dispatch())
|
package/src/index.tsx
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import './config/styles/global.css'
|
|
2
2
|
import './config/styles/index.css'
|
|
3
3
|
|
|
4
|
-
import { StrictMode } from 'react'
|
|
5
4
|
import { createRoot } from 'react-dom/client'
|
|
6
5
|
|
|
7
|
-
import {
|
|
6
|
+
import { queryClient } from '@/src/config/tanstack'
|
|
8
7
|
import { version } from '../package.json'
|
|
9
8
|
|
|
10
|
-
import {
|
|
9
|
+
import { bootstrap } from './bootstrap'
|
|
10
|
+
import { initTheme } from './config/theme'
|
|
11
11
|
import { devMode, productionMode } from './lib/utils'
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { SparkieService } from './modules/sparkie'
|
|
13
|
+
import { createStore } from './modules/widget/store'
|
|
14
14
|
import type { Theme, WidgetSettingProps } from './types'
|
|
15
|
+
import Wrapper from './wrapper'
|
|
15
16
|
|
|
16
17
|
const loadMainStyles = () => {
|
|
17
18
|
const isProduction = productionMode
|
|
@@ -33,28 +34,90 @@ window.startChatWidget = async (
|
|
|
33
34
|
elementId = 'tutor-chat-app-widget',
|
|
34
35
|
settings: WidgetSettingProps
|
|
35
36
|
) => {
|
|
37
|
+
if (window.__CHAT_WIDGET_INSTANCE__) {
|
|
38
|
+
await window.closeChatWidget()
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
if (!devMode) {
|
|
37
42
|
loadMainStyles()
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
if (!rootElement) return
|
|
45
|
+
const container = document.getElementById(elementId) as HTMLElement
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
const theme = (rootElement.getAttribute('data-theme') ?? 'dark') as Theme
|
|
46
|
-
const root = createRoot(rootElement)
|
|
47
|
+
if (!container) return
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
container.setAttribute('id', 'hotmart-app-tutor-ai-consumer-root')
|
|
50
|
+
const theme = (container.getAttribute('data-theme') ?? 'dark') as Theme
|
|
49
51
|
initTheme(theme)
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
const root = createRoot(container)
|
|
54
|
+
const widgetSettings = { ...settings, config: { ...settings.config, theme } }
|
|
55
|
+
const store = createStore()
|
|
56
|
+
|
|
57
|
+
root.render(
|
|
58
|
+
<Wrapper settings={widgetSettings} store={store} queryClient={queryClient} state={'LOADING'} />
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await bootstrap({ queryClient, settings })
|
|
63
|
+
|
|
52
64
|
root.render(
|
|
53
|
-
<
|
|
54
|
-
<Main settings={{ ...settings, config: { ...settings.config, theme } }} />
|
|
55
|
-
</StrictMode>
|
|
65
|
+
<Wrapper settings={widgetSettings} store={store} queryClient={queryClient} state={'READY'} />
|
|
56
66
|
)
|
|
67
|
+
|
|
68
|
+
window.__CHAT_WIDGET_INSTANCE__ = { root, container, queryClient }
|
|
69
|
+
} catch (error) {
|
|
70
|
+
root.render(
|
|
71
|
+
<Wrapper settings={widgetSettings} store={store} queryClient={queryClient} state={'ERROR'} />
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
console.error('ERROR:Initializing Chat Widget', error)
|
|
75
|
+
|
|
76
|
+
window.__CHAT_WIDGET_INSTANCE__ = undefined
|
|
57
77
|
}
|
|
58
78
|
}
|
|
59
79
|
|
|
60
|
-
window.closeChatWidget = () =>
|
|
80
|
+
window.closeChatWidget = async () => {
|
|
81
|
+
const chatWidgetInstance = window.__CHAT_WIDGET_INSTANCE__
|
|
82
|
+
|
|
83
|
+
if (!chatWidgetInstance) return
|
|
84
|
+
|
|
85
|
+
const { root, container, queryClient } = chatWidgetInstance
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await queryClient.cancelQueries()
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error('Error cancelling queries on widget close', err)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
root.unmount()
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.warn('Error unmounting widget root', err)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof queryClient.clear === 'function') {
|
|
100
|
+
try {
|
|
101
|
+
queryClient.clear()
|
|
102
|
+
} catch {
|
|
103
|
+
queryClient.getQueryCache().clear()
|
|
104
|
+
queryClient.getMutationCache().clear()
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
queryClient.getQueryCache().clear()
|
|
108
|
+
queryClient.getMutationCache().clear()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
container.remove()
|
|
113
|
+
} catch {
|
|
114
|
+
// ignore
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await SparkieService.destroySparkie()
|
|
119
|
+
window.__CHAT_WIDGET_INSTANCE__ = undefined
|
|
120
|
+
} catch (err) {
|
|
121
|
+
console.error('Error destroying Sparkie instance', err)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useRef, useState } from 'react'
|
|
2
|
+
import clsx from 'clsx'
|
|
3
|
+
import { useTranslation } from 'react-i18next'
|
|
4
|
+
|
|
5
|
+
import { Button, Icon } from '@/src/lib/components'
|
|
6
|
+
import ButtonDefault from '@/src/lib/components/button/button-default'
|
|
7
|
+
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
8
|
+
import { useClickOutside } from '@/src/lib/hooks'
|
|
9
|
+
|
|
10
|
+
import type { DropdownActionsProps } from './types'
|
|
11
|
+
|
|
12
|
+
function DropdownActions({
|
|
13
|
+
triggerIcon,
|
|
14
|
+
items,
|
|
15
|
+
disabled = false,
|
|
16
|
+
triggerClassName,
|
|
17
|
+
triggerIconClassName
|
|
18
|
+
}: DropdownActionsProps) {
|
|
19
|
+
const { t } = useTranslation()
|
|
20
|
+
const [visible, setVisibile] = useState(false)
|
|
21
|
+
const dropdownRef = useRef<HTMLDivElement>(null)
|
|
22
|
+
useClickOutside(dropdownRef, handleCloseDropdown)
|
|
23
|
+
|
|
24
|
+
function handleCloseDropdown() {
|
|
25
|
+
setVisibile(false)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function handleToggleVisibility() {
|
|
29
|
+
setVisibile((prevState) => !prevState)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
|
|
33
|
+
event.preventDefault()
|
|
34
|
+
event.stopPropagation()
|
|
35
|
+
handleToggleVisibility()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function handleDropdownItemClick(
|
|
39
|
+
event: React.MouseEvent<HTMLButtonElement>,
|
|
40
|
+
callback: () => void
|
|
41
|
+
) {
|
|
42
|
+
event.preventDefault()
|
|
43
|
+
event.stopPropagation()
|
|
44
|
+
handleCloseDropdown()
|
|
45
|
+
callback()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div ref={dropdownRef} className='relative'>
|
|
50
|
+
<Button
|
|
51
|
+
onClick={handleClick}
|
|
52
|
+
disabled={disabled}
|
|
53
|
+
aria-label={t('general.buttons.open_options')}
|
|
54
|
+
className={clsx(
|
|
55
|
+
triggerClassName,
|
|
56
|
+
'bg-neutral-100 p-0 hover:bg-neutral-200 focus:bg-neutral-200'
|
|
57
|
+
)}>
|
|
58
|
+
<Icon
|
|
59
|
+
name={triggerIcon as ValidIconNames}
|
|
60
|
+
className={clsx(triggerIconClassName, 'text-neutral-900')}
|
|
61
|
+
/>
|
|
62
|
+
</Button>
|
|
63
|
+
|
|
64
|
+
{visible && (
|
|
65
|
+
<div className='absolute bottom-full mb-2 flex flex-col rounded-md border border-neutral-300 bg-neutral-100 shadow-lg'>
|
|
66
|
+
{items.map((item) => {
|
|
67
|
+
if (!item.visible) return null
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<ButtonDefault
|
|
71
|
+
key={item.label}
|
|
72
|
+
onClick={(event) => handleDropdownItemClick(event, item.callback)}
|
|
73
|
+
className='flex flex-row items-center justify-start rounded-md px-4 py-3 text-sm font-normal transition-colors duration-150 hover:bg-neutral-200'>
|
|
74
|
+
{item.icon && <Icon name={item.icon} className='mr-4 h-4 w-4 text-neutral-500' />}
|
|
75
|
+
<span className='line-clamp-1 min-w-[5rem] max-w-[10rem] break-words text-left text-neutral-700'>
|
|
76
|
+
{t(item.label)}
|
|
77
|
+
</span>
|
|
78
|
+
</ButtonDefault>
|
|
79
|
+
)
|
|
80
|
+
})}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default DropdownActions
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { chance } from '@/src/config/tests'
|
|
2
|
+
|
|
3
|
+
import type { DropdownActionsProps } from './types'
|
|
4
|
+
|
|
5
|
+
export class DropdownActionsBuilder implements DropdownActionsProps {
|
|
6
|
+
items: DropdownActionsProps['items']
|
|
7
|
+
triggerIcon?: DropdownActionsProps['triggerIcon']
|
|
8
|
+
disabled?: boolean
|
|
9
|
+
className?: string
|
|
10
|
+
triggerClassName?: string
|
|
11
|
+
dropdownClassName?: string
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this.items = [
|
|
15
|
+
{
|
|
16
|
+
label: chance.word(),
|
|
17
|
+
visible: true,
|
|
18
|
+
callback: vi.fn() as () => void
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
this.triggerIcon = 'plus'
|
|
22
|
+
this.disabled = false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
withTriggerIcon(triggerIcon: DropdownActionsProps['triggerIcon']) {
|
|
26
|
+
this.triggerIcon = triggerIcon
|
|
27
|
+
return this
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
withItems(items: DropdownActionsProps['items']) {
|
|
31
|
+
this.items = items
|
|
32
|
+
return this
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
withDisabled(disabled: boolean) {
|
|
36
|
+
this.disabled = disabled
|
|
37
|
+
return this
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
withClassName(className: string) {
|
|
41
|
+
this.className = className
|
|
42
|
+
return this
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
withTriggerClassName(triggerClassName: string) {
|
|
46
|
+
this.triggerClassName = triggerClassName
|
|
47
|
+
return this
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
withDropdownClassName(dropdownClassName: string) {
|
|
51
|
+
this.dropdownClassName = dropdownClassName
|
|
52
|
+
return this
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
build() {
|
|
56
|
+
return this
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { chance, render, screen } from '@/src/config/tests'
|
|
2
|
+
import { DropdownActions } from '../dropdown-actions'
|
|
3
|
+
|
|
4
|
+
import { DropdownActionsBuilder } from './dropdownActions.builder'
|
|
5
|
+
import type { DropdownActionsProps } from './types'
|
|
6
|
+
|
|
7
|
+
const defaultProps = new DropdownActionsBuilder().build()
|
|
8
|
+
|
|
9
|
+
const renderComponent = (props: DropdownActionsProps = defaultProps) => {
|
|
10
|
+
return render(<DropdownActions {...props} />)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('DropdownActions', () => {
|
|
14
|
+
it('should render with success', async () => {
|
|
15
|
+
const { items } = defaultProps
|
|
16
|
+
|
|
17
|
+
const { user } = renderComponent()
|
|
18
|
+
|
|
19
|
+
const dropdown = screen.getByRole('button', { name: /general.buttons.open_options/i })
|
|
20
|
+
|
|
21
|
+
await user.click(dropdown)
|
|
22
|
+
|
|
23
|
+
expect(screen.getByText(items[0].label)).toBeInTheDocument()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
describe('when `visible` is `false`', () => {
|
|
27
|
+
it('should not render dropdown item', async () => {
|
|
28
|
+
const label = chance.word()
|
|
29
|
+
const items = [
|
|
30
|
+
{
|
|
31
|
+
label,
|
|
32
|
+
visible: false,
|
|
33
|
+
callback: vi.fn() as () => void
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
const props = new DropdownActionsBuilder().withItems(items).build()
|
|
38
|
+
|
|
39
|
+
const { user } = renderComponent(props)
|
|
40
|
+
|
|
41
|
+
const dropdown = screen.getByRole('button', { name: /general.buttons.open_options/i })
|
|
42
|
+
|
|
43
|
+
await user.click(dropdown)
|
|
44
|
+
|
|
45
|
+
expect(screen.queryByText(label)).not.toBeInTheDocument()
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
describe('when item has icon', () => {
|
|
50
|
+
it('should render label with icon', async () => {
|
|
51
|
+
const label = chance.word()
|
|
52
|
+
const items = [
|
|
53
|
+
{
|
|
54
|
+
label,
|
|
55
|
+
visible: true,
|
|
56
|
+
icon: 'file' as const,
|
|
57
|
+
callback: vi.fn() as () => void
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
const props = new DropdownActionsBuilder().withItems(items).build()
|
|
62
|
+
|
|
63
|
+
const { user } = renderComponent(props)
|
|
64
|
+
|
|
65
|
+
const dropdown = screen.getByRole('button', { name: /general.buttons.open_options/i })
|
|
66
|
+
|
|
67
|
+
await user.click(dropdown)
|
|
68
|
+
|
|
69
|
+
expect(screen.getByText(label)).toBeInTheDocument()
|
|
70
|
+
|
|
71
|
+
await user.click(screen.getByText(label))
|
|
72
|
+
|
|
73
|
+
expect(props.items[0].callback).toHaveBeenCalledTimes(1)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as DropdownActions } from './dropdown-actions'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
2
|
+
|
|
3
|
+
export type DropdownItem = {
|
|
4
|
+
label: string
|
|
5
|
+
callback: () => void
|
|
6
|
+
icon?: ValidIconNames
|
|
7
|
+
visible?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type DropdownActionsProps = {
|
|
11
|
+
items: DropdownItem[]
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
triggerClassName?: string
|
|
14
|
+
triggerIcon?: ValidIconNames
|
|
15
|
+
triggerIconClassName?: string
|
|
16
|
+
}
|