app-tutor-ai-consumer 1.8.0 → 1.8.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.
Files changed (27) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/config/rspack/rspack.config.js +4 -6
  3. package/config/rspack/utils/plugins.js +2 -2
  4. package/package.json +1 -1
  5. package/public/index.html +1 -1
  6. package/src/development-bootstrap.tsx +16 -1
  7. package/src/index.tsx +21 -11
  8. package/src/lib/contexts/index.ts +1 -0
  9. package/src/lib/contexts/shared-ref/index.ts +1 -0
  10. package/src/lib/contexts/shared-ref/shared-ref.tsx +26 -0
  11. package/src/lib/hooks/use-ref-client-height/use-ref-client-height.tsx +5 -5
  12. package/src/main/main.spec.tsx +6 -3
  13. package/src/modules/global-providers/global-providers.tsx +5 -0
  14. package/src/modules/messages/components/messages-list/messages-list.tsx +12 -19
  15. package/src/modules/sparkie/service.ts +1 -1
  16. package/src/modules/widget/components/chat-page/chat-page.tsx +6 -6
  17. package/src/modules/widget/components/container/container.tsx +2 -4
  18. package/src/modules/widget/components/index.ts +1 -0
  19. package/src/modules/widget/components/onboarding-page/onboarding-page.tsx +4 -3
  20. package/src/modules/widget/components/page-layout/constants.tsx +8 -0
  21. package/src/modules/widget/components/page-layout/index.ts +3 -0
  22. package/src/modules/widget/components/page-layout/page-layout.tsx +42 -0
  23. package/src/modules/widget/components/scroll-to-bottom-button/scroll-to-bottom-button.tsx +3 -4
  24. package/src/modules/widget/components/starter-page/starter-page.tsx +7 -6
  25. package/src/modules/widget/events.ts +31 -6
  26. package/src/modules/widget/store/widget-tabs.atom.ts +2 -2
  27. package/src/modules/widget/types.ts +3 -3
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [1.8.2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.8.1...v1.8.2) (2025-07-16)
2
+
3
+ ## [1.8.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.8.0...v1.8.1) (2025-07-15)
4
+
1
5
  # [1.8.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.7.0...v1.8.0) (2025-07-14)
2
6
 
3
7
  ### Features
@@ -11,6 +11,7 @@ require('dotenv').config({
11
11
  })
12
12
 
13
13
  module.exports = async function (env) {
14
+ const standaloneMode = env?.local
14
15
  const productionMode = env?.production || env?.staging
15
16
  const releaseFullName = `app-tutor-ai-consumer_v${packageJSON.version}`
16
17
  const fileVersion = `${productionMode ? 'prod' : 'dev'}-${packageJSON?.version}`
@@ -23,7 +24,7 @@ module.exports = async function (env) {
23
24
  */
24
25
  const config = {
25
26
  mode: productionMode ? 'production' : 'development',
26
- entry: productionMode ? paths.INDEX : paths.DEV_BOOTSTRAP,
27
+ entry: standaloneMode ? paths.DEV_BOOTSTRAP : paths.INDEX,
27
28
  ...(productionMode ? {} : require('./utils/devserver.config')),
28
29
  optimization: {
29
30
  usedExports: true,
@@ -159,17 +160,14 @@ module.exports = async function (env) {
159
160
  new rspack.DefinePlugin({
160
161
  'process.env.PROJECT_VERSION': JSON.stringify(packageJSON.version),
161
162
  'process.env.TARGET_ENV': JSON.stringify(process.env.NODE_ENV),
162
- 'process.env.RELEASE_FULL_NAME': JSON.stringify(releaseFullName)
163
+ 'process.env.RELEASE_FULL_NAME': JSON.stringify(releaseFullName),
164
+ 'process.env.STANDALONE_MODE': JSON.stringify(standaloneMode)
163
165
  }),
164
166
  new rspack.ProgressPlugin(),
165
167
  new TsCheckerRspackPlugin(),
166
168
  new rspack.HtmlRspackPlugin({
167
169
  template: path.resolve(paths.PUBLIC, 'index.html'),
168
170
  favicon: path.resolve(paths.PUBLIC, 'favicon.ico')
169
- }),
170
- new rspack.CssExtractRspackPlugin({
171
- filename: productionMode ? `app-tutor-ai-consumer.css` : '[name].[contenthash].css',
172
- chunkFilename: productionMode ? '[id].[contenthash].css' : '[id].[contenthash].css'
173
171
  })
174
172
  ].concat(await getEnvironmentPlugins(!productionMode)),
175
173
  watchOptions: {
@@ -27,8 +27,8 @@ async function getEnvironmentPlugins(isDevelopment) {
27
27
  return [
28
28
  new rspack.EnvironmentPlugin(allEnvs),
29
29
  new rspack.CssExtractRspackPlugin({
30
- filename: '[name].[contenthash].css',
31
- chunkFilename: '[id]-app-tutor-ai-consumer.css'
30
+ filename: 'app-tutor-ai-consumer.css',
31
+ chunkFilename: '[id].[contenthash].css'
32
32
  }),
33
33
  new CompressionPlugin({
34
34
  algorithm: 'gzip'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-tutor-ai-consumer",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
package/public/index.html CHANGED
@@ -12,6 +12,6 @@
12
12
  </head>
13
13
 
14
14
  <body class="bg-ai-dark">
15
- <div id="root"></div>
15
+ <div id="c3po-app-widget"></div>
16
16
  </body>
17
17
  </html>
@@ -5,10 +5,25 @@ import { v4 } from 'uuid'
5
5
  import { LANGUAGES } from './config/i18n'
6
6
  import { devMode } from './lib/utils'
7
7
 
8
+ const rootId = 'app-tutor-ai-widget'
9
+
8
10
  if (devMode) {
9
11
  window.TOKEN = process.env.TOKEN ?? ''
10
12
  void (async () => {
11
- await window.startChatWidget('root', {
13
+ // TODO: Remove after sidebar implementation
14
+ // Add Local popup css config
15
+ const root = document.getElementById('c3po-app-widget')
16
+ const container = document.createElement('div')
17
+
18
+ container.setAttribute('id', rootId)
19
+ container.setAttribute(
20
+ 'class',
21
+ 'bg-ai-dark fixed bottom-5 right-5 w-[27rem] h-[min(43rem,calc(100vh-2.5rem))] max-h-[calc(100vh-2.5rem)] z-10 rounded-[0.625rem] border border-neutral-800 shadow-lg overflow-hidden flex flex-col'
22
+ )
23
+
24
+ root?.appendChild(container)
25
+
26
+ await window.startChatWidget(rootId, {
12
27
  hotmartToken: window.TOKEN,
13
28
  locale: LANGUAGES.PT_BR,
14
29
  conversationId: '21506473-a93c-4b38-9c32-68a5ca37ce73', // OWNER
package/src/index.tsx CHANGED
@@ -7,7 +7,7 @@ import { createRoot } from 'react-dom/client'
7
7
  import { initDayjs } from './config/dayjs'
8
8
  import { initLanguage } from './config/i18n'
9
9
  import { initAxios } from './config/request/api'
10
- import { productionMode } from './lib/utils'
10
+ import { devMode, productionMode } from './lib/utils'
11
11
  import { Main } from './main'
12
12
  import { SparkieService } from './modules/sparkie'
13
13
  import { TutorWidgetEvents, TutorWidgetEventTypes } from './modules/widget'
@@ -33,24 +33,34 @@ window.startChatWidget = async (
33
33
  elementId = 'tutor-chat-app-widget',
34
34
  settings: WidgetSettingProps
35
35
  ) => {
36
- loadMainStyles()
36
+ if (!devMode) {
37
+ loadMainStyles()
38
+ }
37
39
 
38
40
  const rootElement = document.getElementById(elementId) as HTMLElement
39
41
  const root = createRoot(rootElement)
40
42
 
41
- await SparkieService.initSparkie({
42
- token: settings.hotmartToken,
43
- skipPresenceSetup: true,
44
- retryOptions: {
45
- maxRetries: 5,
46
- retryDelay: 2000,
47
- backoffMultiplier: 1.5
48
- }
49
- })
50
43
  initAxios(settings.hotmartToken)
51
44
  await initLanguage(settings.locale)
52
45
  await initDayjs(settings.locale)
53
46
 
47
+ try {
48
+ await SparkieService.initSparkie({
49
+ token: settings?.hotmartToken,
50
+ skipPresenceSetup: true,
51
+ retryOptions: {
52
+ maxRetries: 5,
53
+ retryDelay: 2000,
54
+ backoffMultiplier: 1.5
55
+ }
56
+ })
57
+ await SparkieService.ensureInitialized()
58
+ TutorWidgetEvents.get(TutorWidgetEventTypes.LOADED)?.dispatch()
59
+ } catch (error) {
60
+ console.error(error)
61
+ TutorWidgetEvents.get(TutorWidgetEventTypes.LOADED)?.dispatch({ detail: { isSuccess: false } })
62
+ }
63
+
54
64
  if (root)
55
65
  root.render(
56
66
  <StrictMode>
@@ -0,0 +1 @@
1
+ export * from './shared-ref'
@@ -0,0 +1 @@
1
+ export { default as createSharedRefContext } from './shared-ref'
@@ -0,0 +1,26 @@
1
+ import { createContext, useContext, useRef } from 'react'
2
+ import type { PropsWithChildren, RefObject } from 'react'
3
+
4
+ function createSharedRefContext<T extends HTMLElement>() {
5
+ const SharedRefContext = createContext<RefObject<T | null> | null>(null)
6
+
7
+ const SharedRefContextProvider = ({ children }: PropsWithChildren) => {
8
+ const sharedRef = useRef<T>(null)
9
+ return <SharedRefContext.Provider value={sharedRef}>{children}</SharedRefContext.Provider>
10
+ }
11
+
12
+ const useSharedRefContext = () => {
13
+ const ctx = useContext(SharedRefContext)
14
+
15
+ if (!ctx) throw new Error('SharedRefContext must be used inside a SharedRefContextProvider')
16
+ return ctx
17
+ }
18
+
19
+ return {
20
+ SharedRefContext,
21
+ SharedRefContextProvider,
22
+ useSharedRefContext
23
+ }
24
+ }
25
+
26
+ export default createSharedRefContext
@@ -1,18 +1,18 @@
1
- import { type RefObject, useEffect, useState } from 'react'
1
+ import { type RefObject, useEffect, useMemo, useState } from 'react'
2
2
 
3
3
  import { useThrottle } from '@/src/lib/hooks'
4
4
 
5
5
  function useRefClientHeight<T extends HTMLElement>(
6
6
  refElement: RefObject<T | null>,
7
- defaultHeight = '100vh'
7
+ defaultHeight = 0
8
8
  ) {
9
- const [clientHeight, setClientHeight] = useState(defaultHeight)
9
+ const [clientHeight, setClientHeight] = useState<number>(defaultHeight)
10
10
 
11
11
  const { throttledCallback: resizeHandler } = useThrottle({
12
12
  callback: () => {
13
13
  if (!refElement?.current?.clientHeight) return
14
14
 
15
- setClientHeight(`${refElement?.current.clientHeight}px`)
15
+ setClientHeight(refElement.current.clientHeight)
16
16
  },
17
17
  delay: 650
18
18
  })
@@ -32,7 +32,7 @@ function useRefClientHeight<T extends HTMLElement>(
32
32
  }
33
33
  }, [refElement, resizeHandler])
34
34
 
35
- return clientHeight
35
+ return useMemo(() => clientHeight, [clientHeight])
36
36
  }
37
37
 
38
38
  export default useRefClientHeight
@@ -1,9 +1,12 @@
1
1
  import { chance, render, screen, waitFor } from '@/config/tests'
2
+ import { useWidgetSettingsAtom } from '@/src/modules/widget/store/widget-settings.atom'
2
3
  import WidgetSettingPropsBuilder from '../modules/widget/__tests__/widget-settings-props.builder'
3
- import { useWidgetSettingsAtom } from '../modules/widget/store/widget-settings.atom'
4
4
  import { Main } from '.'
5
5
 
6
- vi.mock('../modules/widget/store/widget-settings.atom', () => ({ useWidgetSettingsAtom: vi.fn() }))
6
+ vi.mock('@/src/modules/widget/store/widget-settings.atom', async (importOriginal) => ({
7
+ ...(await importOriginal()),
8
+ useWidgetSettingsAtom: vi.fn()
9
+ }))
7
10
 
8
11
  describe('Main', () => {
9
12
  const defaultProps = new WidgetSettingPropsBuilder()
@@ -20,7 +23,7 @@ describe('Main', () => {
20
23
  renderComponent({ settings: props })
21
24
 
22
25
  await waitFor(() => {
23
- expect(screen.getByText(/onboarding.description/i)).toBeInTheDocument()
26
+ expect(screen.getByText(/send/i)).toBeInTheDocument()
24
27
  })
25
28
  })
26
29
  })
@@ -1,4 +1,5 @@
1
1
  import { type PropsWithChildren, useEffect } from 'react'
2
+ import { v4 } from 'uuid'
2
3
 
3
4
  import { OptimizelyProvider } from '@/src/config/optimizely'
4
5
  import { QueryProvider } from '@/src/config/tanstack'
@@ -13,6 +14,10 @@ function GlobalProviders({ children, settings }: GlobalProvidersProps) {
13
14
  useEffect(() => {
14
15
  if (!settings || !Object.keys(settings)?.length) return
15
16
 
17
+ if (!settings?.sessionId) {
18
+ settings.sessionId = v4()
19
+ }
20
+
16
21
  setWidgetSettings(settings)
17
22
  }, [setWidgetSettings, settings])
18
23
 
@@ -1,8 +1,8 @@
1
1
  import { lazy, useCallback, useRef } from 'react'
2
2
  import clsx from 'clsx'
3
+ import { createPortal } from 'react-dom'
3
4
 
4
- import { useRefClientHeight } from '@/src/lib/hooks'
5
- import { useWidgetLoadingAtomValue } from '@/src/modules/widget'
5
+ import { usePageLayoutMainRefContext, useWidgetLoadingAtomValue } from '@/src/modules/widget'
6
6
  import { useAllMessages, useManageScroll } from '../../hooks'
7
7
  import { useSkeletonRef } from '../../hooks/use-skeleton-ref'
8
8
  import { MessageItem } from '../message-item'
@@ -22,12 +22,12 @@ const ScrollToBottomButton = lazy(
22
22
 
23
23
  function MessagesList() {
24
24
  const scrollerRef = useRef<HTMLDivElement>(null)
25
- const scrollerClientHeight = useRefClientHeight(scrollerRef)
26
25
  const scrollToButtonRef = useRef<HTMLButtonElement>(null)
27
26
  const { allMessages, messagesQuery } = useAllMessages()
28
27
  const widgetIsLoading = useWidgetLoadingAtomValue()
29
28
  const skeletonRef = useSkeletonRef()
30
29
  const { showScrollButton } = useManageScroll(scrollerRef)
30
+ const mainLayoutRef = usePageLayoutMainRefContext()
31
31
 
32
32
  const scrollToBottom = useCallback(() => {
33
33
  const { current: scroller } = scrollerRef
@@ -41,28 +41,21 @@ function MessagesList() {
41
41
  }, [])
42
42
 
43
43
  return (
44
- <div
45
- ref={scrollerRef}
46
- className={clsx(
47
- 'relative mx-2 flex flex-col gap-2 overflow-auto p-4',
48
- `h-[${scrollerClientHeight}]`
49
- )}>
44
+ <div ref={scrollerRef} className='mx-2 my-4 flex flex-col gap-2 overflow-auto px-4'>
50
45
  <MessageItemLoading show={messagesQuery.isFetching} />
51
46
 
52
47
  <MessageItemEndOfScroll
53
48
  show={!messagesQuery.isFetching && !messagesQuery.hasNextPage && allMessages.length > 0}
54
49
  />
55
-
56
- <ScrollToBottomButton
57
- ref={scrollToButtonRef}
58
- top={Math.abs(
59
- Number(scrollerRef.current?.clientHeight) -
60
- Number(scrollToButtonRef.current?.clientHeight) -
61
- 24
50
+ {mainLayoutRef.current &&
51
+ createPortal(
52
+ <ScrollToBottomButton
53
+ ref={scrollToButtonRef}
54
+ show={showScrollButton}
55
+ onClick={scrollToBottom}
56
+ />,
57
+ mainLayoutRef.current
62
58
  )}
63
- show={showScrollButton}
64
- onClick={scrollToBottom}
65
- />
66
59
 
67
60
  {allMessages?.map(([publishingDate, messages], i) => (
68
61
  <div key={i} className='flex flex-1 flex-col justify-center gap-6'>
@@ -153,7 +153,7 @@ class SparkieService {
153
153
  return false
154
154
  }
155
155
 
156
- private async ensureInitialized(): Promise<void> {
156
+ async ensureInitialized(): Promise<void> {
157
157
  if (this.isInitialized) return
158
158
 
159
159
  switch (this.initializationState) {
@@ -4,6 +4,7 @@ import { isTextEmpty } from '@/src/lib/utils/is-text-empty'
4
4
  import { ChatInput, MessagesList, useChatInputValueAtom } from '@/src/modules/messages/components'
5
5
  import { useAllMessages, useSendTextMessage } from '@/src/modules/messages/hooks'
6
6
  import { useWidgetLoadingAtomValue, useWidgetTabsValueAtom } from '../../store'
7
+ import { PageLayout } from '../page-layout'
7
8
 
8
9
  function ChatPage() {
9
10
  const widgetTabs = useWidgetTabsValueAtom()
@@ -27,9 +28,8 @@ function ChatPage() {
27
28
  }
28
29
 
29
30
  return (
30
- <>
31
- <MessagesList />
32
- <div className='border-t border-t-neutral-700 px-5 py-4'>
31
+ <PageLayout
32
+ asideChild={
33
33
  <ChatInput
34
34
  name='new-chat-msg-input'
35
35
  ref={chatInputRef}
@@ -38,9 +38,9 @@ function ChatPage() {
38
38
  inputDisabled={messagesQuery?.isLoading}
39
39
  buttonDisabled={messagesQuery?.isLoading || !value.trim()}
40
40
  />
41
- </div>
42
- </>
41
+ }>
42
+ <MessagesList />
43
+ </PageLayout>
43
44
  )
44
45
  }
45
-
46
46
  export default ChatPage
@@ -7,10 +7,8 @@ function WidgetContainer() {
7
7
  useSubscribeMessageReceivedEvent()
8
8
 
9
9
  return (
10
- <div className='flex min-h-svh flex-col items-center justify-center'>
11
- <div className='grid h-svh w-full grid-rows-[1fr_max-content]'>
12
- {WIDGET_TABS[widgetTabs.currentTab]}
13
- </div>
10
+ <div className='flex h-full flex-col items-center justify-stretch overflow-hidden'>
11
+ {WIDGET_TABS[widgetTabs.currentTab]}
14
12
  </div>
15
13
  )
16
14
  }
@@ -2,4 +2,5 @@ export * from './ai-avatar'
2
2
  export * from './chat-page'
3
3
  export * from './container'
4
4
  export * from './greetings-card'
5
+ export * from './page-layout'
5
6
  export * from './scroll-to-bottom-button'
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
4
4
  import TutorOnboardingSVG from '@/public/assets/svg/tutor-onboarding.svg?url'
5
5
  import { Button } from '@/src/lib/components'
6
6
  import { useWidgetTabsAtom } from '../../store'
7
+ import { PageLayout } from '../page-layout'
7
8
 
8
9
  import styles from './styles.module.css'
9
10
 
@@ -12,8 +13,8 @@ function WidgetOnboardingPage() {
12
13
  const { t } = useTranslation()
13
14
 
14
15
  return (
15
- <>
16
- <div className={styles.bg}>
16
+ <PageLayout>
17
+ <div className={clsx('flex-1', styles.bg)}>
17
18
  <div className='mx-4 flex h-full flex-col justify-center gap-6 px-0.5'>
18
19
  <div className='mx-auto max-w-[67%]'>
19
20
  <img src={TutorOnboardingSVG} aria-hidden />
@@ -33,7 +34,7 @@ function WidgetOnboardingPage() {
33
34
  {t('general.buttons.start')}
34
35
  </Button>
35
36
  </div>
36
- </>
37
+ </PageLayout>
37
38
  )
38
39
  }
39
40
 
@@ -0,0 +1,8 @@
1
+ import { createSharedRefContext } from '@/src/lib/contexts'
2
+
3
+ const { useSharedRefContext, SharedRefContext, SharedRefContextProvider } =
4
+ createSharedRefContext<HTMLDivElement>()
5
+
6
+ export const usePageLayoutMainRefContext = useSharedRefContext
7
+ export const PageLayoutMainRefContext = SharedRefContext
8
+ export const PageLayoutMainRefContextProvider = SharedRefContextProvider
@@ -0,0 +1,3 @@
1
+ export * from './constants'
2
+ export * from './page-layout'
3
+ export { default as PageLayout } from './page-layout'
@@ -0,0 +1,42 @@
1
+ import { type PropsWithChildren, type ReactNode } from 'react'
2
+ import clsx from 'clsx'
3
+
4
+ import { PageLayoutMainRefContextProvider, usePageLayoutMainRefContext } from './constants'
5
+
6
+ export type PageLayoutProps = PropsWithChildren<{
7
+ asideChild?: ReactNode
8
+ className?: string
9
+ }>
10
+
11
+ function PageLayout({ asideChild, children, className }: PageLayoutProps) {
12
+ const mainLayoutRef = usePageLayoutMainRefContext()
13
+
14
+ return (
15
+ <div
16
+ className={clsx(
17
+ 'grid-areas-[main_aside] grid h-full min-h-0 w-full grid-cols-1 grid-rows-[1fr_auto]',
18
+ className
19
+ )}>
20
+ <div
21
+ ref={mainLayoutRef}
22
+ className='grid-area-[main] relative flex min-h-0 flex-col overflow-y-auto overflow-x-hidden'>
23
+ {children}
24
+ </div>
25
+ {asideChild && (
26
+ <div className='grid-area-[aside] flex-shrink-0 border-t border-t-neutral-700 px-5 py-4'>
27
+ {asideChild}
28
+ </div>
29
+ )}
30
+ </div>
31
+ )
32
+ }
33
+
34
+ function PageLayoutWrapper(props: PageLayoutProps) {
35
+ return (
36
+ <PageLayoutMainRefContextProvider>
37
+ <PageLayout {...props} />
38
+ </PageLayoutMainRefContextProvider>
39
+ )
40
+ }
41
+
42
+ export default PageLayoutWrapper
@@ -10,14 +10,13 @@ export interface IScrollToBottomButtonProps
10
10
  }
11
11
 
12
12
  const ScrollToBottomButton = forwardRef<HTMLButtonElement, IScrollToBottomButtonProps>(
13
- ({ show = false, top = 0, onClick, className, ...props }, ref) => (
13
+ ({ show = false, onClick, className, ...props }, ref) => (
14
14
  <button
15
15
  {...props}
16
- style={isNaN(Number(top)) ? undefined : { top }}
17
16
  ref={ref}
18
17
  className={clsx(
19
- 'fixed inset-x-1/2 flex size-7 cursor-pointer flex-col items-center justify-center rounded-full bg-neutral-600 text-sm text-neutral-50 outline-none transition-colors duration-300 ease-in hover:scale-110 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-neutral-500 focus:ring-offset-2',
20
- { 'pointer-events-none opacity-0': !show },
18
+ 'absolute bottom-4 left-1/2 flex size-7 cursor-pointer flex-col items-center justify-center rounded-full bg-neutral-600 text-sm text-neutral-50 outline-none transition-colors duration-300 ease-in hover:scale-110 hover:bg-neutral-700 focus:outline-none focus:ring-neutral-500 focus:ring-offset-2 focus-visible:ring-2 active:ring-2',
19
+ { 'opacity-85': show, 'pointer-events-none opacity-0': !show },
21
20
  className
22
21
  )}
23
22
  onClick={onClick}
@@ -4,6 +4,7 @@ import { useRefEventListener } from '@/src/lib/hooks'
4
4
  import { ChatInput, useChatInputValueAtom } from '@/src/modules/messages/components'
5
5
  import { useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
6
6
  import { GreetingsCard } from '../greetings-card'
7
+ import { PageLayout } from '../page-layout'
7
8
 
8
9
  function WidgetStarterPage() {
9
10
  const [settings] = useWidgetSettingsAtom()
@@ -23,18 +24,18 @@ function WidgetStarterPage() {
23
24
  })
24
25
 
25
26
  return (
26
- <>
27
- <div className='flex flex-col justify-center px-5 py-4'>
28
- <GreetingsCard author={settings?.author ?? ''} tutorName={settings?.tutorName ?? ''} />
29
- </div>
30
- <div className='border-t border-t-neutral-700 px-5 py-4'>
27
+ <PageLayout
28
+ asideChild={
31
29
  <ChatInput
32
30
  name='new-chat-msg-input'
33
31
  ref={chatInputRef}
34
32
  onSend={() => setWidgetTabs('chat')}
35
33
  />
34
+ }>
35
+ <div className='flex flex-col justify-center px-5 py-4'>
36
+ <GreetingsCard author={settings?.author ?? ''} tutorName={settings?.tutorName ?? ''} />
36
37
  </div>
37
- </>
38
+ </PageLayout>
38
39
  )
39
40
  }
40
41
 
@@ -1,21 +1,46 @@
1
1
  import type { ITutorWidgetEvent } from './types'
2
2
 
3
3
  export const TutorWidgetEventTypes = {
4
- OPEN: 'tutor-app-widget-open',
5
- CLOSE: 'tutor-app-widget-close'
4
+ OPEN: 'c3po-app-widget-open',
5
+ CLOSE: 'c3po-app-widget-close',
6
+ LOADED: 'tutor-app-widget-loaded'
6
7
  } as const
7
8
 
8
- const TutorWidgetEventsList: Array<ITutorWidgetEvent> = [
9
+ const TutorWidgetEventsList = [
9
10
  {
10
11
  name: TutorWidgetEventTypes.OPEN,
11
12
  handler: () => () => undefined,
12
- dispatch: () => window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.OPEN))
13
+ dispatch: () => {
14
+ window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.OPEN))
15
+ }
13
16
  },
14
17
  {
15
18
  name: TutorWidgetEventTypes.CLOSE,
16
19
  handler: () => () => undefined,
17
- dispatch: () => window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.CLOSE))
18
- }
20
+ dispatch: () => {
21
+ window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.CLOSE))
22
+ }
23
+ },
24
+ {
25
+ name: TutorWidgetEventTypes.LOADED,
26
+ handler: (callback) => {
27
+ const listener: EventListener = (e) => {
28
+ const evt = e as CustomEvent<{ isSuccess: boolean }>
29
+
30
+ console.log(evt.detail)
31
+
32
+ callback(evt.detail)
33
+ }
34
+ window.addEventListener(TutorWidgetEventTypes.LOADED, listener)
35
+
36
+ return () => {
37
+ window.removeEventListener(TutorWidgetEventTypes.LOADED, listener)
38
+ }
39
+ },
40
+ dispatch: (payload = { detail: { isSuccess: true } }) => {
41
+ window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.LOADED, payload))
42
+ }
43
+ } as ITutorWidgetEvent<{ isSuccess: boolean }>
19
44
  ] as const
20
45
 
21
46
  export const TutorWidgetEvents = new Map(TutorWidgetEventsList.map((e) => [e.name, e]))
@@ -8,8 +8,8 @@ export type WidgetTabsProps = {
8
8
  }
9
9
 
10
10
  const INITIAL_PROPS: WidgetTabsProps = {
11
- currentTab: 'onboarding',
12
- history: new Set(['onboarding'])
11
+ currentTab: 'chat',
12
+ history: new Set(['chat'])
13
13
  }
14
14
 
15
15
  export const widgetTabsAtom = atom<WidgetTabsProps>(INITIAL_PROPS)
@@ -1,7 +1,7 @@
1
1
  import type { TutorWidgetEventTypes } from './events'
2
2
 
3
- export type ITutorWidgetEvent = {
3
+ export type ITutorWidgetEvent<T = unknown> = {
4
4
  name: (typeof TutorWidgetEventTypes)[keyof typeof TutorWidgetEventTypes]
5
- handler: (listener: EventListenerOrEventListenerObject) => () => void
6
- dispatch: <T = unknown>(payload?: CustomEventInit<T>) => void
5
+ handler: (callback: (payload: T) => void) => () => void
6
+ dispatch: (payload?: CustomEventInit<T>) => void
7
7
  }