app-tutor-ai-consumer 1.18.0 → 1.18.2
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 +4 -0
- package/package.json +1 -1
- package/src/config/theme/init-theme.ts +11 -3
- package/src/index.tsx +7 -2
- package/src/lib/components/icons/gallery.svg +3 -0
- package/src/lib/components/icons/icon-names.d.ts +1 -1
- package/src/lib/components/icons/interrogation.svg +2 -8
- package/src/lib/components/markdownrenderer/markdownrenderer.tsx +9 -0
- package/src/lib/hooks/index.ts +0 -1
- package/src/main/main.tsx +2 -3
- package/src/modules/messages/components/message-item/message-item.tsx +1 -1
- package/src/modules/messages/hooks/use-subscribe-message-received-event/use-subscribe-message-received-event.tsx +10 -2
- package/src/modules/widget/components/chat-page/chat-page.tsx +1 -5
- package/src/modules/widget/components/constants.tsx +0 -2
- package/src/modules/widget/components/greetings-card/greetings-card.tsx +20 -10
- package/src/modules/widget/components/header/__tests__/widget-header-props.builder.ts +0 -7
- package/src/modules/widget/components/header/header.tsx +18 -11
- package/src/modules/widget/components/header/types.ts +1 -1
- package/src/modules/widget/components/information-page/constants.ts +6 -2
- package/src/modules/widget/components/information-page/information-card/information-card.tsx +22 -5
- package/src/modules/widget/components/information-page/information-page.tsx +16 -3
- package/src/modules/widget/components/loading-page/loading-page.tsx +5 -5
- package/src/modules/widget/components/starter-page/starter-page.tsx +21 -10
- package/src/modules/widget/events.ts +24 -5
- package/src/modules/widget/hooks/index.ts +1 -0
- package/src/modules/widget/hooks/use-init-widget/use-init-widget.tsx +0 -2
- package/src/modules/widget/hooks/use-listen-to-theme-change-event/index.ts +2 -0
- package/src/modules/widget/hooks/use-listen-to-theme-change-event/use-listen-to-theme-change-event.tsx +30 -0
- package/src/lib/hooks/use-default-id/index.ts +0 -1
- package/src/lib/hooks/use-default-id/use-default-id.tsx +0 -13
- package/src/modules/widget/components/greetings-card/styles.module.css +0 -3
- package/src/modules/widget/components/onboarding-page/index.ts +0 -1
- package/src/modules/widget/components/onboarding-page/onboarding-page.tsx +0 -41
- package/src/modules/widget/components/onboarding-page/styles.module.css +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## [1.18.2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.18.1...v1.18.2) (2025-07-28)
|
|
2
|
+
|
|
3
|
+
## [1.18.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.18.0...v1.18.1) (2025-07-24)
|
|
4
|
+
|
|
1
5
|
# [1.18.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.17.0...v1.18.0) (2025-07-24)
|
|
2
6
|
|
|
3
7
|
### Features
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { Theme } from '@/src/types'
|
|
2
2
|
|
|
3
|
-
export function initTheme(theme
|
|
4
|
-
|
|
3
|
+
export function initTheme(theme: Theme) {
|
|
4
|
+
const rootElement = document.querySelector('#hotmart-app-tutor-ai-consumer-root')
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
if (!rootElement) return
|
|
7
|
+
|
|
8
|
+
if (theme === 'dark') {
|
|
9
|
+
rootElement.classList.remove('bg-neutral-100')
|
|
10
|
+
return rootElement.classList.add(theme, 'bg-ai-dark')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
rootElement.classList.remove('dark', 'bg-ai-dark')
|
|
14
|
+
return rootElement.classList.add('bg-neutral-0')
|
|
7
15
|
}
|
package/src/index.tsx
CHANGED
|
@@ -4,11 +4,13 @@ import './config/styles/index.css'
|
|
|
4
4
|
import { StrictMode } from 'react'
|
|
5
5
|
import { createRoot } from 'react-dom/client'
|
|
6
6
|
|
|
7
|
+
import { initTheme } from '@/src/config/theme'
|
|
8
|
+
|
|
7
9
|
import { initLanguage } from './config/i18n'
|
|
8
10
|
import { devMode, productionMode } from './lib/utils'
|
|
9
11
|
import { Main } from './main'
|
|
10
12
|
import { TutorWidgetEvents } from './modules/widget'
|
|
11
|
-
import type { WidgetSettingProps } from './types'
|
|
13
|
+
import type { Theme, WidgetSettingProps } from './types'
|
|
12
14
|
|
|
13
15
|
const loadMainStyles = () => {
|
|
14
16
|
const isProduction = productionMode
|
|
@@ -35,14 +37,17 @@ window.startChatWidget = async (
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
const rootElement = document.getElementById(elementId) as HTMLElement
|
|
40
|
+
rootElement.setAttribute('id', 'hotmart-app-tutor-ai-consumer-root')
|
|
41
|
+
const theme = (rootElement.getAttribute('data-theme') ?? 'dark') as Theme
|
|
38
42
|
const root = createRoot(rootElement)
|
|
39
43
|
|
|
40
44
|
await initLanguage(settings.locale)
|
|
45
|
+
initTheme(theme)
|
|
41
46
|
|
|
42
47
|
if (root) {
|
|
43
48
|
root.render(
|
|
44
49
|
<StrictMode>
|
|
45
|
-
<Main settings={settings} />
|
|
50
|
+
<Main settings={{ ...settings, config: { ...settings.config, theme } }} />
|
|
46
51
|
</StrictMode>
|
|
47
52
|
)
|
|
48
53
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg width="13" height="14" viewBox="0 0 13 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.7166 0.880408C6.88983 0.963668 7 1.13886 7 1.33106V3.83106H9.23488C9.93358 3.83106 10.5944 4.14867 11.0309 4.69426L12.8904 7.01871C13.0105 7.1688 13.0339 7.37442 12.9507 7.54765C12.8674 7.72089 12.6922 7.83106 12.5 7.83106H10V10.0659C10 10.7646 9.6824 11.4255 9.1368 11.8619L6.81235 13.7215C6.66227 13.8416 6.45664 13.865 6.28341 13.7817C6.11018 13.6985 6 13.5233 6 13.3311V10.8311H3.76513C3.06643 10.8311 2.40561 10.5135 1.96913 9.96786L0.10957 7.64341C-0.0104977 7.49332 -0.0339069 7.2877 0.0493531 7.11447C0.132613 6.94123 0.307802 6.83106 0.500005 6.83106H3V4.59618C3 3.89748 3.31761 3.23666 3.86321 2.80019L6.18766 0.940625C6.33774 0.820557 6.54337 0.797148 6.7166 0.880408ZM1.54032 7.83106L2.75 9.34316C2.9967 9.65154 3.37021 9.83106 3.76513 9.83106H6V7.83106H1.54032ZM6 6.83106H4V4.59618C4 4.20127 4.17952 3.82776 4.4879 3.58105L6 2.37137V6.83106ZM7 7.83106V12.2907L8.51211 11.0811C8.82049 10.8344 9 10.4609 9 10.0659V7.83106H7ZM7 6.83106V4.83106H9.23488C9.6298 4.83106 10.0033 5.01058 10.25 5.31896L11.4597 6.83106H7Z" fill="currentColor"/>
|
|
3
|
+
</svg>
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
<svg width="
|
|
2
|
-
<path d="
|
|
3
|
-
<defs>
|
|
4
|
-
<linearGradient id="paint0_linear_21475_7939" x1="0" y1="8.73584" x2="16" y2="8.73584" gradientUnits="userSpaceOnUse">
|
|
5
|
-
<stop stop-color="#44D0FF"/>
|
|
6
|
-
<stop offset="1" stop-color="#B48EFF"/>
|
|
7
|
-
</linearGradient>
|
|
8
|
-
</defs>
|
|
1
|
+
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 1.83105C3.23858 1.83105 1 4.06963 1 6.83105C1 9.59248 3.23858 11.8311 6 11.8311C8.76142 11.8311 11 9.59248 11 6.83105C11 4.06963 8.76142 1.83105 6 1.83105ZM6 12.8311C2.68629 12.8311 0 10.1448 0 6.83105C0 3.51735 2.68629 0.831055 6 0.831055C9.31371 0.831055 12 3.51735 12 6.83105C12 10.1448 9.31371 12.8311 6 12.8311ZM6 8.83105C6.41421 8.83105 6.75 9.16684 6.75 9.58105C6.75 9.99527 6.41421 10.3311 6 10.3311C5.58579 10.3311 5.25 9.99527 5.25 9.58105C5.25 9.16684 5.58579 8.83105 6 8.83105ZM5 5.33105C5 4.77877 5.44772 4.33105 6 4.33105H6.5C7.05228 4.33105 7.5 4.77877 7.5 5.33105V5.49772C7.5 5.95796 7.1269 6.33105 6.66667 6.33105C6.02233 6.33105 5.5 6.85339 5.5 7.49772V7.83105H6.5V7.49772C6.5 7.40567 6.57462 7.33105 6.66667 7.33105C7.67919 7.33105 8.5 6.51024 8.5 5.49772V5.33105C8.5 4.22649 7.60457 3.33105 6.5 3.33105H6C4.89543 3.33105 4 4.22649 4 5.33105H5Z" fill="currentColor"/>
|
|
9
3
|
</svg>
|
|
@@ -78,6 +78,15 @@ const mdComponents: Partial<Components> = {
|
|
|
78
78
|
},
|
|
79
79
|
p({ children }) {
|
|
80
80
|
return <span className='my-3 inline-block'>{children}</span>
|
|
81
|
+
},
|
|
82
|
+
ul({ children }) {
|
|
83
|
+
return <ul className='my-3 list-inside list-disc'>{children}</ul>
|
|
84
|
+
},
|
|
85
|
+
ol({ children }) {
|
|
86
|
+
return <ol className='my-3 list-inside list-decimal'>{children}</ol>
|
|
87
|
+
},
|
|
88
|
+
li({ children }) {
|
|
89
|
+
return <li className='[&:not(:last-child)]:mb-1'>{children}</li>
|
|
81
90
|
}
|
|
82
91
|
}
|
|
83
92
|
|
package/src/lib/hooks/index.ts
CHANGED
package/src/main/main.tsx
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import '@/config/styles/index.css'
|
|
2
2
|
|
|
3
3
|
import { ErrorBoundary, GenericError } from '@/src/lib/components/errors'
|
|
4
|
-
import { useDefaultId } from '@/src/lib/hooks'
|
|
5
4
|
import { useAppLang } from '../config/i18n'
|
|
6
5
|
import { GlobalProviders } from '../modules/global-providers'
|
|
7
6
|
import { WidgetContainer } from '../modules/widget'
|
|
8
|
-
import { useInitWidget } from '../modules/widget/hooks'
|
|
7
|
+
import { useInitWidget, useListenToThemeChangeEvent } from '../modules/widget/hooks'
|
|
9
8
|
import type { WidgetSettingProps } from '../types'
|
|
10
9
|
|
|
11
10
|
export type MainProps = {
|
|
@@ -14,8 +13,8 @@ export type MainProps = {
|
|
|
14
13
|
|
|
15
14
|
function Main({ settings }: MainProps) {
|
|
16
15
|
const { completeSetup } = useInitWidget(settings)
|
|
17
|
-
useDefaultId()
|
|
18
16
|
useAppLang(settings.locale)
|
|
17
|
+
useListenToThemeChangeEvent()
|
|
19
18
|
|
|
20
19
|
return (
|
|
21
20
|
<ErrorBoundary fallback={<GenericError />}>
|
|
@@ -17,7 +17,7 @@ function MessageItem({ message }: { message: ParsedMessage }) {
|
|
|
17
17
|
'max-w-[min(80%,52rem)] overflow-x-hidden rounded-lg px-3 text-sm/normal text-neutral-900',
|
|
18
18
|
{
|
|
19
19
|
'self-end bg-neutral-200': message.metadata.author === 'user',
|
|
20
|
-
'bg-
|
|
20
|
+
'border border-neutral-300 bg-neutral-100': message.metadata.author === 'ai'
|
|
21
21
|
}
|
|
22
22
|
)}>
|
|
23
23
|
<MarkdownRenderer content={message?.text ?? message?.name} imgComponent={imgComponent} />
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useMemo } from 'react'
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
|
2
2
|
import type { InfiniteData } from '@tanstack/react-query'
|
|
3
3
|
import { useQueryClient } from '@tanstack/react-query'
|
|
4
4
|
import { produce } from 'immer'
|
|
@@ -18,6 +18,7 @@ const useSubscribeMessageReceivedEvent = () => {
|
|
|
18
18
|
const [, setWidgetLoading] = useWidgetLoadingAtom()
|
|
19
19
|
const [, addUnreadMessagesToSet] = useUnreadMessagesSetAtom()
|
|
20
20
|
const useUpdateCursorMutation = useUpdateCursor()
|
|
21
|
+
const idsList = useRef<Set<string>>(new Set())
|
|
21
22
|
|
|
22
23
|
const conversationId = useMemo(() => String(settings?.conversationId), [settings?.conversationId])
|
|
23
24
|
const profileId = useMemo(() => String(profileQuery?.data?.id), [profileQuery?.data?.id])
|
|
@@ -28,8 +29,10 @@ const useSubscribeMessageReceivedEvent = () => {
|
|
|
28
29
|
enabled: true
|
|
29
30
|
})
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
const execute = useCallback(() => {
|
|
32
33
|
const messageReceived = (data: IMessageWithSenderData) => {
|
|
34
|
+
if (idsList.current.has(data.id)) return
|
|
35
|
+
|
|
33
36
|
const queryKey = query.queryKey
|
|
34
37
|
|
|
35
38
|
if (!queryKey || !queryClient) return
|
|
@@ -71,6 +74,7 @@ const useSubscribeMessageReceivedEvent = () => {
|
|
|
71
74
|
// The cursor should update only with my messages
|
|
72
75
|
useUpdateCursorMutation.mutate(data.conversationId)
|
|
73
76
|
}
|
|
77
|
+
idsList.current.add(data.id)
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
SparkieService.subscribeEvents({
|
|
@@ -90,6 +94,10 @@ const useSubscribeMessageReceivedEvent = () => {
|
|
|
90
94
|
setWidgetLoading,
|
|
91
95
|
useUpdateCursorMutation
|
|
92
96
|
])
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
execute()
|
|
100
|
+
}, [execute])
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
export default useSubscribeMessageReceivedEvent
|
|
@@ -47,11 +47,7 @@ function ChatPage() {
|
|
|
47
47
|
}>
|
|
48
48
|
<>
|
|
49
49
|
<div className='mt-4 px-6 py-4'>
|
|
50
|
-
<WidgetHeader
|
|
51
|
-
enabledButtons={['info', 'close']}
|
|
52
|
-
clubName={settings?.clubName}
|
|
53
|
-
tutorName={settings?.tutorName}
|
|
54
|
-
/>
|
|
50
|
+
<WidgetHeader enabledButtons={['info', 'close']} tutorName={settings?.tutorName} />
|
|
55
51
|
</div>
|
|
56
52
|
<MessagesList />
|
|
57
53
|
</>
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { ChatPage } from './chat-page'
|
|
2
2
|
import { WidgetInformationPage } from './information-page'
|
|
3
3
|
import { WidgetLoadingPage } from './loading-page'
|
|
4
|
-
import { WidgetOnboardingPage } from './onboarding-page'
|
|
5
4
|
import { WidgetStarterPage } from './starter-page'
|
|
6
5
|
|
|
7
6
|
export const WIDGET_TABS = {
|
|
8
|
-
onboarding: <WidgetOnboardingPage />,
|
|
9
7
|
starter: <WidgetStarterPage />,
|
|
10
8
|
chat: <ChatPage />,
|
|
11
9
|
loading: <WidgetLoadingPage />,
|
|
@@ -1,31 +1,41 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
2
|
import { useTranslation } from 'react-i18next'
|
|
3
3
|
|
|
4
|
-
import { AIAvatar } from '../ai-avatar'
|
|
5
|
-
|
|
6
|
-
import styles from './styles.module.css'
|
|
7
|
-
|
|
8
4
|
export type GreetingsCardProps = {
|
|
9
5
|
tutorName: string
|
|
10
6
|
author: string
|
|
7
|
+
isDarkTheme?: boolean
|
|
11
8
|
}
|
|
12
9
|
|
|
13
|
-
function GreetingsCard({ author, tutorName }: GreetingsCardProps) {
|
|
10
|
+
function GreetingsCard({ author, tutorName, isDarkTheme = false }: GreetingsCardProps) {
|
|
14
11
|
const { t } = useTranslation()
|
|
15
12
|
|
|
16
13
|
return (
|
|
17
|
-
<div className='flex flex-col items-center justify-center
|
|
14
|
+
<div className='flex flex-col items-center justify-center'>
|
|
18
15
|
<div className='flex flex-col items-center justify-center gap-4 text-center'>
|
|
19
|
-
<AIAvatar />
|
|
20
16
|
<div className='flex flex-col gap-2'>
|
|
21
|
-
<span
|
|
17
|
+
<span
|
|
18
|
+
className={clsx('text-base font-light', {
|
|
19
|
+
'text-white': isDarkTheme,
|
|
20
|
+
'text-gray-900': !isDarkTheme
|
|
21
|
+
})}>
|
|
22
22
|
{t('general.greetings.hello', { name: author })}
|
|
23
23
|
</span>
|
|
24
|
-
<h3
|
|
24
|
+
<h3
|
|
25
|
+
className={clsx('text-xl font-bold', {
|
|
26
|
+
'text-white': isDarkTheme,
|
|
27
|
+
'text-gray-900': !isDarkTheme
|
|
28
|
+
})}>
|
|
25
29
|
{t('general.greetings.firstMessage', { tutorName })}
|
|
26
30
|
</h3>
|
|
27
31
|
</div>
|
|
28
|
-
<p
|
|
32
|
+
<p
|
|
33
|
+
className={clsx('text-sm font-normal', {
|
|
34
|
+
'text-gray-400': isDarkTheme,
|
|
35
|
+
'text-neutral-600': !isDarkTheme
|
|
36
|
+
})}>
|
|
37
|
+
{t('general.greetings.description')}
|
|
38
|
+
</p>
|
|
29
39
|
</div>
|
|
30
40
|
</div>
|
|
31
41
|
)
|
|
@@ -5,7 +5,6 @@ class WidgetHeaderPropsBuilder implements WidgetHeaderProps {
|
|
|
5
5
|
enabledButtons: ValidIconNames[]
|
|
6
6
|
showContent?: boolean
|
|
7
7
|
showContentWithoutMeta?: boolean
|
|
8
|
-
clubName?: string
|
|
9
8
|
tutorName?: string
|
|
10
9
|
|
|
11
10
|
constructor() {
|
|
@@ -30,12 +29,6 @@ class WidgetHeaderPropsBuilder implements WidgetHeaderProps {
|
|
|
30
29
|
return this
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
withClubName(clubName: typeof this.clubName) {
|
|
34
|
-
this.clubName = clubName
|
|
35
|
-
|
|
36
|
-
return this
|
|
37
|
-
}
|
|
38
|
-
|
|
39
32
|
withTutorName(tutorName: typeof this.tutorName) {
|
|
40
33
|
this.tutorName = tutorName
|
|
41
34
|
|
|
@@ -5,13 +5,12 @@ import { AIAvatar } from '../ai-avatar'
|
|
|
5
5
|
|
|
6
6
|
import type { WidgetHeaderContentProps, WidgetHeaderProps } from './types'
|
|
7
7
|
|
|
8
|
-
export function WidgetHeaderContent({
|
|
8
|
+
export function WidgetHeaderContent({ tutorName }: WidgetHeaderContentProps) {
|
|
9
9
|
return (
|
|
10
10
|
<div className='flex w-full gap-2'>
|
|
11
11
|
<AIAvatar />
|
|
12
|
-
<div className='flex flex-col'>
|
|
13
|
-
{tutorName && <h4 className='text-base'>{tutorName}</h4>}
|
|
14
|
-
{clubName && <p className='text-sm/normal text-neutral-600'>{clubName}</p>}
|
|
12
|
+
<div className='flex flex-col justify-center'>
|
|
13
|
+
{tutorName && <h4 className='text-base font-bold'>{tutorName}</h4>}
|
|
15
14
|
</div>
|
|
16
15
|
</div>
|
|
17
16
|
)
|
|
@@ -32,32 +31,40 @@ export function WidgetHeaderContentWithoutMeta({ name }: { name?: string }) {
|
|
|
32
31
|
|
|
33
32
|
function WidgetHeader({
|
|
34
33
|
enabledButtons,
|
|
35
|
-
clubName,
|
|
36
34
|
tutorName,
|
|
37
35
|
showContentWithoutMeta,
|
|
38
36
|
showContent = true
|
|
39
37
|
}: WidgetHeaderProps) {
|
|
40
|
-
const [,
|
|
38
|
+
const [, setTab] = useWidgetTabsAtom()
|
|
39
|
+
|
|
40
|
+
const handleClickArchive = () => {
|
|
41
|
+
setTab('chat')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const handleClickInfo = () => {
|
|
45
|
+
setTab('information')
|
|
46
|
+
}
|
|
41
47
|
|
|
42
48
|
return (
|
|
43
49
|
<div className='grid-areas-[a_b] mt-0.5 grid grid-cols-[1fr_auto] items-center text-neutral-1000'>
|
|
44
50
|
<div className='grid-area-[a]'>
|
|
45
|
-
{showContent && !showContentWithoutMeta &&
|
|
46
|
-
<WidgetHeaderContent clubName={clubName} tutorName={tutorName} />
|
|
47
|
-
)}
|
|
51
|
+
{showContent && !showContentWithoutMeta && <WidgetHeaderContent tutorName={tutorName} />}
|
|
48
52
|
{showContentWithoutMeta && !showContent && (
|
|
49
53
|
<WidgetHeaderContentWithoutMeta name={tutorName} />
|
|
50
54
|
)}
|
|
51
55
|
</div>
|
|
52
56
|
<div className='shrink-0'>
|
|
53
57
|
<div className='grid-area-[b] ml-auto flex max-w-max gap-3 text-neutral-700'>
|
|
54
|
-
<Button
|
|
58
|
+
<Button
|
|
59
|
+
show={enabledButtons.includes('archive')}
|
|
60
|
+
onClick={handleClickArchive}
|
|
61
|
+
aria-label='Archive Icon'>
|
|
55
62
|
<Icon name='archive' className='h-4 w-4' aria-hidden />
|
|
56
63
|
</Button>
|
|
57
64
|
<Button
|
|
58
65
|
show={enabledButtons.includes('info')}
|
|
59
66
|
aria-label='Info Icon'
|
|
60
|
-
onClick={
|
|
67
|
+
onClick={handleClickInfo}>
|
|
61
68
|
<Icon name='info' className='h-4 w-4' aria-hidden />
|
|
62
69
|
</Button>
|
|
63
70
|
<Button
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
2
2
|
|
|
3
|
-
export type WidgetHeaderContentProps = {
|
|
3
|
+
export type WidgetHeaderContentProps = { tutorName?: string }
|
|
4
4
|
|
|
5
5
|
export type WidgetHeaderProps = {
|
|
6
6
|
enabledButtons: ValidIconNames[]
|
|
@@ -12,6 +12,10 @@ export const infoItems: InfoItem[] = [
|
|
|
12
12
|
titleKey: 'info.what_it_does_question',
|
|
13
13
|
descKey: 'info.what_it_does_answer'
|
|
14
14
|
},
|
|
15
|
-
{
|
|
16
|
-
|
|
15
|
+
{
|
|
16
|
+
icon: 'gallery',
|
|
17
|
+
titleKey: 'info.how_it_learns_question',
|
|
18
|
+
descKey: 'info.how_it_learns_answer'
|
|
19
|
+
},
|
|
20
|
+
{ icon: 'info', titleKey: 'info.limitations_question', descKey: 'info.limitations_answer' }
|
|
17
21
|
]
|
package/src/modules/widget/components/information-page/information-card/information-card.tsx
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
|
|
1
3
|
import { Icon } from '@/src/lib/components'
|
|
2
4
|
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
3
5
|
|
|
@@ -5,18 +7,33 @@ export type InformationCardProps = {
|
|
|
5
7
|
icon: ValidIconNames
|
|
6
8
|
title: string
|
|
7
9
|
description: string
|
|
10
|
+
isDarkMode: boolean
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
function InformationCard({ icon, title, description }: InformationCardProps) {
|
|
13
|
+
function InformationCard({ icon, title, description, isDarkMode }: InformationCardProps) {
|
|
11
14
|
return (
|
|
12
|
-
<div
|
|
13
|
-
|
|
15
|
+
<div
|
|
16
|
+
className={clsx('flex justify-center gap-3 border-b pb-5 last:border-none', {
|
|
17
|
+
'border-white/10': isDarkMode,
|
|
18
|
+
'border-black/10': !isDarkMode
|
|
19
|
+
})}>
|
|
20
|
+
<div
|
|
21
|
+
className={clsx('flex h-5 w-5 items-start justify-center', {
|
|
22
|
+
'text-white': isDarkMode,
|
|
23
|
+
'text-neutral-900': !isDarkMode
|
|
24
|
+
})}>
|
|
14
25
|
<Icon name={icon} width={16} height={16} />
|
|
15
26
|
</div>
|
|
16
27
|
|
|
17
28
|
<div className='flex flex-col gap-1'>
|
|
18
|
-
<p className='text-sm font-bold'>{title}</p>
|
|
19
|
-
<p
|
|
29
|
+
<p className='text-sm font-bold text-neutral-900'>{title}</p>
|
|
30
|
+
<p
|
|
31
|
+
className={clsx('text-xs', {
|
|
32
|
+
'text-gray-300': isDarkMode,
|
|
33
|
+
'text-gray-500': !isDarkMode
|
|
34
|
+
})}>
|
|
35
|
+
{description}
|
|
36
|
+
</p>
|
|
20
37
|
</div>
|
|
21
38
|
</div>
|
|
22
39
|
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
1
2
|
import { useTranslation } from 'react-i18next'
|
|
2
3
|
|
|
3
4
|
import { Icon } from '@/src/lib/components'
|
|
@@ -12,10 +13,15 @@ function WidgetInformationPage() {
|
|
|
12
13
|
const { t } = useTranslation()
|
|
13
14
|
const [, setWidgetTabs] = useWidgetTabsAtom()
|
|
14
15
|
const [settings] = useWidgetSettingsAtom()
|
|
16
|
+
const isDarkMode = settings?.config?.theme === 'dark'
|
|
15
17
|
|
|
16
18
|
return (
|
|
17
19
|
<PageLayout className='p-5 text-white'>
|
|
18
|
-
<div
|
|
20
|
+
<div
|
|
21
|
+
className={clsx('relative mb-8 flex h-12 items-center justify-center', {
|
|
22
|
+
'text-white': isDarkMode,
|
|
23
|
+
'text-neutral-700': !isDarkMode
|
|
24
|
+
})}>
|
|
19
25
|
<button
|
|
20
26
|
className='absolute left-0'
|
|
21
27
|
aria-label='Return Button'
|
|
@@ -25,11 +31,17 @@ function WidgetInformationPage() {
|
|
|
25
31
|
<h1 className='mx-auto font-bold'>{t('info.title')}</h1>
|
|
26
32
|
</div>
|
|
27
33
|
|
|
28
|
-
<div className='mb-
|
|
34
|
+
<div className='mb-10 flex justify-center'>
|
|
29
35
|
<div className='flex flex-col items-center gap-2'>
|
|
30
36
|
<AIAvatar />
|
|
31
37
|
|
|
32
|
-
<h3
|
|
38
|
+
<h3
|
|
39
|
+
className={clsx('font-bold', {
|
|
40
|
+
'text-white': isDarkMode,
|
|
41
|
+
'text-neutral-700': !isDarkMode
|
|
42
|
+
})}>
|
|
43
|
+
{settings?.tutorName ?? t('general.name')}
|
|
44
|
+
</h3>
|
|
33
45
|
</div>
|
|
34
46
|
</div>
|
|
35
47
|
|
|
@@ -40,6 +52,7 @@ function WidgetInformationPage() {
|
|
|
40
52
|
icon={item.icon}
|
|
41
53
|
title={t(item.titleKey)}
|
|
42
54
|
description={t(item.descKey)}
|
|
55
|
+
isDarkMode={isDarkMode}
|
|
43
56
|
/>
|
|
44
57
|
))}
|
|
45
58
|
</div>
|
|
@@ -6,13 +6,12 @@ import {
|
|
|
6
6
|
MessageSkeleton,
|
|
7
7
|
useChatInputValueAtom
|
|
8
8
|
} from '@/src/modules/messages/components'
|
|
9
|
-
import {
|
|
9
|
+
import { WidgetHeader } from '../header'
|
|
10
10
|
import { PageLayout } from '../page-layout'
|
|
11
11
|
|
|
12
12
|
function WidgetLoadingPage() {
|
|
13
13
|
const chatInputRef = useRef<HTMLTextAreaElement>(null)
|
|
14
14
|
const [, setChatInputValue] = useChatInputValueAtom()
|
|
15
|
-
const settings = useWidgetSettingsAtomValue()
|
|
16
15
|
|
|
17
16
|
const handler = useCallback(
|
|
18
17
|
(e: Event) => {
|
|
@@ -33,11 +32,12 @@ function WidgetLoadingPage() {
|
|
|
33
32
|
return (
|
|
34
33
|
<PageLayout
|
|
35
34
|
asideChild={<ChatInput name='new-chat-msg-input' ref={chatInputRef} loading={true} />}>
|
|
36
|
-
|
|
37
|
-
<
|
|
35
|
+
<div className='mt-4 flex h-full flex-col justify-start px-6 py-4'>
|
|
36
|
+
<WidgetHeader enabledButtons={['close']} showContent={false} />
|
|
37
|
+
<div className='mt-auto'>
|
|
38
38
|
<MessageSkeleton />
|
|
39
39
|
</div>
|
|
40
|
-
|
|
40
|
+
</div>
|
|
41
41
|
</PageLayout>
|
|
42
42
|
)
|
|
43
43
|
}
|
|
@@ -33,41 +33,52 @@ function WidgetStarterPage() {
|
|
|
33
33
|
}
|
|
34
34
|
})
|
|
35
35
|
|
|
36
|
-
const
|
|
37
|
-
const textContent = e?.currentTarget?.querySelector(
|
|
38
|
-
'span[data-label=text-content]'
|
|
39
|
-
)?.textContent
|
|
40
|
-
|
|
36
|
+
const sendText = (textContent?: string | null) => {
|
|
41
37
|
if (!textContent) return
|
|
42
38
|
|
|
43
39
|
sendTextMessageMutation.mutate(textContent, {
|
|
44
40
|
onSuccess() {
|
|
45
41
|
setWidgetTabs('chat')
|
|
42
|
+
if (chatInputRef.current) chatInputRef.current.value = ''
|
|
43
|
+
setChatInputValue('')
|
|
46
44
|
}
|
|
47
45
|
})
|
|
48
46
|
}
|
|
49
47
|
|
|
48
|
+
const handleAskQuestion: MouseEventHandler<HTMLButtonElement> = (e) => {
|
|
49
|
+
sendText(e?.currentTarget?.querySelector('span[data-label=text-content]')?.textContent)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const handleSend = () => {
|
|
53
|
+
sendText(chatInputRef.current?.value)
|
|
54
|
+
}
|
|
55
|
+
|
|
50
56
|
return (
|
|
51
57
|
<PageLayout
|
|
52
58
|
asideChild={
|
|
53
59
|
<ChatInput
|
|
54
60
|
name='new-chat-msg-input'
|
|
55
61
|
ref={chatInputRef}
|
|
56
|
-
onSend={
|
|
62
|
+
onSend={handleSend}
|
|
57
63
|
buttonDisabled={!chatInputValue.trim()}
|
|
58
64
|
/>
|
|
59
65
|
}>
|
|
60
66
|
<div className='grid-areas-[a_b] grid h-full grid-cols-1 grid-rows-[1fr_auto]'>
|
|
61
|
-
<div
|
|
67
|
+
<div
|
|
68
|
+
className={clsx('grid-area-[a] flex min-h-0 flex-col px-5 py-4', {
|
|
69
|
+
[styles.bg]: settings?.config?.theme === 'dark'
|
|
70
|
+
})}>
|
|
62
71
|
<WidgetHeader
|
|
63
72
|
enabledButtons={['archive', 'info', 'close']}
|
|
64
|
-
clubName={settings?.clubName}
|
|
65
73
|
tutorName={settings?.tutorName}
|
|
66
|
-
showContent={false}
|
|
67
74
|
/>
|
|
68
75
|
|
|
69
76
|
<div className='my-auto'>
|
|
70
|
-
<GreetingsCard
|
|
77
|
+
<GreetingsCard
|
|
78
|
+
author={settings?.author ?? ''}
|
|
79
|
+
tutorName={settings?.tutorName ?? ''}
|
|
80
|
+
isDarkTheme={settings?.config?.theme === 'dark'}
|
|
81
|
+
/>
|
|
71
82
|
</div>
|
|
72
83
|
</div>
|
|
73
84
|
<div className='grid-area-[b] mx-5 my-6 flex flex-shrink-0 snap-x snap-mandatory gap-2 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'>
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
+
import type { Theme } from '@/src/types'
|
|
2
|
+
|
|
1
3
|
import type { ITutorWidgetEvent } from './types'
|
|
2
4
|
|
|
3
5
|
export const TutorWidgetEventTypes = {
|
|
4
6
|
OPEN: 'c3po-app-widget-open',
|
|
5
7
|
CLOSE: 'c3po-app-widget-close',
|
|
6
8
|
HIDE: 'c3po-app-widget-hide',
|
|
7
|
-
LOADED: 'tutor-app-widget-loaded'
|
|
9
|
+
LOADED: 'tutor-app-widget-loaded',
|
|
10
|
+
THEME_CHANGE: 'c3po-app-widget-theme-change'
|
|
8
11
|
} as const
|
|
9
12
|
|
|
10
|
-
const
|
|
13
|
+
export const TutorWidgetEvents = {
|
|
11
14
|
[TutorWidgetEventTypes.OPEN]: {
|
|
12
15
|
name: TutorWidgetEventTypes.OPEN,
|
|
13
16
|
handler: (callback) => {
|
|
@@ -73,10 +76,26 @@ const TutorWidgetEventsObject = {
|
|
|
73
76
|
dispatch: (payload = { detail: { isSuccess: true } }) => {
|
|
74
77
|
window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.LOADED, payload))
|
|
75
78
|
}
|
|
76
|
-
} as ITutorWidgetEvent<{ isSuccess: boolean }
|
|
77
|
-
|
|
79
|
+
} as ITutorWidgetEvent<{ isSuccess: boolean }>,
|
|
80
|
+
|
|
81
|
+
[TutorWidgetEventTypes.THEME_CHANGE]: {
|
|
82
|
+
name: TutorWidgetEventTypes.THEME_CHANGE,
|
|
83
|
+
handler: (callback: (payload: { theme: Theme }) => void) => {
|
|
84
|
+
const listener: EventListener = (e) => {
|
|
85
|
+
const evt = e as CustomEvent<{ theme: Theme }>
|
|
86
|
+
callback(evt.detail)
|
|
87
|
+
}
|
|
88
|
+
window.addEventListener(TutorWidgetEventTypes.THEME_CHANGE, listener)
|
|
78
89
|
|
|
79
|
-
|
|
90
|
+
return () => {
|
|
91
|
+
window.removeEventListener(TutorWidgetEventTypes.THEME_CHANGE, listener)
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
dispatch: (payload) => {
|
|
95
|
+
window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.THEME_CHANGE, payload))
|
|
96
|
+
}
|
|
97
|
+
} as ITutorWidgetEvent<{ theme: Theme }>
|
|
98
|
+
} as const
|
|
80
99
|
|
|
81
100
|
export const ACTION_EVENTS = {
|
|
82
101
|
SCROLL: 'c3po-app-widget-scroll-to-bottom'
|
|
@@ -2,14 +2,12 @@ import { useEffect, useState } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import { initDayjs } from '@/src/config/dayjs'
|
|
4
4
|
import { initAxios } from '@/src/config/request/api'
|
|
5
|
-
import { initTheme } from '@/src/config/theme'
|
|
6
5
|
import { SparkieService } from '@/src/modules/sparkie'
|
|
7
6
|
import type { WidgetSettingProps } from '@/src/types'
|
|
8
7
|
import { TutorWidgetEvents } from '../../events'
|
|
9
8
|
|
|
10
9
|
const init = async (settings: WidgetSettingProps) => {
|
|
11
10
|
try {
|
|
12
|
-
initTheme(settings.config?.theme)
|
|
13
11
|
initAxios(settings.hotmartToken)
|
|
14
12
|
await initDayjs(settings.locale)
|
|
15
13
|
await SparkieService.initSparkie({
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react'
|
|
2
|
+
import { produce } from 'immer'
|
|
3
|
+
|
|
4
|
+
import { initTheme } from '@/src/config/theme'
|
|
5
|
+
import { TutorWidgetEvents } from '../../events'
|
|
6
|
+
import { useWidgetSettingsAtom } from '../../store'
|
|
7
|
+
|
|
8
|
+
function useListenToThemeChangeEvent() {
|
|
9
|
+
const [widgetSettings, setWidgetSettings] = useWidgetSettingsAtom()
|
|
10
|
+
|
|
11
|
+
useLayoutEffect(() => {
|
|
12
|
+
const clear = TutorWidgetEvents['c3po-app-widget-theme-change'].handler(({ theme }) => {
|
|
13
|
+
initTheme(theme)
|
|
14
|
+
|
|
15
|
+
if (!widgetSettings || theme === widgetSettings?.config?.theme) return
|
|
16
|
+
|
|
17
|
+
setWidgetSettings(
|
|
18
|
+
produce(widgetSettings, (draft) => {
|
|
19
|
+
draft.config = { ...draft.config, theme }
|
|
20
|
+
|
|
21
|
+
return draft
|
|
22
|
+
})
|
|
23
|
+
)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
return () => clear?.()
|
|
27
|
+
}, [setWidgetSettings, widgetSettings])
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default useListenToThemeChangeEvent
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as useDefaultId } from './use-default-id'
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { useLayoutEffect } from 'react'
|
|
2
|
-
|
|
3
|
-
const useDefaultId = () => {
|
|
4
|
-
useLayoutEffect(() => {
|
|
5
|
-
document.body.setAttribute('id', 'hotmart-app-tutor-ai-consumer-root')
|
|
6
|
-
|
|
7
|
-
return () => {
|
|
8
|
-
document.body.removeAttribute('id')
|
|
9
|
-
}
|
|
10
|
-
})
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default useDefaultId
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as WidgetOnboardingPage } from './onboarding-page'
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
|
-
import { useTranslation } from 'react-i18next'
|
|
3
|
-
|
|
4
|
-
import TutorOnboardingSVG from '@/public/assets/svg/tutor-onboarding.svg?url'
|
|
5
|
-
import { Button } from '@/src/lib/components'
|
|
6
|
-
import { useWidgetTabsAtom } from '../../store'
|
|
7
|
-
import { PageLayout } from '../page-layout'
|
|
8
|
-
|
|
9
|
-
import styles from './styles.module.css'
|
|
10
|
-
|
|
11
|
-
function WidgetOnboardingPage() {
|
|
12
|
-
const [, setWidgetTabs] = useWidgetTabsAtom()
|
|
13
|
-
const { t } = useTranslation()
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<PageLayout>
|
|
17
|
-
<div className={clsx('flex-1', styles.bg)}>
|
|
18
|
-
<div className='mx-4 flex h-full flex-col justify-center gap-6 px-0.5'>
|
|
19
|
-
<div className='mx-auto max-w-[67%]'>
|
|
20
|
-
<img src={TutorOnboardingSVG} aria-hidden />
|
|
21
|
-
</div>
|
|
22
|
-
<div className='flex flex-col gap-2'>
|
|
23
|
-
<h3 className={clsx(styles.gradientTxt, 'text-center text-xl/tight font-semibold')}>
|
|
24
|
-
{t('onboarding.title')}
|
|
25
|
-
</h3>
|
|
26
|
-
<p className='text-center text-sm/snug font-normal text-gray-400'>
|
|
27
|
-
{t('onboarding.description')}
|
|
28
|
-
</p>
|
|
29
|
-
</div>
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
<div className='mx-4 mb-4 mt-auto flex flex-col gap-4'>
|
|
33
|
-
<Button variant='brand' className='flex-1' onClick={() => setWidgetTabs('starter')}>
|
|
34
|
-
{t('general.buttons.start')}
|
|
35
|
-
</Button>
|
|
36
|
-
</div>
|
|
37
|
-
</PageLayout>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default WidgetOnboardingPage
|