app-tutor-ai-consumer 1.16.0 → 1.18.0
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 +13 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/development-bootstrap.tsx +1 -1
- package/src/lib/components/button/button.tsx +57 -14
- package/src/lib/components/icons/archive.svg +5 -0
- package/src/lib/components/icons/arrow-left.svg +5 -0
- package/src/lib/components/icons/book.svg +14 -0
- package/src/lib/components/icons/close.svg +5 -0
- package/src/lib/components/icons/icon-names.d.ts +13 -1
- package/src/lib/components/icons/info.svg +5 -0
- package/src/lib/components/icons/interrogation.svg +9 -0
- package/src/lib/components/icons/warning.svg +9 -0
- package/src/modules/messages/components/chat-input/chat-input.tsx +12 -22
- package/src/modules/widget/components/ai-avatar/ai-avatar-icon.tsx +17 -0
- package/src/modules/widget/components/ai-avatar/ai-avatar.tsx +13 -8
- package/src/modules/widget/components/ai-avatar/index.ts +3 -1
- package/src/modules/widget/components/ai-avatar/styles.module.css +3 -0
- package/src/modules/widget/components/chat-page/chat-page.tsx +17 -2
- package/src/modules/widget/components/constants.tsx +3 -1
- package/src/modules/widget/components/greetings-card/greetings-card.tsx +2 -8
- package/src/modules/widget/components/greetings-card/styles.module.css +0 -4
- package/src/modules/widget/components/header/__tests__/widget-header-props.builder.ts +46 -0
- package/src/modules/widget/components/header/header.spec.tsx +47 -0
- package/src/modules/widget/components/header/header.tsx +75 -0
- package/src/modules/widget/components/header/index.ts +2 -0
- package/src/modules/widget/components/header/types.ts +9 -0
- package/src/modules/widget/components/index.ts +1 -0
- package/src/modules/widget/components/information-page/constants.ts +17 -0
- package/src/modules/widget/components/information-page/index.ts +1 -0
- package/src/modules/widget/components/information-page/information-card/index.ts +1 -0
- package/src/modules/widget/components/information-page/information-card/information-card.tsx +25 -0
- package/src/modules/widget/components/information-page/information-page.tsx +50 -0
- package/src/modules/widget/components/starter-page/starter-page.tsx +12 -6
- package/src/modules/widget/events.ts +17 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# [1.18.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.17.0...v1.18.0) (2025-07-24)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- add info button onclick ([84dda83](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/84dda83f12399fc785fecae8927a1404cb1d2dae))
|
|
6
|
+
- add information page ([20996ab](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/20996ab224d07e6a44cf27037f7b35b7373aa307))
|
|
7
|
+
|
|
8
|
+
# [1.17.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.16.0...v1.17.0) (2025-07-22)
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
- add header navigation ([1b1f8fb](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/1b1f8fb8ed1cb832bf7bbe4c0ddc1418b9946ce3))
|
|
13
|
+
|
|
1
14
|
# [1.16.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.15.0...v1.16.0) (2025-07-22)
|
|
2
15
|
|
|
3
16
|
### Features
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@ if (devMode) {
|
|
|
18
18
|
container.setAttribute('id', rootId)
|
|
19
19
|
container.setAttribute(
|
|
20
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-
|
|
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-200 shadow-lg overflow-hidden flex flex-col'
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
root?.appendChild(container)
|
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
|
-
import type {
|
|
2
|
+
import type { ButtonHTMLAttributes, PropsWithChildren } from 'react'
|
|
3
|
+
|
|
4
|
+
import { Spinner } from '../spinner'
|
|
3
5
|
|
|
4
6
|
export type ButtonProps = PropsWithChildren<
|
|
5
|
-
|
|
7
|
+
ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
6
8
|
variant?: 'brand' | 'secondary' | 'primary' | 'tertiary' | 'gradient-outline'
|
|
9
|
+
show?: boolean
|
|
10
|
+
loading?: boolean
|
|
7
11
|
}
|
|
8
12
|
>
|
|
9
13
|
|
|
10
|
-
function Button({
|
|
14
|
+
function Button({
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
variant,
|
|
18
|
+
show = true,
|
|
19
|
+
loading = false,
|
|
20
|
+
...props
|
|
21
|
+
}: ButtonProps) {
|
|
11
22
|
const defaultClasses =
|
|
12
23
|
'rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 text-base font-medium'
|
|
13
24
|
const defaultBorder = 'border border-transparent'
|
|
14
25
|
const defaultPadding = 'px-4 py-2'
|
|
26
|
+
const disabledClasses = 'cursor-not-allowed'
|
|
27
|
+
|
|
28
|
+
const content = loading ? <Spinner className='h-full w-full text-current' /> : children
|
|
29
|
+
|
|
30
|
+
if (!show) return null
|
|
15
31
|
|
|
16
32
|
switch (variant) {
|
|
17
33
|
case 'gradient-outline':
|
|
@@ -22,11 +38,13 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
|
|
|
22
38
|
'group relative inline-flex items-center justify-center overflow-hidden rounded-lg bg-gradient-to-br from-[var(--ai-color-gradient-primary)] to-[var(--ai-color-gradient-accent)] p-[1px] hover:text-neutral-1000 group-hover:from-[var(--ai-color-gradient-primary)] group-hover:to-[var(--ai-color-gradient-accent)]',
|
|
23
39
|
className
|
|
24
40
|
)}
|
|
25
|
-
{...props}
|
|
41
|
+
{...props}
|
|
42
|
+
disabled={props.disabled || loading}
|
|
43
|
+
aria-busy={loading}>
|
|
26
44
|
<span
|
|
27
45
|
data-label='text-content'
|
|
28
46
|
className='relative flex-1 rounded-lg bg-neutral-100 px-5 py-2.5 text-neutral-1000 transition-all duration-75 ease-in group-hover:bg-transparent'>
|
|
29
|
-
{
|
|
47
|
+
{content}
|
|
30
48
|
</span>
|
|
31
49
|
</button>
|
|
32
50
|
)
|
|
@@ -40,8 +58,10 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
|
|
|
40
58
|
'bg-primary-500 text-neutral-1000 hover:bg-primary-400 focus:outline-none',
|
|
41
59
|
className
|
|
42
60
|
)}
|
|
43
|
-
{...props}
|
|
44
|
-
{
|
|
61
|
+
{...props}
|
|
62
|
+
disabled={props.disabled || loading}
|
|
63
|
+
aria-busy={loading}>
|
|
64
|
+
{content}
|
|
45
65
|
</button>
|
|
46
66
|
)
|
|
47
67
|
case 'secondary':
|
|
@@ -54,8 +74,10 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
|
|
|
54
74
|
'border-neutral-900 bg-transparent text-neutral-900 hover:bg-neutral-900 hover:text-neutral-100',
|
|
55
75
|
className
|
|
56
76
|
)}
|
|
57
|
-
{...props}
|
|
58
|
-
{
|
|
77
|
+
{...props}
|
|
78
|
+
disabled={props.disabled || loading}
|
|
79
|
+
aria-busy={loading}>
|
|
80
|
+
{content}
|
|
59
81
|
</button>
|
|
60
82
|
)
|
|
61
83
|
case 'tertiary':
|
|
@@ -68,12 +90,13 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
|
|
|
68
90
|
'text-1000 bg-transparent hover:bg-neutral-900',
|
|
69
91
|
className
|
|
70
92
|
)}
|
|
71
|
-
{...props}
|
|
72
|
-
{
|
|
93
|
+
{...props}
|
|
94
|
+
disabled={props.disabled || loading}
|
|
95
|
+
aria-busy={loading}>
|
|
96
|
+
{content}
|
|
73
97
|
</button>
|
|
74
98
|
)
|
|
75
99
|
case 'brand':
|
|
76
|
-
default:
|
|
77
100
|
return (
|
|
78
101
|
<button
|
|
79
102
|
className={clsx(
|
|
@@ -83,8 +106,28 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
|
|
|
83
106
|
'rounded bg-neutral-900 text-neutral-100 outline-none hover:bg-neutral-800',
|
|
84
107
|
className
|
|
85
108
|
)}
|
|
86
|
-
{...props}
|
|
87
|
-
{
|
|
109
|
+
{...props}
|
|
110
|
+
disabled={props.disabled || loading}
|
|
111
|
+
aria-busy={loading}>
|
|
112
|
+
{content}
|
|
113
|
+
</button>
|
|
114
|
+
)
|
|
115
|
+
default:
|
|
116
|
+
return (
|
|
117
|
+
<button
|
|
118
|
+
className={clsx(
|
|
119
|
+
'rounded-full p-2 outline-none transition-colors duration-300 ease-in',
|
|
120
|
+
{
|
|
121
|
+
'cursor-pointer ring-primary-500 hover:bg-neutral-300 focus:bg-neutral-300 focus-visible:ring-2':
|
|
122
|
+
!props.disabled,
|
|
123
|
+
[disabledClasses]: props.disabled
|
|
124
|
+
},
|
|
125
|
+
className
|
|
126
|
+
)}
|
|
127
|
+
{...props}
|
|
128
|
+
disabled={props.disabled || loading}
|
|
129
|
+
aria-busy={loading}>
|
|
130
|
+
{content}
|
|
88
131
|
</button>
|
|
89
132
|
)
|
|
90
133
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path
|
|
3
|
+
d="M14.5 1H1.5C0.65625 1 0 1.6875 0 2.5V4.5C0 4.78125 0.21875 5 0.5 5H1V13C1 14.125 1.875 15 3 15H13C14.0938 15 15 14.125 15 13V5H15.5C15.75 5 16 4.78125 16 4.5V2.5C16 1.6875 15.3125 1 14.5 1ZM14 13C14 13.5625 13.5312 14 13 14H3C2.4375 14 2 13.5625 2 13V5H14V13ZM15 4H1V2.5C1 2.25 1.21875 2 1.5 2H14.5C14.75 2 15 2.25 15 2.5V4ZM5.5 8H10.5C10.75 8 11 7.78125 11 7.5C11 7.25 10.75 7 10.5 7H5.5C5.21875 7 5 7.25 5 7.5C5 7.78125 5.21875 8 5.5 8Z"
|
|
4
|
+
fill="currentColor" />
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_18455_21368)">
|
|
3
|
+
<path d="M15.75 14.2358H4.5C4.08579 14.2358 3.75 14.5716 3.75 14.9858C3.75 15.4001 4.08579 15.7358 4.5 15.7358H15.75V17.2358H4.5C3.25736 17.2358 2.25 16.2285 2.25 14.9858V3.73584C2.25 2.90741 2.92157 2.23584 3.75 2.23584H15.75V14.2358ZM3.75 12.7733C3.87117 12.7487 3.99658 12.7358 4.125 12.7358H14.25V3.73584H3.75V12.7733ZM12 7.48584H6V5.98584H12V7.48584Z" fill="url(#paint0_linear_18455_21368)"/>
|
|
4
|
+
</g>
|
|
5
|
+
<defs>
|
|
6
|
+
<linearGradient id="paint0_linear_18455_21368" x1="2.25" y1="9.73584" x2="15.75" y2="9.73584" gradientUnits="userSpaceOnUse">
|
|
7
|
+
<stop stop-color="#44D0FF"/>
|
|
8
|
+
<stop offset="1" stop-color="#B48EFF"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<clipPath id="clip0_18455_21368">
|
|
11
|
+
<rect width="18" height="18" fill="white" transform="translate(0 0.73584)"/>
|
|
12
|
+
</clipPath>
|
|
13
|
+
</defs>
|
|
14
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path
|
|
3
|
+
d="M15.2656 15.3125C14.9844 15.5938 14.4688 15.5938 14.1875 15.3125L8 9.07812L1.76562 15.3125C1.48438 15.5938 0.96875 15.5938 0.6875 15.3125C0.40625 15.0312 0.40625 14.5156 0.6875 14.2344L6.92188 8L0.6875 1.8125C0.40625 1.53125 0.40625 1.01562 0.6875 0.734375C0.96875 0.453125 1.48438 0.453125 1.76562 0.734375L8 6.96875L14.1875 0.734375C14.4688 0.453125 14.9844 0.453125 15.2656 0.734375C15.5469 1.01562 15.5469 1.53125 15.2656 1.8125L9.03125 8L15.2656 14.2344C15.5469 14.5156 15.5469 15.0312 15.2656 15.3125Z"
|
|
4
|
+
fill="currentColor" />
|
|
5
|
+
</svg>
|
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
// Auto-generated file - DO NOT EDIT
|
|
2
|
-
export type ValidIconNames =
|
|
2
|
+
export type ValidIconNames =
|
|
3
|
+
| 'ai-color'
|
|
4
|
+
| 'archive'
|
|
5
|
+
| 'arrow-down'
|
|
6
|
+
| 'arrow-left'
|
|
7
|
+
| 'chevron-down'
|
|
8
|
+
| 'close'
|
|
9
|
+
| 'info'
|
|
10
|
+
| 'send'
|
|
11
|
+
| 'stop'
|
|
12
|
+
| 'warning'
|
|
13
|
+
| 'interrogation'
|
|
14
|
+
| 'book'
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
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" />
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M8 0.73584C3.5625 0.73584 0 4.32959 0 8.73584C0 13.1733 3.5625 16.7358 8 16.7358C12.4062 16.7358 16 13.1733 16 8.73584C16 4.32959 12.4062 0.73584 8 0.73584ZM8 15.7358C4.125 15.7358 1 12.6108 1 8.73584C1 4.89209 4.125 1.73584 8 1.73584C11.8438 1.73584 15 4.89209 15 8.73584C15 12.6108 11.8438 15.7358 8 15.7358ZM7.5 11.4858C7.0625 11.4858 6.75 11.8296 6.75 12.2358C6.75 12.6733 7.0625 12.9858 7.5 12.9858C7.90625 12.9858 8.25 12.6733 8.25 12.2358C8.25 11.8296 7.90625 11.4858 7.5 11.4858ZM8.90625 4.73584H7.0625C5.90625 4.73584 5 5.67334 5 6.82959V7.11084C5 7.39209 5.21875 7.61084 5.5 7.61084C5.75 7.61084 6 7.39209 6 7.11084V6.82959C6 6.23584 6.46875 5.73584 7.0625 5.73584H8.90625C9.5 5.73584 10 6.23584 10 6.82959C10 7.20459 9.78125 7.57959 9.4375 7.76709L7.5625 8.70459C7.21875 8.89209 7 9.26709 7 9.67334V10.2358C7 10.5171 7.21875 10.7358 7.5 10.7358C7.75 10.7358 8 10.5171 8 10.2358V9.67334C8 9.64209 8 9.61084 8.03125 9.57959L9.90625 8.64209C10.5625 8.26709 11 7.57959 11 6.82959C11 5.67334 10.0625 4.73584 8.90625 4.73584Z" fill="url(#paint0_linear_21475_7939)"/>
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="paint0_linear_21475_7939" x1="0" y1="8.73584" x2="16" y2="8.73584" gradientUnits="userSpaceOnUse">
|
|
5
|
+
<stop stop-color="#44D0FF"/>
|
|
6
|
+
<stop offset="1" stop-color="#B48EFF"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
</svg>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M8 11.3193C7.5625 11.3193 7.25 11.6631 7.25 12.0693C7.25 12.5068 7.59375 12.8193 8 12.8193C8.40625 12.8193 8.71875 12.5068 8.71875 12.0693C8.75 11.6631 8.40625 11.3193 8 11.3193ZM8 10.0693C8.25 10.0693 8.46875 9.85059 8.46875 9.56934V5.06934C8.46875 4.81934 8.21875 4.56934 8 4.56934C7.75 4.56934 7.5 4.81934 7.5 5.06934V9.56934C7.5 9.85059 7.71875 10.0693 8 10.0693ZM15.75 12.5068L9.5 1.94434C9.1875 1.41309 8.625 1.10059 8 1.06934C7.34375 1.06934 6.78125 1.41309 6.46875 1.94434L0.21875 12.5068C-0.09375 13.0381 -0.09375 13.6631 0.21875 14.1943C0.53125 14.7568 1.09375 15.0693 1.75 15.0693H14.25C14.875 15.0693 15.4375 14.7568 15.75 14.1943C16.0625 13.6631 16.0625 13.0381 15.75 12.5068ZM14.875 13.6943C14.75 13.9443 14.5 14.0693 14.2188 14.0693H1.75C1.46875 14.0693 1.21875 13.9443 1.09375 13.6943C0.9375 13.4756 0.96875 13.2256 1.09375 13.0068L7.34375 2.44434C7.46875 2.22559 7.71875 2.06934 8 2.06934C8.25 2.10059 8.5 2.22559 8.625 2.44434L14.875 13.0068C15 13.2256 15.0312 13.4756 14.875 13.6943Z" fill="url(#paint0_linear_21475_9749)"/>
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="paint0_linear_21475_9749" x1="-0.015625" y1="8.06934" x2="15.9844" y2="8.06934" gradientUnits="userSpaceOnUse">
|
|
5
|
+
<stop stop-color="#44D0FF"/>
|
|
6
|
+
<stop offset="1" stop-color="#B48EFF"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
</svg>
|
|
@@ -4,7 +4,7 @@ import type { ChangeEvent, KeyboardEvent } from 'react'
|
|
|
4
4
|
import { useTranslation } from 'react-i18next'
|
|
5
5
|
import TextareaAutosize from 'react-textarea-autosize'
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { Button, Icon } from '@/src/lib/components'
|
|
8
8
|
|
|
9
9
|
import { useChatInputValueAtom } from './chat-input.atom'
|
|
10
10
|
import type { ChatInputProps } from './types'
|
|
@@ -82,30 +82,20 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
|
|
82
82
|
onKeyDown={handleKeyDown}
|
|
83
83
|
disabled={inputDisabled}
|
|
84
84
|
/>
|
|
85
|
-
<
|
|
85
|
+
<Button
|
|
86
86
|
onClick={onSend}
|
|
87
87
|
disabled={buttonDisabled || loading}
|
|
88
|
-
className={clsx(
|
|
89
|
-
'
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
'cursor-not-allowed': buttonDisabled
|
|
94
|
-
}
|
|
95
|
-
)}
|
|
88
|
+
className={clsx('flex size-8 flex-col items-center justify-center', {
|
|
89
|
+
'text-neutral-700': !buttonDisabled,
|
|
90
|
+
'text-neutral-400': buttonDisabled
|
|
91
|
+
})}
|
|
92
|
+
loading={loading}
|
|
96
93
|
aria-label='Submit Button'>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
className={clsx('h-4 w-4 pr-0.5 pt-0.5 transition-colors duration-150', {
|
|
103
|
-
'text-neutral-700': !buttonDisabled,
|
|
104
|
-
'text-neutral-400': buttonDisabled
|
|
105
|
-
})}
|
|
106
|
-
/>
|
|
107
|
-
)}
|
|
108
|
-
</button>
|
|
94
|
+
<Icon
|
|
95
|
+
name='send'
|
|
96
|
+
className='h-4 w-4 pr-0.5 pt-0.5 text-current transition-colors duration-150'
|
|
97
|
+
/>
|
|
98
|
+
</Button>
|
|
109
99
|
</div>
|
|
110
100
|
)
|
|
111
101
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
|
|
3
|
+
import { Icon } from '@/src/lib/components'
|
|
4
|
+
|
|
5
|
+
function AIAvatarIcon({
|
|
6
|
+
className = 'rounded-full border-4 border-neutral-100 bg-neutral-200'
|
|
7
|
+
}: {
|
|
8
|
+
className?: string
|
|
9
|
+
}) {
|
|
10
|
+
return (
|
|
11
|
+
<div className={clsx('flex h-11 w-11 items-center justify-center', className)}>
|
|
12
|
+
<Icon name='ai-color' className='h-6 w-6' aria-label='AI avatar Icon' />
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default AIAvatarIcon
|
|
@@ -2,16 +2,21 @@ import clsx from 'clsx'
|
|
|
2
2
|
|
|
3
3
|
import { Icon } from '@/src/lib/components'
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import styles from './styles.module.css'
|
|
6
|
+
|
|
7
|
+
export type AIAvatarProps = { className?: string }
|
|
8
|
+
|
|
9
|
+
function AIAvatar({
|
|
6
10
|
className = 'rounded-full border-4 border-neutral-100 bg-neutral-200'
|
|
7
|
-
}: {
|
|
8
|
-
className?: string
|
|
9
|
-
}) {
|
|
11
|
+
}: AIAvatarProps) {
|
|
10
12
|
return (
|
|
11
|
-
<
|
|
12
|
-
|
|
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)}>
|
|
16
|
+
<Icon name='ai-color' className='h-6 w-6' aria-label='AI avatar Icon' />
|
|
17
|
+
</div>
|
|
18
|
+
</figure>
|
|
14
19
|
)
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
export default
|
|
22
|
+
export default AIAvatar
|
|
@@ -3,7 +3,12 @@ import { useRef } from 'react'
|
|
|
3
3
|
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
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
useWidgetLoadingAtomValue,
|
|
8
|
+
useWidgetSettingsAtomValue,
|
|
9
|
+
useWidgetTabsValueAtom
|
|
10
|
+
} from '../../store'
|
|
11
|
+
import { WidgetHeader } from '../header'
|
|
7
12
|
import { PageLayout } from '../page-layout'
|
|
8
13
|
|
|
9
14
|
function ChatPage() {
|
|
@@ -13,6 +18,7 @@ function ChatPage() {
|
|
|
13
18
|
const { messagesQuery } = useAllMessages()
|
|
14
19
|
const widgetLoading = useWidgetLoadingAtomValue()
|
|
15
20
|
const [value, setValue] = useChatInputValueAtom()
|
|
21
|
+
const settings = useWidgetSettingsAtomValue()
|
|
16
22
|
|
|
17
23
|
const handleSendMessage = () => {
|
|
18
24
|
const text = chatInputRef.current?.value ?? ''
|
|
@@ -39,7 +45,16 @@ function ChatPage() {
|
|
|
39
45
|
buttonDisabled={messagesQuery?.isLoading || !value.trim()}
|
|
40
46
|
/>
|
|
41
47
|
}>
|
|
42
|
-
|
|
48
|
+
<>
|
|
49
|
+
<div className='mt-4 px-6 py-4'>
|
|
50
|
+
<WidgetHeader
|
|
51
|
+
enabledButtons={['info', 'close']}
|
|
52
|
+
clubName={settings?.clubName}
|
|
53
|
+
tutorName={settings?.tutorName}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
<MessagesList />
|
|
57
|
+
</>
|
|
43
58
|
</PageLayout>
|
|
44
59
|
)
|
|
45
60
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ChatPage } from './chat-page'
|
|
2
|
+
import { WidgetInformationPage } from './information-page'
|
|
2
3
|
import { WidgetLoadingPage } from './loading-page'
|
|
3
4
|
import { WidgetOnboardingPage } from './onboarding-page'
|
|
4
5
|
import { WidgetStarterPage } from './starter-page'
|
|
@@ -7,5 +8,6 @@ export const WIDGET_TABS = {
|
|
|
7
8
|
onboarding: <WidgetOnboardingPage />,
|
|
8
9
|
starter: <WidgetStarterPage />,
|
|
9
10
|
chat: <ChatPage />,
|
|
10
|
-
loading: <WidgetLoadingPage
|
|
11
|
+
loading: <WidgetLoadingPage />,
|
|
12
|
+
information: <WidgetInformationPage />
|
|
11
13
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
2
|
import { useTranslation } from 'react-i18next'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { AIAvatar } from '../ai-avatar'
|
|
5
5
|
|
|
6
6
|
import styles from './styles.module.css'
|
|
7
7
|
|
|
@@ -16,13 +16,7 @@ function GreetingsCard({ author, tutorName }: GreetingsCardProps) {
|
|
|
16
16
|
return (
|
|
17
17
|
<div className='flex flex-col items-center justify-center text-neutral-900'>
|
|
18
18
|
<div className='flex flex-col items-center justify-center gap-4 text-center'>
|
|
19
|
-
<
|
|
20
|
-
className={clsx(
|
|
21
|
-
'flex h-12 w-12 items-center justify-center rounded-full',
|
|
22
|
-
styles.avatar
|
|
23
|
-
)}>
|
|
24
|
-
<AIAvatarIcon />
|
|
25
|
-
</figure>
|
|
19
|
+
<AIAvatar />
|
|
26
20
|
<div className='flex flex-col gap-2'>
|
|
27
21
|
<span className='text-base font-light'>
|
|
28
22
|
{t('general.greetings.hello', { name: author })}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
2
|
+
import type { WidgetHeaderProps } from '../types'
|
|
3
|
+
|
|
4
|
+
class WidgetHeaderPropsBuilder implements WidgetHeaderProps {
|
|
5
|
+
enabledButtons: ValidIconNames[]
|
|
6
|
+
showContent?: boolean
|
|
7
|
+
showContentWithoutMeta?: boolean
|
|
8
|
+
clubName?: string
|
|
9
|
+
tutorName?: string
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.enabledButtons = ['close']
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
withEnabledButtons(enabledButtons: typeof this.enabledButtons) {
|
|
16
|
+
this.enabledButtons = enabledButtons
|
|
17
|
+
|
|
18
|
+
return this
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
withShowContent(showContent: typeof this.showContent) {
|
|
22
|
+
this.showContent = showContent
|
|
23
|
+
|
|
24
|
+
return this
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
withShowContentWithoutMeta(showContentWithoutMeta: typeof this.showContentWithoutMeta) {
|
|
28
|
+
this.showContentWithoutMeta = showContentWithoutMeta
|
|
29
|
+
|
|
30
|
+
return this
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
withClubName(clubName: typeof this.clubName) {
|
|
34
|
+
this.clubName = clubName
|
|
35
|
+
|
|
36
|
+
return this
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
withTutorName(tutorName: typeof this.tutorName) {
|
|
40
|
+
this.tutorName = tutorName
|
|
41
|
+
|
|
42
|
+
return this
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default WidgetHeaderPropsBuilder
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { render, screen } from '@/src/config/tests'
|
|
2
|
+
|
|
3
|
+
import WidgetHeaderPropsBuilder from './__tests__/widget-header-props.builder'
|
|
4
|
+
import WidgetHeader from './header'
|
|
5
|
+
|
|
6
|
+
describe('<WidgetHeader />', () => {
|
|
7
|
+
const defaultProps = new WidgetHeaderPropsBuilder()
|
|
8
|
+
const renderComponent = (props = defaultProps) => render(<WidgetHeader {...props} />)
|
|
9
|
+
|
|
10
|
+
it('should render the only the enabled button', () => {
|
|
11
|
+
renderComponent()
|
|
12
|
+
|
|
13
|
+
expect(screen.getByRole('button', { name: /Close Icon/i })).toBeInTheDocument()
|
|
14
|
+
|
|
15
|
+
expect(screen.queryByRole('button', { name: /Archive Icon/i })).not.toBeInTheDocument()
|
|
16
|
+
expect(screen.queryByRole('button', { name: /Info Icon/i })).not.toBeInTheDocument()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should render WidgetHeaderContent when prop showContent is true', () => {
|
|
20
|
+
renderComponent()
|
|
21
|
+
|
|
22
|
+
expect(screen.getByText(/ai-color/i)).toBeInTheDocument()
|
|
23
|
+
|
|
24
|
+
expect(screen.queryByRole('button', { name: /Arrow Left Icon/i })).not.toBeInTheDocument()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should render WidgetHeaderContentWithoutMeta when prop showContentWithoutMeta is true and showContent is false', () => {
|
|
28
|
+
const props = new WidgetHeaderPropsBuilder()
|
|
29
|
+
.withShowContentWithoutMeta(true)
|
|
30
|
+
.withShowContent(false)
|
|
31
|
+
|
|
32
|
+
renderComponent(props)
|
|
33
|
+
|
|
34
|
+
expect(screen.getByRole('button', { name: /Arrow Left Icon/i })).toBeInTheDocument()
|
|
35
|
+
|
|
36
|
+
expect(screen.queryByText(/ai-color/i)).not.toBeInTheDocument()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should be able to render the remaining icons', () => {
|
|
40
|
+
const props = new WidgetHeaderPropsBuilder().withEnabledButtons(['archive', 'info'])
|
|
41
|
+
|
|
42
|
+
renderComponent(props)
|
|
43
|
+
|
|
44
|
+
expect(screen.getByRole('button', { name: /Archive Icon/i })).toBeInTheDocument()
|
|
45
|
+
expect(screen.getByRole('button', { name: /Info Icon/i })).toBeInTheDocument()
|
|
46
|
+
})
|
|
47
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Button, Icon } from '@/src/lib/components'
|
|
2
|
+
import { TutorWidgetEvents } from '../../events'
|
|
3
|
+
import { useWidgetTabsAtom } from '../../store'
|
|
4
|
+
import { AIAvatar } from '../ai-avatar'
|
|
5
|
+
|
|
6
|
+
import type { WidgetHeaderContentProps, WidgetHeaderProps } from './types'
|
|
7
|
+
|
|
8
|
+
export function WidgetHeaderContent({ clubName, tutorName }: WidgetHeaderContentProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div className='flex w-full gap-2'>
|
|
11
|
+
<AIAvatar />
|
|
12
|
+
<div className='flex flex-col'>
|
|
13
|
+
{tutorName && <h4 className='text-base'>{tutorName}</h4>}
|
|
14
|
+
{clubName && <p className='text-sm/normal text-neutral-600'>{clubName}</p>}
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function WidgetHeaderContentWithoutMeta({ name }: { name?: string }) {
|
|
21
|
+
return (
|
|
22
|
+
<div className='grid-areas-[a_b] grid grid-cols-[auto_1fr] items-center gap-1'>
|
|
23
|
+
<Button className='grid-area-[a]' aria-label='Arrow Left Icon'>
|
|
24
|
+
<Icon name='arrow-left' width={15} height={15} aria-hidden />
|
|
25
|
+
</Button>
|
|
26
|
+
<div className='grid-area-[b] flex justify-center'>
|
|
27
|
+
<span>{name}</span>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function WidgetHeader({
|
|
34
|
+
enabledButtons,
|
|
35
|
+
clubName,
|
|
36
|
+
tutorName,
|
|
37
|
+
showContentWithoutMeta,
|
|
38
|
+
showContent = true
|
|
39
|
+
}: WidgetHeaderProps) {
|
|
40
|
+
const [, setWidgetTabs] = useWidgetTabsAtom()
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className='grid-areas-[a_b] mt-0.5 grid grid-cols-[1fr_auto] items-center text-neutral-1000'>
|
|
44
|
+
<div className='grid-area-[a]'>
|
|
45
|
+
{showContent && !showContentWithoutMeta && (
|
|
46
|
+
<WidgetHeaderContent clubName={clubName} tutorName={tutorName} />
|
|
47
|
+
)}
|
|
48
|
+
{showContentWithoutMeta && !showContent && (
|
|
49
|
+
<WidgetHeaderContentWithoutMeta name={tutorName} />
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
<div className='shrink-0'>
|
|
53
|
+
<div className='grid-area-[b] ml-auto flex max-w-max gap-3 text-neutral-700'>
|
|
54
|
+
<Button show={enabledButtons.includes('archive')} aria-label='Archive Icon'>
|
|
55
|
+
<Icon name='archive' className='h-4 w-4' aria-hidden />
|
|
56
|
+
</Button>
|
|
57
|
+
<Button
|
|
58
|
+
show={enabledButtons.includes('info')}
|
|
59
|
+
aria-label='Info Icon'
|
|
60
|
+
onClick={() => setWidgetTabs('information')}>
|
|
61
|
+
<Icon name='info' className='h-4 w-4' aria-hidden />
|
|
62
|
+
</Button>
|
|
63
|
+
<Button
|
|
64
|
+
show={enabledButtons.includes('close')}
|
|
65
|
+
onClick={() => TutorWidgetEvents['c3po-app-widget-hide'].dispatch()}
|
|
66
|
+
aria-label='Close Icon'>
|
|
67
|
+
<Icon name='close' className='h-4 w-4' aria-hidden />
|
|
68
|
+
</Button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default WidgetHeader
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
2
|
+
|
|
3
|
+
export type WidgetHeaderContentProps = { clubName?: string; tutorName?: string }
|
|
4
|
+
|
|
5
|
+
export type WidgetHeaderProps = {
|
|
6
|
+
enabledButtons: ValidIconNames[]
|
|
7
|
+
showContent?: boolean
|
|
8
|
+
showContentWithoutMeta?: boolean
|
|
9
|
+
} & WidgetHeaderContentProps
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
2
|
+
|
|
3
|
+
type InfoItem = {
|
|
4
|
+
icon: ValidIconNames
|
|
5
|
+
titleKey: string
|
|
6
|
+
descKey: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const infoItems: InfoItem[] = [
|
|
10
|
+
{
|
|
11
|
+
icon: 'interrogation',
|
|
12
|
+
titleKey: 'info.what_it_does_question',
|
|
13
|
+
descKey: 'info.what_it_does_answer'
|
|
14
|
+
},
|
|
15
|
+
{ icon: 'book', titleKey: 'info.how_it_learns_question', descKey: 'info.how_it_learns_answer' },
|
|
16
|
+
{ icon: 'warning', titleKey: 'info.limitations_question', descKey: 'info.limitations_answer' }
|
|
17
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as WidgetInformationPage } from './information-page'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as InformationCard } from './information-card'
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Icon } from '@/src/lib/components'
|
|
2
|
+
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
3
|
+
|
|
4
|
+
export type InformationCardProps = {
|
|
5
|
+
icon: ValidIconNames
|
|
6
|
+
title: string
|
|
7
|
+
description: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function InformationCard({ icon, title, description }: InformationCardProps) {
|
|
11
|
+
return (
|
|
12
|
+
<div className='flex gap-3 border-b border-white/10 pb-5 last:border-none'>
|
|
13
|
+
<div className='flex h-5 w-5 items-start justify-center'>
|
|
14
|
+
<Icon name={icon} width={16} height={16} />
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div className='flex flex-col gap-1'>
|
|
18
|
+
<p className='text-sm font-bold'>{title}</p>
|
|
19
|
+
<p className='text-xs text-gray-300'>{description}</p>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default InformationCard
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next'
|
|
2
|
+
|
|
3
|
+
import { Icon } from '@/src/lib/components'
|
|
4
|
+
import { useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
|
|
5
|
+
import { AIAvatar } from '../ai-avatar'
|
|
6
|
+
import { PageLayout } from '../page-layout'
|
|
7
|
+
|
|
8
|
+
import { infoItems } from './constants'
|
|
9
|
+
import { InformationCard } from './information-card'
|
|
10
|
+
|
|
11
|
+
function WidgetInformationPage() {
|
|
12
|
+
const { t } = useTranslation()
|
|
13
|
+
const [, setWidgetTabs] = useWidgetTabsAtom()
|
|
14
|
+
const [settings] = useWidgetSettingsAtom()
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<PageLayout className='p-5 text-white'>
|
|
18
|
+
<div className='relative mb-8 flex h-12 items-center justify-center'>
|
|
19
|
+
<button
|
|
20
|
+
className='absolute left-0'
|
|
21
|
+
aria-label='Return Button'
|
|
22
|
+
onClick={() => setWidgetTabs('chat')}>
|
|
23
|
+
<Icon name='arrow-left' width={16} height={16} />
|
|
24
|
+
</button>
|
|
25
|
+
<h1 className='mx-auto font-bold'>{t('info.title')}</h1>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div className='mb-8 flex justify-center'>
|
|
29
|
+
<div className='flex flex-col items-center gap-2'>
|
|
30
|
+
<AIAvatar />
|
|
31
|
+
|
|
32
|
+
<h3 className='font-bold'>{settings?.tutorName ?? ''}</h3>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div className='flex flex-col gap-5'>
|
|
37
|
+
{infoItems.map((item) => (
|
|
38
|
+
<InformationCard
|
|
39
|
+
key={item.titleKey}
|
|
40
|
+
icon={item.icon}
|
|
41
|
+
title={t(item.titleKey)}
|
|
42
|
+
description={t(item.descKey)}
|
|
43
|
+
/>
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
</PageLayout>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default WidgetInformationPage
|
|
@@ -9,6 +9,7 @@ import { ChatInput, useChatInputValueAtom } from '@/src/modules/messages/compone
|
|
|
9
9
|
import { useSendTextMessage } from '@/src/modules/messages/hooks'
|
|
10
10
|
import { useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
|
|
11
11
|
import { GreetingsCard } from '../greetings-card'
|
|
12
|
+
import { WidgetHeader } from '../header'
|
|
12
13
|
import { PageLayout } from '../page-layout'
|
|
13
14
|
|
|
14
15
|
import styles from './styles.module.css'
|
|
@@ -57,12 +58,17 @@ function WidgetStarterPage() {
|
|
|
57
58
|
/>
|
|
58
59
|
}>
|
|
59
60
|
<div className='grid-areas-[a_b] grid h-full grid-cols-1 grid-rows-[1fr_auto]'>
|
|
60
|
-
<div
|
|
61
|
-
|
|
62
|
-
'
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
<div className={clsx('grid-area-[a] flex min-h-0 flex-col px-5 py-4', styles.bg)}>
|
|
62
|
+
<WidgetHeader
|
|
63
|
+
enabledButtons={['archive', 'info', 'close']}
|
|
64
|
+
clubName={settings?.clubName}
|
|
65
|
+
tutorName={settings?.tutorName}
|
|
66
|
+
showContent={false}
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
<div className='my-auto'>
|
|
70
|
+
<GreetingsCard author={settings?.author ?? ''} tutorName={settings?.tutorName ?? ''} />
|
|
71
|
+
</div>
|
|
66
72
|
</div>
|
|
67
73
|
<div className='grid-area-[b] mx-5 my-6 flex flex-shrink-0 snap-x snap-mandatory gap-2 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'>
|
|
68
74
|
<Button
|
|
@@ -3,6 +3,7 @@ import type { ITutorWidgetEvent } from './types'
|
|
|
3
3
|
export const TutorWidgetEventTypes = {
|
|
4
4
|
OPEN: 'c3po-app-widget-open',
|
|
5
5
|
CLOSE: 'c3po-app-widget-close',
|
|
6
|
+
HIDE: 'c3po-app-widget-hide',
|
|
6
7
|
LOADED: 'tutor-app-widget-loaded'
|
|
7
8
|
} as const
|
|
8
9
|
|
|
@@ -41,6 +42,22 @@ const TutorWidgetEventsObject = {
|
|
|
41
42
|
dispatch: () => window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.CLOSE))
|
|
42
43
|
} as ITutorWidgetEvent<void>,
|
|
43
44
|
|
|
45
|
+
[TutorWidgetEventTypes.HIDE]: {
|
|
46
|
+
name: TutorWidgetEventTypes.HIDE,
|
|
47
|
+
handler: (callback) => {
|
|
48
|
+
const listener: EventListener = () => {
|
|
49
|
+
void callback()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
window.addEventListener(TutorWidgetEventTypes.HIDE, listener)
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
window.removeEventListener(TutorWidgetEventTypes.HIDE, listener)
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
dispatch: () => window.dispatchEvent(new CustomEvent(TutorWidgetEventTypes.HIDE))
|
|
59
|
+
} as ITutorWidgetEvent<void>,
|
|
60
|
+
|
|
44
61
|
[TutorWidgetEventTypes.LOADED]: {
|
|
45
62
|
name: TutorWidgetEventTypes.LOADED,
|
|
46
63
|
handler: (callback: (payload: { isSuccess: boolean }) => void) => {
|