app-tutor-ai-consumer 1.3.0 → 1.5.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.
Files changed (127) hide show
  1. package/.github/workflows/staging-staging.yml +148 -0
  2. package/.github/workflows/staging.yml +1 -2
  3. package/CHANGELOG.md +25 -0
  4. package/config/rspack/rspack.config.js +5 -1
  5. package/config/vitest/__mocks__/icons.tsx +3 -0
  6. package/config/vitest/__mocks__/intersection-observer.ts +10 -0
  7. package/config/vitest/__mocks__/sparkie.tsx +2 -11
  8. package/config/vitest/__mocks__/use-init-sparkie.tsx +14 -0
  9. package/config/vitest/setupTests.ts +6 -1
  10. package/config/vitest/vitest.config.mts +13 -8
  11. package/environments/.env.test +2 -0
  12. package/package.json +13 -4
  13. package/public/assets/images/default-image.png +0 -0
  14. package/public/index.html +3 -4
  15. package/src/@types/declarations.d.ts +12 -3
  16. package/src/config/dayjs/index.ts +2 -0
  17. package/src/config/dayjs/init.ts +28 -0
  18. package/src/config/dayjs/utils/format-fulldate.ts +7 -0
  19. package/src/config/dayjs/utils/format-time.ts +20 -0
  20. package/src/config/dayjs/utils/index.ts +2 -0
  21. package/src/config/styles/global.css +21 -3
  22. package/src/config/tanstack/query-client.ts +2 -1
  23. package/src/config/tanstack/query-provider.tsx +1 -1
  24. package/src/config/tests/handlers.ts +6 -0
  25. package/src/config/tests/utils.tsx +3 -2
  26. package/src/config/tests/wrappers.tsx +4 -1
  27. package/src/index.tsx +26 -0
  28. package/src/lib/components/icons/arrow-down.svg +5 -0
  29. package/src/lib/components/icons/chevron-down.svg +4 -0
  30. package/src/lib/components/icons/icon-names.d.ts +1 -1
  31. package/src/lib/components/index.ts +1 -0
  32. package/src/lib/components/markdownrenderer/__tests__/markdown.stub.ts +334 -0
  33. package/src/lib/components/markdownrenderer/components/index.ts +1 -0
  34. package/src/lib/components/markdownrenderer/components/md-code-block/index.ts +1 -0
  35. package/src/lib/components/markdownrenderer/components/md-code-block/md-code-block.tsx +71 -0
  36. package/src/lib/components/markdownrenderer/index.ts +2 -0
  37. package/src/lib/components/markdownrenderer/markdownrenderer.tsx +113 -0
  38. package/src/lib/hooks/index.ts +3 -0
  39. package/src/lib/hooks/use-intersection-observer-reverse-scroll/index.ts +2 -0
  40. package/src/lib/hooks/use-intersection-observer-reverse-scroll/use-intersection-observer-reverse-scroll.tsx +147 -0
  41. package/src/lib/hooks/use-ref-client-height/index.ts +2 -0
  42. package/src/lib/hooks/use-ref-client-height/use-ref-client-height.tsx +38 -0
  43. package/src/lib/hooks/use-scroll-to-ref/index.ts +2 -0
  44. package/src/lib/hooks/use-scroll-to-ref/use-scroll-to-ref.tsx +14 -0
  45. package/src/lib/hooks/use-throttle/index.ts +3 -0
  46. package/src/lib/hooks/use-throttle/types.ts +13 -0
  47. package/src/lib/hooks/use-throttle/use-throttle.spec.tsx +296 -0
  48. package/src/lib/hooks/use-throttle/use-throttle.tsx +91 -0
  49. package/src/lib/utils/constants.ts +1 -1
  50. package/src/lib/utils/copy-text-to-clipboard.tsx +13 -0
  51. package/src/lib/utils/extract-text-from-react-nodes.ts +23 -0
  52. package/src/lib/utils/index.ts +3 -0
  53. package/src/lib/utils/urls.ts +20 -0
  54. package/src/main/main.spec.tsx +9 -0
  55. package/src/modules/cursor/__tests__/icursor-update.builder.ts +42 -0
  56. package/src/modules/cursor/hooks/index.ts +1 -0
  57. package/src/modules/cursor/hooks/use-update-cursor/index.ts +2 -0
  58. package/src/modules/cursor/hooks/use-update-cursor/use-update-cursor.spec.tsx +23 -0
  59. package/src/modules/cursor/hooks/use-update-cursor/use-update-cursor.ts +11 -0
  60. package/src/modules/cursor/index.ts +2 -0
  61. package/src/modules/cursor/service.ts +15 -0
  62. package/src/modules/cursor/types.ts +9 -0
  63. package/src/modules/global-providers/index.ts +1 -0
  64. package/src/modules/messages/__tests__/imessage-with-sender-data.builder.ts +113 -0
  65. package/src/modules/messages/__tests__/imessage-with-sender-data.mock.ts +15 -0
  66. package/src/modules/messages/__tests__/parsed-message.builder.ts +164 -0
  67. package/src/modules/messages/components/index.ts +2 -0
  68. package/src/modules/messages/components/message-img/index.ts +1 -0
  69. package/src/modules/messages/components/message-img/message-img.tsx +47 -0
  70. package/src/modules/messages/components/message-item/index.ts +2 -0
  71. package/src/modules/messages/components/message-item/message-item.spec.tsx +26 -0
  72. package/src/modules/messages/components/message-item/message-item.tsx +28 -0
  73. package/src/modules/messages/components/message-item-end-of-scroll/index.ts +2 -0
  74. package/src/modules/messages/components/message-item-end-of-scroll/message-item-end-of-scroll.tsx +14 -0
  75. package/src/modules/messages/components/message-item-error/index.ts +2 -0
  76. package/src/modules/messages/components/message-item-error/message-item-error.tsx +25 -0
  77. package/src/modules/messages/components/message-item-loading/index.ts +2 -0
  78. package/src/modules/messages/components/message-item-loading/message-item-loading.tsx +16 -0
  79. package/src/modules/messages/components/messages-list/index.ts +1 -1
  80. package/src/modules/messages/components/messages-list/messages-list.tsx +76 -17
  81. package/src/modules/messages/constants.ts +1 -0
  82. package/src/modules/messages/hooks/index.ts +4 -0
  83. package/src/modules/messages/hooks/use-all-messages/index.ts +2 -0
  84. package/src/modules/messages/hooks/use-all-messages/use-all-messages.tsx +30 -0
  85. package/src/modules/messages/hooks/use-fetch-messages/index.ts +2 -0
  86. package/src/modules/messages/hooks/use-fetch-messages/use-fetch-messages.spec.tsx +46 -0
  87. package/src/modules/messages/hooks/use-fetch-messages/use-fetch-messages.tsx +103 -0
  88. package/src/modules/messages/hooks/use-infinite-get-messages/index.ts +2 -0
  89. package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.spec.tsx +58 -0
  90. package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.tsx +97 -0
  91. package/src/modules/messages/hooks/use-manage-scroll/index.ts +2 -0
  92. package/src/modules/messages/hooks/use-manage-scroll/use-manage-scroll.tsx +66 -0
  93. package/src/modules/messages/service.ts +26 -3
  94. package/src/modules/messages/types.ts +33 -2
  95. package/src/modules/messages/utils/has-to-update-cursor/has-to-update-cursor.spec.tsx +58 -0
  96. package/src/modules/messages/utils/has-to-update-cursor/has-to-update-cursor.ts +30 -0
  97. package/src/modules/messages/utils/has-to-update-cursor/index.ts +2 -0
  98. package/src/modules/messages/utils/index.ts +1 -0
  99. package/src/modules/messages/utils/messages-parser/index.ts +1 -0
  100. package/src/modules/messages/utils/messages-parser/utils.ts +28 -0
  101. package/src/modules/profile/__tests__/profile-api-props.builder.ts +74 -0
  102. package/src/modules/profile/__tests__/profile-props.builder.ts +42 -0
  103. package/src/modules/profile/constants.ts +3 -0
  104. package/src/modules/profile/hooks/index.ts +1 -0
  105. package/src/modules/profile/hooks/use-get-profile/index.ts +1 -0
  106. package/src/modules/profile/hooks/use-get-profile/use-get-profile.spec.tsx +20 -0
  107. package/src/modules/profile/hooks/use-get-profile/use-get-profile.tsx +14 -0
  108. package/src/modules/profile/index.ts +4 -0
  109. package/src/modules/profile/service.tsx +19 -0
  110. package/src/modules/profile/types.ts +17 -0
  111. package/src/modules/sparkie/__tests__/sparkie.mock.ts +33 -0
  112. package/src/modules/widget/__tests__/widget-settings-props.builder.ts +6 -0
  113. package/src/modules/widget/components/chat-page/chat-page.spec.tsx +28 -0
  114. package/src/modules/widget/components/chat-page/chat-page.tsx +3 -5
  115. package/src/modules/widget/components/container/container.tsx +21 -15
  116. package/src/modules/widget/components/index.ts +1 -0
  117. package/src/modules/widget/components/onboarding-page/onboarding-page.tsx +16 -14
  118. package/src/modules/widget/components/scroll-to-bottom-button/index.ts +2 -0
  119. package/src/modules/widget/components/scroll-to-bottom-button/scroll-to-bottom-button.tsx +32 -0
  120. package/src/modules/widget/components/starter-page/starter-page.tsx +3 -3
  121. package/src/modules/widget/events.ts +4 -0
  122. package/src/modules/widget/hooks/use-init-sparkie/use-init-sparkie.tsx +8 -6
  123. package/src/modules/widget/store/index.ts +1 -0
  124. package/src/modules/widget/store/widget-container-intrinsic-height.atom.ts +13 -0
  125. package/src/modules/widget/store/widget-settings.atom.ts +3 -1
  126. package/src/config/styles/shared-styles.module.css +0 -16
  127. package/src/modules/widget/components/container/styles.module.css +0 -11
@@ -0,0 +1,148 @@
1
+ name: Deploy Staging (staging branch)
2
+ run-name: "Staging deployment: ${{ github.ref_type }} ${{ github.ref_name }} by @${{ github.actor }}"
3
+
4
+ concurrency: staging-${{ github.ref }}
5
+
6
+ on:
7
+ push:
8
+ branches:
9
+ - staging/**
10
+
11
+ env:
12
+ APP_NAME: app-tutor-ai-consumer
13
+ BUCKET_NAME: app-club-microfrontends
14
+ AWS_DEFAULT_REGION: us-east-1
15
+ CLOUDFRONT_ACCOUNT_ID: "449466460580"
16
+ CLOUDFRONT_DISTRIBUTION: "E19SXB8YMSF4QX"
17
+ CLOUDFRONT_URL: https://app-club-microfrontends.buildstaging.com
18
+ GH_TOKEN: ${{ secrets.CI_GH_TOKEN }}
19
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
20
+ LANG_ENV: staging
21
+
22
+ jobs:
23
+ deploy-staging:
24
+ name: Deploy Staging
25
+ runs-on: [buildstaging-high-iac]
26
+ steps:
27
+ - name: Checkout
28
+ uses: actions/checkout@v4
29
+ with:
30
+ token: ${{ secrets.CI_GH_TOKEN }}
31
+ fetch-depth: 0
32
+ ref: ${{ env.BRANCH_REF }}
33
+ fetch-tags: true
34
+
35
+ - name: Restore Cache
36
+ uses: Hotmart-Org/actions/cache@master
37
+ with:
38
+ bucket: buildstaging-pipeline-cache
39
+ cache-key: "${{ hashFiles('package.json') }}"
40
+ restore: true
41
+ mount: 'node_modules/'
42
+
43
+ - name: Setup Node.js
44
+ uses: actions/setup-node@v4
45
+ with:
46
+ node-version-file: '.nvmrc'
47
+
48
+ - name: Retrieve .npmrc
49
+ uses: Hotmart-Org/actions/codeartifact@master
50
+ with:
51
+ npmrc: ${{ secrets.NPM_RC }}
52
+ fontawesome-registry-token: ${{ secrets.FONTAWESOME_REGISTRY_TOKEN }}
53
+
54
+ - name: install-dependencies
55
+ run: npm ci --include=optional
56
+
57
+ - name: Rebuild Cache
58
+ uses: Hotmart-Org/actions/cache@master
59
+ with:
60
+ bucket: buildstaging-pipeline-cache
61
+ cache-key: "${{ hashFiles('package.json') }}"
62
+ rebuild: true
63
+ mount: 'node_modules/'
64
+
65
+ - name: pre-build-staging
66
+ run: |
67
+ echo "${{ env.LANG_ENV }}"
68
+ rm -rf public/locales
69
+ mkdir public/locales
70
+ npm run fetch-langs
71
+
72
+ - name: build-staging
73
+ run: npm run build:staging
74
+
75
+ - name: Deploy Microfrontend s3 - ${{ env.APP_NAME }}
76
+ uses: Hotmart-Org/actions/s3/microfrontend/deploy@master
77
+ with:
78
+ bucket-name: ${{ env.BUCKET_NAME }}
79
+ app-name: ${{ env.APP_NAME }}
80
+ package-json-path: ./package.json
81
+ bundle-source: ./dist
82
+ account-id: ${{ env.CLOUDFRONT_ACCOUNT_ID }}
83
+ cdn-distribution: ${{ env.CLOUDFRONT_DISTRIBUTION }}
84
+ cdn-url: ${{ env.CLOUDFRONT_URL }}
85
+
86
+ staging-notification:
87
+ name: 'Staging Notification'
88
+ runs-on: [buildstaging-iac]
89
+ needs: ['deploy-staging']
90
+ steps:
91
+ - name: Checkout
92
+ uses: actions/checkout@v4
93
+
94
+ - name: Extract version from package.json
95
+ run: |
96
+ VERSION=$(cat package.json | jq -r .version)
97
+ echo "Extracted version: $VERSION"
98
+ echo "VERSION=$VERSION" >> $GITHUB_ENV
99
+
100
+ - name: Notify Google Chat
101
+ uses: Hotmart-Org/actions/notification@master
102
+ env:
103
+ VERSION: ${{ env.VERSION }}
104
+ with:
105
+ type: 'Original'
106
+ author: true
107
+ webhook-chat: 'https://chat.googleapis.com/v1/spaces/AAAA1nvOyjo/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=uuL7DA8zTxUkJjQa39HIM0TYVZV0DvneZ0mklNhEr5M'
108
+ body: |
109
+ {
110
+ "text": "🚧 *[STAGING] app-tutor-ai-consumer v${{ env.VERSION }}*: staging deploy: ${{ github.ref_name }}",
111
+ "cards": [
112
+ {
113
+ "header": {
114
+ "title": "Started by ${{ github.actor }}"
115
+ },
116
+ "sections": [
117
+ {
118
+ "widgets": [
119
+ {
120
+ "buttons": [
121
+ {
122
+ "textButton": {
123
+ "onClick": {
124
+ "openLink": {
125
+ "url": "${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}"
126
+ }
127
+ },
128
+ "text": "View commit"
129
+ }
130
+ },
131
+ {
132
+ "textButton": {
133
+ "onClick": {
134
+ "openLink": {
135
+ "url": "https://github.com/Hotmart-Org/app-tutor-ai-consumer/actions/runs/${{ github.run_id }}"
136
+ }
137
+ },
138
+ "text": "Follow this deploy"
139
+ }
140
+ }
141
+ ]
142
+ }
143
+ ]
144
+ }
145
+ ]
146
+ }
147
+ ]
148
+ }
@@ -5,7 +5,6 @@ on:
5
5
  push:
6
6
  branches:
7
7
  - main
8
- - staging/*
9
8
 
10
9
  concurrency: staging
11
10
 
@@ -141,7 +140,7 @@ jobs:
141
140
  uses: newrelic/deployment-marker-action@v2.5.1
142
141
  env:
143
142
  VERSION: ${{ env.VERSION }}
144
- NEW_RELIC_API_KEY: NRAK-14TLYFWRA5RCKUGK4A3TJSAPU78
143
+ NEW_RELIC_API_KEY: ${{ secrets.NEW_RELIC_API_KEY }}
145
144
  NEW_RELIC_DEPLOYMENT_ENTITY_GUID: Mjc1MDN8QVBNfEFQUExJQ0FUSU9OfDEwOTE1MDMzMTE
146
145
  with:
147
146
  apiKey: ${{ env.NEW_RELIC_API_KEY }}
package/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ # [1.5.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.4.0...v1.5.0) (2025-07-10)
2
+
3
+ ### Bug Fixes
4
+
5
+ - scrolling PR issues ([d2a8faa](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/d2a8faac05c79d1b18f793981d69036687fefa0e))
6
+
7
+ ### Features
8
+
9
+ - add infinite scroll UI ([8c86dd8](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/8c86dd8043d0f10e8deae787e6f8870555f5c5f2))
10
+ - adding specific pipiline to staging branch ([bee74d3](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/bee74d37721e8900163409b5cd493a213c92fa8d))
11
+ - adding styles ([2a33e03](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/2a33e035f1e4b146cc7b03ba9f201a2f494bc31b))
12
+ - adding styles file ([ef4d4ec](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/ef4d4ec50c010f39dfba154b084cbe0dc9ece05b))
13
+
14
+ # [1.4.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.3.0...v1.4.0) (2025-06-26)
15
+
16
+ ### Bug Fixes
17
+
18
+ - pr issues ([74efaec](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/74efaecaec56aa84808325cb05bc50bd0b50a6ef))
19
+ - pr issues ([ab7a3a8](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/ab7a3a80a5d45b857effb2d281d9523d9789af98))
20
+
21
+ ### Features
22
+
23
+ - add tutor message markdown support ([4328fc4](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/4328fc402c47f501ddd9761f612325515fc8c6e2))
24
+ - add tutor messages and profile service ([213611b](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/213611b570a163c27d980a83a8a8a4dd2cf6e077))
25
+
1
26
  # [1.3.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.2.0...v1.3.0) (2025-06-24)
2
27
 
3
28
  ### Bug Fixes
@@ -46,7 +46,7 @@ module.exports = async function (env) {
46
46
  path: paths.DIST,
47
47
  publicPath: bundlePath,
48
48
  pathinfo: !productionMode,
49
- filename: `[name].[fullhash].${fileVersion}.js`,
49
+ filename: `remoteEntry.js`,
50
50
  chunkFilename: `[name].[fullhash].chunk.${fileVersion}.js`,
51
51
  sourceMapFilename: `[name].[fullhash].${fileVersion}.js.map`,
52
52
  clean: true
@@ -166,6 +166,10 @@ module.exports = async function (env) {
166
166
  new rspack.HtmlRspackPlugin({
167
167
  template: path.resolve(paths.PUBLIC, 'index.html'),
168
168
  favicon: path.resolve(paths.PUBLIC, 'favicon.ico')
169
+ }),
170
+ new rspack.CssExtractRspackPlugin({
171
+ filename: productionMode ? `app-tutor-ai-consumer.css` : '[name].[contenthash].css',
172
+ chunkFilename: productionMode ? '[id].[contenthash].css' : '[id].[contenthash].css'
169
173
  })
170
174
  ].concat(await getEnvironmentPlugins(!productionMode)),
171
175
  watchOptions: {
@@ -0,0 +1,3 @@
1
+ vi.mock('@/src/lib/components/icons', () => ({
2
+ Icon: vi.fn(({ name }) => <div data-test='lazy-icon'>{name}</div>)
3
+ }))
@@ -0,0 +1,10 @@
1
+ beforeEach(() => {
2
+ const mockIntersectionObserver = vi.fn()
3
+ mockIntersectionObserver.mockReturnValue({
4
+ observe: () => null,
5
+ unobserve: () => null,
6
+ disconnect: () => null
7
+ })
8
+
9
+ window.IntersectionObserver = mockIntersectionObserver
10
+ })
@@ -1,12 +1,3 @@
1
- vi.mock('@hotmart/sparkie', () => {
2
- const Sparkie = vi.fn()
1
+ import SparkieMock from '@/src/modules/sparkie/__tests__/sparkie.mock'
3
2
 
4
- Sparkie.prototype.destroy = vi.fn()
5
- Sparkie.prototype.init = vi.fn()
6
- Sparkie.prototype.off = vi.fn()
7
- Sparkie.prototype.on = vi.fn()
8
- Sparkie.prototype.listener = { trackTyping: vi.fn() }
9
- Sparkie.prototype.setAPIToken = vi.fn()
10
-
11
- return { default: Sparkie }
12
- })
3
+ vi.mock('@hotmart/sparkie', () => ({ default: SparkieMock }))
@@ -0,0 +1,14 @@
1
+ import { useInitSparkie } from '@/src/modules/widget/hooks/use-init-sparkie'
2
+
3
+ vi.mock('@/src/modules/widget/hooks/use-init-sparkie/use-init-sparkie', () => ({
4
+ useInitSparkie: vi.fn()
5
+ }))
6
+
7
+ beforeEach(() => {
8
+ vi.mocked(useInitSparkie).mockReturnValue({
9
+ data: true,
10
+ isError: false,
11
+ isLoading: false,
12
+ refetch: vi.fn()
13
+ } as unknown as ReturnType<typeof useInitSparkie>)
14
+ })
@@ -1,18 +1,23 @@
1
+ import 'dayjs/locale/pt-br'
1
2
  import '@testing-library/jest-dom/vitest'
2
3
 
3
4
  import { cleanup, configure } from '@testing-library/react'
4
5
 
5
6
  import { serviceWorker } from '@/src/config/tests/worker'
7
+ import { initDayjs } from '@/src/config/dayjs'
8
+ import * as I18n from '@/src/config/i18n'
6
9
 
7
10
  configure({ testIdAttribute: 'data-test' })
8
11
 
9
12
  beforeEach(() => {
10
13
  cleanup()
14
+ vi.spyOn(I18n, 't').mockImplementation((...args) => args[0])
11
15
  })
12
16
 
13
17
  // Start worker before all tests
14
- beforeAll(() => {
18
+ beforeAll(async () => {
15
19
  serviceWorker.listen()
20
+ await initDayjs('pt-br')
16
21
  })
17
22
 
18
23
  // Close serviceWorker after all tests
@@ -12,7 +12,10 @@ export default defineConfig({
12
12
  './config/vitest/setupTests.ts',
13
13
  './config/vitest/__mocks__/i18n.tsx',
14
14
  './config/vitest/__mocks__/sparkie.tsx',
15
- './config/vitest/polyfills/global.js',
15
+ './config/vitest/__mocks__/icons.tsx',
16
+ './config/vitest/__mocks__/intersection-observer.ts',
17
+ './config/vitest/__mocks__/use-init-sparkie.tsx',
18
+ './config/vitest/polyfills/global.js'
16
19
  ],
17
20
  coverage: {
18
21
  include: ['src/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
@@ -28,7 +31,7 @@ export default defineConfig({
28
31
  'src/**/*.builder.ts*',
29
32
  'src/app-injector.tsx',
30
33
  'src/bootstrap.tsx',
31
- 'src/index.ts',
34
+ 'src/index.ts'
32
35
  ],
33
36
  reporter: ['text', 'html'],
34
37
  provider: 'istanbul',
@@ -36,13 +39,15 @@ export default defineConfig({
36
39
  branches: 3,
37
40
  lines: 3,
38
41
  functions: 3,
39
- statements: 3,
40
- },
42
+ statements: 3
43
+ }
41
44
  },
45
+ testTimeout: 10000,
46
+ hookTimeout: 10000,
42
47
  env: {
43
48
  ...config({
44
- path: './environments/.env.test',
45
- }).parsed,
46
- },
47
- },
49
+ path: './environments/.env.test'
50
+ }).parsed
51
+ }
52
+ }
48
53
  })
@@ -39,3 +39,5 @@ AUTH_CLIENT_SECRET=
39
39
 
40
40
  # BUNDLE
41
41
  BUNDLE_PATH=
42
+
43
+ VITE_REACT_QUERY_EXPERIMENTAL_PREFETCH_IN_RENDER=true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-tutor-ai-consumer",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
@@ -8,9 +8,9 @@
8
8
  "generate-icon-types": "ts-node scripts/generate-icon-types.js",
9
9
  "postgenerate-icon-types": "npm run format",
10
10
  "prebuild": "npm run generate-icon-types",
11
- "build": "rspack build --mode=production --env=production --config config/rspack/rspack.config.js",
11
+ "build": "NODE_ENV=production rspack build --mode=production --env=production --config config/rspack/rspack.config.js",
12
12
  "prebuild:staging": "npm run generate-icon-types",
13
- "build:staging": "rspack build --mode=production --env=staging --config config/rspack/rspack.config.js",
13
+ "build:staging": "NODE_ENV=staging rspack build --mode=production --env=staging --config config/rspack/rspack.config.js",
14
14
  "prebuild:start": "npm run build",
15
15
  "build:start": "NODE_ENV=production rspack preview --config config/rspack/rspack.config.js --port 4200",
16
16
  "test": "vitest --config config/vitest/vitest.config.mts",
@@ -54,6 +54,7 @@
54
54
  "@testing-library/user-event": "~14.6.1",
55
55
  "@types/axios": "~0.9.36",
56
56
  "@types/chance": "~1.1.6",
57
+ "@types/linkify-it": "~5.0.0",
57
58
  "@types/node": "~24.0.0",
58
59
  "@types/react": "~19.1.7",
59
60
  "@types/react-dom": "~19.1.6",
@@ -107,12 +108,20 @@
107
108
  "@tanstack/react-query": "~5.80.6",
108
109
  "@tanstack/react-query-persist-client": "~5.80.7",
109
110
  "clsx": "~2.1.1",
111
+ "dayjs": "~1.11.13",
110
112
  "i18next": "~25.2.1",
111
113
  "i18next-resources-to-backend": "~1.2.1",
112
114
  "jotai": "~2.12.5",
115
+ "linkify-it": "~5.0.0",
116
+ "prism-react-renderer": "~2.4.1",
113
117
  "react": "~19.1.0",
114
118
  "react-dom": "~19.1.0",
115
- "react-i18next": "~15.5.2"
119
+ "react-i18next": "~15.5.2",
120
+ "react-markdown": "~10.1.0",
121
+ "rehype-raw": "~7.0.0",
122
+ "rehype-sanitize": "~6.0.0",
123
+ "remark-breaks": "~4.0.0",
124
+ "remark-gfm": "~4.0.1"
116
125
  },
117
126
  "optionalDependencies": {
118
127
  "@rollup/rollup-linux-x64-gnu": "4.6.1",
package/public/index.html CHANGED
@@ -6,13 +6,12 @@
6
6
  <link rel="preconnect" href="https://fonts.googleapis.com" />
7
7
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
8
8
  <link
9
- href="https://fonts.googleapis.com/css?family=Nunito:400,700"
10
- rel="stylesheet"
11
- />
9
+ href="https://fonts.googleapis.com/css?family=Nunito:400,700&display=swap"
10
+ rel="stylesheet" />
12
11
  <title>App Tutor AI Consumer</title>
13
12
  </head>
14
13
 
15
- <body>
14
+ <body class="bg-ai-dark">
16
15
  <div id="root"></div>
17
16
  </body>
18
17
  </html>
@@ -7,11 +7,20 @@ declare module '*.css'
7
7
 
8
8
  declare module '*.css?inline'
9
9
 
10
- declare module '*.png'
10
+ declare module '*.png' {
11
+ const content: string
12
+ export default content
13
+ }
11
14
 
12
- declare module '*.jpg'
15
+ declare module '*.jpg' {
16
+ const content: string
17
+ export default content
18
+ }
13
19
 
14
- declare module '*.gif'
20
+ declare module '*.gif' {
21
+ const content: string
22
+ export default content
23
+ }
15
24
 
16
25
  declare module '*.svg?url' {
17
26
  const content: string
@@ -0,0 +1,2 @@
1
+ export * from './init'
2
+ export * from './utils'
@@ -0,0 +1,28 @@
1
+ import dayjs from 'dayjs'
2
+ import calendar from 'dayjs/plugin/calendar'
3
+ import isBetween from 'dayjs/plugin/isBetween'
4
+ import isToday from 'dayjs/plugin/isToday'
5
+ import localizedFormat from 'dayjs/plugin/localizedFormat'
6
+ import timezone from 'dayjs/plugin/timezone'
7
+ import utc from 'dayjs/plugin/utc'
8
+
9
+ import { DEFAULT_LANGUAGE } from '../i18n'
10
+
11
+ export const initDayjs = async (locale: string = DEFAULT_LANGUAGE) => {
12
+ dayjs.extend(localizedFormat)
13
+ dayjs.extend(utc)
14
+ dayjs.extend(timezone)
15
+ dayjs.extend(isBetween)
16
+ dayjs.extend(isToday)
17
+ dayjs.extend(calendar)
18
+
19
+ const lng = locale.match(/^pt/i) ? 'pt-br' : locale
20
+
21
+ try {
22
+ await import(`dayjs/locale/${lng}`)
23
+ dayjs.locale(lng)
24
+ } catch {
25
+ await import(`dayjs/locale/${DEFAULT_LANGUAGE}`)
26
+ dayjs.locale(DEFAULT_LANGUAGE)
27
+ }
28
+ }
@@ -0,0 +1,7 @@
1
+ import dayjs from 'dayjs'
2
+
3
+ export const formatFullDate = (timestamp?: dayjs.ConfigType) => {
4
+ const time = dayjs(timestamp)
5
+
6
+ return time.format('L')
7
+ }
@@ -0,0 +1,20 @@
1
+ import dayjs from 'dayjs'
2
+
3
+ import { t } from '@/src/config/i18n'
4
+
5
+ export const formatTime = (timestamp: dayjs.ConfigType, ignoreTime = false) => {
6
+ const time = dayjs(timestamp)
7
+
8
+ if (!time.isValid()) {
9
+ return 'Invalid date'
10
+ }
11
+
12
+ const daysDiff = dayjs().diff(time, 'day')
13
+
14
+ return time.calendar(null, {
15
+ sameDay: ignoreTime ? `[${t('general.today')}]` : 'LT',
16
+ lastDay: `[${t('general.yesterday')}]`,
17
+ lastWeek: daysDiff <= 7 ? 'dddd' : 'L',
18
+ sameElse: 'L'
19
+ })
20
+ }
@@ -0,0 +1,2 @@
1
+ export * from './format-fulldate'
2
+ export * from './format-time'
@@ -75,12 +75,30 @@
75
75
  --hc-color-neutral-1000: #000000;
76
76
  --ai-color-primary: #a095ec;
77
77
  --ai-color-secondary: #6ba1f0;
78
- --ai-color-dark: #111925;
79
- --ai-color-chat-response: #26202f;
78
+ --ai-color-dark: #1a1c1f;
79
+ --ai-color-chat-response: #1e1926;
80
+
81
+ /* Size */
82
+ --hc-size-spacing-2: 0.5rem;
83
+ --hc-size-border-medium: 0.5rem;
80
84
  }
81
85
 
82
86
  #hotmart-app-tutor-ai-consumer-root {
83
- composes: scrollbar from './shared-styles.module.css';
87
+ & *::-webkit-scrollbar {
88
+ width: var(--hc-size-spacing-2);
89
+ height: var(--hc-size-spacing-2);
90
+ }
91
+
92
+ & *::-webkit-scrollbar-track {
93
+ background: transparent;
94
+ }
95
+
96
+ & *::-webkit-scrollbar-thumb {
97
+ background: var(--hc-color-neutral-800);
98
+ border-radius: var(--hc-size-border-medium);
99
+ border: calc(var(--hc-size-border-medium) / 2) solid transparent;
100
+ }
101
+
84
102
  font-family:
85
103
  'Nunito Sans',
86
104
  -apple-system,
@@ -21,7 +21,8 @@ export const queryClient = new QueryClient({
21
21
  (statusCode < HttpCodes.BAD_REQUEST || statusCode >= HttpCodes.INTERNAL_SERVER_ERROR) &&
22
22
  failureCount <= maxRetries
23
23
  )
24
- }
24
+ },
25
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)
25
26
  }
26
27
  }
27
28
  })
@@ -10,7 +10,7 @@ function QueryProvider({ children, showDevTools = true }: QueryProviderProps) {
10
10
  return (
11
11
  <PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
12
12
  {children}
13
- {showDevTools && <ReactQueryDevtools />}
13
+ {showDevTools && <ReactQueryDevtools buttonPosition='top-right' />}
14
14
  </PersistQueryClientProvider>
15
15
  )
16
16
  }
@@ -1,10 +1,16 @@
1
1
  import { http, HttpResponse } from 'msw'
2
2
 
3
+ import { ProfileEndpoints } from '@/src/modules/profile'
4
+ import ProfileAPIPropsBuilder from '@/src/modules/profile/__tests__/profile-api-props.builder'
5
+
3
6
  export const handlers = [
4
7
  http.all('https://tracking-api.buildstaging.com/rest/track/event/json/sync', () => {
5
8
  return HttpResponse.json({ ok: true })
6
9
  }),
7
10
  http.all('https://c3po-api-auth.buildstaging.com/v1/auth/sparkie', () => {
8
11
  return HttpResponse.json({ ok: true })
12
+ }),
13
+ http.all(ProfileEndpoints.getProfile(), () => {
14
+ return HttpResponse.json(new ProfileAPIPropsBuilder())
9
15
  })
10
16
  ]
@@ -11,13 +11,14 @@ import MockRequest from './mockRequest'
11
11
 
12
12
  const customRender = (
13
13
  ui: React.ReactElement,
14
- { shallow, withQueryProvider = true, ...options }: IExtendedRenderOptions = {}
14
+ { shallow, withQueryProvider = true, withProvider, ...options }: IExtendedRenderOptions = {}
15
15
  ) => ({
16
16
  user: userEvent.setup(),
17
17
  ...render(
18
18
  setupComponents(ui, {
19
19
  shallow,
20
- withQueryProvider
20
+ withQueryProvider,
21
+ withProvider
21
22
  }),
22
23
  options
23
24
  )
@@ -7,7 +7,10 @@ export const testQueryClient = new QueryClient({
7
7
  defaultOptions: {
8
8
  queries: {
9
9
  retry: false,
10
- refetchOnWindowFocus: false
10
+ gcTime: 0,
11
+ staleTime: 0,
12
+ refetchOnWindowFocus: false,
13
+ experimental_prefetchInRender: true
11
14
  },
12
15
  mutations: {
13
16
  retry: false
package/src/index.tsx CHANGED
@@ -1,19 +1,45 @@
1
+ import './config/styles/global.css'
2
+ import './config/styles/index.css'
3
+
1
4
  import { StrictMode } from 'react'
2
5
  import { createRoot } from 'react-dom/client'
3
6
 
7
+ import { initDayjs } from './config/dayjs'
4
8
  import { initLanguage } from './config/i18n'
9
+ import { initAxios } from './config/request/api'
10
+ import { productionMode } from './lib/utils'
5
11
  import { Main } from './main'
6
12
  import { TutorWidgetEvents, TutorWidgetEventTypes } from './modules/widget'
7
13
  import type { StartTutorWidgetProps } from './types'
8
14
 
15
+ const loadMainStyles = () => {
16
+ const isProduction = productionMode
17
+ const bundlePath = !isProduction
18
+ ? `${process.env.BUNDLE_PATH}/`
19
+ : `${process.env.BUNDLE_PATH}/${process.env.APP_NAME}/_current/`
20
+
21
+ const cssPath = `${bundlePath}app-tutor-ai-consumer.css`
22
+
23
+ if (!document.querySelector(`link[href="${cssPath}"]`)) {
24
+ const linkElement = document.createElement('link')
25
+ linkElement.rel = 'stylesheet'
26
+ linkElement.href = cssPath
27
+ document.head.appendChild(linkElement)
28
+ }
29
+ }
30
+
9
31
  window.startTutorWidget = async ({
10
32
  elementId = 'tutor-chat-app-widget',
11
33
  settings
12
34
  }: StartTutorWidgetProps) => {
35
+ loadMainStyles()
36
+
13
37
  const rootElement = document.getElementById(elementId) as HTMLElement
14
38
  const root = createRoot(rootElement)
15
39
 
40
+ initAxios(settings.hotmartToken)
16
41
  await initLanguage(settings.locale)
42
+ await initDayjs(settings.locale)
17
43
 
18
44
  if (root)
19
45
  root.render(
@@ -0,0 +1,5 @@
1
+ <svg viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path
3
+ d="M13.9748 9.83038L8.47139 15.0179C8.37759 15.1116 8.25251 15.1429 8.1587 15.1429C8.03362 15.1429 7.90855 15.1116 7.81474 15.0179L2.31136 9.83038C2.09248 9.64288 2.09248 9.33038 2.28009 9.11163C2.46771 8.89288 2.7804 8.89288 2.96802 9.08038L7.65839 13.4554L7.65839 1.67413C7.65839 1.39288 7.87728 1.14288 8.12743 1.14288C8.37759 1.14288 8.65901 1.39288 8.65901 1.67413L8.65901 13.4554L13.3181 9.08038C13.5057 8.89288 13.8184 8.89288 14.006 9.11163C14.1937 9.33038 14.1937 9.64288 13.9748 9.83038Z"
4
+ fill="currentColor" />
5
+ </svg>