app-tutor-ai-consumer 1.17.0 → 1.18.1
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/package.json +1 -1
- package/src/config/theme/init-theme.ts +11 -3
- package/src/index.tsx +7 -2
- package/src/lib/components/icons/book.svg +14 -0
- package/src/lib/components/icons/icon-names.d.ts +3 -0
- package/src/lib/components/icons/interrogation.svg +9 -0
- package/src/lib/components/icons/warning.svg +9 -0
- 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 +3 -3
- package/src/modules/widget/components/greetings-card/greetings-card.tsx +8 -2
- package/src/modules/widget/components/header/__tests__/widget-header-props.builder.ts +0 -7
- package/src/modules/widget/components/header/header.tsx +23 -10
- package/src/modules/widget/components/header/types.ts +1 -1
- package/src/modules/widget/components/information-page/constants.ts +17 -0
- package/src/modules/widget/components/information-page/index.ts +1 -0
- package/src/modules/widget/components/information-page/information-card/index.ts +1 -0
- package/src/modules/widget/components/information-page/information-card/information-card.tsx +25 -0
- package/src/modules/widget/components/information-page/information-page.tsx +50 -0
- package/src/modules/widget/components/loading-page/loading-page.tsx +5 -5
- package/src/modules/widget/components/starter-page/starter-page.tsx +21 -9
- 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/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,12 @@
|
|
|
1
|
+
## [1.18.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.18.0...v1.18.1) (2025-07-24)
|
|
2
|
+
|
|
3
|
+
# [1.18.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.17.0...v1.18.0) (2025-07-24)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- add info button onclick ([84dda83](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/84dda83f12399fc785fecae8927a1404cb1d2dae))
|
|
8
|
+
- add information page ([20996ab](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/20996ab224d07e6a44cf27037f7b35b7373aa307))
|
|
9
|
+
|
|
1
10
|
# [1.17.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.16.0...v1.17.0) (2025-07-22)
|
|
2
11
|
|
|
3
12
|
### 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,14 @@
|
|
|
1
|
+
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_18455_21368)">
|
|
3
|
+
<path d="M15.75 14.2358H4.5C4.08579 14.2358 3.75 14.5716 3.75 14.9858C3.75 15.4001 4.08579 15.7358 4.5 15.7358H15.75V17.2358H4.5C3.25736 17.2358 2.25 16.2285 2.25 14.9858V3.73584C2.25 2.90741 2.92157 2.23584 3.75 2.23584H15.75V14.2358ZM3.75 12.7733C3.87117 12.7487 3.99658 12.7358 4.125 12.7358H14.25V3.73584H3.75V12.7733ZM12 7.48584H6V5.98584H12V7.48584Z" fill="url(#paint0_linear_18455_21368)"/>
|
|
4
|
+
</g>
|
|
5
|
+
<defs>
|
|
6
|
+
<linearGradient id="paint0_linear_18455_21368" x1="2.25" y1="9.73584" x2="15.75" y2="9.73584" gradientUnits="userSpaceOnUse">
|
|
7
|
+
<stop stop-color="#44D0FF"/>
|
|
8
|
+
<stop offset="1" stop-color="#B48EFF"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<clipPath id="clip0_18455_21368">
|
|
11
|
+
<rect width="18" height="18" fill="white" transform="translate(0 0.73584)"/>
|
|
12
|
+
</clipPath>
|
|
13
|
+
</defs>
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M8 0.73584C3.5625 0.73584 0 4.32959 0 8.73584C0 13.1733 3.5625 16.7358 8 16.7358C12.4062 16.7358 16 13.1733 16 8.73584C16 4.32959 12.4062 0.73584 8 0.73584ZM8 15.7358C4.125 15.7358 1 12.6108 1 8.73584C1 4.89209 4.125 1.73584 8 1.73584C11.8438 1.73584 15 4.89209 15 8.73584C15 12.6108 11.8438 15.7358 8 15.7358ZM7.5 11.4858C7.0625 11.4858 6.75 11.8296 6.75 12.2358C6.75 12.6733 7.0625 12.9858 7.5 12.9858C7.90625 12.9858 8.25 12.6733 8.25 12.2358C8.25 11.8296 7.90625 11.4858 7.5 11.4858ZM8.90625 4.73584H7.0625C5.90625 4.73584 5 5.67334 5 6.82959V7.11084C5 7.39209 5.21875 7.61084 5.5 7.61084C5.75 7.61084 6 7.39209 6 7.11084V6.82959C6 6.23584 6.46875 5.73584 7.0625 5.73584H8.90625C9.5 5.73584 10 6.23584 10 6.82959C10 7.20459 9.78125 7.57959 9.4375 7.76709L7.5625 8.70459C7.21875 8.89209 7 9.26709 7 9.67334V10.2358C7 10.5171 7.21875 10.7358 7.5 10.7358C7.75 10.7358 8 10.5171 8 10.2358V9.67334C8 9.64209 8 9.61084 8.03125 9.57959L9.90625 8.64209C10.5625 8.26709 11 7.57959 11 6.82959C11 5.67334 10.0625 4.73584 8.90625 4.73584Z" fill="url(#paint0_linear_21475_7939)"/>
|
|
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>
|
|
9
|
+
</svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M8 11.3193C7.5625 11.3193 7.25 11.6631 7.25 12.0693C7.25 12.5068 7.59375 12.8193 8 12.8193C8.40625 12.8193 8.71875 12.5068 8.71875 12.0693C8.75 11.6631 8.40625 11.3193 8 11.3193ZM8 10.0693C8.25 10.0693 8.46875 9.85059 8.46875 9.56934V5.06934C8.46875 4.81934 8.21875 4.56934 8 4.56934C7.75 4.56934 7.5 4.81934 7.5 5.06934V9.56934C7.5 9.85059 7.71875 10.0693 8 10.0693ZM15.75 12.5068L9.5 1.94434C9.1875 1.41309 8.625 1.10059 8 1.06934C7.34375 1.06934 6.78125 1.41309 6.46875 1.94434L0.21875 12.5068C-0.09375 13.0381 -0.09375 13.6631 0.21875 14.1943C0.53125 14.7568 1.09375 15.0693 1.75 15.0693H14.25C14.875 15.0693 15.4375 14.7568 15.75 14.1943C16.0625 13.6631 16.0625 13.0381 15.75 12.5068ZM14.875 13.6943C14.75 13.9443 14.5 14.0693 14.2188 14.0693H1.75C1.46875 14.0693 1.21875 13.9443 1.09375 13.6943C0.9375 13.4756 0.96875 13.2256 1.09375 13.0068L7.34375 2.44434C7.46875 2.22559 7.71875 2.06934 8 2.06934C8.25 2.10059 8.5 2.22559 8.625 2.44434L14.875 13.0068C15 13.2256 15.0312 13.4756 14.875 13.6943Z" fill="url(#paint0_linear_21475_9749)"/>
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="paint0_linear_21475_9749" x1="-0.015625" y1="8.06934" x2="15.9844" y2="8.06934" gradientUnits="userSpaceOnUse">
|
|
5
|
+
<stop stop-color="#44D0FF"/>
|
|
6
|
+
<stop offset="1" stop-color="#B48EFF"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
</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,11 @@
|
|
|
1
1
|
import { ChatPage } from './chat-page'
|
|
2
|
+
import { WidgetInformationPage } from './information-page'
|
|
2
3
|
import { WidgetLoadingPage } from './loading-page'
|
|
3
|
-
import { WidgetOnboardingPage } from './onboarding-page'
|
|
4
4
|
import { WidgetStarterPage } from './starter-page'
|
|
5
5
|
|
|
6
6
|
export const WIDGET_TABS = {
|
|
7
|
-
onboarding: <WidgetOnboardingPage />,
|
|
8
7
|
starter: <WidgetStarterPage />,
|
|
9
8
|
chat: <ChatPage />,
|
|
10
|
-
loading: <WidgetLoadingPage
|
|
9
|
+
loading: <WidgetLoadingPage />,
|
|
10
|
+
information: <WidgetInformationPage />
|
|
11
11
|
}
|
|
@@ -8,15 +8,21 @@ import styles from './styles.module.css'
|
|
|
8
8
|
export type GreetingsCardProps = {
|
|
9
9
|
tutorName: string
|
|
10
10
|
author: string
|
|
11
|
+
isDarkTheme?: boolean
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
function GreetingsCard({ author, tutorName }: GreetingsCardProps) {
|
|
14
|
+
function GreetingsCard({ author, tutorName, isDarkTheme = false }: GreetingsCardProps) {
|
|
14
15
|
const { t } = useTranslation()
|
|
15
16
|
|
|
16
17
|
return (
|
|
17
18
|
<div className='flex flex-col items-center justify-center text-neutral-900'>
|
|
18
19
|
<div className='flex flex-col items-center justify-center gap-4 text-center'>
|
|
19
|
-
<AIAvatar
|
|
20
|
+
<AIAvatar
|
|
21
|
+
className={clsx('rounded-full border-4 border-neutral-100', {
|
|
22
|
+
'bg-[#E7EDF2]/80': !isDarkTheme,
|
|
23
|
+
'bg-neutral-200': isDarkTheme
|
|
24
|
+
})}
|
|
25
|
+
/>
|
|
20
26
|
<div className='flex flex-col gap-2'>
|
|
21
27
|
<span className='text-base font-light'>
|
|
22
28
|
{t('general.greetings.hello', { name: author })}
|
|
@@ -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
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { Button, Icon } from '@/src/lib/components'
|
|
2
2
|
import { TutorWidgetEvents } from '../../events'
|
|
3
|
+
import { useWidgetTabsAtom } from '../../store'
|
|
3
4
|
import { AIAvatar } from '../ai-avatar'
|
|
4
5
|
|
|
5
6
|
import type { WidgetHeaderContentProps, WidgetHeaderProps } from './types'
|
|
6
7
|
|
|
7
|
-
export function WidgetHeaderContent({
|
|
8
|
+
export function WidgetHeaderContent({ tutorName }: WidgetHeaderContentProps) {
|
|
8
9
|
return (
|
|
9
10
|
<div className='flex w-full gap-2'>
|
|
10
11
|
<AIAvatar />
|
|
11
|
-
<div className='flex flex-col'>
|
|
12
|
-
{tutorName && <h4 className='text-base'>{tutorName}</h4>}
|
|
13
|
-
{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>}
|
|
14
14
|
</div>
|
|
15
15
|
</div>
|
|
16
16
|
)
|
|
@@ -31,27 +31,40 @@ export function WidgetHeaderContentWithoutMeta({ name }: { name?: string }) {
|
|
|
31
31
|
|
|
32
32
|
function WidgetHeader({
|
|
33
33
|
enabledButtons,
|
|
34
|
-
clubName,
|
|
35
34
|
tutorName,
|
|
36
35
|
showContentWithoutMeta,
|
|
37
36
|
showContent = true
|
|
38
37
|
}: WidgetHeaderProps) {
|
|
38
|
+
const [, setTab] = useWidgetTabsAtom()
|
|
39
|
+
|
|
40
|
+
const handleClickArchive = () => {
|
|
41
|
+
setTab('chat')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const handleClickInfo = () => {
|
|
45
|
+
setTab('information')
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
return (
|
|
40
49
|
<div className='grid-areas-[a_b] mt-0.5 grid grid-cols-[1fr_auto] items-center text-neutral-1000'>
|
|
41
50
|
<div className='grid-area-[a]'>
|
|
42
|
-
{showContent && !showContentWithoutMeta &&
|
|
43
|
-
<WidgetHeaderContent clubName={clubName} tutorName={tutorName} />
|
|
44
|
-
)}
|
|
51
|
+
{showContent && !showContentWithoutMeta && <WidgetHeaderContent tutorName={tutorName} />}
|
|
45
52
|
{showContentWithoutMeta && !showContent && (
|
|
46
53
|
<WidgetHeaderContentWithoutMeta name={tutorName} />
|
|
47
54
|
)}
|
|
48
55
|
</div>
|
|
49
56
|
<div className='shrink-0'>
|
|
50
57
|
<div className='grid-area-[b] ml-auto flex max-w-max gap-3 text-neutral-700'>
|
|
51
|
-
<Button
|
|
58
|
+
<Button
|
|
59
|
+
show={enabledButtons.includes('archive')}
|
|
60
|
+
onClick={handleClickArchive}
|
|
61
|
+
aria-label='Archive Icon'>
|
|
52
62
|
<Icon name='archive' className='h-4 w-4' aria-hidden />
|
|
53
63
|
</Button>
|
|
54
|
-
<Button
|
|
64
|
+
<Button
|
|
65
|
+
show={enabledButtons.includes('info')}
|
|
66
|
+
aria-label='Info Icon'
|
|
67
|
+
onClick={handleClickInfo}>
|
|
55
68
|
<Icon name='info' className='h-4 w-4' aria-hidden />
|
|
56
69
|
</Button>
|
|
57
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[]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
2
|
+
|
|
3
|
+
type InfoItem = {
|
|
4
|
+
icon: ValidIconNames
|
|
5
|
+
titleKey: string
|
|
6
|
+
descKey: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const infoItems: InfoItem[] = [
|
|
10
|
+
{
|
|
11
|
+
icon: 'interrogation',
|
|
12
|
+
titleKey: 'info.what_it_does_question',
|
|
13
|
+
descKey: 'info.what_it_does_answer'
|
|
14
|
+
},
|
|
15
|
+
{ icon: 'book', titleKey: 'info.how_it_learns_question', descKey: 'info.how_it_learns_answer' },
|
|
16
|
+
{ icon: 'warning', titleKey: 'info.limitations_question', descKey: 'info.limitations_answer' }
|
|
17
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as WidgetInformationPage } from './information-page'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as InformationCard } from './information-card'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Icon } from '@/src/lib/components'
|
|
2
|
+
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
3
|
+
|
|
4
|
+
export type InformationCardProps = {
|
|
5
|
+
icon: ValidIconNames
|
|
6
|
+
title: string
|
|
7
|
+
description: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function InformationCard({ icon, title, description }: InformationCardProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className='flex gap-3 border-b border-white/10 pb-5 last:border-none'>
|
|
13
|
+
<div className='flex h-5 w-5 items-start justify-center'>
|
|
14
|
+
<Icon name={icon} width={16} height={16} />
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div className='flex flex-col gap-1'>
|
|
18
|
+
<p className='text-sm font-bold'>{title}</p>
|
|
19
|
+
<p className='text-xs text-gray-300'>{description}</p>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default InformationCard
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next'
|
|
2
|
+
|
|
3
|
+
import { Icon } from '@/src/lib/components'
|
|
4
|
+
import { useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
|
|
5
|
+
import { AIAvatar } from '../ai-avatar'
|
|
6
|
+
import { PageLayout } from '../page-layout'
|
|
7
|
+
|
|
8
|
+
import { infoItems } from './constants'
|
|
9
|
+
import { InformationCard } from './information-card'
|
|
10
|
+
|
|
11
|
+
function WidgetInformationPage() {
|
|
12
|
+
const { t } = useTranslation()
|
|
13
|
+
const [, setWidgetTabs] = useWidgetTabsAtom()
|
|
14
|
+
const [settings] = useWidgetSettingsAtom()
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<PageLayout className='p-5 text-white'>
|
|
18
|
+
<div className='relative mb-8 flex h-12 items-center justify-center'>
|
|
19
|
+
<button
|
|
20
|
+
className='absolute left-0'
|
|
21
|
+
aria-label='Return Button'
|
|
22
|
+
onClick={() => setWidgetTabs('chat')}>
|
|
23
|
+
<Icon name='arrow-left' width={16} height={16} />
|
|
24
|
+
</button>
|
|
25
|
+
<h1 className='mx-auto font-bold'>{t('info.title')}</h1>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div className='mb-8 flex justify-center'>
|
|
29
|
+
<div className='flex flex-col items-center gap-2'>
|
|
30
|
+
<AIAvatar />
|
|
31
|
+
|
|
32
|
+
<h3 className='font-bold'>{settings?.tutorName ?? ''}</h3>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div className='flex flex-col gap-5'>
|
|
37
|
+
{infoItems.map((item) => (
|
|
38
|
+
<InformationCard
|
|
39
|
+
key={item.titleKey}
|
|
40
|
+
icon={item.icon}
|
|
41
|
+
title={t(item.titleKey)}
|
|
42
|
+
description={t(item.descKey)}
|
|
43
|
+
/>
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
</PageLayout>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default WidgetInformationPage
|
|
@@ -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,53 @@ 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
74
|
showContent={false}
|
|
67
75
|
/>
|
|
68
76
|
|
|
69
77
|
<div className='my-auto'>
|
|
70
|
-
<GreetingsCard
|
|
78
|
+
<GreetingsCard
|
|
79
|
+
author={settings?.author ?? ''}
|
|
80
|
+
tutorName={settings?.tutorName ?? ''}
|
|
81
|
+
isDarkTheme={settings?.config?.theme === 'dark'}
|
|
82
|
+
/>
|
|
71
83
|
</div>
|
|
72
84
|
</div>
|
|
73
85
|
<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
|