app-tutor-ai-consumer 1.0.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 (111) hide show
  1. package/.adr-dir +1 -0
  2. package/.github/CODEOWNERS +1 -0
  3. package/.github/pull_request_template.md +46 -0
  4. package/.github/workflows/jira.yaml +13 -0
  5. package/.github/workflows/merge-checker.yaml +14 -0
  6. package/.github/workflows/pr-agent.yaml +17 -0
  7. package/.github/workflows/production.yml +155 -0
  8. package/.github/workflows/quality.yml +63 -0
  9. package/.github/workflows/rollback.yml +133 -0
  10. package/.github/workflows/staging.yml +153 -0
  11. package/.husky/commit-msg +1 -0
  12. package/.husky/post-merge +1 -0
  13. package/.husky/pre-commit +1 -0
  14. package/.nvmrc +1 -0
  15. package/.prettierignore +8 -0
  16. package/.prettierrc +20 -0
  17. package/.releaserc.json +22 -0
  18. package/CHANGELOG.md +7 -0
  19. package/babel.config.js +3 -0
  20. package/catalog-info.yaml +21 -0
  21. package/commitlint.config.js +8 -0
  22. package/config/certs/.keep +0 -0
  23. package/config/certs/ssl-generate.sh +12 -0
  24. package/config/rspack/rspack.config.js +176 -0
  25. package/config/rspack/utils/alias.js +12 -0
  26. package/config/rspack/utils/devserver.config.js +34 -0
  27. package/config/rspack/utils/envs.js +39 -0
  28. package/config/rspack/utils/paths.js +29 -0
  29. package/config/rspack/utils/plugins.js +41 -0
  30. package/config/vitest/__mocks__/i18n.tsx +15 -0
  31. package/config/vitest/index.ts +1 -0
  32. package/config/vitest/polyfills/global.js +19 -0
  33. package/config/vitest/setupTests.ts +26 -0
  34. package/config/vitest/vitest.config.mts +47 -0
  35. package/docs/README.md +15 -0
  36. package/docs/architecture/decisions/index.md +0 -0
  37. package/docs/architecture/decisions/templates/template.md +82 -0
  38. package/docs/architecture/design/index.md +0 -0
  39. package/environments/.env.development +42 -0
  40. package/environments/.env.production +41 -0
  41. package/environments/.env.staging +40 -0
  42. package/environments/.env.test +41 -0
  43. package/eslint.config.mjs +87 -0
  44. package/mkdocs.yaml +11 -0
  45. package/package.json +130 -0
  46. package/postcss.config.js +3 -0
  47. package/public/favicon.ico +0 -0
  48. package/public/index.html +18 -0
  49. package/scripts/generate-icon-types.js +31 -0
  50. package/src/@types/declarations.d.ts +25 -0
  51. package/src/@types/env.d.ts +1 -0
  52. package/src/@types/global.types.ts +64 -0
  53. package/src/@types/index.d.ts +16 -0
  54. package/src/config/i18n/constants.ts +9 -0
  55. package/src/config/i18n/hooks/index.ts +1 -0
  56. package/src/config/i18n/hooks/use-app-lang/index.ts +1 -0
  57. package/src/config/i18n/hooks/use-app-lang/use-app-lang.tsx +22 -0
  58. package/src/config/i18n/index.ts +5 -0
  59. package/src/config/i18n/init.ts +44 -0
  60. package/src/config/i18n/types.ts +5 -0
  61. package/src/config/i18n/utils/get-lang.ts +10 -0
  62. package/src/config/styles/global.css +171 -0
  63. package/src/config/styles/index.css +5 -0
  64. package/src/config/styles/shared-styles.module.css +16 -0
  65. package/src/config/tests/abstract-mock-generator.ts +9 -0
  66. package/src/config/tests/customRenderHook.tsx +32 -0
  67. package/src/config/tests/handlers.ts +7 -0
  68. package/src/config/tests/index.ts +4 -0
  69. package/src/config/tests/mockRequest.tsx +36 -0
  70. package/src/config/tests/types.ts +10 -0
  71. package/src/config/tests/utils.tsx +38 -0
  72. package/src/config/tests/worker.ts +5 -0
  73. package/src/config/tests/wrappers.tsx +50 -0
  74. package/src/development-bootstrap.tsx +26 -0
  75. package/src/index.tsx +26 -0
  76. package/src/lib/components/icons/icon-names.d.ts +2 -0
  77. package/src/lib/components/icons/icon.tsx +41 -0
  78. package/src/lib/components/icons/index.ts +1 -0
  79. package/src/lib/components/icons/send.svg +3 -0
  80. package/src/lib/components/index.ts +1 -0
  81. package/src/lib/components/spinner/index.ts +2 -0
  82. package/src/lib/components/spinner/spinner.tsx +25 -0
  83. package/src/lib/components/spinner/styles.module.css +31 -0
  84. package/src/lib/components/spinner/types.ts +1 -0
  85. package/src/lib/hooks/index.ts +1 -0
  86. package/src/lib/hooks/use-default-id/index.ts +1 -0
  87. package/src/lib/hooks/use-default-id/use-default-id.tsx +13 -0
  88. package/src/lib/utils/constants.ts +2 -0
  89. package/src/lib/utils/http-codes.ts +13 -0
  90. package/src/lib/utils/index.ts +3 -0
  91. package/src/lib/utils/languages.ts +7 -0
  92. package/src/main/index.ts +1 -0
  93. package/src/main/main.spec.tsx +15 -0
  94. package/src/main/main.tsx +27 -0
  95. package/src/main/styles.module.css +15 -0
  96. package/src/modules/create-message/components/chat-input/chat-input.tsx +29 -0
  97. package/src/modules/create-message/components/chat-input/index.ts +2 -0
  98. package/src/modules/create-message/components/chat-input/types.ts +3 -0
  99. package/src/modules/create-message/components/index.ts +1 -0
  100. package/src/modules/widget/components/ai-avatar/ai-avatar.tsx +59 -0
  101. package/src/modules/widget/components/ai-avatar/index.ts +1 -0
  102. package/src/modules/widget/components/greetings-card/greetings-card.tsx +40 -0
  103. package/src/modules/widget/components/greetings-card/index.ts +1 -0
  104. package/src/modules/widget/components/greetings-card/styles.module.css +9 -0
  105. package/src/modules/widget/components/index.ts +1 -0
  106. package/src/modules/widget/events.ts +21 -0
  107. package/src/modules/widget/index.ts +2 -0
  108. package/src/modules/widget/types.ts +7 -0
  109. package/src/types.ts +23 -0
  110. package/tailwind.config.js +8 -0
  111. package/tsconfig.json +128 -0
@@ -0,0 +1,171 @@
1
+ :root {
2
+ --hc-color-primary-100: #ebf0ff;
3
+ /* Hotmart Product */
4
+ --hc-color-primary-200: #bed1ff;
5
+ /* Hotmart Product */
6
+ --hc-color-primary-300: #89a8f8;
7
+ /* Hotmart Product */
8
+ --hc-color-primary-400: #5981e3;
9
+ /* Hotmart Product */
10
+ --hc-color-primary-500: #355cc0;
11
+ /* Hotmart Product */
12
+ --hc-color-primary-600: #253f82;
13
+ /* Hotmart Product */
14
+ --hc-color-primary-700: #162c64;
15
+ /* Hotmart Product */
16
+ --hc-color-secondary-100: #edffff;
17
+ --hc-color-secondary-200: #a2eaea;
18
+ --hc-color-secondary-300: #7cdbdb;
19
+ --hc-color-secondary-400: #51c1c3;
20
+ --hc-color-secondary-500: #0a9090;
21
+ --hc-color-secondary-600: #066262;
22
+ --hc-color-secondary-700: #024242;
23
+ --hc-color-success-100: #edfff5;
24
+ --hc-color-success-200: #99e9bb;
25
+ --hc-color-success-300: #4acc82;
26
+ --hc-color-success-400: #009d43;
27
+ --hc-color-success-500: #006e2f;
28
+ --hc-color-success-600: #005122;
29
+ --hc-color-success-700: #003416;
30
+ --hc-color-warning-100: #fffaeb;
31
+ --hc-color-warning-200: #f9e298;
32
+ --hc-color-warning-300: #fbd458;
33
+ --hc-color-warning-400: #efba0f;
34
+ --hc-color-warning-500: #a47c00;
35
+ --hc-color-warning-600: #6c5200;
36
+ --hc-color-warning-700: #453400;
37
+ --hc-color-danger-100: #fff0f0;
38
+ --hc-color-danger-200: #f9cac8;
39
+ --hc-color-danger-300: #e37570;
40
+ --hc-color-danger-400: #d6342c;
41
+ --hc-color-danger-500: #a81a0a;
42
+ --hc-color-danger-600: #830d00;
43
+ --hc-color-danger-700: #590900;
44
+ --hc-color-info-100: #f0f4ff;
45
+ --hc-color-info-200: #bed1ff;
46
+ --hc-color-info-300: #89a8f8;
47
+ --hc-color-info-400: #5981e3;
48
+ --hc-color-info-500: #355cc0;
49
+ --hc-color-info-600: #253f82;
50
+ --hc-color-info-700: #162c64;
51
+ --hc-color-andromeda-100: #f6f2ff;
52
+ --hc-color-andromeda-200: #d3c6fd;
53
+ --hc-color-andromeda-300: #ac93fc;
54
+ --hc-color-andromeda-400: #7c5ee2;
55
+ --hc-color-andromeda-500: #5a38c6;
56
+ --hc-color-andromeda-600: #4727b0;
57
+ --hc-color-andromeda-700: #260a81;
58
+ --hc-color-sirius-100: #fff6fe;
59
+ --hc-color-sirius-200: #ffb8f8;
60
+ --hc-color-sirius-300: #f899ee;
61
+ --hc-color-sirius-400: #e472d9;
62
+ --hc-color-sirius-500: #b832ab;
63
+ --hc-color-sirius-600: #7e2274;
64
+ --hc-color-sirius-700: #55154f;
65
+ --hc-color-neutral-0: #ffffff;
66
+ --hc-color-neutral-100: #f7f9fa;
67
+ --hc-color-neutral-200: #e6e9ed;
68
+ --hc-color-neutral-300: #c9ced4;
69
+ --hc-color-neutral-400: #9ea4ac;
70
+ --hc-color-neutral-500: #707780;
71
+ --hc-color-neutral-600: #464b52;
72
+ --hc-color-neutral-700: #32363b;
73
+ --hc-color-neutral-800: #282c2f;
74
+ --hc-color-neutral-900: #191c1f;
75
+ --hc-color-neutral-1000: #000000;
76
+ --ai-color-primary: #a095ec;
77
+ --ai-color-secondary: #6ba1f0;
78
+ --ai-color-dark: #111925;
79
+ }
80
+
81
+ #hotmart-app-tutor-ai-consumer-root {
82
+ composes: scrollbar from './shared-styles.module.css';
83
+ font-family:
84
+ 'Nunito Sans',
85
+ -apple-system,
86
+ BlinkMacSystemFont,
87
+ 'Segoe UI',
88
+ Helvetica,
89
+ Arial,
90
+ sans-serif,
91
+ 'Apple Color Emoji',
92
+ 'Segoe UI Emoji',
93
+ 'Segoe UI Symbol';
94
+ }
95
+
96
+ #hotmart-app-tutor-ai-consumer-root.dark {
97
+ --hc-color-primary-100: #162c64;
98
+ /* Hotmart Product */
99
+ --hc-color-primary-200: #253f82;
100
+ /* Hotmart Product */
101
+ --hc-color-primary-300: #355cc0;
102
+ /* Hotmart Product */
103
+ --hc-color-primary-400: #5981e3;
104
+ /* Hotmart Product */
105
+ --hc-color-primary-500: #89a8f8;
106
+ /* Hotmart Product */
107
+ --hc-color-primary-600: #bed1ff;
108
+ /* Hotmart Product */
109
+ --hc-color-primary-700: #ebf0ff;
110
+ /* Hotmart Product */
111
+ --hc-color-secondary-100: #024242;
112
+ --hc-color-secondary-200: #066262;
113
+ --hc-color-secondary-300: #0a9090;
114
+ --hc-color-secondary-400: #51c1c3;
115
+ --hc-color-secondary-500: #7cdbdb;
116
+ --hc-color-secondary-600: #a2eaea;
117
+ --hc-color-secondary-700: #edffff;
118
+ --hc-color-success-100: #003416;
119
+ --hc-color-success-200: #005122;
120
+ --hc-color-success-300: #006e2f;
121
+ --hc-color-success-400: #009d43;
122
+ --hc-color-success-500: #4acc82;
123
+ --hc-color-success-600: #99e9bb;
124
+ --hc-color-success-700: #edfff5;
125
+ --hc-color-warning-100: #453400;
126
+ --hc-color-warning-200: #6c5200;
127
+ --hc-color-warning-300: #a47c00;
128
+ --hc-color-warning-400: #efba0f;
129
+ --hc-color-warning-500: #fbd458;
130
+ --hc-color-warning-600: #f9e298;
131
+ --hc-color-warning-700: #fffaeb;
132
+ --hc-color-danger-100: #590900;
133
+ --hc-color-danger-200: #830d00;
134
+ --hc-color-danger-300: #a81a0a;
135
+ --hc-color-danger-400: #d6342c;
136
+ --hc-color-danger-500: #e37570;
137
+ --hc-color-danger-600: #f9cac8;
138
+ --hc-color-danger-700: #fff0f0;
139
+ --hc-color-info-100: #162c64;
140
+ --hc-color-info-200: #253f82;
141
+ --hc-color-info-300: #355cc0;
142
+ --hc-color-info-400: #5981e3;
143
+ --hc-color-info-500: #89a8f8;
144
+ --hc-color-info-600: #bed1ff;
145
+ --hc-color-info-700: #f0f4ff;
146
+ --hc-color-andromeda-100: #260a81;
147
+ --hc-color-andromeda-200: #4727b0;
148
+ --hc-color-andromeda-300: #5a38c6;
149
+ --hc-color-andromeda-400: #7c5ee2;
150
+ --hc-color-andromeda-500: #ac93fc;
151
+ --hc-color-andromeda-600: #d3c6fd;
152
+ --hc-color-andromeda-700: #f6f2ff;
153
+ --hc-color-sirius-100: #55154f;
154
+ --hc-color-sirius-200: #7e2274;
155
+ --hc-color-sirius-300: #b832ab;
156
+ --hc-color-sirius-400: #e472d9;
157
+ --hc-color-sirius-500: #f899ee;
158
+ --hc-color-sirius-600: #ffb8f8;
159
+ --hc-color-sirius-700: #fff6fe;
160
+ --hc-color-neutral-0: #000000;
161
+ --hc-color-neutral-100: #191c1f;
162
+ --hc-color-neutral-200: #282c2f;
163
+ --hc-color-neutral-300: #32363b;
164
+ --hc-color-neutral-400: #464b52;
165
+ --hc-color-neutral-500: #707780;
166
+ --hc-color-neutral-600: #9ea4ac;
167
+ --hc-color-neutral-700: #c9ced4;
168
+ --hc-color-neutral-800: #e6e9ed;
169
+ --hc-color-neutral-900: #f7f9fa;
170
+ --hc-color-neutral-1000: #ffffff;
171
+ }
@@ -0,0 +1,5 @@
1
+ @import './global.css';
2
+
3
+ @tailwind base;
4
+ @tailwind components;
5
+ @tailwind utilities;
@@ -0,0 +1,16 @@
1
+ .scrollbar {
2
+ &::-webkit-scrollbar {
3
+ width: var(--hc-size-spacing-2);
4
+ height: var(--hc-size-spacing-2);
5
+ }
6
+
7
+ &::-webkit-scrollbar-track {
8
+ background: transparent;
9
+ }
10
+
11
+ &::-webkit-scrollbar-thumb {
12
+ background: var(--hc-color-neutral-400);
13
+ border-radius: var(--hc-size-border-medium);
14
+ border: calc(var(--hc-size-border-medium) / 2) solid transparent;
15
+ }
16
+ }
@@ -0,0 +1,9 @@
1
+ abstract class MockGenerator<T = unknown> {
2
+ abstract getOne(properties?: T): T
3
+
4
+ getMany(amount = 1, props?: T) {
5
+ return Array(amount).fill(this.getOne(props)) as Array<T>
6
+ }
7
+ }
8
+
9
+ export default MockGenerator
@@ -0,0 +1,32 @@
1
+ import { renderHook } from '@testing-library/react'
2
+ import userEvent from '@testing-library/user-event'
3
+
4
+ import type { CustomRenderHooksOptions } from '@/src/config/tests/types'
5
+ import { setupComponents } from '@/src/config/tests/wrappers'
6
+
7
+ const customRenderHook = <TProps, TResult>(
8
+ callback: (props: TProps) => TResult,
9
+ {
10
+ shallow,
11
+ withQueryProvider = true,
12
+ withProvider,
13
+ ...options
14
+ }: CustomRenderHooksOptions<TProps> = {}
15
+ ) => {
16
+ const result = renderHook(callback, {
17
+ wrapper: (props) =>
18
+ setupComponents(<div {...props} />, {
19
+ shallow,
20
+ withQueryProvider,
21
+ withProvider
22
+ }),
23
+ ...options
24
+ })
25
+
26
+ return {
27
+ ...result,
28
+ user: userEvent.setup()
29
+ }
30
+ }
31
+
32
+ export default customRenderHook
@@ -0,0 +1,7 @@
1
+ import { http, HttpResponse } from 'msw'
2
+
3
+ export const handlers = [
4
+ http.all('https://tracking-api.buildstaging.com/rest/track/event/json/sync', () => {
5
+ return HttpResponse.json({ ok: true })
6
+ })
7
+ ]
@@ -0,0 +1,4 @@
1
+ export * from './types'
2
+ export * from './utils'
3
+ export * from './worker'
4
+ export * from './wrappers'
@@ -0,0 +1,36 @@
1
+ import { delay, http, HttpResponse } from 'msw'
2
+
3
+ import { serviceWorker } from '@/src/config/tests/worker'
4
+ import { HttpCodes } from '@/src/lib/utils'
5
+
6
+ type Method = 'get' | 'post' | 'put' | 'delete'
7
+
8
+ type Options = {
9
+ status?: number
10
+ delay?: number
11
+ httpMethod?: Method
12
+ }
13
+
14
+ class MockRequest {
15
+ rest<ResponseType extends BodyInit | null = never>(
16
+ url: string,
17
+ response: ResponseType,
18
+ options?: Options
19
+ ) {
20
+ const restAPI = http[options?.httpMethod ?? 'get']
21
+
22
+ return restAPI(url, async () => {
23
+ await delay(options?.delay)
24
+
25
+ return new HttpResponse(response, {
26
+ status: options?.status ?? HttpCodes.OK
27
+ })
28
+ })
29
+ }
30
+
31
+ mock<ResponseType = object>(url: string, response: ResponseType, options?: Options) {
32
+ serviceWorker.use(this.rest(url, JSON.stringify(response), options))
33
+ }
34
+ }
35
+
36
+ export default new MockRequest()
@@ -0,0 +1,10 @@
1
+ import type { RenderHookOptions, RenderOptions } from '@testing-library/react'
2
+ import type { ComponentType, ReactNode } from 'react'
3
+
4
+ export type IExtendedRenderOptions = RenderOptions & {
5
+ shallow?: boolean
6
+ withQueryProvider?: boolean
7
+ withProvider?: ComponentType<{ children: ReactNode }>
8
+ }
9
+
10
+ export type CustomRenderHooksOptions<TProps> = RenderHookOptions<TProps> & IExtendedRenderOptions
@@ -0,0 +1,38 @@
1
+ import { render } from '@testing-library/react'
2
+ import userEvent from '@testing-library/user-event'
3
+ import Chance from 'chance'
4
+
5
+ import customRenderHook from '@/src/config/tests/customRenderHook'
6
+ import type { IExtendedRenderOptions } from '@/src/config/tests/types'
7
+ import { setupComponents } from '@/src/config/tests/wrappers'
8
+
9
+ import MockGenerator from './abstract-mock-generator'
10
+ import MockRequest from './mockRequest'
11
+
12
+ const customRender = (
13
+ ui: React.ReactElement,
14
+ { shallow, withQueryProvider = true, ...options }: IExtendedRenderOptions = {}
15
+ ) => ({
16
+ user: userEvent.setup(),
17
+ ...render(
18
+ setupComponents(ui, {
19
+ shallow,
20
+ withQueryProvider
21
+ }),
22
+ options
23
+ )
24
+ })
25
+
26
+ const chance = new Chance()
27
+
28
+ export * from '@testing-library/react'
29
+ export { default as userEvent } from '@testing-library/user-event'
30
+
31
+ export {
32
+ chance,
33
+ MockGenerator,
34
+ MockRequest,
35
+ // override render export
36
+ customRender as render,
37
+ customRenderHook as renderHook
38
+ }
@@ -0,0 +1,5 @@
1
+ import { setupServer } from 'msw/node'
2
+
3
+ import { handlers } from '@/src/config/tests/handlers'
4
+
5
+ export const serviceWorker = setupServer(...handlers)
@@ -0,0 +1,50 @@
1
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
2
+ import type { ComponentType, JSX, ReactNode } from 'react'
3
+
4
+ import type { IExtendedRenderOptions } from '@/src/config/tests/types'
5
+
6
+ export const testQueryClient = new QueryClient({
7
+ defaultOptions: {
8
+ queries: {
9
+ retry: false,
10
+ refetchOnWindowFocus: false
11
+ },
12
+ mutations: {
13
+ retry: false
14
+ }
15
+ }
16
+ })
17
+
18
+ const wrapInQueryProvider = (componentTree: JSX.Element) => (
19
+ <QueryClientProvider client={testQueryClient}>{componentTree}</QueryClientProvider>
20
+ )
21
+
22
+ const wrapInProvider = ({
23
+ componentTree,
24
+ provider: Provider
25
+ }: {
26
+ provider: ComponentType<{ children: ReactNode }>
27
+ componentTree: JSX.Element
28
+ }) => <Provider>{componentTree}</Provider>
29
+
30
+ export const setupComponents = (
31
+ ui: JSX.Element,
32
+ renderOptions?: IExtendedRenderOptions
33
+ ): JSX.Element => {
34
+ let componentTree: JSX.Element = <>{ui}</>
35
+
36
+ if (!renderOptions || renderOptions?.shallow) return componentTree
37
+
38
+ if (renderOptions?.withQueryProvider) {
39
+ componentTree = wrapInQueryProvider(componentTree)
40
+ }
41
+
42
+ if (renderOptions.withProvider) {
43
+ componentTree = wrapInProvider({
44
+ provider: renderOptions.withProvider,
45
+ componentTree
46
+ })
47
+ }
48
+
49
+ return componentTree
50
+ }
@@ -0,0 +1,26 @@
1
+ import './index'
2
+
3
+ import { LANGUAGES } from './config/i18n'
4
+ import { devMode } from './lib/utils'
5
+
6
+ if (devMode) {
7
+ window.TOKEN = process.env.TOKEN ?? ''
8
+ void (async () => {
9
+ await window.startTutorWidget({
10
+ elementId: 'root',
11
+ settings: {
12
+ hotmartToken: window.TOKEN,
13
+ locale: LANGUAGES.PT_BR,
14
+ conversationId: '21506473-a93c-4b38-9c32-68a5ca37ce73', // OWNER
15
+ tutorName: 'Prof Jou Robots',
16
+ contactId: '38138170-6009-40cd-be50-001249e80a0d',
17
+ membershipId: '6297a4efa488cc775ac5e1dd',
18
+ namespace: 'tutor_v1-2',
19
+ author: 'Jonathan',
20
+ clubName: 'comofazerumvideodeteste',
21
+ productName: 'Curso de Assinatura',
22
+ productId: 4266504
23
+ }
24
+ })
25
+ })()
26
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,26 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+
4
+ import { initLanguage } from './config/i18n'
5
+ import { Main } from './main'
6
+ import { TutorWidgetEvents, TutorWidgetEventTypes } from './modules/widget'
7
+ import type { StartTutorWidgetProps } from './types'
8
+
9
+ window.startTutorWidget = async ({
10
+ elementId = 'tutor-chat-app-widget',
11
+ settings
12
+ }: StartTutorWidgetProps) => {
13
+ const rootElement = document.getElementById(elementId) as HTMLElement
14
+ const root = createRoot(rootElement)
15
+
16
+ await initLanguage(settings.locale)
17
+
18
+ if (root)
19
+ root.render(
20
+ <StrictMode>
21
+ <Main settings={settings} />
22
+ </StrictMode>
23
+ )
24
+ }
25
+
26
+ window.closeTutorWidget = () => TutorWidgetEvents.get(TutorWidgetEventTypes.CLOSE)?.dispatch()
@@ -0,0 +1,2 @@
1
+ // Auto-generated file - DO NOT EDIT
2
+ export type ValidIconNames = 'send'
@@ -0,0 +1,41 @@
1
+ import { useEffect, useState } from 'react'
2
+ import type { FunctionComponent, SVGProps } from 'react'
3
+
4
+ import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
5
+ import { Spinner } from '../spinner'
6
+
7
+ export type IconProps = SVGProps<SVGSVGElement> & {
8
+ name: ValidIconNames
9
+ }
10
+
11
+ function Icon({ name, ...rest }: IconProps) {
12
+ const [IconComponent, setIconComponent] = useState<FunctionComponent<
13
+ SVGProps<SVGSVGElement>
14
+ > | null>(null)
15
+ const [loading, setLoading] = useState(false)
16
+
17
+ useEffect(() => {
18
+ const loadIcon = async () => {
19
+ setLoading(true)
20
+ try {
21
+ // Dynamic import with explicit path
22
+ const imported = (await import(`@/src/lib/components/icons/${name}.svg`)) as {
23
+ default: FunctionComponent<SVGProps<SVGSVGElement>>
24
+ }
25
+ setIconComponent(() => imported.default)
26
+ } catch (error) {
27
+ console.error(`Error loading icon ${name}:`, error)
28
+ } finally {
29
+ setLoading(false)
30
+ }
31
+ }
32
+
33
+ void loadIcon()
34
+ }, [name])
35
+
36
+ if (loading) return <Spinner className={rest.className} />
37
+
38
+ return IconComponent ? <IconComponent {...rest} /> : null
39
+ }
40
+
41
+ export default Icon
@@ -0,0 +1 @@
1
+ export { default as Icon } from './icon'
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M0.459613 5.98654C0.0677077 5.85591 0.0646103 5.64514 0.467675 5.51078L14.7823 0.739229C15.1787 0.607109 15.406 0.828966 15.2949 1.21768L11.205 15.5324C11.0918 15.9287 10.8633 15.9425 10.6959 15.5657L7.99999 9.49997L12.5 3.50001L6.5 7.99997L0.459613 5.98654Z" fill="currentColor"/>
3
+ </svg>
@@ -0,0 +1 @@
1
+ export * from './icons'
@@ -0,0 +1,2 @@
1
+ export { default as Spinner } from './spinner'
2
+ export * from './types'
@@ -0,0 +1,25 @@
1
+ import type { SpinnerProps } from './types'
2
+
3
+ import styles from './styles.module.css'
4
+
5
+ function Spinner({ className }: SpinnerProps) {
6
+ return (
7
+ <svg
8
+ className={className}
9
+ stroke='currentColor'
10
+ viewBox='0 0 24 24'
11
+ xmlns='http://www.w3.org/2000/svg'>
12
+ <g className={styles.spinner}>
13
+ <circle
14
+ className={styles.circle}
15
+ cx='12'
16
+ cy='12'
17
+ r='9.5'
18
+ fill='none'
19
+ strokeWidth='3'></circle>
20
+ </g>
21
+ </svg>
22
+ )
23
+ }
24
+
25
+ export default Spinner
@@ -0,0 +1,31 @@
1
+ .spinner {
2
+ transform-origin: center;
3
+ animation: spinner_zKoa 2s linear infinite;
4
+ }
5
+
6
+ .circle {
7
+ stroke-linecap: round;
8
+ animation: spinner_YpZS 1.5s ease-in-out infinite;
9
+ }
10
+
11
+ @keyframes spinner_zKoa {
12
+ 100% {
13
+ transform: rotate(360deg);
14
+ }
15
+ }
16
+
17
+ @keyframes spinner_YpZS {
18
+ 0% {
19
+ stroke-dasharray: 0 150;
20
+ stroke-dashoffset: 0;
21
+ }
22
+ 47.5% {
23
+ stroke-dasharray: 42 150;
24
+ stroke-dashoffset: -16;
25
+ }
26
+ 95%,
27
+ 100% {
28
+ stroke-dasharray: 42 150;
29
+ stroke-dashoffset: -59;
30
+ }
31
+ }
@@ -0,0 +1 @@
1
+ export type SpinnerProps = { className?: string }
@@ -0,0 +1 @@
1
+ export * from './use-default-id'
@@ -0,0 +1 @@
1
+ export { default as useDefaultId } from './use-default-id'
@@ -0,0 +1,13 @@
1
+ import { useLayoutEffect } from 'react'
2
+
3
+ const useDefaultId = () => {
4
+ useLayoutEffect(() => {
5
+ document.body.setAttribute('id', 'hotmart-app-tutor-ai-consumer-root')
6
+
7
+ return () => {
8
+ document.body.removeAttribute('id')
9
+ }
10
+ })
11
+ }
12
+
13
+ export default useDefaultId
@@ -0,0 +1,2 @@
1
+ export const devMode = process.env.NODE_ENV === 'development'
2
+ export const productionMode = process.env.NODE_ENV === 'production'
@@ -0,0 +1,13 @@
1
+ const HttpCodes = {
2
+ OK: 200,
3
+ NO_CONTENT: 204,
4
+ BAD_REQUEST: 400,
5
+ UNAUTHORIZED: 401,
6
+ FORBIDDEN: 403,
7
+ NOT_FOUND: 404,
8
+ NOT_ALLOWED: 405,
9
+ UNPROCESSABLE_ENTITY: 422,
10
+ INTERNAL_SERVER_ERROR: 500
11
+ }
12
+
13
+ export default HttpCodes
@@ -0,0 +1,3 @@
1
+ export * from './constants'
2
+ export { default as HttpCodes } from './http-codes'
3
+ export * from './languages'
@@ -0,0 +1,7 @@
1
+ export const Languages = {
2
+ EN: 'en',
3
+ PT_BR: 'pt-BR',
4
+ FR: 'fr',
5
+ ES: 'es',
6
+ AR: 'ar'
7
+ }
@@ -0,0 +1 @@
1
+ export { default as Main } from './main'
@@ -0,0 +1,15 @@
1
+ import { render, screen, waitFor } from '@/config/tests'
2
+ import type { WidgetSettingProps } from '../types'
3
+ import { Main } from '.'
4
+
5
+ describe('Main', () => {
6
+ const renderComponent = () => render(<Main settings={{} as WidgetSettingProps} />)
7
+
8
+ it('should render without errors', async () => {
9
+ renderComponent()
10
+
11
+ await waitFor(() =>
12
+ expect(screen.getByText(/general.greetings.description/i)).toBeInTheDocument()
13
+ )
14
+ })
15
+ })
@@ -0,0 +1,27 @@
1
+ import '@/config/styles/index.css'
2
+
3
+ import clsx from 'clsx'
4
+
5
+ import { useDefaultId } from '@/src/lib/hooks'
6
+ import { useAppLang } from '../config/i18n'
7
+ import { ChatInput } from '../modules/create-message/components'
8
+ import { GreetingsCard } from '../modules/widget/components'
9
+ import type { WidgetSettingProps } from '../types'
10
+
11
+ import styles from './styles.module.css'
12
+
13
+ function Main({ settings }: { settings: WidgetSettingProps }) {
14
+ useDefaultId()
15
+ useAppLang(settings.locale)
16
+
17
+ return (
18
+ <div className={clsx('flex min-h-svh flex-col items-center justify-center p-5', styles.main)}>
19
+ <div className='flex flex-1 flex-col justify-center gap-6 lg:max-w-sm'>
20
+ <GreetingsCard author={settings.author ?? ''} tutorName={settings.tutorName ?? ''} />
21
+ <ChatInput name='new-chat-msg-input' />
22
+ </div>
23
+ </div>
24
+ )
25
+ }
26
+
27
+ export default Main