app-tutor-ai-consumer 1.20.0 → 1.21.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +2 -1
  3. package/src/config/datahub/actions.ts +10 -0
  4. package/src/config/datahub/constants.ts +43 -0
  5. package/src/config/datahub/entities.ts +4 -0
  6. package/src/config/datahub/index.ts +5 -0
  7. package/src/config/datahub/schemas/base-schema.ts +72 -0
  8. package/src/config/datahub/schemas/constants.ts +10 -0
  9. package/src/config/datahub/schemas/index.ts +2 -0
  10. package/src/config/datahub/schemas/tutor/__tests__/click-hotmart-tutor.spec.ts +81 -0
  11. package/src/config/datahub/schemas/tutor/click-hotmart-tutor.ts +61 -0
  12. package/src/config/datahub/schemas/tutor/constants.ts +4 -0
  13. package/src/config/datahub/schemas/tutor/index.ts +3 -0
  14. package/src/config/datahub/schemas/tutor/types.ts +3 -0
  15. package/src/config/datahub/schemas/types.ts +19 -0
  16. package/src/config/datahub/service.ts +32 -0
  17. package/src/config/datahub/store.ts +87 -0
  18. package/src/config/datahub/types.ts +35 -0
  19. package/src/development-bootstrap.tsx +1 -0
  20. package/src/lib/components/button/button.tsx +25 -78
  21. package/src/lib/components/icons/info.svg +3 -3
  22. package/src/modules/messages/components/chat-input/chat-input.tsx +1 -1
  23. package/src/modules/messages/components/message-actions/message-actions.spec.tsx +53 -0
  24. package/src/modules/messages/components/message-actions/message-actions.tsx +48 -11
  25. package/src/modules/messages/components/messages-container/messages-container.tsx +3 -1
  26. package/src/modules/messages/hooks/use-scroller/use-scroller.tsx +1 -1
  27. package/src/modules/widget/components/ai-avatar/ai-avatar.tsx +2 -2
  28. package/src/modules/widget/components/chat-page/chat-page.tsx +1 -15
  29. package/src/modules/widget/components/header/header.tsx +49 -27
  30. package/src/modules/widget/components/header/styles.module.css +11 -0
  31. package/src/modules/widget/components/information-page/information-page.tsx +11 -17
  32. package/src/modules/widget/components/loading-page/loading-page.tsx +1 -1
  33. package/src/modules/widget/components/starter-page/starter-page.tsx +2 -6
  34. package/src/modules/widget/hooks/use-init-widget/use-init-widget.tsx +8 -0
  35. package/src/modules/widget/store/widget-tabs.atom.ts +1 -1
  36. package/tailwind.config.js +6 -8
package/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [1.21.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.21.0...v1.21.1) (2025-07-31)
2
+
3
+ # [1.21.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.20.0...v1.21.0) (2025-07-29)
4
+
5
+ ### Features
6
+
7
+ - add DataHub Config ([745264a](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/745264a166e868884958abb19d0d720e06294ffe))
8
+
1
9
  # [1.20.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.19.0...v1.20.0) (2025-07-29)
2
10
 
3
11
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-tutor-ai-consumer",
3
- "version": "1.20.0",
3
+ "version": "1.21.1",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
@@ -104,6 +104,7 @@
104
104
  },
105
105
  "dependencies": {
106
106
  "@hot-observability-js/react": "~1.1.0",
107
+ "@hotmart/event-agent-js": "~1.1.2",
107
108
  "@hotmart/sparkie": "~5.1.0",
108
109
  "@optimizely/react-sdk": "~3.2.4",
109
110
  "@tanstack/query-sync-storage-persister": "~5.80.7",
@@ -0,0 +1,10 @@
1
+ export const DataHubActions = {
2
+ OPEN: 'open',
3
+ TRY: 'try',
4
+ CLICK: 'click',
5
+ CLOSE: 'close',
6
+ SELECT: 'select',
7
+ VIEW: 'view'
8
+ } as const
9
+
10
+ export type DataHubActionTypes = (typeof DataHubActions)[keyof typeof DataHubActions]
@@ -0,0 +1,43 @@
1
+ export const DATA_VERSION = '1.0'
2
+
3
+ export const EVENT_VERSION = '1.1'
4
+
5
+ export const System = {
6
+ HOTMART_CLUB: 'hotmart_club'
7
+ } as const
8
+
9
+ export const ActionNames = {
10
+ CLICK_HOTMART_TUTOR: 'click_hotmart_tutor'
11
+ } as const
12
+
13
+ export const ScreenNames = {
14
+ NOT_APP_EVENT: 'NOT_APP_EVENT',
15
+ HOME_CONSUMER: 'HOME_CONSUMER'
16
+ } as const
17
+
18
+ export const Platform = {
19
+ WEB: 'WEB'
20
+ } as const
21
+
22
+ export const ResultType = {
23
+ SUCCESS: 'SUCCESSFUL',
24
+ FAILURE: 'FAILURE',
25
+ FAILURE_DESCRIPTION: 'NOT_FAILURE_RESULT_EVENT'
26
+ } as const
27
+
28
+ export const ComponentNames = {
29
+ BUTTON_LIKE_ANSWER: 'BUTTON_LIKE_ANSWER',
30
+ BUTTON_DISLIKE_ANSWER: 'BUTTON_DISLIKE_ANSWER'
31
+ } as const
32
+
33
+ export const ComponentSource = {
34
+ ACTION_BAR_STATUS: 'ACTION_BAR_STATUS'
35
+ } as const
36
+
37
+ export const ArrivedFrom = {
38
+ HOME: 'HOME'
39
+ } as const
40
+
41
+ export const PushKind = {
42
+ NOT_PUSH_EVENT: 'NOT_PUSH_EVENT'
43
+ } as const
@@ -0,0 +1,4 @@
1
+ export const DataHubEntities = {
2
+ ADMIN: 'admin',
3
+ HOME: 'home'
4
+ } as const
@@ -0,0 +1,5 @@
1
+ export * from './constants'
2
+ export * from './entities'
3
+ export { default as DataHubService } from './service'
4
+ export { default as DataHubStore } from './store'
5
+ export * from './types'
@@ -0,0 +1,72 @@
1
+ import { APP_VERSION } from '@/src/lib/utils'
2
+ import { DataHubStore } from '..'
3
+ import { Platform } from '../constants'
4
+ import type {
5
+ ActionNamesType,
6
+ DataHubEntityTypes,
7
+ InitDataParams,
8
+ PlatformType,
9
+ SchemaType
10
+ } from '../types'
11
+
12
+ import { ClubVersion, UserRole } from './constants'
13
+ import type { ClubVersionType, UserRoleType } from './types'
14
+
15
+ abstract class BaseSchema implements SchemaType, InitDataParams {
16
+ appVersion: string
17
+ osVersion: string
18
+ platform: PlatformType
19
+ platformDetail: string
20
+
21
+ membershipId: string
22
+ membershipSlug: string
23
+ sessionId: string
24
+ ucode: string
25
+ userId: number
26
+
27
+ clubVersion: ClubVersionType
28
+ userRole: UserRoleType
29
+ url: string
30
+
31
+ abstract action: ActionNamesType
32
+ abstract entity: DataHubEntityTypes
33
+ abstract isLogged: boolean
34
+ abstract getDataHubEventData(): Record<string, unknown>
35
+
36
+ constructor() {
37
+ this.appVersion = APP_VERSION
38
+ this.clubVersion = ClubVersion.MEMBERSHIP
39
+ this.membershipId = DataHubStore.membershipId
40
+ this.membershipSlug = DataHubStore.membershipSlug
41
+ this.osVersion = DataHubStore.browserInfo.version ?? ''
42
+ this.platform = Platform.WEB
43
+ this.platformDetail = DataHubStore.browserInfo.name ?? ''
44
+ this.sessionId = DataHubStore.sessionId
45
+ this.ucode = DataHubStore.ucode
46
+ this.url = window.location.href
47
+ this.userId = DataHubStore.userId
48
+ this.userRole = UserRole.CONSUMER
49
+ }
50
+
51
+ prepare(): Record<string, unknown> {
52
+ return {
53
+ action: this.action,
54
+ entity: this.entity,
55
+ isLogged: this.isLogged,
56
+ appVersion: this.appVersion,
57
+ osVersion: this.osVersion,
58
+ platform: this.platform,
59
+ platformDetail: this.platformDetail,
60
+ membershipId: this.membershipId,
61
+ membershipSlug: this.membershipSlug,
62
+ sessionId: this.sessionId,
63
+ ucode: this.ucode,
64
+ userId: this.userId,
65
+ clubVersion: this.clubVersion,
66
+ userRole: this.userRole,
67
+ url: this.url
68
+ }
69
+ }
70
+ }
71
+
72
+ export default BaseSchema
@@ -0,0 +1,10 @@
1
+ export const ClubVersion = {
2
+ MEMBERSHIP: 'MEMBERSHIP'
3
+ } as const
4
+
5
+ export const UserRole = {
6
+ OWNER: 'OWNER',
7
+ MODERATOR: 'MODERATOR',
8
+ ADMIN: 'ADMIN',
9
+ CONSUMER: 'CONSUMER'
10
+ } as const
@@ -0,0 +1,2 @@
1
+ export * from './constants'
2
+ export * from './types'
@@ -0,0 +1,81 @@
1
+ import { chance } from '@/src/config/tests'
2
+ import { DataHubStore } from '../../..'
3
+ import { ActionNames, ComponentNames, ScreenNames } from '../../../constants'
4
+ import { DataHubEntities } from '../../../entities'
5
+ import { UserRole } from '../../constants'
6
+ import ClickHotmartTutor from '../click-hotmart-tutor'
7
+ import { ButtonReactions } from '../constants'
8
+
9
+ describe('ClickHotmartTutor', () => {
10
+ it('should send the event with the correct structure', () => {
11
+ const event = {
12
+ reactionType: ButtonReactions.DISLIKE,
13
+ messageId: chance.guid(),
14
+ hasAccess: true,
15
+ isLogged: true
16
+ }
17
+
18
+ expect(new ClickHotmartTutor(event).getDataHubEventData()).toMatchObject({
19
+ action: ActionNames.CLICK_HOTMART_TUTOR,
20
+ appVersion: '',
21
+ clubVersion: 'MEMBERSHIP',
22
+ componentName: ComponentNames.BUTTON_DISLIKE_ANSWER,
23
+ entity: DataHubEntities.HOME,
24
+ hasAccess: event.hasAccess,
25
+ isLogged: event.isLogged,
26
+ membershipId: 'UNDEFINED',
27
+ membershipSlug: 'UNDEFINED',
28
+ messageId: event.messageId,
29
+ osVersion: '537.36',
30
+ platform: 'WEB',
31
+ platformDetail: 'WebKit',
32
+ screenName: ScreenNames.HOME_CONSUMER,
33
+ sessionId: 'UNDEFINED',
34
+ ucode: 'UNDEFINED',
35
+ url: 'http://localhost:3000/',
36
+ userId: 0,
37
+ userRole: UserRole.CONSUMER
38
+ })
39
+ })
40
+
41
+ it('should send reflect DataHubStore current state when sending the event', () => {
42
+ const event = {
43
+ reactionType: ButtonReactions.LIKE,
44
+ messageId: chance.guid(),
45
+ hasAccess: true,
46
+ isLogged: true
47
+ }
48
+
49
+ const store = {
50
+ ucode: chance.guid(),
51
+ membershipId: chance.guid(),
52
+ membershipSlug: chance.animal(),
53
+ sessionId: chance.guid(),
54
+ userId: chance.integer()
55
+ }
56
+
57
+ DataHubStore.initData(store)
58
+
59
+ expect(new ClickHotmartTutor(event).getDataHubEventData()).toMatchObject({
60
+ action: ActionNames.CLICK_HOTMART_TUTOR,
61
+ appVersion: '',
62
+ clubVersion: 'MEMBERSHIP',
63
+ componentName: ComponentNames.BUTTON_LIKE_ANSWER,
64
+ entity: DataHubEntities.HOME,
65
+ hasAccess: event.hasAccess,
66
+ isLogged: event.isLogged,
67
+ membershipId: store.membershipId,
68
+ membershipSlug: store.membershipSlug,
69
+ messageId: event.messageId,
70
+ osVersion: '537.36',
71
+ platform: 'WEB',
72
+ platformDetail: 'WebKit',
73
+ screenName: ScreenNames.HOME_CONSUMER,
74
+ sessionId: store.sessionId,
75
+ ucode: store.ucode,
76
+ url: 'http://localhost:3000/',
77
+ userId: store.userId,
78
+ userRole: UserRole.CONSUMER
79
+ })
80
+ })
81
+ })
@@ -0,0 +1,61 @@
1
+ import { ActionNames, ComponentNames, ScreenNames } from '../../constants'
2
+ import { DataHubEntities } from '../../entities'
3
+ import type {
4
+ ActionNamesType,
5
+ ComponentNamesType,
6
+ DataHubEntityTypes,
7
+ ScreenNamesType
8
+ } from '../../types'
9
+ import BaseSchema from '../base-schema'
10
+
11
+ import { ButtonReactions } from './constants'
12
+ import type { ButtonReactionsType } from './types'
13
+
14
+ class ClickHotmartTutor extends BaseSchema {
15
+ private componentName: ComponentNamesType
16
+ private screenName: ScreenNamesType
17
+ private messageId: string
18
+ private hasAccess: boolean
19
+
20
+ action: ActionNamesType
21
+ entity: DataHubEntityTypes
22
+ isLogged: boolean
23
+
24
+ constructor({
25
+ reactionType,
26
+ messageId,
27
+ hasAccess = true,
28
+ isLogged = true
29
+ }: {
30
+ reactionType: ButtonReactionsType
31
+ messageId: string
32
+ hasAccess?: boolean
33
+ isLogged?: boolean
34
+ }) {
35
+ super()
36
+
37
+ this.action = ActionNames.CLICK_HOTMART_TUTOR
38
+ this.entity = DataHubEntities.HOME
39
+
40
+ this.componentName =
41
+ reactionType === ButtonReactions.LIKE
42
+ ? ComponentNames.BUTTON_LIKE_ANSWER
43
+ : ComponentNames.BUTTON_DISLIKE_ANSWER
44
+ this.screenName = ScreenNames.HOME_CONSUMER
45
+ this.messageId = messageId
46
+ this.hasAccess = hasAccess
47
+ this.isLogged = isLogged
48
+ }
49
+
50
+ getDataHubEventData(): Record<string, unknown> {
51
+ return {
52
+ ...super.prepare(),
53
+ componentName: this.componentName,
54
+ screenName: this.screenName,
55
+ messageId: this.messageId,
56
+ hasAccess: this.hasAccess
57
+ }
58
+ }
59
+ }
60
+
61
+ export default ClickHotmartTutor
@@ -0,0 +1,4 @@
1
+ export const ButtonReactions = {
2
+ LIKE: 'LIKE',
3
+ DISLIKE: 'DISLIKE'
4
+ } as const
@@ -0,0 +1,3 @@
1
+ export { default as ClickHotmartTutor } from './click-hotmart-tutor'
2
+ export * from './constants'
3
+ export * from './types'
@@ -0,0 +1,3 @@
1
+ import type { ButtonReactions } from './constants'
2
+
3
+ export type ButtonReactionsType = (typeof ButtonReactions)[keyof typeof ButtonReactions]
@@ -0,0 +1,19 @@
1
+ import type { ClubVersion, UserRole } from './constants'
2
+
3
+ export type UserRoleType = (typeof UserRole)[keyof typeof UserRole]
4
+
5
+ export type ClubVersionType = (typeof ClubVersion)[keyof typeof ClubVersion]
6
+
7
+ export type SchemaBasedProps = {
8
+ membershipSlug?: string
9
+ membershipId?: string
10
+ ucode?: string
11
+ userId?: number
12
+ hasAccess?: boolean
13
+ }
14
+
15
+ export type BaseSchemaProps = {
16
+ membershipId: string
17
+ membershipSlug: string
18
+ userId: string
19
+ }
@@ -0,0 +1,32 @@
1
+ import EventAgent from '@hotmart/event-agent-js'
2
+
3
+ import { productionMode } from '@/src/lib/utils'
4
+
5
+ import { DATA_VERSION, EVENT_VERSION, System } from './constants'
6
+ import type { SendEventParams } from './types'
7
+
8
+ EventAgent.mode = productionMode ? 'production' : 'staging'
9
+
10
+ class DataHubService {
11
+ sendEvent({
12
+ schema,
13
+ dataVersion = DATA_VERSION,
14
+ eventVersion = EVENT_VERSION,
15
+ system = System.HOTMART_CLUB
16
+ }: SendEventParams): void {
17
+ const dataHubEvent = {
18
+ system,
19
+ action: schema.action,
20
+ entity: schema.entity,
21
+ data_version: dataVersion,
22
+ event_version: eventVersion,
23
+ event: schema.getDataHubEventData()
24
+ }
25
+
26
+ if (!productionMode) console.warn('DataHub event dispatched: ', dataHubEvent)
27
+
28
+ void EventAgent.send(dataHubEvent)
29
+ }
30
+ }
31
+
32
+ export default new DataHubService()
@@ -0,0 +1,87 @@
1
+ import { UAParser } from 'ua-parser-js'
2
+
3
+ import type { InitDataParams } from './types'
4
+
5
+ class DataHubStore {
6
+ private instanceObj?: {
7
+ userAgentParser: UAParser.IResult
8
+ browserInfo: UAParser.IBrowser
9
+ deviceType: string
10
+ deviceVendor: UAParser.IDevice['vendor']
11
+ deviceModel: UAParser.IDevice['model']
12
+ ucode: string
13
+ sessionId: string
14
+ userId: number
15
+ membershipSlug: string
16
+ membershipId: string
17
+ }
18
+
19
+ private get instance() {
20
+ return this.instanceObj ?? this.resetData()
21
+ }
22
+
23
+ initData({ ucode, membershipId, membershipSlug, sessionId, userId }: InitDataParams) {
24
+ this.instance.userAgentParser = UAParser(navigator.userAgent)
25
+ this.instance.browserInfo = this.instance.userAgentParser.browser
26
+ this.instance.deviceType =
27
+ this.instance.userAgentParser.device?.type?.toUpperCase() || 'DESKTOP'
28
+ this.instance.deviceVendor = this.instance.userAgentParser.device?.vendor?.toUpperCase() ?? ''
29
+ this.instance.deviceModel = this.instance.userAgentParser.device?.model?.toUpperCase() ?? ''
30
+ this.instance.ucode = ucode
31
+ this.instance.sessionId = sessionId
32
+ this.instance.userId = userId || 0
33
+ this.instance.membershipSlug = membershipSlug
34
+ this.instance.membershipId = membershipId
35
+ }
36
+
37
+ resetData() {
38
+ const userAgent = UAParser(navigator.userAgent)
39
+
40
+ const clearData = {
41
+ userAgentParser: userAgent,
42
+ browserInfo: userAgent.browser,
43
+ deviceType: userAgent.device.type?.toUpperCase() || 'DESKTOP',
44
+ deviceVendor: userAgent.device.vendor,
45
+ deviceModel: userAgent.device.model,
46
+ ucode: 'UNDEFINED',
47
+ sessionId: 'UNDEFINED',
48
+ userId: 0,
49
+ membershipSlug: 'UNDEFINED',
50
+ membershipId: 'UNDEFINED'
51
+ }
52
+
53
+ this.instanceObj = clearData
54
+
55
+ return clearData
56
+ }
57
+
58
+ get userAgentParser() {
59
+ return this.instance.userAgentParser
60
+ }
61
+
62
+ get browserInfo() {
63
+ return this.instance.browserInfo
64
+ }
65
+
66
+ get ucode() {
67
+ return this.instance.ucode
68
+ }
69
+
70
+ get sessionId() {
71
+ return this.instance.sessionId
72
+ }
73
+
74
+ get userId() {
75
+ return this.instance.userId
76
+ }
77
+
78
+ get membershipSlug() {
79
+ return this.instance.membershipSlug
80
+ }
81
+
82
+ get membershipId() {
83
+ return this.instance.membershipId
84
+ }
85
+ }
86
+
87
+ export default new DataHubStore()
@@ -0,0 +1,35 @@
1
+ import type { ActionNames, ComponentNames, Platform, ScreenNames, System } from './constants'
2
+ import type { DataHubEntities } from './entities'
3
+
4
+ export type ActionNamesType = (typeof ActionNames)[keyof typeof ActionNames]
5
+ export type DataHubEntityTypes = (typeof DataHubEntities)[keyof typeof DataHubEntities]
6
+ export type PlatformType = (typeof Platform)[keyof typeof Platform]
7
+ export type SystemType = (typeof System)[keyof typeof System]
8
+ export type ComponentNamesType = (typeof ComponentNames)[keyof typeof ComponentNames]
9
+ export type ScreenNamesType = (typeof ScreenNames)[keyof typeof ScreenNames]
10
+
11
+ export type SchemaType = {
12
+ action: ActionNamesType
13
+ entity: DataHubEntityTypes
14
+ osVersion: string
15
+ appVersion: string
16
+ platformDetail: string
17
+ platform: PlatformType
18
+ prepare: () => Record<string, unknown>
19
+ getDataHubEventData: () => Record<string, unknown>
20
+ }
21
+
22
+ export type SendEventParams = {
23
+ schema: SchemaType
24
+ system?: SystemType
25
+ dataVersion?: string
26
+ eventVersion?: string
27
+ }
28
+
29
+ export type InitDataParams = {
30
+ ucode: string
31
+ sessionId: string
32
+ userId: number
33
+ membershipSlug: string
34
+ membershipId: string
35
+ }
@@ -35,6 +35,7 @@ if (devMode) {
35
35
  namespace: 'tutor_v1-2',
36
36
  author: 'user',
37
37
  clubName: 'comofazerumvideodeteste',
38
+ membershipSlug: 'comofazerumvideodeteste',
38
39
  productName: 'Curso de Assinatura',
39
40
  productId: 4266504,
40
41
  sessionId: v4(),
@@ -5,6 +5,26 @@ import { Spinner } from '../spinner'
5
5
 
6
6
  import styles from './styles.module.css'
7
7
 
8
+ function ButtonContent({
9
+ children,
10
+ loading
11
+ }: PropsWithChildren<{
12
+ loading?: boolean
13
+ }>) {
14
+ if (loading)
15
+ return (
16
+ <div className='col-start-1 row-start-1'>
17
+ <Spinner className='col-start-1 row-start-1 mx-auto my-auto h-[1em] w-[1em] text-current' />
18
+ </div>
19
+ )
20
+
21
+ return (
22
+ <span data-label='text-content' className='col-start-1 row-start-1 flex flex-nowrap gap-2'>
23
+ {children}
24
+ </span>
25
+ )
26
+ }
27
+
8
28
  export type ButtonProps = PropsWithChildren<
9
29
  ButtonHTMLAttributes<HTMLButtonElement> & {
10
30
  variant?: 'brand' | 'secondary' | 'primary' | 'tertiary' | 'gradient-outline'
@@ -77,20 +97,7 @@ function Button({
77
97
  {...props}
78
98
  disabled={props.disabled || loading}
79
99
  aria-busy={loading}>
80
- <span
81
- data-label='text-content'
82
- className={clsx('flex flex-nowrap gap-2 [grid-area:stack]', {
83
- visible: !loading,
84
- invisible: loading
85
- })}>
86
- {children}
87
- </span>
88
- <Spinner
89
- className={clsx('mx-auto my-auto h-[1em] w-[1em] text-current', '[grid-area:stack]', {
90
- visible: loading,
91
- invisible: !loading
92
- })}
93
- />
100
+ <ButtonContent loading={loading}>{children}</ButtonContent>
94
101
  </button>
95
102
  )
96
103
  case 'secondary':
@@ -107,20 +114,7 @@ function Button({
107
114
  {...props}
108
115
  disabled={props.disabled || loading}
109
116
  aria-busy={loading}>
110
- <span
111
- data-label='text-content'
112
- className={clsx('flex flex-nowrap gap-2 [grid-area:stack]', {
113
- visible: !loading,
114
- invisible: loading
115
- })}>
116
- {children}
117
- </span>
118
- <Spinner
119
- className={clsx('mx-auto my-auto h-[1em] w-[1em] text-current', '[grid-area:stack]', {
120
- visible: loading,
121
- invisible: !loading
122
- })}
123
- />
117
+ <ButtonContent loading={loading}>{children}</ButtonContent>
124
118
  </button>
125
119
  )
126
120
  case 'tertiary':
@@ -134,28 +128,7 @@ function Button({
134
128
  {...props}
135
129
  disabled={props.disabled || loading}
136
130
  aria-busy={loading}>
137
- <span
138
- data-label='text-content'
139
- className={clsx(`col-start-1 row-start-1 flex flex-nowrap gap-2`, {
140
- invisible: loading,
141
- visible: !loading
142
- })}>
143
- {children}
144
- </span>
145
-
146
- {loading && (
147
- <div className='col-start-1 row-start-1'>
148
- <Spinner
149
- className={clsx(
150
- 'col-start-1 row-start-1 mx-auto my-auto h-[1em] w-[1em] text-current',
151
- {
152
- visible: loading,
153
- invisible: !loading
154
- }
155
- )}
156
- />
157
- </div>
158
- )}
131
+ <ButtonContent loading={loading}>{children}</ButtonContent>
159
132
  </button>
160
133
  )
161
134
  case 'brand':
@@ -172,20 +145,7 @@ function Button({
172
145
  {...props}
173
146
  disabled={props.disabled || loading}
174
147
  aria-busy={loading}>
175
- <span
176
- data-label='text-content'
177
- className={clsx('flex flex-nowrap gap-2 [grid-area:stack]', {
178
- visible: !loading,
179
- invisible: loading
180
- })}>
181
- {children}
182
- </span>
183
- <Spinner
184
- className={clsx('mx-auto my-auto h-[1em] w-[1em] text-current', '[grid-area:stack]', {
185
- visible: loading,
186
- invisible: !loading
187
- })}
188
- />
148
+ <ButtonContent loading={loading}>{children}</ButtonContent>
189
149
  </button>
190
150
  )
191
151
  default:
@@ -205,20 +165,7 @@ function Button({
205
165
  {...props}
206
166
  disabled={props.disabled || loading}
207
167
  aria-busy={loading}>
208
- <span
209
- data-label='text-content'
210
- className={clsx('flex flex-nowrap gap-2 [grid-area:stack]', {
211
- visible: !loading,
212
- invisible: loading
213
- })}>
214
- {children}
215
- </span>
216
- <Spinner
217
- className={clsx('mx-auto my-auto h-[1em] w-[1em] text-current', '[grid-area:stack]', {
218
- visible: loading,
219
- invisible: !loading
220
- })}
221
- />
168
+ <ButtonContent loading={loading}>{children}</ButtonContent>
222
169
  </button>
223
170
  )
224
171
  }
@@ -1,5 +1,5 @@
1
- <svg viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
1
+ <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
2
  <path fill-rule="evenodd" clip-rule="evenodd"
3
- d="M0 8.5C0 4.08172 3.58172 0.5 8 0.5C12.4183 0.5 16 4.08172 16 8.5C16 12.9183 12.4183 16.5 8 16.5C3.58172 16.5 0 12.9183 0 8.5ZM8 1.5C4.13401 1.5 1 4.63401 1 8.5C1 12.366 4.13401 15.5 8 15.5C11.866 15.5 15 12.366 15 8.5C15 4.63401 11.866 1.5 8 1.5ZM7 5.5C7 4.94772 7.44772 4.5 8 4.5C8.55229 4.5 9 4.94772 9 5.5C9 6.05228 8.55229 6.5 8 6.5C7.44772 6.5 7 6.05228 7 5.5ZM7 7.5H7.5C8.32843 7.5 9 8.17157 9 9V12.5H8V9C8 8.72386 7.77614 8.5 7.5 8.5H7V7.5Z"
4
- fill="currentColor" />
3
+ d="M0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8ZM8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1ZM7 5C7 4.44772 7.44772 4 8 4C8.55229 4 9 4.44772 9 5C9 5.55228 8.55229 6 8 6C7.44772 6 7 5.55228 7 5ZM7 7H7.5C8.32843 7 9 7.67157 9 8.5V12H8V8.5C8 8.22386 7.77614 8 7.5 8H7V7Z"
4
+ fill="currentColor" shape-rendering="geometricPrecision" />
5
5
  </svg>
@@ -71,7 +71,7 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
71
71
  ref={ref}
72
72
  className={clsx(
73
73
  clsx(
74
- 'max-h-12 w-full resize-none border-none bg-transparent text-neutral-900 outline-none outline-0 placeholder:text-neutral-600',
74
+ 'max-h-12 w-full resize-none border-none bg-transparent text-sm/normal text-neutral-900 outline-none outline-0 placeholder:text-neutral-600',
75
75
  styles.textArea
76
76
  ),
77
77
  { 'cursor-not-allowed opacity-40': inputDisabled }
@@ -0,0 +1,53 @@
1
+ import { DataHubService } from '@/src/config/datahub'
2
+ import { ButtonReactions, ClickHotmartTutor } from '@/src/config/datahub/schemas/tutor'
3
+ import { render, screen } from '@/src/config/tests'
4
+ import ParsedMessageBuilder from '../../__tests__/parsed-message.builder'
5
+
6
+ import MessageActions from './message-actions'
7
+
8
+ describe('<MessageActions/>', () => {
9
+ const defaultProps = {
10
+ message: new ParsedMessageBuilder()
11
+ }
12
+
13
+ const renderComponent = (props = defaultProps) => render(<MessageActions {...props} />)
14
+
15
+ it('should render without errors', () => {
16
+ renderComponent()
17
+
18
+ expect(screen.getByRole('button', { name: /general.buttons.like/i })).toBeInTheDocument()
19
+ expect(screen.getByRole('button', { name: /general.buttons.dislike/i })).toBeInTheDocument()
20
+ expect(screen.getByRole('button', { name: /general.buttons.copy/i })).toBeInTheDocument()
21
+ })
22
+
23
+ it('should copy the current message text when clicking the copy button', async () => {
24
+ vi.spyOn(navigator.clipboard, 'writeText').mockImplementation(() => Promise.resolve(undefined))
25
+
26
+ const { user } = renderComponent()
27
+
28
+ const copyBtn = screen.getByRole('button', { name: /general.buttons.copy/i })
29
+
30
+ await user.click(copyBtn)
31
+
32
+ expect(navigator.clipboard.writeText).toBeCalledTimes(1)
33
+ expect(navigator.clipboard.writeText).toHaveBeenNthCalledWith(1, defaultProps.message.text)
34
+ })
35
+
36
+ it('should send like event when clicking the like button', async () => {
37
+ vi.spyOn(DataHubService, 'sendEvent').mockImplementation(() => undefined)
38
+
39
+ const { user } = renderComponent()
40
+
41
+ const likeBtn = screen.getByRole('button', { name: /general.buttons.like/i })
42
+
43
+ await user.click(likeBtn)
44
+
45
+ expect(DataHubService.sendEvent).toBeCalledTimes(1)
46
+ expect(DataHubService.sendEvent).toHaveBeenNthCalledWith(1, {
47
+ schema: new ClickHotmartTutor({
48
+ messageId: defaultProps.message.id,
49
+ reactionType: ButtonReactions.LIKE
50
+ })
51
+ })
52
+ })
53
+ })
@@ -1,8 +1,11 @@
1
+ import { type CSSProperties, useState } from 'react'
1
2
  import clsx from 'clsx'
2
3
  import dayjs from 'dayjs'
3
- import type { CSSProperties } from 'react'
4
4
  import { useTranslation } from 'react-i18next'
5
5
 
6
+ import { DataHubService } from '@/src/config/datahub'
7
+ import type { ButtonReactionsType } from '@/src/config/datahub/schemas/tutor'
8
+ import { ButtonReactions, ClickHotmartTutor } from '@/src/config/datahub/schemas/tutor'
6
9
  import { Button, Icon } from '@/src/lib/components'
7
10
  import type { ParsedMessage } from '../../types'
8
11
 
@@ -14,12 +17,28 @@ export type MessageActionsProps = {
14
17
 
15
18
  function MessageActions({ message, className, showActions = false }: MessageActionsProps) {
16
19
  const { t } = useTranslation()
17
- const copyToClipboard = () => {
20
+ const [copying, setCopying] = useState(false)
21
+ const [reaction, setReaction] = useState<ButtonReactionsType | null>(null)
22
+
23
+ const copyToClipboard = (): void => {
18
24
  if (!message.text) return
19
25
 
20
- navigator.clipboard.writeText(message.text).catch((err) => {
21
- console.error('Failed to copy text: ', err)
22
- })
26
+ setCopying(true)
27
+
28
+ navigator.clipboard
29
+ .writeText(message.text)
30
+ .catch((err) => {
31
+ console.error('Failed to copy text: ', err)
32
+ })
33
+ .finally(() => {
34
+ setTimeout(() => setCopying(false), 1000)
35
+ })
36
+ }
37
+
38
+ const handleReaction = (reactionType: ButtonReactionsType = ButtonReactions.LIKE): void => {
39
+ const schema = new ClickHotmartTutor({ messageId: message.id, reactionType })
40
+ DataHubService.sendEvent({ schema })
41
+ setReaction(reactionType)
23
42
  }
24
43
 
25
44
  return (
@@ -32,14 +51,32 @@ function MessageActions({ message, className, showActions = false }: MessageActi
32
51
  className={clsx('flex flex-nowrap gap-2 text-neutral-600', {
33
52
  hidden: !showActions
34
53
  })}>
35
- <Button aria-label={t('general.buttons.like')}>
36
- <Icon name='like' className='h-3 w-3.5' />
54
+ <Button onClick={() => handleReaction()} aria-label={t('general.buttons.like')}>
55
+ <Icon
56
+ name='like'
57
+ className={clsx('h-3 w-3.5', {
58
+ 'text-info-500': reaction === ButtonReactions.LIKE
59
+ })}
60
+ />
37
61
  </Button>
38
- <Button className='rotate-180 scale-x-[-1]' aria-label={t('general.buttons.dislike')}>
39
- <Icon name='like' className='h-3 w-3.5' />
62
+ <Button
63
+ className='rotate-180 scale-x-[-1]'
64
+ onClick={() => handleReaction(ButtonReactions.DISLIKE)}
65
+ aria-label={t('general.buttons.dislike')}>
66
+ <Icon
67
+ name='like'
68
+ className={clsx('h-3 w-3.5', {
69
+ 'text-danger-500': reaction === ButtonReactions.DISLIKE
70
+ })}
71
+ />
40
72
  </Button>
41
- <Button onClick={copyToClipboard} aria-label={t('general.buttons.copy')}>
42
- <Icon name='copy' className='h-3 w-3.5' />
73
+ <Button onClick={copyToClipboard} aria-label={t('general.buttons.copy')} disabled={copying}>
74
+ <Icon
75
+ name='copy'
76
+ className={clsx('h-3 w-3.5', {
77
+ 'text-info-500': copying
78
+ })}
79
+ />
43
80
  </Button>
44
81
  </div>
45
82
  </div>
@@ -52,7 +52,9 @@ const MessagesContainer = forwardRef<
52
52
  }
53
53
 
54
54
  return (
55
- <div ref={scrollerRef} className='mx-2 my-4 flex h-full flex-col gap-2 overflow-auto px-4'>
55
+ <div
56
+ ref={scrollerRef}
57
+ className='flex h-full flex-col gap-2 overflow-auto max-md:p-[1.125rem] md:p-5'>
56
58
  <div className='mb-auto flex-1 self-center'>
57
59
  <Button
58
60
  className='max-w-max rounded-full border border-neutral-300 bg-neutral-200 px-2 py-1 text-xs/normal tracking-wide text-neutral-900'
@@ -34,7 +34,7 @@ function useScroller(forwardedRef: ForwardedRef<HTMLDivElement>) {
34
34
  if (!scroller) return
35
35
 
36
36
  scroller.scrollTo({
37
- top: scroller.scrollHeight,
37
+ top: scroller.scrollHeight + 10,
38
38
  behavior: 'smooth'
39
39
  })
40
40
  }, [])
@@ -11,8 +11,8 @@ function AIAvatar({
11
11
  }: AIAvatarProps) {
12
12
  return (
13
13
  <figure
14
- className={clsx('flex h-12 w-12 items-center justify-center rounded-full', styles.avatar)}>
15
- <div className={clsx('flex h-11 w-11 items-center justify-center', className)}>
14
+ className={clsx('flex h-9 w-9 items-center justify-center rounded-full', styles.avatar)}>
15
+ <div className={clsx('flex h-8 w-8 items-center justify-center', className)}>
16
16
  <Icon name='ai-color' className='h-6 w-6' aria-label='AI avatar Icon' />
17
17
  </div>
18
18
  </figure>
@@ -19,13 +19,6 @@ const MessageItemError = lazy(
19
19
  () => import('@/src/modules/messages/components/message-item-error/message-item-error')
20
20
  )
21
21
 
22
- const MessageItemEndOfScroll = lazy(
23
- () =>
24
- import(
25
- '@/src/modules/messages/components/message-item-end-of-scroll/message-item-end-of-scroll'
26
- )
27
- )
28
-
29
22
  function ChatPage() {
30
23
  const chatInputRef = useRef<HTMLTextAreaElement>(null)
31
24
  const scrollerRef = useRef<HTMLDivElement>(null)
@@ -82,7 +75,7 @@ function ChatPage() {
82
75
  buttonDisabled={messagesQuery?.isLoading || !value.trim()}
83
76
  />
84
77
  }>
85
- <div className='mt-4 px-6 py-4'>
78
+ <div className='max-md:px-[1.125rem] max-md:pt-[1.125rem] md:px-5 md:pt-5'>
86
79
  <WidgetHeader enabledButtons={['info', 'close']} tutorName={settings?.tutorName} />
87
80
  </div>
88
81
  <MessagesContainer
@@ -92,13 +85,6 @@ function ChatPage() {
92
85
  }}
93
86
  showButton={messagesQuery.hasNextPage}
94
87
  loading={messagesQuery.isFetchingNextPage}>
95
- <MessageItemEndOfScroll
96
- show={
97
- !messagesQuery.isFetching &&
98
- !messagesQuery.hasNextPage &&
99
- Number(messagesQuery.data?.size) > 0
100
- }
101
- />
102
88
  {messagesQuery.data && <MessagesList messagesMap={messagesQuery.data} />}
103
89
  <MessageItemError
104
90
  show={messagesQuery.isError}
@@ -1,31 +1,42 @@
1
+ import clsx from 'clsx'
1
2
  import { useTranslation } from 'react-i18next'
2
3
 
3
4
  import { Button, Icon } from '@/src/lib/components'
4
5
  import { TutorWidgetEvents } from '../../events'
5
- import { useWidgetTabsAtom } from '../../store'
6
+ import { useWidgetGoBackTabAtom, useWidgetTabsAtom } from '../../store'
6
7
  import { AIAvatar } from '../ai-avatar'
7
8
 
8
9
  import type { WidgetHeaderContentProps, WidgetHeaderProps } from './types'
9
10
 
11
+ import styles from './styles.module.css'
12
+
10
13
  export function WidgetHeaderContent({ tutorName }: WidgetHeaderContentProps) {
11
14
  return (
12
15
  <div className='flex w-full gap-2'>
13
16
  <AIAvatar />
14
17
  <div className='flex flex-col justify-center'>
15
- {tutorName && <h4 className='text-base font-bold'>{tutorName}</h4>}
18
+ {tutorName && <h4 className='text-sm/loose font-bold'>{tutorName}</h4>}
16
19
  </div>
17
20
  </div>
18
21
  )
19
22
  }
20
23
 
21
24
  export function WidgetHeaderContentWithoutMeta({ name }: { name?: string }) {
25
+ const [, goBack] = useWidgetGoBackTabAtom()
26
+
22
27
  return (
23
- <div className='grid-areas-[a_b] grid grid-cols-[auto_1fr] items-center gap-1'>
24
- <Button className='grid-area-[a]' aria-label='Arrow Left Icon'>
25
- <Icon name='arrow-left' width={15} height={15} aria-hidden />
28
+ <div
29
+ className={clsx(
30
+ 'grid-areas-[a_b] grid grid-cols-[auto_1fr] items-center gap-1',
31
+ styles.withoutMetaContainer
32
+ )}>
33
+ <Button className='grid-area-[a]' aria-label='Arrow Left Icon' onClick={goBack}>
34
+ <Icon name='arrow-left' className='h-3.5 w-3.5' aria-hidden />
26
35
  </Button>
27
- <div className='grid-area-[b] flex justify-center'>
28
- <span>{name}</span>
36
+ <div className='grid-area-[b] flex min-h-6 justify-center text-center'>
37
+ <span className='absolute bottom-0 left-1/2 -translate-x-1/2 text-base font-bold'>
38
+ {name}
39
+ </span>
29
40
  </div>
30
41
  </div>
31
42
  )
@@ -50,33 +61,44 @@ function WidgetHeader({
50
61
  }
51
62
 
52
63
  return (
53
- <div className='grid-areas-[a_b] mt-0.5 grid grid-cols-[1fr_auto] items-center text-neutral-1000'>
54
- <div className='grid-area-[a]'>
55
- {showContent && !showContentWithoutMeta && <WidgetHeaderContent tutorName={name} />}
56
- {showContentWithoutMeta && !showContent && <WidgetHeaderContentWithoutMeta name={name} />}
57
- </div>
58
- <div className='shrink-0'>
59
- <div className='grid-area-[b] ml-auto flex max-w-max gap-3 text-neutral-700'>
60
- <Button
61
- show={enabledButtons.includes('archive')}
62
- onClick={handleClickArchive}
63
- aria-label='Archive Icon'>
64
- <Icon name='archive' className='h-4 w-4' aria-hidden />
65
- </Button>
66
- <Button
67
- show={enabledButtons.includes('info')}
68
- aria-label='Info Icon'
69
- onClick={handleClickInfo}>
70
- <Icon name='info' className='h-4 w-4' aria-hidden />
71
- </Button>
64
+ <div className='mt-0.5 flex flex-col gap-2 text-neutral-900'>
65
+ <div className='flex justify-end'>
66
+ <div className={styles.closeContainer}>
72
67
  <Button
68
+ className='text-neutral-500'
73
69
  show={enabledButtons.includes('close')}
74
70
  onClick={() => TutorWidgetEvents['c3po-app-widget-hide'].dispatch()}
75
71
  aria-label='Close Icon'>
76
- <Icon name='close' className='h-4 w-4' aria-hidden />
72
+ <Icon name='close' className='h-3 w-3' aria-hidden />
77
73
  </Button>
78
74
  </div>
79
75
  </div>
76
+ <div className='grid-areas-[a_b] grid grid-cols-[1fr_auto] items-center'>
77
+ <div className='grid-area-[a] relative'>
78
+ {showContent && !showContentWithoutMeta && <WidgetHeaderContent tutorName={name} />}
79
+ {showContentWithoutMeta && !showContent && <WidgetHeaderContentWithoutMeta name={name} />}
80
+ </div>
81
+ <div className='shrink-0'>
82
+ <div
83
+ className={clsx(
84
+ 'grid-area-[b] ml-auto flex max-w-max gap-3 text-neutral-700',
85
+ styles.btnContainer
86
+ )}>
87
+ <Button
88
+ show={enabledButtons.includes('archive')}
89
+ onClick={handleClickArchive}
90
+ aria-label='Archive Icon'>
91
+ <Icon name='archive' className='h-4 w-4' aria-hidden />
92
+ </Button>
93
+ <Button
94
+ show={enabledButtons.includes('info')}
95
+ aria-label='Info Icon'
96
+ onClick={handleClickInfo}>
97
+ <Icon name='info' className='h-4 w-4' aria-hidden />
98
+ </Button>
99
+ </div>
100
+ </div>
101
+ </div>
80
102
  </div>
81
103
  )
82
104
  }
@@ -0,0 +1,11 @@
1
+ .closeContainer {
2
+ --custom-btn-padding: 0.375rem;
3
+ }
4
+
5
+ .btnContainer {
6
+ --custom-btn-padding: 0.25rem;
7
+ }
8
+
9
+ .withoutMetaContainer {
10
+ --custom-btn-padding: 0.25rem;
11
+ }
@@ -1,9 +1,9 @@
1
1
  import clsx from 'clsx'
2
2
  import { useTranslation } from 'react-i18next'
3
3
 
4
- import { Icon } from '@/src/lib/components'
5
- import { useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
4
+ import { useWidgetSettingsAtom } from '../../store'
6
5
  import { AIAvatar } from '../ai-avatar'
6
+ import { WidgetHeader } from '../header'
7
7
  import { PageLayout } from '../page-layout'
8
8
 
9
9
  import { infoItems } from './constants'
@@ -11,27 +11,21 @@ import { InformationCard } from './information-card'
11
11
 
12
12
  function WidgetInformationPage() {
13
13
  const { t } = useTranslation()
14
- const [, setWidgetTabs] = useWidgetTabsAtom()
15
14
  const [settings] = useWidgetSettingsAtom()
16
15
  const isDarkMode = settings?.config?.theme === 'dark'
17
16
 
18
17
  return (
19
- <PageLayout className='p-5 text-white'>
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
- })}>
25
- <button
26
- className='absolute left-0'
27
- aria-label='Return Button'
28
- onClick={() => setWidgetTabs('chat')}>
29
- <Icon name='arrow-left' width={16} height={16} />
30
- </button>
31
- <h1 className='mx-auto font-bold'>{t('info.title')}</h1>
18
+ <PageLayout className='flex min-h-0 flex-col text-neutral-900 max-md:p-[1.125rem] md:p-5'>
19
+ <div className='mb-4'>
20
+ <WidgetHeader
21
+ enabledButtons={['close']}
22
+ showContent={false}
23
+ showContentWithoutMeta
24
+ tutorName={t('info.title')}
25
+ />
32
26
  </div>
33
27
 
34
- <div className='mb-10 flex justify-center'>
28
+ <div className='my-8 flex justify-center'>
35
29
  <div className='flex flex-col items-center gap-2'>
36
30
  <AIAvatar />
37
31
 
@@ -32,7 +32,7 @@ function WidgetLoadingPage() {
32
32
  return (
33
33
  <PageLayout
34
34
  asideChild={<ChatInput name='new-chat-msg-input' ref={chatInputRef} loading={true} />}>
35
- <div className='mt-4 flex h-full flex-col justify-start px-6 py-4'>
35
+ <div className='flex h-full flex-col justify-start max-md:p-[1.125rem] md:p-5'>
36
36
  <WidgetHeader enabledButtons={['close']} showContent={false} />
37
37
  <div className='mt-auto'>
38
38
  <MessageSkeleton />
@@ -90,14 +90,10 @@ function WidgetStarterPage() {
90
90
  }>
91
91
  <div className='grid-areas-[a_b] grid h-full grid-cols-1 grid-rows-[1fr_auto]'>
92
92
  <div
93
- className={clsx('grid-area-[a] flex min-h-0 flex-col px-5 py-4', {
93
+ className={clsx('grid-area-[a] flex min-h-0 flex-col max-md:p-[1.125rem] md:p-5', {
94
94
  [styles.bg]: settings?.config?.theme === 'dark'
95
95
  })}>
96
- <WidgetHeader
97
- enabledButtons={['archive', 'info', 'close']}
98
- tutorName={name}
99
- showContent={false}
100
- />
96
+ <WidgetHeader enabledButtons={['archive', 'info', 'close']} tutorName={name} />
101
97
 
102
98
  <div className='my-auto'>
103
99
  <GreetingsCard
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useState } from 'react'
2
2
 
3
+ import { DataHubStore } from '@/src/config/datahub'
3
4
  import { initDayjs } from '@/src/config/dayjs'
4
5
  import { initAxios } from '@/src/config/request/api'
5
6
  import { SparkieService } from '@/src/modules/sparkie'
@@ -10,6 +11,13 @@ const init = async (settings: WidgetSettingProps) => {
10
11
  try {
11
12
  initAxios(settings.hotmartToken)
12
13
  await initDayjs(settings.locale)
14
+ DataHubStore.initData({
15
+ ucode: settings.user?.ucode ?? '',
16
+ membershipId: settings.membershipId ?? '',
17
+ membershipSlug: settings.membershipSlug ?? '',
18
+ sessionId: settings.sessionId,
19
+ userId: Number(settings.userId)
20
+ })
13
21
  await SparkieService.initSparkie({
14
22
  token: settings?.hotmartToken,
15
23
  skipPresenceSetup: true,
@@ -51,4 +51,4 @@ export const goBackTabAtom = atom(null, (get, set) => {
51
51
  export const useGetWidgetTabsAtom = () => useAtom(widgetTabsAtom)
52
52
  export const useWidgetTabsAtom = () => useAtom(setWidgetTabsAtom)
53
53
  export const useWidgetTabsValueAtom = () => useAtomValue(setWidgetTabsAtom)
54
- export const useWidgetGoBackTabAton = () => useAtom(goBackTabAtom)
54
+ export const useWidgetGoBackTabAtom = () => useAtom(goBackTabAtom)
@@ -90,14 +90,12 @@ module.exports = {
90
90
  900: 'var(--hc-color-neutral-900)',
91
91
  1000: 'var(--hc-color-neutral-1000)'
92
92
  },
93
- ai: {
94
- primary: 'var(--ai-color-primary)',
95
- secondary: 'var(--ai-color-secondary)',
96
- dark: 'var(--ai-color-dark)',
97
- 'chat-response': 'var(--ai-color-chat-response)',
98
- 'gradient-primary': 'var(--ai-color-gradient-primary)',
99
- 'gradient-accent': 'var(--ai-color-gradient-accent)'
100
- }
93
+ 'ai-primary': 'var(--ai-color-primary)',
94
+ 'ai-secondary': 'var(--ai-color-secondary)',
95
+ 'ai-dark': 'var(--ai-color-dark)',
96
+ 'ai-chat-response': 'var(--ai-color-chat-response)',
97
+ 'ai-gradient-primary': 'var(--ai-color-gradient-primary)',
98
+ 'ai-gradient-accent': 'var(--ai-color-gradient-accent)'
101
99
  }
102
100
  }
103
101
  },