@nocios/crudify-ui 1.0.83 → 1.0.84
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/README.md +302 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -369,6 +369,308 @@ function AuthenticatedApp() {
|
|
|
369
369
|
export default AuthenticatedApp;
|
|
370
370
|
```
|
|
371
371
|
|
|
372
|
+
## 🏗️ Implementación con UnifiedProvider (Recomendado)
|
|
373
|
+
|
|
374
|
+
### Patrón Unificado para Apps Completas
|
|
375
|
+
|
|
376
|
+
Para aplicaciones que necesitan gestión completa de estado y autenticación, recomendamos usar el patrón UnifiedProvider que combina la gestión de configuración y autenticación en un solo componente:
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
// src/contexts/UnifiedProvider.tsx
|
|
380
|
+
import React, { createContext, useContext, useMemo, useState, useCallback, useEffect, type ReactNode } from "react"
|
|
381
|
+
import { createTheme } from "@mui/material"
|
|
382
|
+
import { crudify, type CrudifyEnvType } from "@nocios/crudify-ui"
|
|
383
|
+
|
|
384
|
+
interface AppContextValue {
|
|
385
|
+
logo: string | null
|
|
386
|
+
setLogo: (logo: string) => void
|
|
387
|
+
publicApiKey: string | null
|
|
388
|
+
setPublicApiKey: (key: string | null) => void
|
|
389
|
+
env: CrudifyEnvType
|
|
390
|
+
setEnv: (env: CrudifyEnvType) => void
|
|
391
|
+
appName: string | null
|
|
392
|
+
setAppName: (name: string | null) => void
|
|
393
|
+
colors: { primaryColor: string; [key: string]: string }
|
|
394
|
+
setColors: (colors: any) => void
|
|
395
|
+
theme: any
|
|
396
|
+
setTheme: (theme: any) => void
|
|
397
|
+
configLoading: boolean
|
|
398
|
+
configError: string | null
|
|
399
|
+
loginActions: string[]
|
|
400
|
+
setLoginActions: (actions: string[]) => void
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
interface DataContextValue {
|
|
404
|
+
token: string | null
|
|
405
|
+
setToken: (token: string | null) => void
|
|
406
|
+
user: Record<string, any> | null
|
|
407
|
+
isInitialized: boolean
|
|
408
|
+
initializationError: string | null
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const AppContext = createContext<AppContextValue | null>(null)
|
|
412
|
+
const DataContext = createContext<DataContextValue | null>(null)
|
|
413
|
+
|
|
414
|
+
const UnifiedProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
|
415
|
+
// Estado para token y usuario
|
|
416
|
+
const [token, setTokenState] = useState<string | null>(null)
|
|
417
|
+
const [user, setUser] = useState<Record<string, any> | null>(null)
|
|
418
|
+
const [isFullyInitialized, setIsFullyInitialized] = useState(false)
|
|
419
|
+
|
|
420
|
+
// Configuración desde variables de entorno
|
|
421
|
+
const config = useMemo(() => ({
|
|
422
|
+
publicApiKey: import.meta.env.VITE_TEST_PUBLIC_API_KEY || 'default-key',
|
|
423
|
+
env: import.meta.env.VITE_TEST_ENV || 'prod',
|
|
424
|
+
appName: 'Mi Aplicación'
|
|
425
|
+
}), [])
|
|
426
|
+
|
|
427
|
+
const handleSetToken = useCallback((newToken: string | null) => {
|
|
428
|
+
setTokenState(newToken)
|
|
429
|
+
if (newToken) {
|
|
430
|
+
sessionStorage.setItem('crud_token', newToken)
|
|
431
|
+
} else {
|
|
432
|
+
sessionStorage.removeItem('crud_token')
|
|
433
|
+
}
|
|
434
|
+
}, [])
|
|
435
|
+
|
|
436
|
+
// Inicializar token desde sessionStorage
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
const savedToken = sessionStorage.getItem('crud_token')
|
|
439
|
+
if (savedToken) {
|
|
440
|
+
setTokenState(savedToken)
|
|
441
|
+
}
|
|
442
|
+
}, [])
|
|
443
|
+
|
|
444
|
+
// Inicialización de Crudify cuando hay token
|
|
445
|
+
useEffect(() => {
|
|
446
|
+
if (!token) {
|
|
447
|
+
setIsFullyInitialized(false)
|
|
448
|
+
return
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const initializeCrudify = async () => {
|
|
452
|
+
if (isFullyInitialized) return
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
await crudify.config(config.env)
|
|
456
|
+
await crudify.init(config.publicApiKey, "none")
|
|
457
|
+
crudify.setToken(token)
|
|
458
|
+
setIsFullyInitialized(true)
|
|
459
|
+
} catch (error) {
|
|
460
|
+
console.error('Error initializing Crudify:', error)
|
|
461
|
+
setIsFullyInitialized(true) // Evitar bucles
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
initializeCrudify()
|
|
466
|
+
}, [token, config.env, config.publicApiKey, isFullyInitialized])
|
|
467
|
+
|
|
468
|
+
// Contexto de App
|
|
469
|
+
const appContextValue: AppContextValue = {
|
|
470
|
+
logo: null,
|
|
471
|
+
setLogo: () => {},
|
|
472
|
+
publicApiKey: config.publicApiKey,
|
|
473
|
+
setPublicApiKey: () => {},
|
|
474
|
+
env: config.env as CrudifyEnvType,
|
|
475
|
+
setEnv: () => {},
|
|
476
|
+
appName: config.appName,
|
|
477
|
+
setAppName: () => {},
|
|
478
|
+
colors: { primaryColor: "#1066BA" },
|
|
479
|
+
setColors: () => {},
|
|
480
|
+
theme: createTheme({ palette: { primary: { main: "#1066BA" } } }),
|
|
481
|
+
setTheme: () => {},
|
|
482
|
+
configLoading: false,
|
|
483
|
+
configError: null,
|
|
484
|
+
loginActions: [],
|
|
485
|
+
setLoginActions: () => {}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Contexto de Data
|
|
489
|
+
const dataContextValue: DataContextValue = {
|
|
490
|
+
token,
|
|
491
|
+
setToken: handleSetToken,
|
|
492
|
+
user,
|
|
493
|
+
isInitialized: !!token && isFullyInitialized,
|
|
494
|
+
initializationError: null
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return (
|
|
498
|
+
<AppContext.Provider value={appContextValue}>
|
|
499
|
+
<DataContext.Provider value={dataContextValue}>
|
|
500
|
+
{children}
|
|
501
|
+
</DataContext.Provider>
|
|
502
|
+
</AppContext.Provider>
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export const useApp = (): AppContextValue => {
|
|
507
|
+
const context = useContext(AppContext)
|
|
508
|
+
if (!context) throw new Error("useApp should be used within a UnifiedProvider")
|
|
509
|
+
return context
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export const useData = (): DataContextValue => {
|
|
513
|
+
const context = useContext(DataContext)
|
|
514
|
+
if (!context) throw new Error("useData should be used within a UnifiedProvider")
|
|
515
|
+
return context
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export { UnifiedProvider }
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Implementación del AppStatusHandler
|
|
522
|
+
|
|
523
|
+
```tsx
|
|
524
|
+
// src/components/AppStatusHandler/index.tsx
|
|
525
|
+
import { type ReactNode } from "react"
|
|
526
|
+
import { useLocation } from "react-router-dom"
|
|
527
|
+
import { useApp, useData } from "@contexts/UnifiedProvider"
|
|
528
|
+
import LoadingApp from "@components/LoadingApp"
|
|
529
|
+
import { CssBaseline, ThemeProvider } from "@mui/material"
|
|
530
|
+
import InitError from "@components/InitError"
|
|
531
|
+
|
|
532
|
+
interface AppStatusHandlerProps {
|
|
533
|
+
children: ReactNode
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const AppStatusHandler = ({ children }: AppStatusHandlerProps) => {
|
|
537
|
+
const location = useLocation()
|
|
538
|
+
const { theme, configLoading, configError } = useApp()
|
|
539
|
+
const { isInitialized, initializationError } = useData()
|
|
540
|
+
|
|
541
|
+
// Rutas que no requieren inicialización completa
|
|
542
|
+
const authRoutes = ['/login', '/login/forgotPassword', '/login/checkCode', '/login/resetPassword']
|
|
543
|
+
const isAuthRoute = authRoutes.some(route => location.pathname.startsWith(route))
|
|
544
|
+
|
|
545
|
+
if (configLoading) {
|
|
546
|
+
return <LoadingApp stage="config" />
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (!theme) {
|
|
550
|
+
return <LoadingApp stage="theme" />
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Solo requerir inicialización para rutas protegidas
|
|
554
|
+
if (!isInitialized && !isAuthRoute) {
|
|
555
|
+
return <LoadingApp stage="initializing" />
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (configError || initializationError) {
|
|
559
|
+
return <InitError configError={configError} initializationError={Boolean(initializationError)} />
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return (
|
|
563
|
+
<ThemeProvider theme={theme}>
|
|
564
|
+
<CssBaseline />
|
|
565
|
+
{children}
|
|
566
|
+
</ThemeProvider>
|
|
567
|
+
)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export default AppStatusHandler
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Implementación de ProtectedRoute
|
|
574
|
+
|
|
575
|
+
```tsx
|
|
576
|
+
// src/components/Auth/ProtectedRoute/index.tsx
|
|
577
|
+
import { type ReactNode } from "react"
|
|
578
|
+
import { Navigate, useLocation } from "react-router-dom"
|
|
579
|
+
import { useData } from "@contexts/UnifiedProvider"
|
|
580
|
+
|
|
581
|
+
interface ProtectedRouteProps {
|
|
582
|
+
children: ReactNode
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
|
586
|
+
const { token } = useData()
|
|
587
|
+
const location = useLocation()
|
|
588
|
+
|
|
589
|
+
if (!token) {
|
|
590
|
+
const fullPath = location.pathname + location.search
|
|
591
|
+
const redirectPath = encodeURIComponent(fullPath)
|
|
592
|
+
return <Navigate to={`/login?redirect=${redirectPath}`} replace />
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return children
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export default ProtectedRoute
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Configuración de la App Principal
|
|
602
|
+
|
|
603
|
+
```tsx
|
|
604
|
+
// src/main.tsx
|
|
605
|
+
import React from 'react'
|
|
606
|
+
import ReactDOM from 'react-dom/client'
|
|
607
|
+
import { BrowserRouter } from 'react-router-dom'
|
|
608
|
+
import { UnifiedProvider } from '@contexts/UnifiedProvider'
|
|
609
|
+
import { GlobalNotificationProvider } from '@contexts/GlobalNotificationProvider'
|
|
610
|
+
import AppStatusHandler from '@components/AppStatusHandler'
|
|
611
|
+
import App from './App'
|
|
612
|
+
|
|
613
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
614
|
+
<React.StrictMode>
|
|
615
|
+
<BrowserRouter>
|
|
616
|
+
<UnifiedProvider>
|
|
617
|
+
<GlobalNotificationProvider>
|
|
618
|
+
<AppStatusHandler>
|
|
619
|
+
<App />
|
|
620
|
+
</AppStatusHandler>
|
|
621
|
+
</GlobalNotificationProvider>
|
|
622
|
+
</UnifiedProvider>
|
|
623
|
+
</BrowserRouter>
|
|
624
|
+
</React.StrictMode>,
|
|
625
|
+
)
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Uso en Componentes
|
|
629
|
+
|
|
630
|
+
```tsx
|
|
631
|
+
// Cualquier componente de la app
|
|
632
|
+
import { useApp, useData } from '@contexts/UnifiedProvider'
|
|
633
|
+
|
|
634
|
+
function Dashboard() {
|
|
635
|
+
const { appName, theme } = useApp()
|
|
636
|
+
const { user, token, isInitialized } = useData()
|
|
637
|
+
|
|
638
|
+
if (!isInitialized) {
|
|
639
|
+
return <div>Inicializando...</div>
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return (
|
|
643
|
+
<div>
|
|
644
|
+
<h1>Bienvenido a {appName}</h1>
|
|
645
|
+
<p>Usuario: {user?.email}</p>
|
|
646
|
+
<p>Token válido: {token ? 'Sí' : 'No'}</p>
|
|
647
|
+
</div>
|
|
648
|
+
)
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### Variables de Entorno Requeridas
|
|
653
|
+
|
|
654
|
+
```env
|
|
655
|
+
# .env.development
|
|
656
|
+
VITE_TEST_PUBLIC_API_KEY=tu-clave-de-api-de-desarrollo
|
|
657
|
+
VITE_TEST_ENV=dev
|
|
658
|
+
|
|
659
|
+
# .env.production
|
|
660
|
+
VITE_TEST_PUBLIC_API_KEY=tu-clave-de-api-de-produccion
|
|
661
|
+
VITE_TEST_ENV=prod
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
### Ventajas de este Patrón
|
|
665
|
+
|
|
666
|
+
✅ **Inicialización automática**: Crudify se inicializa automáticamente cuando hay token
|
|
667
|
+
✅ **Gestión unificada**: Un solo provider para configuración y autenticación
|
|
668
|
+
✅ **Rutas protegidas**: Manejo automático de redirecciones según autenticación
|
|
669
|
+
✅ **Estados de carga**: Diferentes estados para config, theme e inicialización
|
|
670
|
+
✅ **Variables de entorno**: Configuración centralizada desde .env
|
|
671
|
+
✅ **Compatibilidad completa**: Mantiene la API de providers anteriores
|
|
672
|
+
✅ **Sin recursión**: Evita problemas de bucles infinitos en la inicialización
|
|
673
|
+
|
|
372
674
|
---
|
|
373
675
|
|
|
374
676
|
**Version:** 1.0.0+
|