app-tutor-ai-consumer 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/production.yml +1 -1
- package/CHANGELOG.md +12 -0
- package/config/rspack/rspack.config.js +2 -1
- package/eslint.config.mjs +5 -0
- package/package.json +6 -1
- package/src/config/i18n/init.ts +1 -1
- package/src/config/request/api.ts +27 -0
- package/src/config/request/error.ts +42 -0
- package/src/config/request/index.ts +2 -0
- package/src/config/request/interceptors.ts +53 -0
- package/src/config/request/types.ts +20 -0
- package/src/config/tanstack/index.ts +3 -0
- package/src/config/tanstack/query-client.ts +31 -0
- package/src/config/tanstack/query-provider.tsx +18 -0
- package/src/config/tests/abstract-mock-generator.ts +1 -1
- package/src/lib/components/errors/boundary.tsx +68 -0
- package/src/lib/components/errors/error-boundary.spec.tsx +38 -0
- package/src/lib/components/errors/error-boundary.tsx +26 -0
- package/src/lib/components/errors/generic/generic-error.tsx +13 -0
- package/src/lib/components/errors/generic/index.ts +1 -0
- package/src/lib/components/errors/index.ts +2 -0
- package/src/lib/components/index.ts +2 -0
- package/src/lib/utils/constants.ts +2 -0
- package/src/lib/utils/index.ts +1 -0
- package/src/lib/utils/message-types.ts +3 -0
- package/src/main/main.tsx +17 -5
- package/src/modules/global-providers/global-providers.tsx +16 -0
- package/src/modules/global-providers/index.ts +1 -0
- package/src/modules/widget/index.ts +2 -0
- package/src/modules/widget/store/index.ts +1 -0
- package/src/modules/widget/store/widget-settings.atom.ts +12 -0
- 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-
|
|
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,15 @@
|
|
|
1
|
+
# [1.1.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.0.1...v1.1.0) (2025-06-18)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
- pr issues ([0004e58](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/0004e58014621a4a85ecabb6996372c470292d75))
|
|
6
|
+
- pr issues ([897bbe8](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/897bbe8a5a049f8cbb3d30f854a8a73875dab174))
|
|
7
|
+
- pr issues ([14353de](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/14353de72df8cecb70d673e87d91ed361eb5662b))
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- add Tanstack Query Config ([14c40ec](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/14c40ec1fc022e8e45a4df29baef670505623be5))
|
|
12
|
+
|
|
1
13
|
## [1.0.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.0.0...v1.0.1) (2025-06-18)
|
|
2
14
|
|
|
3
15
|
### 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
|
|
3
|
+
"version": "1.1.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,14 @@
|
|
|
99
100
|
"vitest": "~3.2.3"
|
|
100
101
|
},
|
|
101
102
|
"dependencies": {
|
|
103
|
+
"@hot-observability-js/react": "~1.1.0",
|
|
104
|
+
"@tanstack/query-sync-storage-persister": "~5.80.7",
|
|
102
105
|
"@tanstack/react-query": "~5.80.6",
|
|
106
|
+
"@tanstack/react-query-persist-client": "~5.80.7",
|
|
103
107
|
"clsx": "~2.1.1",
|
|
104
108
|
"i18next": "~25.2.1",
|
|
105
109
|
"i18next-resources-to-backend": "~1.2.1",
|
|
110
|
+
"jotai": "~2.12.5",
|
|
106
111
|
"react": "~19.1.0",
|
|
107
112
|
"react-dom": "~19.1.0",
|
|
108
113
|
"react-i18next": "~15.5.2"
|
package/src/config/i18n/init.ts
CHANGED
|
@@ -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
|
|
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,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,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,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
|
|
@@ -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 @@
|
|
|
1
|
+
export { default as GenericError } from './generic-error'
|
|
@@ -1,2 +1,4 @@
|
|
|
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
|
package/src/lib/utils/index.ts
CHANGED
package/src/main/main.tsx
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import '@/config/styles/index.css'
|
|
2
2
|
|
|
3
|
+
import { useEffect } from 'react'
|
|
3
4
|
import clsx from 'clsx'
|
|
4
5
|
|
|
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'
|
|
10
|
+
import { useWidgetSettingsAtom } from '../modules/widget'
|
|
8
11
|
import { GreetingsCard } from '../modules/widget/components'
|
|
9
12
|
import type { WidgetSettingProps } from '../types'
|
|
10
13
|
|
|
@@ -13,14 +16,23 @@ import styles from './styles.module.css'
|
|
|
13
16
|
function Main({ settings }: { settings: WidgetSettingProps }) {
|
|
14
17
|
useDefaultId()
|
|
15
18
|
useAppLang(settings.locale)
|
|
19
|
+
const [, setWidgetSettings] = useWidgetSettingsAtom()
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!settings || !Object.keys(settings)?.length) return
|
|
23
|
+
|
|
24
|
+
setWidgetSettings(settings)
|
|
25
|
+
}, [setWidgetSettings, settings])
|
|
16
26
|
|
|
17
27
|
return (
|
|
18
|
-
<
|
|
19
|
-
<div className='flex
|
|
20
|
-
<
|
|
21
|
-
|
|
28
|
+
<GlobalProviders>
|
|
29
|
+
<div className={clsx('flex min-h-svh flex-col items-center justify-center p-5', styles.main)}>
|
|
30
|
+
<div className='flex flex-1 flex-col justify-center gap-6 lg:max-w-sm'>
|
|
31
|
+
<GreetingsCard author={settings.author ?? ''} tutorName={settings.tutorName ?? ''} />
|
|
32
|
+
<ChatInput name='new-chat-msg-input' />
|
|
33
|
+
</div>
|
|
22
34
|
</div>
|
|
23
|
-
</
|
|
35
|
+
</GlobalProviders>
|
|
24
36
|
)
|
|
25
37
|
}
|
|
26
38
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PropsWithChildren } from 'react'
|
|
2
|
+
|
|
3
|
+
import { QueryProvider } from '@/src/config/tanstack'
|
|
4
|
+
import { ErrorBoundary, GenericError } from '@/src/lib/components/errors'
|
|
5
|
+
|
|
6
|
+
export type GlobalProvidersProps = PropsWithChildren
|
|
7
|
+
|
|
8
|
+
function GlobalProviders({ children }: GlobalProvidersProps) {
|
|
9
|
+
return (
|
|
10
|
+
<ErrorBoundary fallback={<GenericError />}>
|
|
11
|
+
<QueryProvider>{children}</QueryProvider>
|
|
12
|
+
</ErrorBoundary>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default GlobalProviders
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as GlobalProviders } from './global-providers'
|
|
@@ -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
|
}
|