app-tutor-ai-consumer 1.0.1 → 1.2.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 (35) hide show
  1. package/.github/workflows/production.yml +1 -1
  2. package/CHANGELOG.md +18 -0
  3. package/config/rspack/rspack.config.js +2 -1
  4. package/eslint.config.mjs +5 -0
  5. package/package.json +7 -1
  6. package/src/config/i18n/init.ts +1 -1
  7. package/src/config/optimizely/index.ts +1 -0
  8. package/src/config/optimizely/optimizely-provider.tsx +31 -0
  9. package/src/config/optimizely/optimizely.ts +9 -0
  10. package/src/config/request/api.ts +27 -0
  11. package/src/config/request/error.ts +42 -0
  12. package/src/config/request/index.ts +2 -0
  13. package/src/config/request/interceptors.ts +53 -0
  14. package/src/config/request/types.ts +20 -0
  15. package/src/config/tanstack/index.ts +3 -0
  16. package/src/config/tanstack/query-client.ts +31 -0
  17. package/src/config/tanstack/query-provider.tsx +18 -0
  18. package/src/config/tests/abstract-mock-generator.ts +1 -1
  19. package/src/lib/components/errors/boundary.tsx +68 -0
  20. package/src/lib/components/errors/error-boundary.spec.tsx +38 -0
  21. package/src/lib/components/errors/error-boundary.tsx +26 -0
  22. package/src/lib/components/errors/generic/generic-error.tsx +13 -0
  23. package/src/lib/components/errors/generic/index.ts +1 -0
  24. package/src/lib/components/errors/index.ts +2 -0
  25. package/src/lib/components/index.ts +2 -0
  26. package/src/lib/utils/constants.ts +4 -0
  27. package/src/lib/utils/index.ts +1 -0
  28. package/src/lib/utils/message-types.ts +3 -0
  29. package/src/main/main.tsx +13 -6
  30. package/src/modules/global-providers/global-providers.tsx +26 -0
  31. package/src/modules/global-providers/index.ts +1 -0
  32. package/src/modules/widget/index.ts +2 -0
  33. package/src/modules/widget/store/index.ts +1 -0
  34. package/src/modules/widget/store/widget-settings.atom.ts +12 -0
  35. package/src/types.ts +12 -0
@@ -114,7 +114,7 @@ jobs:
114
114
  webhook-chat: 'https://chat.googleapis.com/v1/spaces/AAAA1nvOyjo/messages?key=AIzaSyDdI0hCZtE6vySjMm-WEfRq3CPzqKqqsHI&token=uuL7DA8zTxUkJjQa39HIM0TYVZV0DvneZ0mklNhEr5M'
115
115
  body: |
116
116
  {
117
- "text": "🚀 *[PRODUCTION] app-club-consumer v${{ env.VERSION }}*: production deploy: ${{ github.ref_name }}",
117
+ "text": "🚀 *[PRODUCTION] app-tutor-ai-consumer v${{ env.VERSION }}*: production deploy: ${{ github.ref_name }}",
118
118
  "cards": [
119
119
  {
120
120
  "header": {
package/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ # [1.2.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.1.0...v1.2.0) (2025-06-20)
2
+
3
+ ### Features
4
+
5
+ - add Optimizely Config ([ad2162f](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/ad2162f6e92b606cb72078d75a972067a8988e6f))
6
+
7
+ # [1.1.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.0.1...v1.1.0) (2025-06-18)
8
+
9
+ ### Bug Fixes
10
+
11
+ - pr issues ([0004e58](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/0004e58014621a4a85ecabb6996372c470292d75))
12
+ - pr issues ([897bbe8](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/897bbe8a5a049f8cbb3d30f854a8a73875dab174))
13
+ - pr issues ([14353de](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/14353de72df8cecb70d673e87d91ed361eb5662b))
14
+
15
+ ### Features
16
+
17
+ - add Tanstack Query Config ([14c40ec](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/14c40ec1fc022e8e45a4df29baef670505623be5))
18
+
1
19
  ## [1.0.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.0.0...v1.0.1) (2025-06-18)
2
20
 
3
21
  ### Bug Fixes
@@ -158,7 +158,8 @@ module.exports = async function (env) {
158
158
  }),
159
159
  new rspack.DefinePlugin({
160
160
  'process.env.PROJECT_VERSION': JSON.stringify(packageJSON.version),
161
- 'process.env.TARGET_ENV': JSON.stringify(process.env.NODE_ENV)
161
+ 'process.env.TARGET_ENV': JSON.stringify(process.env.NODE_ENV),
162
+ 'process.env.RELEASE_FULL_NAME': JSON.stringify(releaseFullName)
162
163
  }),
163
164
  new rspack.ProgressPlugin(),
164
165
  new TsCheckerRspackPlugin(),
package/eslint.config.mjs CHANGED
@@ -7,6 +7,7 @@ import reactHooks from 'eslint-plugin-react-hooks'
7
7
  import globals from 'globals'
8
8
  import simpleImportSort from 'eslint-plugin-simple-import-sort'
9
9
  import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
10
+ import pluginQuery from '@tanstack/eslint-plugin-query'
10
11
 
11
12
  export default tseslint.config(
12
13
  {
@@ -19,12 +20,16 @@ export default tseslint.config(
19
20
  'eslint.config.mjs',
20
21
  'commitlint.config.js',
21
22
  'babel.config.js',
23
+ 'config/certs/',
24
+ 'config/vitest/',
25
+ 'scripts/',
22
26
  '**/*.d.ts',
23
27
  ],
24
28
  },
25
29
  eslintPluginPrettierRecommended,
26
30
  eslint.configs.recommended,
27
31
  tseslint.configs.recommendedTypeChecked,
32
+ ...pluginQuery.configs['flat/recommended'],
28
33
  { languageOptions: { globals: globals.browser } },
29
34
  {
30
35
  languageOptions: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-tutor-ai-consumer",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
@@ -47,6 +47,7 @@
47
47
  "@semantic-release/release-notes-generator": "~14.0.3",
48
48
  "@stylistic/eslint-plugin-js": "~4.4.1",
49
49
  "@svgr/webpack": "~8.1.0",
50
+ "@tanstack/eslint-plugin-query": "~5.78.0",
50
51
  "@tanstack/react-query-devtools": "~5.80.6",
51
52
  "@testing-library/jest-dom": "~6.6.3",
52
53
  "@testing-library/react": "~16.3.0",
@@ -99,10 +100,15 @@
99
100
  "vitest": "~3.2.3"
100
101
  },
101
102
  "dependencies": {
103
+ "@hot-observability-js/react": "~1.1.0",
104
+ "@optimizely/react-sdk": "~3.2.4",
105
+ "@tanstack/query-sync-storage-persister": "~5.80.7",
102
106
  "@tanstack/react-query": "~5.80.6",
107
+ "@tanstack/react-query-persist-client": "~5.80.7",
103
108
  "clsx": "~2.1.1",
104
109
  "i18next": "~25.2.1",
105
110
  "i18next-resources-to-backend": "~1.2.1",
111
+ "jotai": "~2.12.5",
106
112
  "react": "~19.1.0",
107
113
  "react-dom": "~19.1.0",
108
114
  "react-i18next": "~15.5.2"
@@ -13,7 +13,7 @@ export const initLanguage = async (language: string) => {
13
13
  if (i18nInitialized) return
14
14
 
15
15
  const lang = language?.toUpperCase() as keyof ILangs
16
- let lng = language.match(/^pt/gi) ? LANGUAGES.PT_BR : LANGUAGES[lang]
16
+ let lng = language?.match(/^pt/gi) ? LANGUAGES.PT_BR : LANGUAGES[lang]
17
17
 
18
18
  if (!LANG_RESOURCES.has(lng)) lng = DEFAULT_LANGUAGE
19
19
 
@@ -0,0 +1 @@
1
+ export { default as OptimizelyProvider } from './optimizely-provider'
@@ -0,0 +1,31 @@
1
+ import { OptimizelyProvider as Provider } from '@optimizely/react-sdk'
2
+ import type { PropsWithChildren } from 'react'
3
+
4
+ import { APP_SYSTEM, APP_VERSION, productionMode } from '@/src/lib/utils'
5
+ import type { WidgetSettingProps } from '@/src/types'
6
+
7
+ import optimizelyClient from './optimizely'
8
+
9
+ function OptimizelyProvider({
10
+ settings,
11
+ children,
12
+ }: PropsWithChildren<{ settings: WidgetSettingProps }>) {
13
+ const userInfo = {
14
+ id: settings.user?.ucode ?? '',
15
+ attributes: {
16
+ appVersion: APP_VERSION,
17
+ appSystem: APP_SYSTEM,
18
+ appDebug: !productionMode,
19
+ locale: settings.locale ?? '',
20
+ slug: settings.membershipSlug ?? '',
21
+ },
22
+ }
23
+
24
+ return (
25
+ <Provider optimizely={optimizelyClient} user={userInfo}>
26
+ {children}
27
+ </Provider>
28
+ )
29
+ }
30
+
31
+ export default OptimizelyProvider
@@ -0,0 +1,9 @@
1
+ import { createInstance } from '@optimizely/react-sdk'
2
+
3
+ const optimizelyClient = createInstance({
4
+ sdkKey: process.env.OPTIMIZELY_SDK_KEY,
5
+ eventBatchSize: 10,
6
+ eventFlushInterval: 1000,
7
+ })
8
+
9
+ export default optimizelyClient
@@ -0,0 +1,27 @@
1
+ import type { InternalAxiosRequestConfig } from 'axios'
2
+ import axios from 'axios'
3
+
4
+ import { RELEASE_FULL_NAME } from '@/src/lib/utils'
5
+
6
+ import { whenRequestWithError, whenResponseWithError } from './interceptors'
7
+
8
+ const addAppNameToHeader = (config: InternalAxiosRequestConfig) => {
9
+ if (config.headers) {
10
+ config.headers['x-app-name'] = RELEASE_FULL_NAME
11
+ }
12
+
13
+ return config
14
+ }
15
+
16
+ const api = axios.create()
17
+
18
+ export function initAxios(token: string): void {
19
+ if (!token) throw new Error('Error initializing axios. Missing token')
20
+
21
+ api.defaults.headers.common.Authorization = `Bearer ${token}`
22
+ }
23
+
24
+ api.interceptors.request.use((config) => addAppNameToHeader(config), whenRequestWithError)
25
+ api.interceptors.response.use((response) => response, whenResponseWithError)
26
+
27
+ export default api
@@ -0,0 +1,42 @@
1
+ import type { AxiosResponse } from 'axios'
2
+
3
+ import type { ApiErrorConstructorProps } from './types'
4
+
5
+ class ApiError extends Error {
6
+ private _message: string
7
+ private _extra?: unknown
8
+ private _statusCode: number
9
+ private _response?: AxiosResponse
10
+ private _headers?: Record<string, unknown>
11
+
12
+ constructor({ statusCode, message, headers, extra, response }: ApiErrorConstructorProps) {
13
+ super(message)
14
+ this._statusCode = statusCode
15
+ this._message = message
16
+ this._headers = headers
17
+ this._extra = extra
18
+ this._response = response
19
+ }
20
+
21
+ get statusCode() {
22
+ return this._statusCode
23
+ }
24
+
25
+ get message() {
26
+ return this._message
27
+ }
28
+
29
+ get headers() {
30
+ return this._headers
31
+ }
32
+
33
+ get extra() {
34
+ return this?._extra
35
+ }
36
+
37
+ get response() {
38
+ return this._response
39
+ }
40
+ }
41
+
42
+ export default ApiError
@@ -0,0 +1,2 @@
1
+ export { default as api } from './api'
2
+ export { default as ApiError } from './error'
@@ -0,0 +1,53 @@
1
+ import { type AxiosError } from 'axios'
2
+
3
+ import { HttpCodes, MessageTypes } from '@/src/lib/utils'
4
+ import packageJson from '../../../package.json'
5
+
6
+ import ApiError from './error'
7
+ import type { ResponseError } from './types'
8
+
9
+ const whenGenericError = (error: AxiosError) => {
10
+ const genericError = new ApiError({
11
+ message: error?.message,
12
+ statusCode: HttpCodes.INTERNAL_SERVER_ERROR
13
+ })
14
+
15
+ return Promise.reject(genericError)
16
+ }
17
+
18
+ export const whenRequestWithError = (error: AxiosError) => {
19
+ if (error.request) {
20
+ const code = parseInt(String(error.code), 10)
21
+ const requestError = new ApiError({
22
+ statusCode: !Number.isNaN(code) ? code : HttpCodes.INTERNAL_SERVER_ERROR,
23
+ message: String(error.request)
24
+ })
25
+
26
+ return Promise.reject(requestError)
27
+ }
28
+
29
+ return whenGenericError(error)
30
+ }
31
+
32
+ export const whenResponseWithError = (error: AxiosError<ResponseError>) => {
33
+ if (error.response) {
34
+ const responseError = new ApiError({
35
+ response: error.response,
36
+ headers: error.response.headers,
37
+ extra: error?.response?.data?.extra ?? '',
38
+ statusCode: error.response.status,
39
+ message: error?.response?.data?.message ?? ''
40
+ })
41
+
42
+ if (error.response.status === HttpCodes.UNAUTHORIZED) {
43
+ window.postMessage(
44
+ { appName: packageJson.name, type: MessageTypes.SSO_EXPIRED_TOKEN },
45
+ window.location.origin
46
+ )
47
+ }
48
+
49
+ return Promise.reject(responseError)
50
+ }
51
+
52
+ return whenGenericError(error)
53
+ }
@@ -0,0 +1,20 @@
1
+ import type { AxiosRequestHeaders, AxiosResponse } from 'axios'
2
+
3
+ export type RequestHeaders = AxiosRequestHeaders
4
+
5
+ export type ResponseError = {
6
+ currentDate: string
7
+ status: string
8
+ code: string
9
+ message: string
10
+ details: string
11
+ extra: unknown
12
+ }
13
+
14
+ export type ApiErrorConstructorProps = {
15
+ message: string
16
+ statusCode: number
17
+ extra?: unknown
18
+ response?: AxiosResponse
19
+ headers?: Record<string, unknown>
20
+ }
@@ -0,0 +1,3 @@
1
+ export * from './query-client'
2
+ export * from './query-provider'
3
+ export { default as QueryProvider } from './query-provider'
@@ -0,0 +1,31 @@
1
+ import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'
2
+ import { QueryClient } from '@tanstack/react-query'
3
+
4
+ import { DEFAULT_STALE_TIME, HttpCodes } from '@/src/lib/utils'
5
+ import type { ApiError } from '../request'
6
+ import api from '../request/api'
7
+
8
+ export const queryClient = new QueryClient({
9
+ defaultOptions: {
10
+ queries: {
11
+ gcTime: 1000 * 60 * 60 * 24, // 24 hours
12
+ staleTime: DEFAULT_STALE_TIME,
13
+ refetchOnWindowFocus: false,
14
+ retry(failureCount, error) {
15
+ if (!api?.defaults?.headers?.common?.['Authorization']) return false
16
+
17
+ const maxRetries = 2
18
+ const statusCode = (error as ApiError).statusCode
19
+
20
+ return (
21
+ (statusCode < HttpCodes.BAD_REQUEST || statusCode >= HttpCodes.INTERNAL_SERVER_ERROR) &&
22
+ failureCount <= maxRetries
23
+ )
24
+ }
25
+ }
26
+ }
27
+ })
28
+
29
+ export const persister = createSyncStoragePersister({
30
+ storage: window.localStorage
31
+ })
@@ -0,0 +1,18 @@
1
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
2
+ import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
3
+ import type { PropsWithChildren } from 'react'
4
+
5
+ import { persister, queryClient } from './query-client'
6
+
7
+ export type QueryProviderProps = PropsWithChildren<{ showDevTools?: boolean }>
8
+
9
+ function QueryProvider({ children, showDevTools = true }: QueryProviderProps) {
10
+ return (
11
+ <PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
12
+ {children}
13
+ {showDevTools && <ReactQueryDevtools />}
14
+ </PersistQueryClientProvider>
15
+ )
16
+ }
17
+
18
+ export default QueryProvider
@@ -2,7 +2,7 @@ abstract class MockGenerator<T = unknown> {
2
2
  abstract getOne(properties?: T): T
3
3
 
4
4
  getMany(amount = 1, props?: T) {
5
- return Array(amount).fill(this.getOne(props)) as Array<T>
5
+ return Array.from({ length: amount }, () => this.getOne(props))
6
6
  }
7
7
  }
8
8
 
@@ -0,0 +1,68 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
2
+ import { Component } from 'react'
3
+ import { HotmartRum } from '@hot-observability-js/react'
4
+ import type { ErrorInfo, PropsWithChildren, ReactNode } from 'react'
5
+
6
+ export type BoundaryState = {
7
+ hasError: boolean
8
+ }
9
+
10
+ export type BoundaryProps = PropsWithChildren<{
11
+ user: {
12
+ id?: string
13
+ email?: string
14
+ }
15
+ pathname?: string
16
+ fallback: ReactNode
17
+ onError?: () => void
18
+ }>
19
+
20
+ const logError = (error: Error, errorInfo: ErrorInfo, user?: BoundaryProps['user']) => {
21
+ if (process.env.TARGET_ENV !== 'production' && typeof vi === 'undefined') {
22
+ return console.error(`Uncaught error: ${JSON.stringify({ error, errorInfo })}`)
23
+ }
24
+
25
+ HotmartRum.withScope((scope) => {
26
+ scope.setLevel('error')
27
+
28
+ if (user?.id && user?.email) {
29
+ scope.setUser({ id: user?.id, email: user?.email })
30
+ }
31
+
32
+ const msg = errorInfo?.componentStack?.toString()
33
+ if (msg) HotmartRum.captureMessage(msg)
34
+
35
+ HotmartRum.captureException(error)
36
+ })
37
+ }
38
+
39
+ class Boundary extends Component<BoundaryProps, BoundaryState> {
40
+ state: BoundaryState = { hasError: false }
41
+
42
+ static getDerivedStateFromError() {
43
+ return { hasError: true }
44
+ }
45
+
46
+ componentDidUpdate(prevProps: BoundaryProps, prevState: BoundaryState) {
47
+ const hasPathnameChanged = this.props.pathname !== prevProps.pathname
48
+
49
+ if (hasPathnameChanged && prevState.hasError) {
50
+ this.setState({ hasError: false })
51
+ }
52
+ }
53
+
54
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
55
+ logError(error, errorInfo, this.props?.user)
56
+ this.props.onError?.()
57
+ }
58
+
59
+ render() {
60
+ if (this.state.hasError) {
61
+ return this.props.fallback
62
+ }
63
+
64
+ return this.props.children
65
+ }
66
+ }
67
+
68
+ export default Boundary
@@ -0,0 +1,38 @@
1
+ import { render, screen } from '@/src/config/tests'
2
+
3
+ import ErrorBoundary from './error-boundary'
4
+
5
+ describe('ErrorBoundary', () => {
6
+ const onErrorMock = vi.fn()
7
+
8
+ const InternalError = () => <h1>oops, an error occurred</h1>
9
+
10
+ const ThrowError = () => {
11
+ throw new Error('Test')
12
+ }
13
+
14
+ const renderComponent = () =>
15
+ render(
16
+ <ErrorBoundary fallback={<InternalError />} onError={onErrorMock}>
17
+ <ThrowError />
18
+ </ErrorBoundary>
19
+ )
20
+
21
+ beforeEach(() => vi.spyOn(console, 'error').mockImplementation(vi.fn()))
22
+
23
+ afterEach(() => vi.clearAllMocks())
24
+
25
+ it('should be intercept by error boundary when an error happen', () => {
26
+ renderComponent()
27
+
28
+ expect(screen.getByRole('heading', { name: /oops, an error occurred/i })).toBeInTheDocument()
29
+ expect(console.error).toHaveBeenCalled()
30
+ })
31
+
32
+ it('should, when intercept an error, execute callback', () => {
33
+ renderComponent()
34
+
35
+ expect(screen.getByRole('heading', { name: /oops, an error occurred/i })).toBeInTheDocument()
36
+ expect(onErrorMock).toHaveBeenCalled()
37
+ })
38
+ })
@@ -0,0 +1,26 @@
1
+ import type { PropsWithChildren, ReactNode } from 'react'
2
+
3
+ import { useWidgetSettingsAtom } from '@/src/modules/widget'
4
+
5
+ import Boundary from './boundary'
6
+
7
+ export type ErrorBoundaryProps = PropsWithChildren<{
8
+ fallback: ReactNode
9
+ onError?: () => void
10
+ }>
11
+
12
+ const ErrorBoundary = ({ children, fallback, onError }: ErrorBoundaryProps) => {
13
+ const [settings] = useWidgetSettingsAtom()
14
+
15
+ return (
16
+ <Boundary
17
+ user={{ id: settings?.userId, email: settings?.user?.email }}
18
+ fallback={fallback}
19
+ pathname={location.pathname}
20
+ onError={onError}>
21
+ {children}
22
+ </Boundary>
23
+ )
24
+ }
25
+
26
+ export default ErrorBoundary
@@ -0,0 +1,13 @@
1
+ import { useTranslation } from 'react-i18next'
2
+
3
+ function GenericError() {
4
+ const { t } = useTranslation()
5
+
6
+ return (
7
+ <div>
8
+ <h4 className='text-xl font-bold'>{t('generic.error')}</h4>
9
+ </div>
10
+ )
11
+ }
12
+
13
+ export default GenericError
@@ -0,0 +1 @@
1
+ export { default as GenericError } from './generic-error'
@@ -0,0 +1,2 @@
1
+ export { default as ErrorBoundary } from './error-boundary'
2
+ export * from './generic'
@@ -1 +1,3 @@
1
+ export * from './errors'
1
2
  export * from './icons'
3
+ export * from './spinner'
@@ -1,2 +1,6 @@
1
1
  export const devMode = process.env.NODE_ENV === 'development'
2
2
  export const productionMode = process.env.NODE_ENV === 'production'
3
+ export const RELEASE_FULL_NAME = `${process.env.RELEASE_FULL_NAME}_${process.env.NODE_ENV}`
4
+ export const DEFAULT_STALE_TIME = 1000 * 60 * 30 // 30 minutes
5
+ export const APP_VERSION = process.env.PROJECT_VERSION ?? ''
6
+ export const APP_SYSTEM = 'Web'
@@ -1,3 +1,4 @@
1
1
  export * from './constants'
2
2
  export { default as HttpCodes } from './http-codes'
3
3
  export * from './languages'
4
+ export * from './message-types'
@@ -0,0 +1,3 @@
1
+ export const MessageTypes = {
2
+ SSO_EXPIRED_TOKEN: 'THIRD-PARTY-APP.EXPIRED_TOKEN'
3
+ } as const
package/src/main/main.tsx CHANGED
@@ -2,9 +2,11 @@ import '@/config/styles/index.css'
2
2
 
3
3
  import clsx from 'clsx'
4
4
 
5
+ import { ErrorBoundary, GenericError } from '@/src/lib/components/errors'
5
6
  import { useDefaultId } from '@/src/lib/hooks'
6
7
  import { useAppLang } from '../config/i18n'
7
8
  import { ChatInput } from '../modules/create-message/components'
9
+ import { GlobalProviders } from '../modules/global-providers'
8
10
  import { GreetingsCard } from '../modules/widget/components'
9
11
  import type { WidgetSettingProps } from '../types'
10
12
 
@@ -15,12 +17,17 @@ function Main({ settings }: { settings: WidgetSettingProps }) {
15
17
  useAppLang(settings.locale)
16
18
 
17
19
  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>
20
+ <ErrorBoundary fallback={<GenericError />}>
21
+ <GlobalProviders settings={settings}>
22
+ <div
23
+ className={clsx('flex min-h-svh flex-col items-center justify-center p-5', styles.main)}>
24
+ <div className='flex flex-1 flex-col justify-center gap-6 lg:max-w-sm'>
25
+ <GreetingsCard author={settings.author ?? ''} tutorName={settings.tutorName ?? ''} />
26
+ <ChatInput name='new-chat-msg-input' />
27
+ </div>
28
+ </div>
29
+ </GlobalProviders>
30
+ </ErrorBoundary>
24
31
  )
25
32
  }
26
33
 
@@ -0,0 +1,26 @@
1
+ import { type PropsWithChildren, useEffect } from 'react'
2
+
3
+ import { OptimizelyProvider } from '@/src/config/optimizely'
4
+ import { QueryProvider } from '@/src/config/tanstack'
5
+ import { useWidgetSettingsAtom } from '@/src/modules/widget'
6
+ import type { WidgetSettingProps } from '@/src/types'
7
+
8
+ export type GlobalProvidersProps = PropsWithChildren<{ settings: WidgetSettingProps }>
9
+
10
+ function GlobalProviders({ children, settings }: GlobalProvidersProps) {
11
+ const [, setWidgetSettings] = useWidgetSettingsAtom()
12
+
13
+ useEffect(() => {
14
+ if (!settings || !Object.keys(settings)?.length) return
15
+
16
+ setWidgetSettings(settings)
17
+ }, [setWidgetSettings, settings])
18
+
19
+ return (
20
+ <OptimizelyProvider settings={settings}>
21
+ <QueryProvider>{children}</QueryProvider>
22
+ </OptimizelyProvider>
23
+ )
24
+ }
25
+
26
+ export default GlobalProviders
@@ -0,0 +1 @@
1
+ export { default as GlobalProviders } from './global-providers'
@@ -1,2 +1,4 @@
1
+ export * from './components'
1
2
  export * from './events'
3
+ export * from './store'
2
4
  export * from './types'
@@ -0,0 +1 @@
1
+ export * from './widget-settings.atom'
@@ -0,0 +1,12 @@
1
+ import { atom, useAtom } from 'jotai'
2
+
3
+ import type { WidgetSettingProps } from '@/src/types'
4
+
5
+ export const widgetSettingsAtom = atom<WidgetSettingProps | null>(null)
6
+
7
+ export const setWidgetSettingsAtom = atom(
8
+ (get) => get(widgetSettingsAtom),
9
+ (_, set, config: WidgetSettingProps) => set(widgetSettingsAtom, config)
10
+ )
11
+
12
+ export const useWidgetSettingsAtom = () => useAtom(setWidgetSettingsAtom)
package/src/types.ts CHANGED
@@ -5,6 +5,17 @@ export type StartTutorWidgetProps = {
5
5
  settings: WidgetSettingProps
6
6
  }
7
7
 
8
+ export type User = {
9
+ id: string
10
+ name?: string
11
+ email?: string
12
+ token?: string
13
+ ucode?: string
14
+ authorities?: string[]
15
+ avatar?: string
16
+ sessionId?: string
17
+ }
18
+
8
19
  export type WidgetSettingProps = {
9
20
  hotmartToken: string
10
21
  locale: ILanguages
@@ -20,4 +31,5 @@ export type WidgetSettingProps = {
20
31
  membershipSlug?: string
21
32
  userId?: string
22
33
  tutorName?: string
34
+ user?: User
23
35
  }