@nocios/crudify-ui 1.0.83 → 1.0.85

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 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+
package/dist/index.js CHANGED
@@ -2674,7 +2674,7 @@ var _TokenManager = class _TokenManager {
2674
2674
  * Parse the current JWT token
2675
2675
  */
2676
2676
  parseToken(token) {
2677
- const targetToken = token || this.getToken();
2677
+ const targetToken = token || this.tokenCache;
2678
2678
  if (!targetToken) {
2679
2679
  return null;
2680
2680
  }
@@ -2691,7 +2691,7 @@ var _TokenManager = class _TokenManager {
2691
2691
  * Check if a token is valid (properly formatted and not expired)
2692
2692
  */
2693
2693
  isTokenValid(token) {
2694
- const targetToken = token || this.getToken();
2694
+ const targetToken = token || this.tokenCache;
2695
2695
  if (!targetToken) {
2696
2696
  return false;
2697
2697
  }
package/dist/index.mjs CHANGED
@@ -2639,7 +2639,7 @@ var _TokenManager = class _TokenManager {
2639
2639
  * Parse the current JWT token
2640
2640
  */
2641
2641
  parseToken(token) {
2642
- const targetToken = token || this.getToken();
2642
+ const targetToken = token || this.tokenCache;
2643
2643
  if (!targetToken) {
2644
2644
  return null;
2645
2645
  }
@@ -2656,7 +2656,7 @@ var _TokenManager = class _TokenManager {
2656
2656
  * Check if a token is valid (properly formatted and not expired)
2657
2657
  */
2658
2658
  isTokenValid(token) {
2659
- const targetToken = token || this.getToken();
2659
+ const targetToken = token || this.tokenCache;
2660
2660
  if (!targetToken) {
2661
2661
  return false;
2662
2662
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocios/crudify-ui",
3
- "version": "1.0.83",
3
+ "version": "1.0.85",
4
4
  "description": "Biblioteca de componentes UI para Crudify",
5
5
  "author": "Nocios",
6
6
  "license": "MIT",