@basictech/react 0.8.0-beta.1 → 0.8.0-beta.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basictech/react",
3
- "version": "0.8.0-beta.1",
3
+ "version": "0.8.0-beta.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -12,11 +12,13 @@
12
12
  "scripts": {
13
13
  "build": "tsup",
14
14
  "dev": "tsup --watch",
15
+ "prepublishOnly": "npm run build",
15
16
  "test": "echo \"Error: no test specified\" && exit 1"
16
17
  },
17
18
  "author": "",
18
19
  "license": "ISC",
19
20
  "dependencies": {
21
+ "semver": "^7.7.2",
20
22
  "ajv": "^8.17.1",
21
23
  "dexie": "^4.2.1",
22
24
  "dexie-observable": "^4.0.1-beta.13",
package/readme.md CHANGED
@@ -99,6 +99,7 @@ Root provider component. Must wrap your entire app.
99
99
  schema={schema} // Required: Your Basic schema
100
100
  debug={false} // Optional: Enable console logging
101
101
  dbMode="sync" // Optional: "sync" (default) or "remote"
102
+ devToolbar={false} // Optional: Floating dev status bar (localhost / dev / debug)
102
103
  />
103
104
  ```
104
105
 
@@ -109,6 +110,7 @@ Root provider component. Must wrap your entire app.
109
110
  | `schema` | `object` | required | Schema with `project_id` and `tables` |
110
111
  | `debug` | `boolean` | `false` | Enable debug logging |
111
112
  | `dbMode` | `"sync" \| "remote"` | `"sync"` | Database mode |
113
+ | `devToolbar` | `boolean` | `false` | Show the Basic dev toolbar (only when `localhost`, `NODE_ENV === "development"`, or `debug={true}`) |
112
114
 
113
115
  #### Database Modes
114
116
 
@@ -139,6 +141,10 @@ const {
139
141
  db, // Database instance
140
142
  dbStatus, // DBStatus - see below
141
143
  dbMode, // "sync" | "remote"
144
+
145
+ // Dev / schema snapshot (for custom tooling)
146
+ devInfo, // BasicSchemaDevInfo | null — local vs remote schema status
147
+ refreshSchemaStatus, // () => Promise<void> — re-fetch schema status from API
142
148
  } = useBasic()
143
149
  ```
144
150
 
@@ -160,6 +166,33 @@ Import the enum for comparisons: `import { useBasic, DBStatus } from '@basictech
160
166
 
161
167
  ---
162
168
 
169
+ ### Development toolbar
170
+
171
+ A small floating bar (similar in spirit to Next.js dev indicators) shows **auth**, **database mode**, **sync status**, and **schema vs server** health. It is **opt-in** and only appears in development: `localhost` / `127.0.0.1` / `.local`, `NODE_ENV === "development"`, or when `debug` is `true` on the provider or on the standalone component.
172
+
173
+ **Option A — provider flag**
174
+
175
+ ```tsx
176
+ <BasicProvider schema={schema} devToolbar debug>
177
+ <App />
178
+ </BasicProvider>
179
+ ```
180
+
181
+ **Option B — place the component yourself** (must be under `BasicProvider`; respects the same visibility rules, or pass `debug` to force):
182
+
183
+ ```tsx
184
+ import { BasicDevToolbar } from '@basictech/react'
185
+
186
+ <BasicProvider schema={schema}>
187
+ <App />
188
+ <BasicDevToolbar />
189
+ </BasicProvider>
190
+ ```
191
+
192
+ The expanded panel includes **Refresh schema** (re-runs the remote schema check) and **Copy debug info** (JSON snapshot **without** raw access tokens). You can also read `devInfo` and call `refreshSchemaStatus()` from `useBasic()` for your own UI.
193
+
194
+ ---
195
+
163
196
  ### `useQuery()`
164
197
 
165
198
  Live query hook - automatically re-renders when data changes.
@@ -1,165 +1,75 @@
1
- import React, { createContext, useContext, useEffect, useState, useRef } from 'react'
1
+ import React, { useCallback, useEffect, useRef, useState, Suspense, lazy } from 'react'
2
2
 
3
3
  import { BasicSync, initDexieExtensions } from './sync'
4
4
  import { RemoteDB, DBMode, BasicDB } from './core/db'
5
5
  import { AuthManager } from './core/auth/AuthManager'
6
- import type { Token, User, AuthResult, GetTokenOptions, PdsEndpoints } from './core/auth/AuthManager'
6
+ import type { User, AuthResult, GetTokenOptions, PdsEndpoints } from './core/auth/AuthManager'
7
7
 
8
8
  import { log } from './config'
9
9
  import { version as currentVersion } from '../package.json'
10
10
  import { createVersionUpdater } from './updater/versionUpdater'
11
11
  import { getMigrations } from './updater/updateMigrations'
12
- import { BasicStorage, LocalStorageAdapter, STORAGE_KEYS } from './utils/storage'
12
+ import { BasicStorage, LocalStorageAdapter } from './utils/storage'
13
13
  import { isDevelopment, checkForNewVersion, getSyncStatus } from './utils/network'
14
14
  import { validateAndCheckSchema } from './utils/schema'
15
+ import { BasicContext, DBStatus, noDb, type BasicSchemaDevInfo } from './context'
16
+
17
+ const BasicDevToolbar = lazy(() =>
18
+ import('./dev/BasicDevToolbar').then((m) => ({ default: m.BasicDevToolbar }))
19
+ )
15
20
 
16
21
  export type { BasicStorage, LocalStorageAdapter } from './utils/storage'
17
22
  export type { DBMode, BasicDB, Collection } from './core/db'
18
- export type { Token, User, AuthResult, GetTokenOptions, PdsEndpoints }
23
+ export type { Token, User, AuthResult, GetTokenOptions, PdsEndpoints } from './core/auth/AuthManager'
24
+ export type { BasicContextType, BasicSchemaDevInfo } from './context'
25
+ export { DBStatus, useBasic, BasicContext } from './context'
19
26
 
20
27
  export type AuthConfig = {
21
- scopes?: string | string[];
28
+ scopes?: string | string[]
22
29
  /** @deprecated Use pds_url instead */
23
- server_url?: string;
30
+ server_url?: string
24
31
  /** PDS URL for auth and data (default: https://pds.basic.id) */
25
- pds_url?: string;
32
+ pds_url?: string
26
33
  /** Admin server URL for connect reporting (default: https://api.basic.tech) */
27
- admin_url?: string;
28
- ws_url?: string;
34
+ admin_url?: string
35
+ ws_url?: string
29
36
  }
30
37
 
31
38
  export type BasicProviderProps = {
32
- children: React.ReactNode;
33
- /**
34
- * @deprecated Project ID is now extracted from schema.project_id.
39
+ children: React.ReactNode
40
+ /**
41
+ * @deprecated Project ID is now extracted from schema.project_id.
35
42
  * This prop is kept for backward compatibility but can be omitted.
36
43
  */
37
- project_id?: string;
44
+ project_id?: string
38
45
  /** The Basic schema object containing project_id and table definitions */
39
- schema?: any;
40
- debug?: boolean;
41
- storage?: BasicStorage;
42
- auth?: AuthConfig;
46
+ schema?: any
47
+ debug?: boolean
48
+ storage?: BasicStorage
49
+ auth?: AuthConfig
43
50
  /**
44
51
  * Database mode - determines which implementation is used
45
52
  * - 'sync': Uses Dexie + WebSocket for local-first sync (default)
46
53
  * - 'remote': Uses REST API calls directly to server
47
54
  */
48
- dbMode?: DBMode;
55
+ dbMode?: DBMode
56
+ /** Show floating dev toolbar (localhost, NODE_ENV=development, or debug=true). */
57
+ devToolbar?: boolean
49
58
  }
50
59
 
51
60
  const DEFAULT_AUTH_CONFIG = {
52
61
  scopes: 'profile,email,app:admin',
53
62
  pds_url: 'https://pds.basic.id',
54
63
  admin_url: 'https://api.basic.tech',
55
- ws_url: 'wss://pds.basic.id/ws'
64
+ ws_url: 'wss://pds.basic.id/ws',
56
65
  } as const
57
66
 
58
-
59
- export enum DBStatus {
60
- LOADING = "LOADING",
61
- OFFLINE = "OFFLINE",
62
- CONNECTING = "CONNECTING",
63
- ONLINE = "ONLINE",
64
- SYNCING = "SYNCING",
65
- ERROR = "ERROR",
66
- /** Sync reported an error but will retry (e.g. expired token). Used for status code 4 from dexie-syncable. */
67
- ERROR_WILL_RETRY = "ERROR_WILL_RETRY",
68
- /** Token expired; the SDK is refreshing and will reconnect automatically. */
69
- ERROR_TOKEN_EXPIRED = "ERROR_TOKEN_EXPIRED"
70
- }
71
-
72
- /**
73
- * Context type for useBasic hook
74
- */
75
- export type BasicContextType = {
76
- // Auth state
77
- isReady: boolean;
78
- isSignedIn: boolean;
79
- user: User | null;
80
- /** The user's DID (Decentralized Identifier), extracted from the access token `sub` claim */
81
- did: string | null;
82
- /** Space-separated scope string from the access token */
83
- scope: string | null;
84
- /** Check if a specific scope is granted (e.g., hasScope('profile')) */
85
- hasScope: (scope: string) => boolean;
86
- /** Returns scopes that were requested but not granted in the current token */
87
- missingScopes: () => string[];
88
-
89
- // Auth actions (new camelCase naming)
90
- signIn: () => Promise<void>;
91
- signInWithHandle: (handle: string) => Promise<void>;
92
- signOut: () => Promise<void>;
93
- signInWithCode: (code: string, state?: string) => Promise<AuthResult>;
94
-
95
- // Token management
96
- getToken: (options?: GetTokenOptions) => Promise<string>;
97
- getSignInUrl: (redirectUri?: string) => Promise<string>;
98
-
99
- // DB access
100
- db: BasicDB;
101
- dbStatus: DBStatus;
102
- dbMode: DBMode;
103
-
104
- // Legacy aliases (deprecated - will be removed in future version)
105
- /** @deprecated Use isReady instead */
106
- isAuthReady: boolean;
107
- /** @deprecated Use signIn instead */
108
- signin: () => Promise<void>;
109
- /** @deprecated Use signOut instead */
110
- signout: () => Promise<void>;
111
- /** @deprecated Use signInWithCode instead */
112
- signinWithCode: (code: string, state?: string) => Promise<AuthResult>;
113
- /** @deprecated Use getSignInUrl instead */
114
- getSignInLink: (redirectUri?: string) => Promise<string>;
115
- }
116
-
117
- const noDb: BasicDB = {
118
- collection: () => {
119
- throw new Error('no basicdb found - initialization failed. double check your schema.')
120
- }
121
- }
122
-
123
- export const BasicContext = createContext<BasicContextType>({
124
- // Auth state
125
- isReady: false,
126
- isSignedIn: false,
127
- user: null,
128
- did: null,
129
- scope: null,
130
- hasScope: () => false,
131
- missingScopes: () => [],
132
-
133
- // Auth actions
134
- signIn: () => Promise.resolve(),
135
- signInWithHandle: () => Promise.resolve(),
136
- signOut: () => Promise.resolve(),
137
- signInWithCode: () => Promise.resolve({ success: false }),
138
-
139
- // Token management
140
- getToken: (_options?: GetTokenOptions) => Promise.reject(new Error('no token')),
141
- getSignInUrl: () => Promise.resolve(""),
142
-
143
- // DB access
144
- db: noDb,
145
- dbStatus: DBStatus.LOADING,
146
- dbMode: 'sync',
147
-
148
- // Legacy aliases
149
- isAuthReady: false,
150
- signin: () => Promise.resolve(),
151
- signout: () => Promise.resolve(),
152
- signinWithCode: () => Promise.resolve({ success: false }),
153
- getSignInLink: () => Promise.resolve("")
154
- });
155
-
156
67
  type ErrorObject = {
157
- code: string;
158
- title: string;
159
- message: string;
68
+ code: string
69
+ title: string
70
+ message: string
160
71
  }
161
72
 
162
- // Tracks the subset of AuthManager state that React effects depend on.
163
73
  type AuthSnapshot = {
164
74
  isSignedIn: boolean
165
75
  hasToken: boolean
@@ -187,11 +97,11 @@ export function BasicProvider({
187
97
  debug = false,
188
98
  storage,
189
99
  auth,
190
- dbMode = 'sync'
100
+ dbMode = 'sync',
101
+ devToolbar = false,
191
102
  }: BasicProviderProps) {
192
103
  const project_id = schema?.project_id || project_id_prop
193
104
 
194
- // Merge auth config with defaults (server_url is deprecated in favor of pds_url)
195
105
  if (auth?.server_url && !auth?.pds_url) {
196
106
  log('Warning: auth.server_url is deprecated, use auth.pds_url instead')
197
107
  }
@@ -199,7 +109,7 @@ export function BasicProvider({
199
109
  scopes: auth?.scopes || DEFAULT_AUTH_CONFIG.scopes,
200
110
  pds_url: auth?.pds_url || auth?.server_url || DEFAULT_AUTH_CONFIG.pds_url,
201
111
  admin_url: auth?.admin_url || DEFAULT_AUTH_CONFIG.admin_url,
202
- ws_url: auth?.ws_url || DEFAULT_AUTH_CONFIG.ws_url
112
+ ws_url: auth?.ws_url || DEFAULT_AUTH_CONFIG.ws_url,
203
113
  }
204
114
 
205
115
  const scopesString = Array.isArray(authConfig.scopes)
@@ -209,7 +119,9 @@ export function BasicProvider({
209
119
  const storageRef = useRef<BasicStorage>(storage || new LocalStorageAdapter())
210
120
  const storageAdapter = storageRef.current
211
121
 
212
- // --- AuthManager (stable instance held in a ref) ---
122
+ const schemaRef = useRef(schema)
123
+ schemaRef.current = schema
124
+
213
125
  const [authState, setAuthState] = useState<AuthSnapshot>({
214
126
  isSignedIn: false,
215
127
  hasToken: false,
@@ -234,19 +146,56 @@ export function BasicProvider({
234
146
  )
235
147
  }
236
148
 
237
- // --- DB state (stays in React) ---
238
149
  const syncRef = useRef<BasicSync | null>(null)
239
150
  const remoteDbRef = useRef<RemoteDB | null>(null)
240
151
  const [shouldConnect, setShouldConnect] = useState(false)
241
152
  const [dbStatus, setDbStatus] = useState<DBStatus>(DBStatus.OFFLINE)
242
153
  const [isReady, setIsReady] = useState(false)
243
154
  const [error, setError] = useState<ErrorObject | null>(null)
155
+ const [schemaDevInfo, setSchemaDevInfo] = useState<BasicSchemaDevInfo | null>(null)
244
156
 
245
157
  const isDevMode = () => isDevelopment(debug)
246
158
 
247
- // --- Mount: version updater + auth init + DB init ---
159
+ const refreshSchemaStatus = useCallback(async () => {
160
+ const s = schemaRef.current
161
+ if (!s) {
162
+ setSchemaDevInfo(
163
+ project_id
164
+ ? {
165
+ projectId: project_id,
166
+ localVersion: undefined,
167
+ status: 'no_schema',
168
+ valid: false,
169
+ lastCheckedAt: Date.now(),
170
+ }
171
+ : null,
172
+ )
173
+ return
174
+ }
175
+ const result = await validateAndCheckSchema(s)
176
+ if (!result.isValid) {
177
+ const errText =
178
+ result.errors?.map((e: { message?: string }) => e.message || '').join('; ') || 'invalid'
179
+ setSchemaDevInfo({
180
+ projectId: s.project_id ?? null,
181
+ localVersion: s.version,
182
+ status: 'invalid',
183
+ valid: false,
184
+ lastCheckedAt: Date.now(),
185
+ error: errText,
186
+ })
187
+ return
188
+ }
189
+ setSchemaDevInfo({
190
+ projectId: s.project_id ?? null,
191
+ localVersion: s.version,
192
+ status: result.schemaStatus.status ?? 'unknown',
193
+ valid: result.schemaStatus.valid,
194
+ lastCheckedAt: Date.now(),
195
+ })
196
+ }, [project_id])
197
+
248
198
  useEffect(() => {
249
- // Version updater (SDK migration, not auth-related)
250
199
  const runVersionUpdater = async () => {
251
200
  try {
252
201
  const versionUpdater = createVersionUpdater(storageAdapter, currentVersion, getMigrations())
@@ -268,7 +217,6 @@ export function BasicProvider({
268
217
  return authRef.current.setupNetworkListeners()
269
218
  }, [])
270
219
 
271
- // --- DB init (separate mount effect) ---
272
220
  useEffect(() => {
273
221
  async function initSyncDb(options: { shouldConnect: boolean }) {
274
222
  if (!syncRef.current) {
@@ -276,7 +224,7 @@ export function BasicProvider({
276
224
 
277
225
  await initDexieExtensions()
278
226
 
279
- syncRef.current = new BasicSync('basicdb', { schema: schema });
227
+ syncRef.current = new BasicSync('basicdb', { schema: schema })
280
228
 
281
229
  syncRef.current.syncable.on('statusChanged', (status: number) => {
282
230
  const newStatus = getSyncStatus(status) as DBStatus
@@ -304,7 +252,8 @@ export function BasicProvider({
304
252
  setError({
305
253
  code: 'missing_project_id',
306
254
  title: 'Project ID Required',
307
- message: 'Remote mode requires a project_id. Provide it via schema.project_id or the project_id prop.'
255
+ message:
256
+ 'Remote mode requires a project_id. Provide it via schema.project_id or the project_id prop.',
308
257
  })
309
258
  setIsReady(true)
310
259
  return
@@ -320,7 +269,7 @@ export function BasicProvider({
320
269
  onAuthError: (error) => {
321
270
  log('RemoteDB auth error:', error)
322
271
  handleSignOut()
323
- }
272
+ },
324
273
  })
325
274
  setDbStatus(DBStatus.ONLINE)
326
275
  setIsReady(true)
@@ -333,19 +282,35 @@ export function BasicProvider({
333
282
  if (!result.isValid) {
334
283
  let errorMessage = ''
335
284
  if (result.errors) {
336
- result.errors.forEach((error: any, index: number) => {
337
- errorMessage += `${index + 1}: ${error.message} - at ${error.instancePath}\n`
285
+ result.errors.forEach((err: any, index: number) => {
286
+ errorMessage += `${index + 1}: ${err.message} - at ${err.instancePath}\n`
338
287
  })
339
288
  }
289
+ setSchemaDevInfo({
290
+ projectId: schema?.project_id ?? null,
291
+ localVersion: schema?.version,
292
+ status: 'invalid',
293
+ valid: false,
294
+ lastCheckedAt: Date.now(),
295
+ error: errorMessage.trim() || undefined,
296
+ })
340
297
  setError({
341
298
  code: 'schema_invalid',
342
299
  title: 'Basic Schema is invalid!',
343
- message: errorMessage
300
+ message: errorMessage,
344
301
  })
345
302
  setIsReady(true)
346
303
  return null
347
304
  }
348
305
 
306
+ setSchemaDevInfo({
307
+ projectId: schema?.project_id ?? null,
308
+ localVersion: schema?.version,
309
+ status: result.schemaStatus.status ?? 'unknown',
310
+ valid: result.schemaStatus.valid,
311
+ lastCheckedAt: Date.now(),
312
+ })
313
+
349
314
  if (dbMode === 'remote') {
350
315
  initRemoteDb()
351
316
  } else {
@@ -367,30 +332,40 @@ export function BasicProvider({
367
332
  if (schema) {
368
333
  checkSchema()
369
334
  } else {
335
+ setSchemaDevInfo(
336
+ project_id
337
+ ? {
338
+ projectId: project_id,
339
+ localVersion: undefined,
340
+ status: 'no_schema',
341
+ valid: false,
342
+ lastCheckedAt: Date.now(),
343
+ }
344
+ : null,
345
+ )
370
346
  if (dbMode === 'remote' && project_id) {
371
347
  initRemoteDb()
372
348
  } else {
373
349
  setIsReady(true)
374
350
  }
375
351
  }
376
- }, []);
352
+ }, [])
377
353
 
378
- // --- Connect sync DB when auth is ready ---
379
354
  useEffect(() => {
380
355
  if (authState.hasToken && syncRef.current && authState.isSignedIn && shouldConnect) {
381
356
  log('connecting to db...')
382
357
 
383
- syncRef.current?.connect({
384
- getToken: (opts?: GetTokenOptions) => authRef.current.getToken(opts),
385
- ws_url: authConfig.ws_url
386
- })
358
+ syncRef.current
359
+ ?.connect({
360
+ getToken: (opts?: GetTokenOptions) => authRef.current.getToken(opts),
361
+ ws_url: authConfig.ws_url,
362
+ })
387
363
  .catch((e: any) => {
388
364
  log('error connecting to db', e)
389
365
  })
390
366
  }
391
367
  }, [authState.isSignedIn, authState.hasToken, shouldConnect])
392
368
 
393
- // --- Sign out (auth cleanup + sync teardown) ---
394
369
  const handleSignOut = async () => {
395
370
  await authRef.current.signOut()
396
371
  if (syncRef.current) {
@@ -405,7 +380,6 @@ export function BasicProvider({
405
380
  }
406
381
  }
407
382
 
408
- // --- Sign in wrappers (add dev-mode error display) ---
409
383
  const handleSignIn = async () => {
410
384
  try {
411
385
  await authRef.current.signIn()
@@ -414,7 +388,8 @@ export function BasicProvider({
414
388
  setError({
415
389
  code: 'signin_error',
416
390
  title: 'Sign-in Failed',
417
- message: (error as Error).message || 'An error occurred during sign-in. Please try again.'
391
+ message:
392
+ (error as Error).message || 'An error occurred during sign-in. Please try again.',
418
393
  })
419
394
  }
420
395
  throw error
@@ -429,14 +404,14 @@ export function BasicProvider({
429
404
  setError({
430
405
  code: 'signin_error',
431
406
  title: 'Sign-in Failed',
432
- message: (error as Error).message || 'An error occurred during sign-in. Please try again.'
407
+ message:
408
+ (error as Error).message || 'An error occurred during sign-in. Please try again.',
433
409
  })
434
410
  }
435
411
  throw error
436
412
  }
437
413
  }
438
414
 
439
- // --- DB accessor ---
440
415
  const getCurrentDb = (): BasicDB => {
441
416
  if (dbMode === 'remote') {
442
417
  return remoteDbRef.current || noDb
@@ -444,33 +419,30 @@ export function BasicProvider({
444
419
  return syncRef.current || noDb
445
420
  }
446
421
 
447
- // --- Context value ---
448
- const contextValue: BasicContextType = {
449
- // Auth state
422
+ const contextValue = {
450
423
  isReady: authState.isAuthReady,
451
424
  isSignedIn: authState.isSignedIn,
452
425
  user: authState.user,
453
426
  did: authState.did,
454
427
  scope: authState.tokenScope,
455
- hasScope: (scope: string) => authRef.current.hasScope(scope),
428
+ hasScope: (s: string) => authRef.current.hasScope(s),
456
429
  missingScopes: () => authRef.current.missingScopes(),
457
430
 
458
- // Auth actions
459
431
  signIn: handleSignIn,
460
432
  signInWithHandle: handleSignInWithHandle,
461
433
  signOut: handleSignOut,
462
434
  signInWithCode: (code: string, state?: string) => authRef.current.signInWithCode(code, state),
463
435
 
464
- // Token management
465
436
  getToken: (opts?: GetTokenOptions) => authRef.current.getToken(opts),
466
437
  getSignInUrl: (redirectUri?: string) => authRef.current.getSignInUrl(redirectUri),
467
438
 
468
- // DB access
469
439
  db: getCurrentDb(),
470
440
  dbStatus,
471
441
  dbMode,
472
442
 
473
- // Legacy aliases (deprecated)
443
+ devInfo: schemaDevInfo,
444
+ refreshSchemaStatus,
445
+
474
446
  isAuthReady: authState.isAuthReady,
475
447
  signin: handleSignIn,
476
448
  signout: handleSignOut,
@@ -481,33 +453,37 @@ export function BasicProvider({
481
453
  return (
482
454
  <BasicContext.Provider value={contextValue}>
483
455
  {error && isDevMode() && <ErrorDisplay error={error} />}
456
+ {devToolbar && isDevMode() && (
457
+ <Suspense fallback={null}>
458
+ <BasicDevToolbar debug={debug} />
459
+ </Suspense>
460
+ )}
484
461
  {isReady && children}
485
462
  </BasicContext.Provider>
486
463
  )
487
464
  }
488
465
 
489
466
  function ErrorDisplay({ error }: { error: ErrorObject }) {
490
- return <div style={{
491
- position: 'absolute',
492
- top: 20,
493
- left: 20,
494
- color: 'black',
495
- backgroundColor: '#f8d7da',
496
- border: '1px solid #f5c6cb',
497
- borderRadius: '4px',
498
- padding: '20px',
499
- maxWidth: '400px',
500
- margin: '20px auto',
501
- boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
502
- fontFamily: 'monospace',
503
- }}>
504
- <h3 style={{ fontSize: '0.8rem', opacity: 0.8 }}>code: {error.code}</h3>
505
- <h1 style={{ fontSize: '1.2rem', lineHeight: '1.5' }}>{error.title}</h1>
506
- <p>{error.message}</p>
507
- </div>
508
- }
509
-
510
-
511
- export function useBasic() {
512
- return useContext(BasicContext);
467
+ return (
468
+ <div
469
+ style={{
470
+ position: 'absolute',
471
+ top: 20,
472
+ left: 20,
473
+ color: 'black',
474
+ backgroundColor: '#f8d7da',
475
+ border: '1px solid #f5c6cb',
476
+ borderRadius: '4px',
477
+ padding: '20px',
478
+ maxWidth: '400px',
479
+ margin: '20px auto',
480
+ boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
481
+ fontFamily: 'monospace',
482
+ }}
483
+ >
484
+ <h3 style={{ fontSize: '0.8rem', opacity: 0.8 }}>code: {error.code}</h3>
485
+ <h1 style={{ fontSize: '1.2rem', lineHeight: 1.5 }}>{error.title}</h1>
486
+ <p>{error.message}</p>
487
+ </div>
488
+ )
513
489
  }