app-tutor-ai-consumer 1.22.1 → 1.22.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/config/vitest/__mocks__/window.ts +11 -0
- package/config/vitest/vitest.config.mts +1 -0
- package/package.json +1 -1
- package/src/config/datahub/actions.ts +6 -0
- package/src/config/datahub/constants.ts +3 -6
- package/src/config/datahub/entities.ts +2 -1
- package/src/config/datahub/schemas/base-click-schema.ts +48 -0
- package/src/config/datahub/schemas/base-try-schema.ts +64 -0
- package/src/config/datahub/schemas/tutor/__tests__/click-hotmart-tutor.spec.ts +2 -1
- package/src/config/datahub/schemas/tutor/click-hotmart-tutor.ts +2 -1
- package/src/config/datahub/schemas/tutor/click-tutor-minimize.ts +24 -0
- package/src/config/datahub/schemas/tutor/index.ts +1 -0
- package/src/config/datahub/schemas/tutor/try-product-tutor.ts +24 -0
- package/src/config/datahub/types.ts +3 -1
- package/src/config/theme/constants.ts +7 -0
- package/src/lib/components/button/button.tsx +7 -8
- package/src/lib/components/errors/generic/generic-error.tsx +9 -25
- package/src/lib/components/icons/copy-solid.svg +5 -0
- package/src/lib/components/icons/icon-names.d.ts +2 -0
- package/src/lib/components/icons/like-solid.svg +5 -0
- package/src/lib/components/index.ts +1 -0
- package/src/lib/components/tooltip/index.ts +2 -0
- package/src/lib/components/tooltip/styles.module.css +39 -0
- package/src/lib/components/tooltip/tooltip.tsx +41 -0
- package/src/lib/hooks/index.ts +1 -0
- package/src/lib/hooks/use-media-query/index.ts +2 -0
- package/src/lib/hooks/use-media-query/use-media-query.tsx +20 -0
- package/src/main/main.tsx +1 -1
- package/src/modules/messages/components/chat-input/chat-input.tsx +2 -1
- package/src/modules/messages/components/message-actions/message-actions.tsx +12 -12
- package/src/modules/widget/components/header/header.spec.tsx +32 -4
- package/src/modules/widget/components/header/header.tsx +33 -21
- package/src/modules/widget/components/information-page/information-page.tsx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [1.22.2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.22.1...v1.22.2) (2025-08-07)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
- header qa issues part 1 ([4910041](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/4910041905995e3b8161d015b261c82331131d20))
|
|
6
|
+
- qa issues part 2 ([65d5eb6](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/65d5eb64768f7496a1f767a6d2bd9e614abef547))
|
|
7
|
+
|
|
1
8
|
## [1.22.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.22.0...v1.22.1) (2025-08-06)
|
|
2
9
|
|
|
3
10
|
### Bug Fixes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
2
|
+
writable: true,
|
|
3
|
+
value: vi.fn().mockImplementation((query) => ({
|
|
4
|
+
matches: false,
|
|
5
|
+
media: query,
|
|
6
|
+
onchange: null,
|
|
7
|
+
addEventListener: vi.fn(),
|
|
8
|
+
removeEventListener: vi.fn(),
|
|
9
|
+
dispatchEvent: vi.fn()
|
|
10
|
+
}))
|
|
11
|
+
})
|
|
@@ -15,6 +15,7 @@ export default defineConfig({
|
|
|
15
15
|
'./config/vitest/__mocks__/icons.tsx',
|
|
16
16
|
'./config/vitest/__mocks__/animation-avatar.tsx',
|
|
17
17
|
'./config/vitest/__mocks__/intersection-observer.ts',
|
|
18
|
+
'./config/vitest/__mocks__/window.ts',
|
|
18
19
|
'./config/vitest/polyfills/global.js'
|
|
19
20
|
],
|
|
20
21
|
coverage: {
|
package/package.json
CHANGED
|
@@ -8,3 +8,9 @@ export const DataHubActions = {
|
|
|
8
8
|
} as const
|
|
9
9
|
|
|
10
10
|
export type DataHubActionTypes = (typeof DataHubActions)[keyof typeof DataHubActions]
|
|
11
|
+
|
|
12
|
+
export const ActionNames = {
|
|
13
|
+
CLICK_HOTMART_TUTOR: `${DataHubActions.CLICK}_hotmart_tutor`,
|
|
14
|
+
CLICK_TUTOR_MINIMIZE: `${DataHubActions.CLICK}_tutor_minimize`,
|
|
15
|
+
TRY_PRODUCT_TUTOR: `${DataHubActions.TRY}_product_tutor`
|
|
16
|
+
} as const
|
|
@@ -6,10 +6,6 @@ export const System = {
|
|
|
6
6
|
HOTMART_CLUB: 'hotmart_club'
|
|
7
7
|
} as const
|
|
8
8
|
|
|
9
|
-
export const ActionNames = {
|
|
10
|
-
CLICK_HOTMART_TUTOR: 'click_hotmart_tutor'
|
|
11
|
-
} as const
|
|
12
|
-
|
|
13
9
|
export const ScreenNames = {
|
|
14
10
|
NOT_APP_EVENT: 'NOT_APP_EVENT',
|
|
15
11
|
HOME_CONSUMER: 'HOME_CONSUMER'
|
|
@@ -19,7 +15,7 @@ export const Platform = {
|
|
|
19
15
|
WEB: 'WEB'
|
|
20
16
|
} as const
|
|
21
17
|
|
|
22
|
-
export const
|
|
18
|
+
export const Result = {
|
|
23
19
|
SUCCESS: 'SUCCESSFUL',
|
|
24
20
|
FAILURE: 'FAILURE',
|
|
25
21
|
FAILURE_DESCRIPTION: 'NOT_FAILURE_RESULT_EVENT'
|
|
@@ -27,7 +23,8 @@ export const ResultType = {
|
|
|
27
23
|
|
|
28
24
|
export const ComponentNames = {
|
|
29
25
|
BUTTON_LIKE_ANSWER: 'BUTTON_LIKE_ANSWER',
|
|
30
|
-
BUTTON_DISLIKE_ANSWER: 'BUTTON_DISLIKE_ANSWER'
|
|
26
|
+
BUTTON_DISLIKE_ANSWER: 'BUTTON_DISLIKE_ANSWER',
|
|
27
|
+
BUTTON_CLOSE_TUTOR_CHAT: 'BUTTON_CLOSE_TUTOR_CHAT'
|
|
31
28
|
} as const
|
|
32
29
|
|
|
33
30
|
export const ComponentSource = {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ScreenNames } from '../constants'
|
|
2
|
+
import { DataHubEntities } from '../entities'
|
|
3
|
+
import type { ComponentNamesType, DataHubEntityTypes, ScreenNamesType } from '../types'
|
|
4
|
+
|
|
5
|
+
import BaseSchema from './base-schema'
|
|
6
|
+
|
|
7
|
+
export type BaseClickSchemaConstructorProps = {
|
|
8
|
+
componentName: ComponentNamesType
|
|
9
|
+
entity?: DataHubEntityTypes
|
|
10
|
+
isLogged?: boolean
|
|
11
|
+
screenName?: ScreenNamesType
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
abstract class BaseClickSchema extends BaseSchema {
|
|
15
|
+
protected componentName: ComponentNamesType
|
|
16
|
+
protected screenName: ScreenNamesType
|
|
17
|
+
|
|
18
|
+
entity: DataHubEntityTypes
|
|
19
|
+
isLogged: boolean
|
|
20
|
+
|
|
21
|
+
constructor(args: BaseClickSchemaConstructorProps) {
|
|
22
|
+
const {
|
|
23
|
+
componentName,
|
|
24
|
+
entity = DataHubEntities.HOME,
|
|
25
|
+
isLogged = true,
|
|
26
|
+
screenName = ScreenNames.HOME_CONSUMER
|
|
27
|
+
} = args
|
|
28
|
+
|
|
29
|
+
super()
|
|
30
|
+
|
|
31
|
+
this.componentName = componentName
|
|
32
|
+
this.entity = entity
|
|
33
|
+
this.isLogged = isLogged
|
|
34
|
+
this.screenName = screenName
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
prepare() {
|
|
38
|
+
return {
|
|
39
|
+
...super.prepare(),
|
|
40
|
+
componentName: this.componentName,
|
|
41
|
+
entity: this.entity,
|
|
42
|
+
isLogged: this.isLogged,
|
|
43
|
+
screenName: this.screenName
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default BaseClickSchema
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { HttpCodes } from '@/src/lib/utils'
|
|
2
|
+
import { Result } from '../constants'
|
|
3
|
+
import { DataHubEntities } from '../entities'
|
|
4
|
+
import type { DataHubEntityTypes, ResultType } from '../types'
|
|
5
|
+
|
|
6
|
+
import BaseSchema from './base-schema'
|
|
7
|
+
|
|
8
|
+
export type BaseTrySchemaConstructorArgs = {
|
|
9
|
+
componentName: string
|
|
10
|
+
componentSource: string
|
|
11
|
+
entity?: DataHubEntityTypes
|
|
12
|
+
isLogged?: boolean
|
|
13
|
+
result?: ResultType
|
|
14
|
+
statusCode?: number
|
|
15
|
+
failureDescription?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
abstract class BaseTrySchema extends BaseSchema {
|
|
19
|
+
private componentName: string
|
|
20
|
+
private componentSource: string
|
|
21
|
+
private failureDescription: string
|
|
22
|
+
private result: ResultType
|
|
23
|
+
private statusCode: number
|
|
24
|
+
|
|
25
|
+
entity: DataHubEntityTypes
|
|
26
|
+
isLogged: boolean
|
|
27
|
+
|
|
28
|
+
constructor(args: BaseTrySchemaConstructorArgs) {
|
|
29
|
+
const {
|
|
30
|
+
componentName,
|
|
31
|
+
componentSource,
|
|
32
|
+
entity = DataHubEntities.HOME,
|
|
33
|
+
failureDescription = Result.FAILURE_DESCRIPTION,
|
|
34
|
+
isLogged = true,
|
|
35
|
+
result = Result.SUCCESS,
|
|
36
|
+
statusCode = HttpCodes.OK
|
|
37
|
+
} = args
|
|
38
|
+
|
|
39
|
+
super()
|
|
40
|
+
|
|
41
|
+
this.componentName = componentName
|
|
42
|
+
this.componentSource = componentSource
|
|
43
|
+
this.entity = entity
|
|
44
|
+
this.failureDescription = failureDescription
|
|
45
|
+
this.isLogged = isLogged
|
|
46
|
+
this.result = result
|
|
47
|
+
this.statusCode = statusCode
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
prepare(): Record<string, unknown> {
|
|
51
|
+
return {
|
|
52
|
+
...super.prepare(),
|
|
53
|
+
componentName: this.componentName,
|
|
54
|
+
componentSource: this.componentSource,
|
|
55
|
+
entity: this.entity,
|
|
56
|
+
failureDescription: this.failureDescription,
|
|
57
|
+
isLogged: this.isLogged,
|
|
58
|
+
result: this.result,
|
|
59
|
+
statusCode: this.statusCode
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default BaseTrySchema
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chance } from '@/src/config/tests'
|
|
2
2
|
import { DataHubStore } from '../../..'
|
|
3
|
-
import { ActionNames
|
|
3
|
+
import { ActionNames } from '../../../actions'
|
|
4
|
+
import { ComponentNames, ScreenNames } from '../../../constants'
|
|
4
5
|
import { DataHubEntities } from '../../../entities'
|
|
5
6
|
import { UserRole } from '../../constants'
|
|
6
7
|
import ClickHotmartTutor from '../click-hotmart-tutor'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ActionNames
|
|
1
|
+
import { ActionNames } from '../../actions'
|
|
2
|
+
import { ComponentNames, ScreenNames } from '../../constants'
|
|
2
3
|
import { DataHubEntities } from '../../entities'
|
|
3
4
|
import type {
|
|
4
5
|
ActionNamesType,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ActionNames } from '../../actions'
|
|
2
|
+
import { ComponentNames } from '../../constants'
|
|
3
|
+
import { DataHubEntities } from '../../entities'
|
|
4
|
+
import type { ActionNamesType } from '../../types'
|
|
5
|
+
import BaseClickSchema from '../base-click-schema'
|
|
6
|
+
|
|
7
|
+
class ClickTutorMinimizeSchema extends BaseClickSchema {
|
|
8
|
+
action: ActionNamesType
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
super({ componentName: ComponentNames.BUTTON_CLOSE_TUTOR_CHAT })
|
|
12
|
+
|
|
13
|
+
this.action = ActionNames.CLICK_TUTOR_MINIMIZE
|
|
14
|
+
this.entity = DataHubEntities.PRODUCT_CONSUME
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getDataHubEventData(): Record<string, unknown> {
|
|
18
|
+
return {
|
|
19
|
+
...super.prepare()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default ClickTutorMinimizeSchema
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ActionNames } from '../../actions'
|
|
2
|
+
import type { ActionNamesType } from '../../types'
|
|
3
|
+
import type { BaseTrySchemaConstructorArgs } from '../base-try-schema'
|
|
4
|
+
import BaseTrySchema from '../base-try-schema'
|
|
5
|
+
|
|
6
|
+
export type TryProductTutorConstructorArgs = BaseTrySchemaConstructorArgs
|
|
7
|
+
|
|
8
|
+
class TryProductTutor extends BaseTrySchema {
|
|
9
|
+
action: ActionNamesType
|
|
10
|
+
|
|
11
|
+
constructor(args: TryProductTutorConstructorArgs) {
|
|
12
|
+
super(args)
|
|
13
|
+
|
|
14
|
+
this.action = ActionNames.TRY_PRODUCT_TUTOR
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getDataHubEventData(): Record<string, unknown> {
|
|
18
|
+
return {
|
|
19
|
+
...this.prepare()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default TryProductTutor
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ActionNames
|
|
1
|
+
import type { ActionNames } from './actions'
|
|
2
|
+
import type { ComponentNames, Platform, Result, ScreenNames, System } from './constants'
|
|
2
3
|
import type { DataHubEntities } from './entities'
|
|
3
4
|
|
|
4
5
|
export type ActionNamesType = (typeof ActionNames)[keyof typeof ActionNames]
|
|
@@ -7,6 +8,7 @@ export type PlatformType = (typeof Platform)[keyof typeof Platform]
|
|
|
7
8
|
export type SystemType = (typeof System)[keyof typeof System]
|
|
8
9
|
export type ComponentNamesType = (typeof ComponentNames)[keyof typeof ComponentNames]
|
|
9
10
|
export type ScreenNamesType = (typeof ScreenNames)[keyof typeof ScreenNames]
|
|
11
|
+
export type ResultType = (typeof Result)[keyof typeof Result]
|
|
10
12
|
|
|
11
13
|
export type SchemaType = {
|
|
12
14
|
action: ActionNamesType
|
|
@@ -98,20 +98,19 @@ function Button({
|
|
|
98
98
|
)
|
|
99
99
|
case 'secondary':
|
|
100
100
|
return (
|
|
101
|
-
<
|
|
101
|
+
<ButtonDefault
|
|
102
102
|
className={clsx(
|
|
103
|
-
gridClasses,
|
|
104
103
|
defaultPadding,
|
|
105
104
|
defaultClasses,
|
|
106
105
|
defaultBorder,
|
|
107
|
-
'border-neutral-900 bg-transparent text-neutral-900 hover:bg-
|
|
106
|
+
'border-neutral-900 bg-transparent text-neutral-900 hover:bg-[rgb(from_var(--hc-color-neutral-900)_r_g_b_/_0.05)]',
|
|
108
107
|
className
|
|
109
108
|
)}
|
|
110
109
|
{...props}
|
|
111
110
|
disabled={props.disabled || loading}
|
|
112
111
|
aria-busy={loading}>
|
|
113
112
|
<ButtonContent loading={loading}>{children}</ButtonContent>
|
|
114
|
-
</
|
|
113
|
+
</ButtonDefault>
|
|
115
114
|
)
|
|
116
115
|
case 'tertiary':
|
|
117
116
|
return (
|
|
@@ -129,9 +128,8 @@ function Button({
|
|
|
129
128
|
)
|
|
130
129
|
case 'brand':
|
|
131
130
|
return (
|
|
132
|
-
<
|
|
131
|
+
<ButtonDefault
|
|
133
132
|
className={clsx(
|
|
134
|
-
gridClasses,
|
|
135
133
|
defaultPadding,
|
|
136
134
|
defaultClasses,
|
|
137
135
|
defaultBorder,
|
|
@@ -142,7 +140,7 @@ function Button({
|
|
|
142
140
|
disabled={props.disabled || loading}
|
|
143
141
|
aria-busy={loading}>
|
|
144
142
|
<ButtonContent loading={loading}>{children}</ButtonContent>
|
|
145
|
-
</
|
|
143
|
+
</ButtonDefault>
|
|
146
144
|
)
|
|
147
145
|
default:
|
|
148
146
|
return (
|
|
@@ -150,7 +148,8 @@ function Button({
|
|
|
150
148
|
className={clsx(
|
|
151
149
|
'rounded-full outline-none transition-colors duration-100',
|
|
152
150
|
{
|
|
153
|
-
'cursor-pointer hover:bg-neutral-
|
|
151
|
+
'cursor-pointer hover:bg-neutral-300 hover:text-current focus:bg-neutral-300 focus:text-current':
|
|
152
|
+
!props.disabled,
|
|
154
153
|
[disabledClasses]: props.disabled
|
|
155
154
|
},
|
|
156
155
|
styles.defaultButton,
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import clsx from 'clsx'
|
|
2
1
|
import { useTranslation } from 'react-i18next'
|
|
3
2
|
|
|
4
3
|
import ErrorDarkSVG from '@/public/assets/svg/error-dark.svg?url'
|
|
5
4
|
import ErrorLightSVG from '@/public/assets/svg/error-light.svg?url'
|
|
6
5
|
import { Button } from '@/src/lib/components'
|
|
7
|
-
import { PageLayout, TutorWidgetEvents
|
|
6
|
+
import { PageLayout, TutorWidgetEvents } from '@/src/modules/widget'
|
|
8
7
|
import { WidgetHeader } from '@/src/modules/widget/components/header'
|
|
9
8
|
|
|
10
|
-
function GenericError() {
|
|
9
|
+
function GenericError({ isDarkMode = false }: { isDarkMode?: boolean }) {
|
|
11
10
|
const { t } = useTranslation()
|
|
12
|
-
const [settings] = useWidgetSettingsAtom()
|
|
13
|
-
const isDarkMode = settings?.config?.theme === 'dark'
|
|
14
11
|
|
|
15
12
|
return (
|
|
16
13
|
<PageLayout className='p-5'>
|
|
17
|
-
<WidgetHeader enabledButtons={['close']}
|
|
14
|
+
<WidgetHeader enabledButtons={['close']} />
|
|
18
15
|
|
|
19
16
|
<div className='flex h-full flex-col items-center justify-center p-10'>
|
|
20
17
|
<div className='mb-8 flex flex-col items-center gap-1 text-center'>
|
|
@@ -26,38 +23,25 @@ function GenericError() {
|
|
|
26
23
|
height={200}
|
|
27
24
|
/>
|
|
28
25
|
|
|
29
|
-
<h2
|
|
30
|
-
|
|
31
|
-
'text-white': isDarkMode,
|
|
32
|
-
'text-gray-800': !isDarkMode
|
|
33
|
-
})}>
|
|
34
|
-
{t('generic_error.title')}
|
|
35
|
-
</h2>
|
|
36
|
-
|
|
37
|
-
<p
|
|
38
|
-
className={clsx('text-sm', {
|
|
39
|
-
'text-gray-400': isDarkMode,
|
|
40
|
-
'text-gray-500': !isDarkMode
|
|
41
|
-
})}>
|
|
42
|
-
{t('generic_error.description')}
|
|
43
|
-
</p>
|
|
26
|
+
<h2 className='text-xl font-bold text-neutral-900'>{t('generic_error.title')}</h2>
|
|
27
|
+
<p className='text-sm text-neutral-600'>{t('generic_error.description')}</p>
|
|
44
28
|
</div>
|
|
45
29
|
|
|
46
30
|
<div className='flex w-full flex-col gap-4'>
|
|
47
31
|
<Button
|
|
48
32
|
variant='brand'
|
|
49
|
-
className='w-full rounded-lg py-2'
|
|
33
|
+
className='mx-auto w-full max-w-max rounded-lg !px-9 py-2 !font-light'
|
|
50
34
|
onClick={() => window.location.reload()}
|
|
51
35
|
aria-label='Retry Button'>
|
|
52
|
-
|
|
36
|
+
{t('general.buttons.try_again')}
|
|
53
37
|
</Button>
|
|
54
38
|
|
|
55
39
|
<Button
|
|
56
40
|
variant='secondary'
|
|
57
|
-
className='w-full rounded-lg py-2'
|
|
41
|
+
className='mx-auto w-full max-w-max rounded-lg !px-9 py-2 !font-light'
|
|
58
42
|
onClick={() => TutorWidgetEvents['c3po-app-widget-hide'].dispatch()}
|
|
59
43
|
aria-label='Close Button'>
|
|
60
|
-
|
|
44
|
+
{t('general.buttons.close')}
|
|
61
45
|
</Button>
|
|
62
46
|
</div>
|
|
63
47
|
</div>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg viewBox="0 0 13 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path
|
|
3
|
+
d="M6.32585 0.734428H9.55762C9.88835 0.734428 10.2061 0.867241 10.4404 1.10162L12.2087 2.86984C12.443 3.10422 12.5758 3.42193 12.5758 3.75266V9.48443C12.5758 10.1745 12.0159 10.7344 11.3258 10.7344H6.32585C5.63574 10.7344 5.07585 10.1745 5.07585 9.48443V1.98443C5.07585 1.29432 5.63574 0.734428 6.32585 0.734428ZM2.15918 4.06776H4.24251V5.73443H2.57585V12.4011H7.57585V11.5678H9.24251V12.8178C9.24251 13.5079 8.68262 14.0678 7.99251 14.0678H2.15918C1.46908 14.0678 0.90918 13.5079 0.90918 12.8178V5.31776C0.90918 4.62766 1.46908 4.06776 2.15918 4.06776Z"
|
|
4
|
+
fill="currentColor" />
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path
|
|
3
|
+
d="M8.40316 0.426269C9.08024 0.565542 9.52034 1.24316 9.38493 1.93953L9.32503 2.24486C9.18701 2.95997 8.9318 3.64027 8.57503 4.25896H12.325C13.0151 4.25896 13.575 4.8348 13.575 5.54456C13.575 6.04005 13.3016 6.47126 12.9006 6.68553C13.1844 6.92122 13.3667 7.2828 13.3667 7.68723C13.3667 8.31396 12.9292 8.83623 12.3537 8.94872C12.4683 9.14424 12.5334 9.3719 12.5334 9.61563C12.5334 10.1861 12.1714 10.6709 11.6714 10.8369C11.6896 10.9253 11.7 11.0191 11.7 11.1155C11.7 11.8253 11.1401 12.4011 10.45 12.4011H7.91097C7.41618 12.4011 6.93441 12.2511 6.52295 11.9699L5.52034 11.2815C4.82503 10.8048 4.40837 10.0013 4.40837 9.14156V8.11576V6.83016V6.16326C4.40837 5.38118 4.75472 4.64464 5.34587 4.15451L5.53857 3.99649C6.22868 3.42868 6.70003 2.63054 6.87191 1.74133L6.9318 1.436C7.06722 0.739634 7.72607 0.286996 8.40316 0.426269ZM1.07503 4.6875H2.7417C3.20264 4.6875 3.57503 5.0705 3.57503 5.54456V11.544C3.57503 12.0181 3.20264 12.4011 2.7417 12.4011H1.07503C0.614095 12.4011 0.241699 12.0181 0.241699 11.544V5.54456C0.241699 5.0705 0.614095 4.6875 1.07503 4.6875Z"
|
|
4
|
+
fill="currentColor" />
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.triangle {
|
|
2
|
+
z-index: 5;
|
|
3
|
+
|
|
4
|
+
&:before {
|
|
5
|
+
content: '';
|
|
6
|
+
display: inline-block;
|
|
7
|
+
width: 0;
|
|
8
|
+
height: 0;
|
|
9
|
+
border-style: solid;
|
|
10
|
+
border-width: 0px 0.5rem 0.625rem 0.5rem;
|
|
11
|
+
border-color: transparent transparent var(--hc-color-neutral-300) transparent;
|
|
12
|
+
position: absolute;
|
|
13
|
+
z-index: 4;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.top:before {
|
|
18
|
+
top: 100%;
|
|
19
|
+
left: 50%;
|
|
20
|
+
transform: translateX(-50%) rotateZ(180deg);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.right:before {
|
|
24
|
+
top: 50%;
|
|
25
|
+
right: 97%;
|
|
26
|
+
transform: translateY(-50%) rotateZ(-90deg);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.bottom:before {
|
|
30
|
+
bottom: 100%;
|
|
31
|
+
left: 50%;
|
|
32
|
+
transform: translateX(-50%);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.left:before {
|
|
36
|
+
top: 50%;
|
|
37
|
+
left: 97%;
|
|
38
|
+
transform: translateY(-50%) rotateZ(90deg);
|
|
39
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import type { PropsWithChildren, ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
import styles from './styles.module.css'
|
|
5
|
+
|
|
6
|
+
const POSITION = {
|
|
7
|
+
top: 'left-1/2 transform -translate-x-1/2 bottom-full mb-3',
|
|
8
|
+
right: 'left-full top-1/2 transform -translate-y-1/2 ml-3',
|
|
9
|
+
bottom: 'left-1/2 transform -translate-x-1/2 top-full mt-3',
|
|
10
|
+
left: 'right-full top-1/2 transform -translate-y-1/2 mr-3'
|
|
11
|
+
} as const
|
|
12
|
+
|
|
13
|
+
export type TooltipProps = PropsWithChildren<{
|
|
14
|
+
content?: ReactNode
|
|
15
|
+
position?: keyof typeof POSITION
|
|
16
|
+
show?: boolean
|
|
17
|
+
}>
|
|
18
|
+
|
|
19
|
+
function Tooltip({ children, content, position = 'top', show = true }: TooltipProps) {
|
|
20
|
+
if (!show) return children
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className='group relative flex items-center justify-center'>
|
|
24
|
+
{children}
|
|
25
|
+
{content && (
|
|
26
|
+
<div
|
|
27
|
+
className={clsx(
|
|
28
|
+
'absolute hidden rounded-lg group-hover:block',
|
|
29
|
+
'bg-neutral-300 px-4 py-2 text-xs text-neutral-900',
|
|
30
|
+
POSITION[position],
|
|
31
|
+
styles.triangle,
|
|
32
|
+
styles[position]
|
|
33
|
+
)}>
|
|
34
|
+
{content}
|
|
35
|
+
</div>
|
|
36
|
+
)}
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default Tooltip
|
package/src/lib/hooks/index.ts
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useLayoutEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { SCREEN_SIZES } from '@/src/config/theme/constants'
|
|
4
|
+
|
|
5
|
+
function useMediaQuery({ maxSize }: { maxSize: keyof typeof SCREEN_SIZES }) {
|
|
6
|
+
const [matches, setMatches] = useState(false)
|
|
7
|
+
|
|
8
|
+
useLayoutEffect(() => {
|
|
9
|
+
const mediaquery = window.matchMedia(`(max-width: ${SCREEN_SIZES[maxSize]}px)`)
|
|
10
|
+
const listener = () => setMatches(mediaquery.matches)
|
|
11
|
+
|
|
12
|
+
mediaquery.addEventListener('change', listener)
|
|
13
|
+
|
|
14
|
+
return () => mediaquery.removeEventListener('change', listener)
|
|
15
|
+
}, [maxSize])
|
|
16
|
+
|
|
17
|
+
return matches
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default useMediaQuery
|
package/src/main/main.tsx
CHANGED
|
@@ -17,7 +17,7 @@ function Main({ settings }: MainProps) {
|
|
|
17
17
|
useListenToThemeChangeEvent()
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
|
-
<ErrorBoundary fallback={<GenericError />}>
|
|
20
|
+
<ErrorBoundary fallback={<GenericError isDarkMode={settings.config?.theme === 'dark'} />}>
|
|
21
21
|
<GlobalProviders settings={settings}>
|
|
22
22
|
<WidgetContainer />
|
|
23
23
|
</GlobalProviders>
|
|
@@ -86,7 +86,8 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
|
|
86
86
|
onClick={onSend}
|
|
87
87
|
disabled={buttonDisabled || loading}
|
|
88
88
|
className={clsx('flex flex-col items-center justify-center', styles.send, {
|
|
89
|
-
'bg-neutral-900 text-neutral-100':
|
|
89
|
+
'bg-neutral-900 text-neutral-100 hover:text-neutral-900 focus:text-neutral-900':
|
|
90
|
+
!buttonDisabled,
|
|
90
91
|
'text-neutral-900': buttonDisabled
|
|
91
92
|
})}
|
|
92
93
|
loading={loading}
|
|
@@ -55,36 +55,36 @@ function MessageActions({ message, className, showActions = false }: MessageActi
|
|
|
55
55
|
hidden: !showActions
|
|
56
56
|
})}>
|
|
57
57
|
<Button
|
|
58
|
-
className='hover:!bg-transparent hover:text-
|
|
58
|
+
className='hover:!bg-transparent hover:text-neutral-500 focus:!bg-transparent'
|
|
59
59
|
onClick={() => handleReaction()}
|
|
60
60
|
aria-label={t('general.buttons.like')}>
|
|
61
61
|
<Icon
|
|
62
|
-
name='like'
|
|
63
|
-
className={clsx('size-3', {
|
|
64
|
-
'text-
|
|
62
|
+
name={reaction === ButtonReactions.LIKE ? 'like-solid' : 'like'}
|
|
63
|
+
className={clsx('size-3 transition-colors duration-100', {
|
|
64
|
+
'text-neutral-900': reaction === ButtonReactions.LIKE
|
|
65
65
|
})}
|
|
66
66
|
/>
|
|
67
67
|
</Button>
|
|
68
68
|
<Button
|
|
69
|
-
className='rotate-180 scale-x-[-1] hover:!bg-transparent hover:text-
|
|
69
|
+
className='rotate-180 scale-x-[-1] hover:!bg-transparent hover:text-neutral-500 focus:!bg-transparent'
|
|
70
70
|
onClick={() => handleReaction(ButtonReactions.DISLIKE)}
|
|
71
71
|
aria-label={t('general.buttons.dislike')}>
|
|
72
72
|
<Icon
|
|
73
|
-
name='like'
|
|
74
|
-
className={clsx('size-3', {
|
|
75
|
-
'text-
|
|
73
|
+
name={reaction === ButtonReactions.DISLIKE ? 'like-solid' : 'like'}
|
|
74
|
+
className={clsx('size-3 transition-colors duration-100', {
|
|
75
|
+
'text-neutral-900': reaction === ButtonReactions.DISLIKE
|
|
76
76
|
})}
|
|
77
77
|
/>
|
|
78
78
|
</Button>
|
|
79
79
|
<Button
|
|
80
|
-
className='hover:!bg-transparent hover:text-
|
|
80
|
+
className='hover:!bg-transparent hover:text-neutral-500 focus:!bg-transparent'
|
|
81
81
|
onClick={copyToClipboard}
|
|
82
82
|
aria-label={t('general.buttons.copy')}
|
|
83
83
|
disabled={copying}>
|
|
84
84
|
<Icon
|
|
85
|
-
name={copied ? '
|
|
86
|
-
className={clsx('size-3', {
|
|
87
|
-
'text-
|
|
85
|
+
name={copied ? 'copy-solid' : 'copy'}
|
|
86
|
+
className={clsx('size-3 transition-colors duration-100', {
|
|
87
|
+
'text-neutral-900': copied
|
|
88
88
|
})}
|
|
89
89
|
/>
|
|
90
90
|
</Button>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { render, screen } from '@/src/config/tests'
|
|
2
|
+
import * as Hooks from '@/src/lib/hooks'
|
|
2
3
|
|
|
3
4
|
import WidgetHeaderPropsBuilder from './__tests__/widget-header-props.builder'
|
|
4
5
|
import WidgetHeader from './header'
|
|
@@ -12,8 +13,12 @@ describe('<WidgetHeader />', () => {
|
|
|
12
13
|
|
|
13
14
|
expect(screen.getByRole('button', { name: /Close Icon/i })).toBeInTheDocument()
|
|
14
15
|
|
|
15
|
-
expect(
|
|
16
|
-
|
|
16
|
+
expect(
|
|
17
|
+
screen.queryByRole('button', { name: /general.buttons.archive Icon/i })
|
|
18
|
+
).not.toBeInTheDocument()
|
|
19
|
+
expect(
|
|
20
|
+
screen.queryByRole('button', { name: /general.buttons.info Icon/i })
|
|
21
|
+
).not.toBeInTheDocument()
|
|
17
22
|
})
|
|
18
23
|
|
|
19
24
|
it('should render WidgetHeaderContent when prop showContent is true', () => {
|
|
@@ -41,7 +46,30 @@ describe('<WidgetHeader />', () => {
|
|
|
41
46
|
|
|
42
47
|
renderComponent(props)
|
|
43
48
|
|
|
44
|
-
expect(
|
|
45
|
-
|
|
49
|
+
expect(
|
|
50
|
+
screen.getByRole('button', { name: /general.buttons.archive Icon/i })
|
|
51
|
+
).toBeInTheDocument()
|
|
52
|
+
expect(screen.getByRole('button', { name: /general.buttons.info Icon/i })).toBeInTheDocument()
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should show header buttons tooltip when window is not in mobile view', () => {
|
|
56
|
+
const props = new WidgetHeaderPropsBuilder().withEnabledButtons(['archive', 'info'])
|
|
57
|
+
|
|
58
|
+
renderComponent(props)
|
|
59
|
+
|
|
60
|
+
expect(screen.getByText(/general.buttons.archive/i)).toBeInTheDocument()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should not show header buttons tooltip when window is in mobile view', () => {
|
|
64
|
+
vi.spyOn(Hooks, 'useMediaQuery').mockReturnValue(true)
|
|
65
|
+
|
|
66
|
+
const props = new WidgetHeaderPropsBuilder().withEnabledButtons(['archive', 'info'])
|
|
67
|
+
|
|
68
|
+
renderComponent(props)
|
|
69
|
+
|
|
70
|
+
expect(
|
|
71
|
+
screen.getByRole('button', { name: /general.buttons.archive Icon/i })
|
|
72
|
+
).toBeInTheDocument()
|
|
73
|
+
expect(screen.queryByText(/general.buttons.archive/i)).not.toBeInTheDocument()
|
|
46
74
|
})
|
|
47
75
|
})
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
2
|
import { useTranslation } from 'react-i18next'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { DataHubService } from '@/src/config/datahub'
|
|
5
|
+
import { ClickTutorMinimizeSchema } from '@/src/config/datahub/schemas/tutor'
|
|
6
|
+
import { Button, Icon, Tooltip } from '@/src/lib/components'
|
|
7
|
+
import { useMediaQuery } from '@/src/lib/hooks'
|
|
5
8
|
import { TutorWidgetEvents } from '../../events'
|
|
6
9
|
import { useWidgetGoBackTabAtom, useWidgetTabsAtom } from '../../store'
|
|
7
10
|
import { AIAvatar } from '../ai-avatar'
|
|
@@ -22,7 +25,9 @@ export function WidgetHeaderContent({ tutorName }: WidgetHeaderContentProps) {
|
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export function WidgetHeaderContentWithoutMeta({ name }: { name?: string }) {
|
|
28
|
+
const { t } = useTranslation()
|
|
25
29
|
const [, goBack] = useWidgetGoBackTabAtom()
|
|
30
|
+
const tutorName = name ?? t('general.name')
|
|
26
31
|
|
|
27
32
|
return (
|
|
28
33
|
<div
|
|
@@ -35,7 +40,7 @@ export function WidgetHeaderContentWithoutMeta({ name }: { name?: string }) {
|
|
|
35
40
|
</Button>
|
|
36
41
|
<div className='grid-area-[b] flex min-h-6 justify-center text-center'>
|
|
37
42
|
<span className='absolute bottom-0 left-1/2 -translate-x-1/2 text-base font-bold'>
|
|
38
|
-
{
|
|
43
|
+
{tutorName}
|
|
39
44
|
</span>
|
|
40
45
|
</div>
|
|
41
46
|
</div>
|
|
@@ -51,6 +56,7 @@ function WidgetHeader({
|
|
|
51
56
|
const { t } = useTranslation()
|
|
52
57
|
const [, setTab] = useWidgetTabsAtom()
|
|
53
58
|
const name = tutorName ?? t('general.name')
|
|
59
|
+
const isMobile = useMediaQuery({ maxSize: 'md' })
|
|
54
60
|
|
|
55
61
|
const handleClickArchive = () => {
|
|
56
62
|
setTab('chat')
|
|
@@ -60,6 +66,11 @@ function WidgetHeader({
|
|
|
60
66
|
setTab('information')
|
|
61
67
|
}
|
|
62
68
|
|
|
69
|
+
const handleHideWidget = () => {
|
|
70
|
+
TutorWidgetEvents['c3po-app-widget-hide'].dispatch()
|
|
71
|
+
DataHubService.sendEvent({ schema: new ClickTutorMinimizeSchema() })
|
|
72
|
+
}
|
|
73
|
+
|
|
63
74
|
return (
|
|
64
75
|
<div className='mt-0.5 flex flex-col gap-2 text-neutral-900'>
|
|
65
76
|
<div className='flex justify-end'>
|
|
@@ -67,7 +78,7 @@ function WidgetHeader({
|
|
|
67
78
|
<Button
|
|
68
79
|
className='text-neutral-500'
|
|
69
80
|
show={enabledButtons.includes('close')}
|
|
70
|
-
onClick={
|
|
81
|
+
onClick={handleHideWidget}
|
|
71
82
|
aria-label='Close Icon'>
|
|
72
83
|
<Icon name='close' className='h-3 w-3' aria-hidden />
|
|
73
84
|
</Button>
|
|
@@ -78,24 +89,25 @@ function WidgetHeader({
|
|
|
78
89
|
{showContent && !showContentWithoutMeta && <WidgetHeaderContent tutorName={name} />}
|
|
79
90
|
{showContentWithoutMeta && !showContent && <WidgetHeaderContentWithoutMeta name={name} />}
|
|
80
91
|
</div>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
|
|
93
|
+
<div className='grid-area-[b] ml-auto shrink-0'>
|
|
94
|
+
<div className={clsx('flex max-w-max gap-3 text-neutral-700', styles.btnContainer)}>
|
|
95
|
+
<Tooltip show={!isMobile} content={t('general.buttons.archive')}>
|
|
96
|
+
<Button
|
|
97
|
+
show={enabledButtons.includes('archive')}
|
|
98
|
+
onClick={handleClickArchive}
|
|
99
|
+
aria-label={t('general.buttons.archive') + ' Icon'}>
|
|
100
|
+
<Icon name='archive' className='h-4 w-4' aria-hidden />
|
|
101
|
+
</Button>
|
|
102
|
+
</Tooltip>
|
|
103
|
+
<Tooltip show={!isMobile} content={t('general.buttons.info')} position='left'>
|
|
104
|
+
<Button
|
|
105
|
+
show={enabledButtons.includes('info')}
|
|
106
|
+
onClick={handleClickInfo}
|
|
107
|
+
aria-label={t('general.buttons.info') + ' Icon'}>
|
|
108
|
+
<Icon name='info' className='h-4 w-4' aria-hidden />
|
|
109
|
+
</Button>
|
|
110
|
+
</Tooltip>
|
|
99
111
|
</div>
|
|
100
112
|
</div>
|
|
101
113
|
</div>
|
|
@@ -41,7 +41,7 @@ function WidgetInformationPage() {
|
|
|
41
41
|
</div>
|
|
42
42
|
|
|
43
43
|
<div className='flex flex-col gap-5'>
|
|
44
|
-
{infoItems({ tutorName }).map((item) => (
|
|
44
|
+
{infoItems({ tutorName: t('general.name') }).map((item) => (
|
|
45
45
|
<InformationCard
|
|
46
46
|
key={item.titleKey}
|
|
47
47
|
icon={item.icon}
|