app-tutor-ai-consumer 1.15.0 → 1.17.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 (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +180 -0
  3. package/docs/images/hotmart-brand.svg +16 -0
  4. package/package.json +1 -1
  5. package/public/index.html +1 -1
  6. package/src/config/styles/global.css +13 -3
  7. package/src/config/styles/utilities/bg-utilities.module.css +5 -5
  8. package/src/config/theme/index.ts +1 -0
  9. package/src/config/theme/init-theme.ts +7 -0
  10. package/src/development-bootstrap.tsx +5 -2
  11. package/src/lib/components/button/button.tsx +63 -20
  12. package/src/lib/components/icons/archive.svg +5 -0
  13. package/src/lib/components/icons/arrow-left.svg +5 -0
  14. package/src/lib/components/icons/close.svg +5 -0
  15. package/src/lib/components/icons/icon-names.d.ts +10 -1
  16. package/src/lib/components/icons/info.svg +5 -0
  17. package/src/modules/messages/components/chat-input/chat-input.tsx +14 -24
  18. package/src/modules/messages/components/message-item/message-item.tsx +2 -2
  19. package/src/modules/messages/components/message-skeleton/message-skeleton.tsx +3 -3
  20. package/src/modules/messages/components/messages-list/messages-list.tsx +1 -1
  21. package/src/modules/widget/components/ai-avatar/ai-avatar-icon.tsx +17 -0
  22. package/src/modules/widget/components/ai-avatar/ai-avatar.tsx +14 -9
  23. package/src/modules/widget/components/ai-avatar/index.ts +3 -1
  24. package/src/modules/widget/components/ai-avatar/styles.module.css +3 -0
  25. package/src/modules/widget/components/chat-page/chat-page.tsx +17 -2
  26. package/src/modules/widget/components/greetings-card/greetings-card.tsx +4 -10
  27. package/src/modules/widget/components/greetings-card/styles.module.css +0 -4
  28. package/src/modules/widget/components/header/__tests__/widget-header-props.builder.ts +46 -0
  29. package/src/modules/widget/components/header/header.spec.tsx +47 -0
  30. package/src/modules/widget/components/header/header.tsx +69 -0
  31. package/src/modules/widget/components/header/index.ts +2 -0
  32. package/src/modules/widget/components/header/types.ts +9 -0
  33. package/src/modules/widget/components/index.ts +1 -0
  34. package/src/modules/widget/components/loading-page/loading-page.tsx +7 -3
  35. package/src/modules/widget/components/page-layout/page-layout.tsx +1 -1
  36. package/src/modules/widget/components/scroll-to-bottom-button/scroll-to-bottom-button.tsx +2 -2
  37. package/src/modules/widget/components/starter-page/starter-page.tsx +14 -7
  38. package/src/modules/widget/events.ts +17 -0
  39. package/src/modules/widget/hooks/use-init-widget/use-init-widget.tsx +2 -0
  40. package/src/types.ts +5 -0
  41. package/tailwind.config.js +1 -0
  42. package/docs/README.md +0 -15
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [1.17.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.16.0...v1.17.0) (2025-07-22)
2
+
3
+ ### Features
4
+
5
+ - add header navigation ([1b1f8fb](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/1b1f8fb8ed1cb832bf7bbe4c0ddc1418b9946ce3))
6
+
7
+ # [1.16.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.15.0...v1.16.0) (2025-07-22)
8
+
9
+ ### Features
10
+
11
+ - add Theming capability ([b6801c7](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/b6801c7547c554718858f0d0cd0170917dc1228c))
12
+
1
13
  # [1.15.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.14.0...v1.15.0) (2025-07-21)
2
14
 
3
15
  ### Features
package/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # App Tutor AI Consumer ![Hotmart Brand](/docs/images/hotmart-brand.svg)
2
+
3
+ Consumer components library for Hotmart Tutor Widget
4
+
5
+ ![Typescript](https://img.shields.io/badge/TypeScript-007ACC?style=flat-square&logo=typescript&logoColor=white)
6
+ ![React](https://img.shields.io/badge/React-20232A?style=flat-square&logo=react&logoColor=61DAFB)
7
+ ![React Query](https://img.shields.io/badge/-React%20Query-FF4154?style=flat-square&logo=react%20query&logoColor=white)
8
+ ![Eslint](https://img.shields.io/badge/eslint-3A33D1?style=flat-square&logo=eslint&logoColor=white)
9
+ ![Prettier](https://img.shields.io/badge/prettier-1A2C34?style=flat-square&logo=prettier&logoColor=F7BA3E)
10
+ ![Vitest](https://img.shields.io/badge/vitest-6E9F18?style=flat-square&logo=vitest&logoColor=white)
11
+ ![Rspack](https://img.shields.io/badge/Rspack-ff8b00?style=flat-square&logo=esbuild&logoColor=white)
12
+ ![NPM](https://img.shields.io/badge/npm-CB3837?style=flat-square&logo=NPM&logoColor=white)
13
+ ![GitHub](https://img.shields.io/badge/GitHub%20Actions-212121?style=flat-square&logo=GitHub&logoColor=white)
14
+
15
+ > Micro-frontend responsible for the Hotmart Tutor AI components and pages
16
+
17
+ ## Table of Contents
18
+
19
+ - [App Tutor AI Consumer ](#app-tutor-ai-consumer-)
20
+ - [Table of Contents](#table-of-contents)
21
+ - [Installation](#installation)
22
+ - [Configuring](#configuring)
23
+ - [Node \& NPM](#node--npm)
24
+ - [Running locally](#running-locally)
25
+ - [Building](#building)
26
+ - [Technologies](#technologies)
27
+ - [Public Packages](#public-packages)
28
+ - [Private Packages](#private-packages)
29
+ - [Commits and PR's](#commits-and-prs)
30
+ - [Copyright](#copyright)
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ ### Configuring
37
+
38
+ - Download and install [Git](http://git-scm.com);
39
+ - Download and install [NVM](https://github.com/nvm-sh/nvm);
40
+ - Download and install [NodeJS and NPM](http://nodejs.org);
41
+ - Create an `.npmrc` file at root of your user folder;
42
+
43
+ ```sh
44
+ //registry.npmjs.org/:_authToken=<TOKEN>
45
+ //npm.fontawesome.com/:_authToken=<TOKEN>
46
+ @fortawesome:registry=https://npm.fontawesome.com/
47
+ ```
48
+
49
+ - Configure `hotctl` in your computer. See how [here](https://github.com/Hotmart-Org/hotctl#instala%C3%A7%C3%A3o)
50
+ - After configuration is done, execute the commands below and you good to go
51
+
52
+ ```sh
53
+ hotctl sso login
54
+ hotctl sso accounts apply -a
55
+ hotctl codeartifact token --type npm --profile buildstaging
56
+ ```
57
+
58
+ > Consult the code owners of to fill the .npmrc
59
+
60
+ ### Node & NPM
61
+
62
+ Ensure that your node version is greater than what's specified in the repository root directory's `.nvmrc` file by running `node -v`
63
+
64
+ You might need to install [NVM (Node Version Manager)](https://github.com/nvm-sh/nvm) and run:
65
+
66
+ ```sh
67
+ nvm install
68
+ ```
69
+
70
+ ## Running locally
71
+
72
+ 1. Add the following line your `/etc/hosts` file:
73
+
74
+ ```sh
75
+ 127.0.0.1 local.buildstaging.com
76
+ ```
77
+
78
+ 2. To generate SSL certificates locally, run:
79
+
80
+ ```sh
81
+ cd ./config/certs/ && ./ssl-generate.sh && cd ../../
82
+ ```
83
+
84
+ 3. To install package dependencies, run:
85
+
86
+ ```sh
87
+ npm i
88
+ ```
89
+
90
+ 4. To fetch locale files from [api-languages](https://github.com/Hotmart-Org/api-languages):
91
+
92
+ ```sh
93
+ npm run fetch-langs | npm run fetch-langs:local
94
+ ```
95
+
96
+ 5. To run the project:
97
+
98
+ - inside a shell app:
99
+
100
+ ```sh
101
+ npm run dev
102
+ ```
103
+
104
+ - standalone mode:
105
+
106
+ ```sh
107
+ npm run dev:local
108
+ ```
109
+
110
+ ## Building
111
+
112
+ Building for `staging`:
113
+
114
+ ```sh
115
+ npm run build:staging
116
+ ```
117
+
118
+ Building for `production`
119
+
120
+ ```sh
121
+ npm run build
122
+ ```
123
+
124
+ [Back to top](#table-of-contents)
125
+
126
+ ## Technologies
127
+
128
+ ### Public Packages
129
+
130
+ - [![React](https://img.shields.io/badge/React-20232A?style=flat-square&logo=react&logoColor=61DAFB)](https://github.com/facebook/react)
131
+ - [![Typescript](https://img.shields.io/badge/TypeScript-007ACC?style=flat-square&logo=typescript&logoColor=white)](https://github.com/microsoft/TypeScript)
132
+ - [![Vitest](https://img.shields.io/badge/vitest-6E9F18?style=flat-square&logo=vitest&logoColor=white)](https://vitest.dev)
133
+ - [![Testing Library](https://img.shields.io/badge/Testing%20Library-E33332?style=flat-square&logo=Testing%20Library&logoColor=white)](https://testing-library.com)
134
+ - [![MSW](https://img.shields.io/badge/MSW-ff6a33?style=flat-square&logo=gnu-bash&logoColor=white)](https://mswjs.io/)
135
+ - [![Tanstack Query](https://img.shields.io/badge/Tanstack_Query-FF4154?style=flat-square&logo=ReactQuery&logoColor=white)](https://react-query.tanstack.com/)
136
+ - [![Chance](https://img.shields.io/badge/chance-FF4154?style=flat-square&logo=nuget&logoColor=white)](https://github.com/chancejs/chancejs)
137
+
138
+ ### Private Packages
139
+
140
+ - [DataHub](https://datahub-docs.buildstaging.com)
141
+
142
+ [Back to top](#table-of-contents)
143
+
144
+ ## Commits and PR's
145
+
146
+ To keep some pattern between commits we use [commitlint](https://github.com/conventional-changelog/commitlint). Please pay attention to the rules of the commit messages.
147
+
148
+ Below there is the types accepted in commits:
149
+
150
+ - build
151
+ - chore
152
+ - ci
153
+ - docs
154
+ - feat
155
+ - fix
156
+ - perf
157
+ - refactor
158
+ - revert
159
+ - style
160
+ - test
161
+
162
+ All commits should be written in english and start with one of the types listed before:
163
+
164
+ ```sh
165
+ feat: adding infinity load
166
+ ```
167
+
168
+ or if want to specify the scope of the commit:
169
+
170
+ ```sh
171
+ feat(widget): adding infinity load
172
+ ```
173
+
174
+ [Back to top](#table-of-contents)
175
+
176
+ ## Copyright
177
+
178
+ ![Hotmart Brand](/docs/images/hotmart-brand.svg) &copy; 2025
179
+
180
+ Made with ❤
@@ -0,0 +1,16 @@
1
+
2
+ <svg width="71" height="22" viewBox="0 0 71 22" fill="none" xmlns="http://www.w3.org/2000/svg">
3
+ <g id="Hotmart brand" clip-path="url(#clip0_878_7768)">
4
+ <g id="Brand/Horizontal/Preto">
5
+ <g id="Group">
6
+ <path id="hotmart" fill-rule="evenodd" clip-rule="evenodd" d="M38.4769 9.13141C38.5234 9.13141 38.5688 9.16637 38.5688 9.22196V11.1627H40.1513C40.1922 11.1627 40.2435 11.1943 40.2435 11.2529V12.8286C40.2435 12.8854 40.1949 12.9183 40.1513 12.9183H38.5688V16.1394C38.5688 16.4731 38.7615 16.6762 39.2356 16.6762H40.1513C40.2131 16.6762 40.2435 16.7284 40.2435 16.7668V18.1517C40.2435 18.2019 40.2019 18.2295 40.1817 18.2369C39.6283 18.4409 39.3159 18.5625 38.6725 18.5625C37.0757 18.5625 36.4279 17.6482 36.4279 16.3571V12.9183H35.5271C35.4723 12.9183 35.435 12.874 35.435 12.8283V11.5932C35.435 11.5504 35.4677 11.5159 35.4991 11.507C35.5302 11.498 36.6924 11.1627 36.6924 11.1627C36.6924 11.1627 37.3242 9.22178 37.3336 9.19291C37.3429 9.16386 37.3762 9.13141 37.4213 9.13141H38.4769ZM69.2329 9.13141C69.2796 9.13141 69.3249 9.16637 69.3249 9.22196V11.1627H70.9076C70.9484 11.1627 70.9997 11.1943 70.9997 11.2529V12.8286C70.9997 12.8854 70.9512 12.9183 70.9076 12.9183H69.3249V16.1394C69.3249 16.4731 69.5177 16.6762 69.9919 16.6762H70.9076C70.9693 16.6762 70.9997 16.7284 70.9997 16.7668V18.1517C70.9997 18.2019 70.9581 18.2295 70.938 18.2369C70.3845 18.4409 70.0721 18.5625 69.4285 18.5625C67.8319 18.5625 67.1842 17.6482 67.1842 16.3571V12.9183H66.2833C66.2284 12.9183 66.1912 12.874 66.1912 12.8283V11.5932C66.1912 11.5504 66.224 11.5159 66.2553 11.507C66.2864 11.498 67.4486 11.1627 67.4486 11.1627C67.4486 11.1627 68.0804 9.22178 68.0896 9.19291C68.0991 9.16386 68.1325 9.13141 68.1775 9.13141H69.2329ZM56.4521 10.9824C58.3596 10.9824 59.4774 11.4291 59.4774 13.4173V18.2408C59.4774 18.2935 59.4318 18.3305 59.3848 18.3305H57.5024C57.4553 18.3287 57.4236 18.3006 57.4131 18.2639C57.4052 18.236 57.2899 17.8346 57.236 17.6473L57.2317 17.6332L57.1678 17.7094C56.6478 18.3025 55.9799 18.4744 55.182 18.4744C53.781 18.4744 53.0359 17.4226 53.0359 16.1836C53.0359 14.3106 54.4516 13.936 55.5098 13.936H57.3245V13.4622C57.3245 12.8787 56.8899 12.7491 55.2521 12.7438L55.1411 12.7436C54.411 12.7436 53.632 12.7995 53.5911 12.7995C53.5456 12.7995 53.4979 12.7609 53.4979 12.7096V11.402C53.4979 11.3554 53.5377 11.3207 53.5727 11.3141C54.602 11.1198 55.5499 10.9824 56.4521 10.9824ZM31.5262 10.9824C34.1509 10.9824 35.0721 12.4377 35.0721 14.7285C35.0721 17.0336 34.1509 18.4744 31.5262 18.4744C28.9164 18.4744 27.9954 17.0336 27.9954 14.7285C27.9954 12.4377 28.9164 10.9824 31.5262 10.9824ZM22.7817 8.25C22.8387 8.25 22.8736 8.29653 22.8736 8.34001V11.9278H22.9032C23.4208 11.291 24.2011 11.0011 25.0587 11.0011C26.5967 11.0011 27.0881 11.9858 27.0881 13.2892V18.2962C27.0881 18.3374 27.0534 18.3862 26.9962 18.3862H25.0439C24.9911 18.3862 24.9518 18.3427 24.9518 18.2962V13.4724C24.9518 12.9079 24.3489 12.7341 23.9644 12.7341C23.5799 12.7341 22.8736 12.9802 22.8736 13.6899V18.2962C22.8736 18.3334 22.8437 18.3862 22.7813 18.3862H20.8292C20.7865 18.3862 20.7373 18.3522 20.7373 18.2962V8.34001C20.7373 8.2933 20.7751 8.25 20.8292 8.25H22.7817ZM50.0899 10.9824C51.6349 10.9824 52.1286 11.9696 52.1286 13.2763V18.2958C52.1286 18.3489 52.0829 18.3862 52.0363 18.3862H50.063C50.0045 18.3862 49.9709 18.336 49.9709 18.2958V13.4937C49.9709 12.9277 49.4228 12.7085 49.0367 12.7085C48.588 12.7085 47.924 12.9859 47.8943 13.6245V18.2958C47.8943 18.3405 47.8605 18.3862 47.8016 18.3862H45.8273C45.7768 18.3862 45.7364 18.3426 45.7364 18.296V13.4937C45.7364 12.9277 45.1553 12.7085 44.7328 12.7085C44.3467 12.7085 43.6598 13.0004 43.6598 13.7117V18.2958C43.6598 18.3363 43.6267 18.3862 43.5687 18.3862H41.6054C41.5476 18.3862 41.5136 18.3372 41.5136 18.296V11.2179C41.5136 11.1668 41.5568 11.1277 41.606 11.1277H43.3785C43.4226 11.1277 43.4582 11.16 43.4665 11.1908C43.4749 11.2217 43.6715 11.9115 43.6715 11.9115H43.7011C44.2213 11.273 44.9937 10.9824 45.8554 10.9824C46.9104 10.9824 47.4188 11.447 47.7011 12.1583H47.7604C48.2955 11.2875 49.2282 10.9824 50.0899 10.9824ZM65.099 10.9824C65.1448 10.981 65.1932 11.0169 65.1932 11.0727V13.0469C65.1932 13.1018 65.1456 13.1381 65.1014 13.1381H64.6026C63.2589 13.1381 62.7897 13.6672 62.7897 14.3207V18.2958C62.7897 18.3537 62.7394 18.3862 62.6985 18.3862H60.7487C60.6987 18.3862 60.6569 18.3446 60.6569 18.2958V11.2167C60.6569 11.167 60.6999 11.1263 60.7483 11.1263H62.492C62.5405 11.1263 62.5748 11.1611 62.5823 11.201C62.5898 11.241 62.7897 12.3025 62.7897 12.3025H62.8193C63.2888 11.351 64.2624 11.0063 65.099 10.9824ZM57.3245 15.2184H56.142C55.5138 15.2184 55.1072 15.4508 55.1072 16.0937C55.1072 16.7904 55.6247 16.951 56.1236 16.951C56.5855 16.951 57.3245 16.6832 57.3245 15.79V15.2184ZM31.5262 12.7404C30.5021 12.7404 30.3137 13.4894 30.3137 14.7285C30.3137 15.9675 30.5021 16.7277 31.5262 16.7277C32.5647 16.7277 32.7536 15.9675 32.7536 14.7285C32.7536 13.4894 32.5647 12.7404 31.5262 12.7404Z" fill="#191C1F"/>
7
+ <path id="fire" fill-rule="evenodd" clip-rule="evenodd" d="M8.22426 17.7318C6.10943 17.715 4.40935 16.0485 4.42672 14.0093C4.44409 11.9701 6.17261 10.3307 8.28726 10.3476C10.4021 10.3643 12.1022 12.031 12.0848 14.0701C12.0674 16.1093 10.3389 17.7487 8.22426 17.7318ZM16.5098 14.0339C16.6136 12.0731 15.9165 8.86349 14.2098 6.23999C14.1645 6.17194 14.0875 6.19884 14.1039 6.27081C14.2177 6.67074 14.2451 7.39328 13.5857 7.35053C12.4175 7.275 13.6265 4.86419 11.0609 3.19179C11.0096 3.1583 10.9482 3.20302 10.9763 3.25646C11.1522 3.5789 11.2568 4.58433 10.8503 4.92708C10.5242 5.20195 9.92563 5.12802 9.35013 4.22253C8.39091 2.71366 8.75505 1.10022 9.40685 0.0924685C9.45581 0.0171142 9.38874 -0.0128138 9.33295 0.00500047C5.80348 1.12819 5.09884 5.13889 4.34783 6.6018C4.22183 6.84692 4.11264 6.97518 3.89722 6.96129C3.25448 6.91978 3.71746 5.59743 3.94009 5.13711C3.97131 5.07227 3.89408 5.03646 3.85252 5.07832C2.0355 6.90945 0.390663 10.0269 0.0780651 12.9239C0.0880417 12.8553 0.0488746 13.1633 0.0298453 13.3888C0.0298453 13.389 0.0298453 13.3894 0.0298453 13.3894C0.0224553 13.4777 0.0163585 13.5663 0.0113702 13.655C0.00619724 13.761 0.00120898 13.8672 0.000285227 13.9742C-0.0372191 18.3704 3.6286 21.9636 8.18787 21.9997C12.7471 22.0359 16.4736 18.5015 16.5112 14.1053C16.5114 14.0813 16.5098 14.0578 16.5098 14.0339Z" fill="#191C1F"/>
8
+ </g>
9
+ </g>
10
+ </g>
11
+ <defs>
12
+ <clipPath id="clip0_878_7768">
13
+ <rect width="71" height="22" fill="white"/>
14
+ </clipPath>
15
+ </defs>
16
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-tutor-ai-consumer",
3
- "version": "1.15.0",
3
+ "version": "1.17.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
package/public/index.html CHANGED
@@ -11,7 +11,7 @@
11
11
  <title>App Tutor AI Consumer</title>
12
12
  </head>
13
13
 
14
- <body class="bg-ai-dark">
14
+ <body>
15
15
  <div id="c3po-app-widget"></div>
16
16
  </body>
17
17
  </html>
@@ -73,13 +73,17 @@
73
73
  --hc-color-neutral-800: #282c2f;
74
74
  --hc-color-neutral-900: #191c1f;
75
75
  --hc-color-neutral-1000: #000000;
76
+
77
+ /* Ai Colors */
76
78
  --ai-color-primary: #a095ec;
77
79
  --ai-color-secondary: #6ba1f0;
78
- --ai-color-dark: #1a1c1f;
79
- --ai-color-chat-response: #1e1926;
80
+ --ai-color-dark: var(--hc-color-neutral-100);
81
+ --ai-color-chat-response: rgb(from var(--ai-color-primary) r g b / 0.3);
80
82
  --ai-color-gradient-primary: #44d0ff;
81
83
  --ai-color-gradient-accent: #b48eff;
82
84
 
85
+ --ai-gradient-alpha: 0.6;
86
+
83
87
  /* Size */
84
88
  --hc-size-spacing-1: 0.25rem;
85
89
  --hc-size-spacing-2: 0.5rem;
@@ -97,7 +101,7 @@
97
101
  }
98
102
 
99
103
  & *::-webkit-scrollbar-thumb {
100
- background: var(--hc-color-neutral-800);
104
+ background: var(--hc-color-neutral-200);
101
105
  border-radius: var(--hc-size-border-medium);
102
106
  border: calc(var(--hc-size-border-medium) / 2) solid transparent;
103
107
  }
@@ -190,4 +194,10 @@
190
194
  --hc-color-neutral-800: #e6e9ed;
191
195
  --hc-color-neutral-900: #f7f9fa;
192
196
  --hc-color-neutral-1000: #ffffff;
197
+
198
+ /* Ai Colors */
199
+ --ai-color-dark: #1a1c1f;
200
+ --ai-color-chat-response: #1e1926;
201
+
202
+ --ai-gradient-alpha: 0.1;
193
203
  }
@@ -2,10 +2,10 @@
2
2
  background:
3
3
  linear-gradient(
4
4
  186.9deg,
5
- rgb(from var(--ai-color-primary) r g b / 0.1) 6.65%,
6
- rgb(from var(--ai-color-secondary) r g b / 0.1) 28.99%,
7
- rgb(from var(--ai-color-dark) r g b / 0.1) 46.97%,
8
- rgb(from var(--hc-color-neutral-900) r g b / 0.8) 57.9%
5
+ rgb(from var(--ai-color-primary) r g b / var(--ai-gradient-alpha, 0.1)) 6.65%,
6
+ rgb(from var(--ai-color-secondary) r g b / var(--ai-gradient-alpha, 0.1)) 28.99%,
7
+ rgb(from var(--ai-color-dark) r g b / var(--ai-gradient-alpha, 0.1)) 46.97%,
8
+ rgb(from var(--hc-color-neutral-100) r g b / 0.8) 57.9%
9
9
  ),
10
- linear-gradient(0deg, var(--hc-color-neutral-900), var(--hc-color-neutral-900));
10
+ linear-gradient(0deg, var(--hc-color-neutral-100), var(--hc-color-neutral-100));
11
11
  }
@@ -0,0 +1 @@
1
+ export * from './init-theme'
@@ -0,0 +1,7 @@
1
+ import type { Theme } from '@/src/types'
2
+
3
+ export function initTheme(theme?: Theme) {
4
+ if (theme === 'dark') return document.body.classList.add(theme, 'bg-ai-dark')
5
+
6
+ return document.body.classList.add('bg-neutral-100')
7
+ }
@@ -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-800 shadow-lg overflow-hidden flex flex-col'
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)
@@ -35,7 +35,10 @@ if (devMode) {
35
35
  clubName: 'comofazerumvideodeteste',
36
36
  productName: 'Curso de Assinatura',
37
37
  productId: 4266504,
38
- sessionId: v4()
38
+ sessionId: v4(),
39
+ config: {
40
+ theme: 'dark'
41
+ }
39
42
  })
40
43
  })()
41
44
  }
@@ -1,17 +1,33 @@
1
1
  import clsx from 'clsx'
2
- import type { HTMLAttributes, PropsWithChildren } from 'react'
2
+ import type { ButtonHTMLAttributes, PropsWithChildren } from 'react'
3
+
4
+ import { Spinner } from '../spinner'
3
5
 
4
6
  export type ButtonProps = PropsWithChildren<
5
- HTMLAttributes<HTMLButtonElement> & {
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({ children, className, variant = 'brand', ...props }: ButtonProps) {
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':
@@ -19,14 +35,16 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
19
35
  <button
20
36
  className={clsx(
21
37
  defaultClasses,
22
- '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-0 group-hover:from-[var(--ai-color-gradient-primary)] group-hover:to-[var(--ai-color-gradient-accent)]',
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
- className='relative flex-1 rounded-lg bg-neutral-900 px-5 py-2.5 text-neutral-0 transition-all duration-75 ease-in group-hover:bg-transparent'>
29
- {children}
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'>
47
+ {content}
30
48
  </span>
31
49
  </button>
32
50
  )
@@ -37,11 +55,13 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
37
55
  defaultPadding,
38
56
  defaultClasses,
39
57
  defaultBorder,
40
- 'bg-primary-500 text-neutral-0 hover:bg-primary-600 focus:outline-none',
58
+ 'bg-primary-500 text-neutral-1000 hover:bg-primary-400 focus:outline-none',
41
59
  className
42
60
  )}
43
- {...props}>
44
- {children}
61
+ {...props}
62
+ disabled={props.disabled || loading}
63
+ aria-busy={loading}>
64
+ {content}
45
65
  </button>
46
66
  )
47
67
  case 'secondary':
@@ -51,11 +71,13 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
51
71
  defaultPadding,
52
72
  defaultClasses,
53
73
  defaultBorder,
54
- 'border-neutral-100 bg-transparent text-neutral-100 hover:bg-neutral-100 hover:text-neutral-900',
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
- {children}
77
+ {...props}
78
+ disabled={props.disabled || loading}
79
+ aria-busy={loading}>
80
+ {content}
59
81
  </button>
60
82
  )
61
83
  case 'tertiary':
@@ -65,26 +87,47 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
65
87
  defaultPadding,
66
88
  defaultClasses,
67
89
  defaultBorder,
68
- 'bg-transparent text-neutral-0 hover:bg-neutral-900',
90
+ 'text-1000 bg-transparent hover:bg-neutral-900',
69
91
  className
70
92
  )}
71
- {...props}>
72
- {children}
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(
80
103
  defaultPadding,
81
104
  defaultClasses,
82
105
  defaultBorder,
83
- 'rounded bg-neutral-100 text-neutral-900 outline-none hover:bg-neutral-200',
106
+ 'rounded bg-neutral-900 text-neutral-100 outline-none hover:bg-neutral-800',
107
+ className
108
+ )}
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
+ },
84
125
  className
85
126
  )}
86
- {...props}>
87
- {children}
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,5 @@
1
+ <svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path
3
+ d="M3.99297 6.96303H15.0076V8.77293H3.99297L8.84708 13.627L7.5673 14.9068L0.528442 7.86798L7.5673 0.829102L8.84708 2.10889L3.99297 6.96303Z"
4
+ fill="currentColor" />
5
+ </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,11 @@
1
1
  // Auto-generated file - DO NOT EDIT
2
- export type ValidIconNames = 'ai-color' | 'arrow-down' | 'chevron-down' | 'send' | 'stop'
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'
@@ -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>
@@ -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 { Icon, Spinner } from '@/src/lib/components'
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'
@@ -62,7 +62,7 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
62
62
  return (
63
63
  <div
64
64
  className={clsx(
65
- 'flex items-center rounded-full border border-neutral-800 bg-neutral-900 px-4 py-2',
65
+ 'flex items-center rounded-full border border-neutral-300 bg-neutral-100 px-4 py-2',
66
66
  { 'cursor-not-allowed opacity-40': inputDisabled }
67
67
  )}>
68
68
  <TextareaAutosize
@@ -71,7 +71,7 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
71
71
  ref={ref}
72
72
  className={clsx(
73
73
  clsx(
74
- 'max-h-12 w-full resize-none border-none bg-transparent text-neutral-100 outline-none outline-0 placeholder:text-neutral-400',
74
+ 'max-h-12 w-full resize-none border-none bg-transparent text-neutral-900 outline-none outline-0 placeholder:text-neutral-600',
75
75
  styles.textArea
76
76
  ),
77
77
  { 'cursor-not-allowed opacity-40': inputDisabled }
@@ -82,30 +82,20 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
82
82
  onKeyDown={handleKeyDown}
83
83
  disabled={inputDisabled}
84
84
  />
85
- <button
85
+ <Button
86
86
  onClick={onSend}
87
87
  disabled={buttonDisabled || loading}
88
- className={clsx(
89
- 'flex size-8 flex-col items-center justify-center rounded-full outline-none transition-colors duration-300 ease-in',
90
- {
91
- 'cursor-pointer hover:scale-110 hover:bg-neutral-600 focus:bg-neutral-700 focus:outline-none focus:ring-1 focus:ring-neutral-500 focus:ring-offset-2':
92
- !buttonDisabled,
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
- {loading ? (
98
- <Spinner className='h-5 w-5 text-neutral-500' />
99
- ) : (
100
- <Icon
101
- name='send'
102
- className={clsx('h-4 w-4 pr-0.5 pt-0.5 transition-colors duration-150', {
103
- 'text-neutral-50': !buttonDisabled,
104
- 'text-neutral-500': 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
  }
@@ -14,9 +14,9 @@ function MessageItem({ message }: { message: ParsedMessage }) {
14
14
  <div
15
15
  data-test='messages-item'
16
16
  className={clsx(
17
- 'max-w-[min(80%,52rem)] overflow-x-hidden rounded-lg px-3 text-sm/normal text-neutral-50',
17
+ 'max-w-[min(80%,52rem)] overflow-x-hidden rounded-lg px-3 text-sm/normal text-neutral-900',
18
18
  {
19
- 'self-end bg-neutral-800': message.metadata.author === 'user',
19
+ 'self-end bg-neutral-200': message.metadata.author === 'user',
20
20
  'bg-ai-chat-response': message.metadata.author === 'ai'
21
21
  }
22
22
  )}>
@@ -10,9 +10,9 @@ const MessageSkeleton = forwardRef<HTMLDivElement>((_, ref) => {
10
10
  aria-label='Loading Component'>
11
11
  <AIAvatarIcon className='rounded-lg bg-ai-chat-response' />
12
12
  <div className='flex w-full flex-col items-start gap-2'>
13
- <div className='h-3 w-full animate-pulse rounded-full bg-neutral-800 transition-colors' />
14
- <div className='h-3 w-[83%] animate-pulse rounded-full bg-neutral-800 transition-colors delay-75' />
15
- <div className='h-3 w-[56%] animate-pulse rounded-full bg-neutral-800 transition-colors delay-100' />
13
+ <div className='h-3 w-full animate-pulse rounded-full bg-neutral-200 transition-colors delay-0' />
14
+ <div className='h-3 w-[83%] animate-pulse rounded-full bg-neutral-200 transition-colors delay-75' />
15
+ <div className='h-3 w-[56%] animate-pulse rounded-full bg-neutral-200 transition-colors delay-100' />
16
16
  </div>
17
17
  </div>
18
18
  )
@@ -59,7 +59,7 @@ function MessagesList() {
59
59
 
60
60
  {allMessages?.map(([publishingDate, messages], i) => (
61
61
  <div key={i} className='flex flex-1 flex-col justify-center gap-6'>
62
- <span className='self-center rounded-full border border-neutral-700 bg-neutral-800 px-4 py-2 text-xs capitalize text-neutral-50'>
62
+ <span className='self-center rounded-full border border-neutral-300 bg-neutral-200 px-4 py-2 text-xs capitalize text-neutral-900'>
63
63
  {publishingDate}
64
64
  </span>
65
65
  {messages.map((msg, k) => (
@@ -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
- function AIAvatarIcon({
6
- className = 'rounded-full border-4 border-neutral-900 bg-neutral-800'
7
- }: {
8
- className?: string
9
- }) {
5
+ import styles from './styles.module.css'
6
+
7
+ export type AIAvatarProps = { className?: string }
8
+
9
+ function AIAvatar({
10
+ className = 'rounded-full border-4 border-neutral-100 bg-neutral-200'
11
+ }: AIAvatarProps) {
10
12
  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>
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 AIAvatarIcon
22
+ export default AIAvatar
@@ -1 +1,3 @@
1
- export { default as AIAvatarIcon } from './ai-avatar'
1
+ export * from './ai-avatar'
2
+ export { default as AIAvatar } from './ai-avatar'
3
+ export { default as AIAvatarIcon } from './ai-avatar-icon'
@@ -0,0 +1,3 @@
1
+ .avatar {
2
+ background: linear-gradient(90deg, #44d0ff 0%, #b48eff 100%);
3
+ }
@@ -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 { useWidgetLoadingAtomValue, useWidgetTabsValueAtom } from '../../store'
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
- <MessagesList />
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,7 +1,7 @@
1
1
  import clsx from 'clsx'
2
2
  import { useTranslation } from 'react-i18next'
3
3
 
4
- import { AIAvatarIcon } from '../ai-avatar'
4
+ import { AIAvatar } from '../ai-avatar'
5
5
 
6
6
  import styles from './styles.module.css'
7
7
 
@@ -14,15 +14,9 @@ function GreetingsCard({ author, tutorName }: GreetingsCardProps) {
14
14
  const { t } = useTranslation()
15
15
 
16
16
  return (
17
- <div className='flex flex-col items-center justify-center text-neutral-50'>
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
- <figure
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 })}
@@ -31,7 +25,7 @@ function GreetingsCard({ author, tutorName }: GreetingsCardProps) {
31
25
  {t('general.greetings.firstMessage', { tutorName })}
32
26
  </h3>
33
27
  </div>
34
- <p className='text-sm font-normal text-neutral-400'>{t('general.greetings.description')}</p>
28
+ <p className='text-sm font-normal text-neutral-600'>{t('general.greetings.description')}</p>
35
29
  </div>
36
30
  </div>
37
31
  )
@@ -1,7 +1,3 @@
1
- .avatar {
2
- background: linear-gradient(90deg, #44d0ff 0%, #b48eff 100%);
3
- }
4
-
5
1
  .faceTxt {
6
2
  composes: gradientText from '../../../../config/styles/utilities/text-utilities.module.css';
7
3
  }
@@ -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,69 @@
1
+ import { Button, Icon } from '@/src/lib/components'
2
+ import { TutorWidgetEvents } from '../../events'
3
+ import { AIAvatar } from '../ai-avatar'
4
+
5
+ import type { WidgetHeaderContentProps, WidgetHeaderProps } from './types'
6
+
7
+ export function WidgetHeaderContent({ clubName, tutorName }: WidgetHeaderContentProps) {
8
+ return (
9
+ <div className='flex w-full gap-2'>
10
+ <AIAvatar />
11
+ <div className='flex flex-col'>
12
+ {tutorName && <h4 className='text-base'>{tutorName}</h4>}
13
+ {clubName && <p className='text-sm/normal text-neutral-600'>{clubName}</p>}
14
+ </div>
15
+ </div>
16
+ )
17
+ }
18
+
19
+ export function WidgetHeaderContentWithoutMeta({ name }: { name?: string }) {
20
+ return (
21
+ <div className='grid-areas-[a_b] grid grid-cols-[auto_1fr] items-center gap-1'>
22
+ <Button className='grid-area-[a]' aria-label='Arrow Left Icon'>
23
+ <Icon name='arrow-left' width={15} height={15} aria-hidden />
24
+ </Button>
25
+ <div className='grid-area-[b] flex justify-center'>
26
+ <span>{name}</span>
27
+ </div>
28
+ </div>
29
+ )
30
+ }
31
+
32
+ function WidgetHeader({
33
+ enabledButtons,
34
+ clubName,
35
+ tutorName,
36
+ showContentWithoutMeta,
37
+ showContent = true
38
+ }: WidgetHeaderProps) {
39
+ return (
40
+ <div className='grid-areas-[a_b] mt-0.5 grid grid-cols-[1fr_auto] items-center text-neutral-1000'>
41
+ <div className='grid-area-[a]'>
42
+ {showContent && !showContentWithoutMeta && (
43
+ <WidgetHeaderContent clubName={clubName} tutorName={tutorName} />
44
+ )}
45
+ {showContentWithoutMeta && !showContent && (
46
+ <WidgetHeaderContentWithoutMeta name={tutorName} />
47
+ )}
48
+ </div>
49
+ <div className='shrink-0'>
50
+ <div className='grid-area-[b] ml-auto flex max-w-max gap-3 text-neutral-700'>
51
+ <Button show={enabledButtons.includes('archive')} aria-label='Archive Icon'>
52
+ <Icon name='archive' className='h-4 w-4' aria-hidden />
53
+ </Button>
54
+ <Button show={enabledButtons.includes('info')} aria-label='Info Icon'>
55
+ <Icon name='info' className='h-4 w-4' aria-hidden />
56
+ </Button>
57
+ <Button
58
+ show={enabledButtons.includes('close')}
59
+ onClick={() => TutorWidgetEvents['c3po-app-widget-hide'].dispatch()}
60
+ aria-label='Close Icon'>
61
+ <Icon name='close' className='h-4 w-4' aria-hidden />
62
+ </Button>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ )
67
+ }
68
+
69
+ export default WidgetHeader
@@ -0,0 +1,2 @@
1
+ export * from './header'
2
+ export { default as WidgetHeader } from './header'
@@ -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
@@ -2,6 +2,7 @@ export * from './ai-avatar'
2
2
  export * from './chat-page'
3
3
  export * from './container'
4
4
  export * from './greetings-card'
5
+ export * from './header'
5
6
  export * from './loading-page'
6
7
  export * from './page-layout'
7
8
  export * from './scroll-to-bottom-button'
@@ -6,11 +6,13 @@ import {
6
6
  MessageSkeleton,
7
7
  useChatInputValueAtom
8
8
  } from '@/src/modules/messages/components'
9
+ import { useWidgetSettingsAtomValue } from '../../store'
9
10
  import { PageLayout } from '../page-layout'
10
11
 
11
12
  function WidgetLoadingPage() {
12
13
  const chatInputRef = useRef<HTMLTextAreaElement>(null)
13
14
  const [, setChatInputValue] = useChatInputValueAtom()
15
+ const settings = useWidgetSettingsAtomValue()
14
16
 
15
17
  const handler = useCallback(
16
18
  (e: Event) => {
@@ -31,9 +33,11 @@ function WidgetLoadingPage() {
31
33
  return (
32
34
  <PageLayout
33
35
  asideChild={<ChatInput name='new-chat-msg-input' ref={chatInputRef} loading={true} />}>
34
- <div className='flex h-full flex-col justify-end px-5 py-4'>
35
- <MessageSkeleton />
36
- </div>
36
+ {settings?.config?.theme && (
37
+ <div className='flex h-full flex-col justify-end px-5 py-4'>
38
+ <MessageSkeleton />
39
+ </div>
40
+ )}
37
41
  </PageLayout>
38
42
  )
39
43
  }
@@ -23,7 +23,7 @@ function PageLayout({ asideChild, children, className }: PageLayoutProps) {
23
23
  {children}
24
24
  </div>
25
25
  {asideChild && (
26
- <div className='grid-area-[aside] flex-shrink-0 border-t border-t-neutral-700 px-5 py-4'>
26
+ <div className='grid-area-[aside] flex-shrink-0 border-t border-t-neutral-300 px-5 py-4'>
27
27
  {asideChild}
28
28
  </div>
29
29
  )}
@@ -15,8 +15,8 @@ const ScrollToBottomButton = forwardRef<HTMLButtonElement, IScrollToBottomButton
15
15
  {...props}
16
16
  ref={ref}
17
17
  className={clsx(
18
- 'absolute bottom-4 left-1/2 flex size-7 cursor-pointer flex-col items-center justify-center rounded-full bg-neutral-600 text-sm text-neutral-50 outline-none transition-colors duration-300 ease-in hover:scale-110 hover:bg-neutral-700 focus:outline-none focus:ring-neutral-500 focus:ring-offset-2 focus-visible:ring-2 active:ring-2',
19
- { 'opacity-85': show, 'pointer-events-none opacity-0': !show },
18
+ 'absolute bottom-4 left-1/2 flex size-7 cursor-pointer flex-col items-center justify-center rounded-full bg-neutral-400 text-sm text-neutral-900 outline-none transition-colors duration-300 ease-in hover:scale-110 hover:bg-neutral-500 focus:outline-none focus:ring-neutral-500 focus:ring-offset-2 focus-visible:ring-2 active:ring-2',
19
+ { 'opacity-80': show, 'pointer-events-none opacity-0': !show },
20
20
  className
21
21
  )}
22
22
  onClick={onClick}
@@ -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'
@@ -17,7 +18,7 @@ function WidgetStarterPage() {
17
18
  const { t } = useTranslation()
18
19
  const [settings] = useWidgetSettingsAtom()
19
20
  const chatInputRef = useRef<HTMLTextAreaElement>(null)
20
- const [, setChatInputValue] = useChatInputValueAtom()
21
+ const [chatInputValue, setChatInputValue] = useChatInputValueAtom()
21
22
  const [, setWidgetTabs] = useWidgetTabsAtom()
22
23
  const sendTextMessageMutation = useSendTextMessage()
23
24
 
@@ -53,15 +54,21 @@ function WidgetStarterPage() {
53
54
  name='new-chat-msg-input'
54
55
  ref={chatInputRef}
55
56
  onSend={() => setWidgetTabs('chat')}
57
+ buttonDisabled={!chatInputValue.trim()}
56
58
  />
57
59
  }>
58
60
  <div className='grid-areas-[a_b] grid h-full grid-cols-1 grid-rows-[1fr_auto]'>
59
- <div
60
- className={clsx(
61
- 'grid-area-[a] flex min-h-0 flex-col justify-center px-5 py-4',
62
- styles.bg
63
- )}>
64
- <GreetingsCard author={settings?.author ?? ''} tutorName={settings?.tutorName ?? ''} />
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>
65
72
  </div>
66
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'>
67
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) => {
@@ -2,12 +2,14 @@ import { useEffect, useState } from 'react'
2
2
 
3
3
  import { initDayjs } from '@/src/config/dayjs'
4
4
  import { initAxios } from '@/src/config/request/api'
5
+ import { initTheme } from '@/src/config/theme'
5
6
  import { SparkieService } from '@/src/modules/sparkie'
6
7
  import type { WidgetSettingProps } from '@/src/types'
7
8
  import { TutorWidgetEvents } from '../../events'
8
9
 
9
10
  const init = async (settings: WidgetSettingProps) => {
10
11
  try {
12
+ initTheme(settings.config?.theme)
11
13
  initAxios(settings.hotmartToken)
12
14
  await initDayjs(settings.locale)
13
15
  await SparkieService.initSparkie({
package/src/types.ts CHANGED
@@ -26,6 +26,8 @@ export enum ClassTypes {
26
26
  Other = 'OTHER'
27
27
  }
28
28
 
29
+ export type Theme = 'light' | 'dark'
30
+
29
31
  export type WidgetSettingProps = {
30
32
  hotmartToken: string
31
33
  locale: ILanguages
@@ -47,6 +49,9 @@ export type WidgetSettingProps = {
47
49
  current_media_codes?: string
48
50
  productType?: string
49
51
  classType?: ClassTypes
52
+ config?: {
53
+ theme?: Theme
54
+ }
50
55
  }
51
56
 
52
57
  export interface ICustomEvent<T = object> {
@@ -1,6 +1,7 @@
1
1
  /** @type {import('tailwindcss').Config} */
2
2
  module.exports = {
3
3
  content: ['./src/**/*.{js,ts,jsx,tsx}', './public/**/*.html'],
4
+ darkMode: 'class',
4
5
  theme: {
5
6
  extend: {
6
7
  colors: {
package/docs/README.md DELETED
@@ -1,15 +0,0 @@
1
- # app-tutor-ai-consumer
2
-
3
- Consumer components library for Hotmart Tutor Widget
4
-
5
- ---
6
- ## Components
7
-
8
- ### component 1
9
-
10
- Description of component 1
11
-
12
- Example of usage:
13
- ```
14
- ...put a code example here
15
- ```