@nocios/crudify-ui 1.3.0 → 1.3.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/README.md +155 -1108
- package/README_DEPTH.md +1046 -0
- package/dist/index.d.mts +181 -1
- package/dist/index.d.ts +181 -1
- package/dist/index.js +393 -20
- package/dist/index.mjs +388 -19
- package/package.json +1 -1
- package/MIGRATION-GUIDE.md +0 -312
- package/MIGRATION.md +0 -201
- package/MIGRATION_EXAMPLE.md +0 -538
- package/TECHNICAL_SPECIFICATION.md +0 -344
- package/example-app.tsx +0 -197
- package/mejoras_npm_lib.md +0 -790
- package/tsup.config.ts +0 -9
package/README.md
CHANGED
|
@@ -1,1244 +1,291 @@
|
|
|
1
|
-
# @nocios/crudify-ui
|
|
1
|
+
# @nocios/crudify-ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://badge.fury.io/js/%40nocios%2Fcrudify-ui)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Biblioteca completa de componentes UI y hooks de React para aplicaciones Crudify. Proporciona autenticación moderna, manejo de sesiones y una API unificada.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
npm install @nocios/crudify-ui
|
|
9
|
-
```
|
|
8
|
+
## 🚀 Características
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
- **🔐 Autenticación Moderna**: Sistema completo con Refresh Token Pattern
|
|
11
|
+
- **⚡ Manejo de Sesiones**: Persistencia automática y sincronización cross-tab
|
|
12
|
+
- **🎨 Componentes UI**: Componentes React listos con Material-UI
|
|
13
|
+
- **🪝 React Hooks**: Hooks especializados para CRUD y autenticación
|
|
14
|
+
- **🔄 API Unificada**: Re-exporta completamente @nocios/crudify-browser
|
|
15
|
+
- **🌍 Internacionalización**: Soporte para múltiples idiomas
|
|
16
|
+
- **📱 TypeScript**: Completamente tipado
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
## 📦 Instalación
|
|
14
19
|
|
|
15
20
|
```bash
|
|
16
|
-
npm install
|
|
17
|
-
```
|
|
21
|
+
npm install @nocios/crudify-ui
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
# Peer dependencies
|
|
24
|
+
npm install @mui/material @mui/icons-material react react-dom
|
|
25
|
+
```
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
## 🏗️ Configuración Rápida
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
### 1. Configurar SessionProvider
|
|
24
30
|
|
|
25
31
|
```tsx
|
|
26
|
-
|
|
27
|
-
import React from 'react';
|
|
28
|
-
import { CrudifyDataProvider } from '@nocios/crudify-ui';
|
|
29
|
-
import AppContent from './AppContent';
|
|
32
|
+
import { SessionProvider } from '@nocios/crudify-ui';
|
|
30
33
|
|
|
31
34
|
function App() {
|
|
32
35
|
return (
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
appName="Mi Aplicación"
|
|
37
|
-
>
|
|
38
|
-
<AppContent />
|
|
39
|
-
</CrudifyDataProvider>
|
|
36
|
+
<SessionProvider>
|
|
37
|
+
<YourApp />
|
|
38
|
+
</SessionProvider>
|
|
40
39
|
);
|
|
41
40
|
}
|
|
42
|
-
|
|
43
|
-
export default App;
|
|
44
41
|
```
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
// AppContent.tsx - Lógica de autenticación
|
|
48
|
-
import React from 'react';
|
|
49
|
-
import { useCrudifyAuth, CrudifyLogin } from '@nocios/crudify-ui';
|
|
50
|
-
import Dashboard from './Dashboard';
|
|
43
|
+
### 2. Variables de Entorno
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return <div>Cargando...</div>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!isAuthenticated) {
|
|
60
|
-
return (
|
|
61
|
-
<CrudifyLogin
|
|
62
|
-
onLoginSuccess={(newToken) => {
|
|
63
|
-
setToken(newToken); // ✅ Sincronización automática
|
|
64
|
-
// No necesitas manejar redirección manual
|
|
65
|
-
}}
|
|
66
|
-
config={{
|
|
67
|
-
colors: { primaryColor: "#1066BA" },
|
|
68
|
-
loginActions: ["forgotPassword"]
|
|
69
|
-
}}
|
|
70
|
-
/>
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return <Dashboard user={user} />;
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
```tsx
|
|
79
|
-
// Dashboard.tsx - Uso de operaciones CRUD
|
|
80
|
-
import React, { useEffect, useState } from 'react';
|
|
81
|
-
import { useCrudifyData } from '@nocios/crudify-ui';
|
|
82
|
-
|
|
83
|
-
function Dashboard({ user }) {
|
|
84
|
-
const { readItems, createItem, isReady, loading } = useCrudifyData();
|
|
85
|
-
const [items, setItems] = useState([]);
|
|
86
|
-
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
if (isReady) {
|
|
89
|
-
loadItems();
|
|
90
|
-
}
|
|
91
|
-
}, [isReady]);
|
|
92
|
-
|
|
93
|
-
const loadItems = async () => {
|
|
94
|
-
try {
|
|
95
|
-
const response = await readItems('products', { active: true });
|
|
96
|
-
if (response.success) {
|
|
97
|
-
setItems(response.data);
|
|
98
|
-
}
|
|
99
|
-
} catch (error) {
|
|
100
|
-
console.error('Error loading items:', error);
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
if (!isReady) {
|
|
105
|
-
return <div>Inicializando sistema...</div>;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<div>
|
|
110
|
-
<h1>Dashboard - {user?.name}</h1>
|
|
111
|
-
<div>
|
|
112
|
-
{items.map(item => (
|
|
113
|
-
<div key={item.id}>{item.name}</div>
|
|
114
|
-
))}
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
45
|
+
```bash
|
|
46
|
+
# .env
|
|
47
|
+
REACT_APP_CRUDIFY_PUBLIC_API_KEY=tu_api_key
|
|
48
|
+
REACT_APP_CRUDIFY_ENV=dev
|
|
119
49
|
```
|
|
120
50
|
|
|
121
|
-
##
|
|
122
|
-
|
|
123
|
-
### 🎯 **useCrudifyAuth** - Gestión de Autenticación
|
|
51
|
+
## 🪝 Hooks Principales
|
|
124
52
|
|
|
125
|
-
|
|
53
|
+
### useAuth - Autenticación
|
|
126
54
|
|
|
127
55
|
```tsx
|
|
128
|
-
import {
|
|
129
|
-
|
|
130
|
-
function AuthComponent() {
|
|
131
|
-
const {
|
|
132
|
-
isAuthenticated, // boolean - Si el usuario está autenticado
|
|
133
|
-
token, // string | null - Token JWT actual
|
|
134
|
-
setToken, // (token: string | null) => void - Función para establecer token
|
|
135
|
-
user, // object | null - Datos del usuario
|
|
136
|
-
loading, // boolean - Estado de carga
|
|
137
|
-
login, // (email: string, password: string) => Promise<CrudifyApiResponse>
|
|
138
|
-
logout, // () => void - Función para logout
|
|
139
|
-
refreshUserData // () => Promise<void> - Actualizar datos del usuario
|
|
140
|
-
} = useCrudifyAuth();
|
|
141
|
-
|
|
142
|
-
return (
|
|
143
|
-
<div>
|
|
144
|
-
{isAuthenticated ? (
|
|
145
|
-
<div>
|
|
146
|
-
<h1>Bienvenido, {user?.name}</h1>
|
|
147
|
-
<button onClick={logout}>Cerrar Sesión</button>
|
|
148
|
-
</div>
|
|
149
|
-
) : (
|
|
150
|
-
<div>No autenticado</div>
|
|
151
|
-
)}
|
|
152
|
-
</div>
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
```
|
|
56
|
+
import { useAuth } from '@nocios/crudify-ui';
|
|
156
57
|
|
|
157
|
-
|
|
58
|
+
function LoginComponent() {
|
|
59
|
+
const { login, logout, isAuthenticated, isLoading } = useAuth();
|
|
158
60
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
```tsx
|
|
162
|
-
import { useCrudifyData } from '@nocios/crudify-ui';
|
|
163
|
-
|
|
164
|
-
function DataComponent() {
|
|
165
|
-
const {
|
|
166
|
-
readItems, // (moduleKey: string, filter?: object, options?: any) => Promise<CrudifyApiResponse>
|
|
167
|
-
readItem, // (moduleKey: string, filter: object, options?: any) => Promise<CrudifyApiResponse>
|
|
168
|
-
createItem, // (moduleKey: string, data: object, options?: any) => Promise<CrudifyApiResponse>
|
|
169
|
-
updateItem, // (moduleKey: string, data: object, options?: any) => Promise<CrudifyApiResponse>
|
|
170
|
-
deleteItem, // (moduleKey: string, id: string, options?: any) => Promise<CrudifyApiResponse>
|
|
171
|
-
transaction, // (operations: any[], options?: any) => Promise<CrudifyApiResponse>
|
|
172
|
-
getStructure, // (options?: any) => Promise<CrudifyApiResponse>
|
|
173
|
-
getStructurePublic,// (options?: any) => Promise<CrudifyApiResponse>
|
|
174
|
-
isReady, // boolean - Si el sistema está listo para operaciones
|
|
175
|
-
loading // boolean - Estado de carga
|
|
176
|
-
} = useCrudifyData();
|
|
177
|
-
|
|
178
|
-
const handleLoadProducts = async () => {
|
|
179
|
-
if (!isReady) return;
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
const response = await readItems('products', { active: true });
|
|
183
|
-
if (response.success) {
|
|
184
|
-
console.log('Productos:', response.data);
|
|
185
|
-
}
|
|
186
|
-
} catch (error) {
|
|
187
|
-
console.error('Error:', error);
|
|
188
|
-
}
|
|
61
|
+
const handleLogin = async () => {
|
|
62
|
+
const result = await login('user@example.com', 'password');
|
|
189
63
|
};
|
|
190
64
|
|
|
191
|
-
return (
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
</
|
|
65
|
+
return isAuthenticated ? (
|
|
66
|
+
<button onClick={logout}>Cerrar Sesión</button>
|
|
67
|
+
) : (
|
|
68
|
+
<button onClick={handleLogin} disabled={isLoading}>
|
|
69
|
+
Iniciar Sesión
|
|
70
|
+
</button>
|
|
197
71
|
);
|
|
198
72
|
}
|
|
199
73
|
```
|
|
200
74
|
|
|
201
|
-
###
|
|
202
|
-
|
|
203
|
-
Hook especializado para datos del usuario:
|
|
75
|
+
### useUserData - Datos del Usuario
|
|
204
76
|
|
|
205
77
|
```tsx
|
|
206
|
-
import {
|
|
78
|
+
import { useUserData } from '@nocios/crudify-ui';
|
|
207
79
|
|
|
208
80
|
function UserProfile() {
|
|
209
|
-
const {
|
|
210
|
-
user, // object | null - Datos del usuario
|
|
211
|
-
loading, // boolean - Estado de carga
|
|
212
|
-
error, // string | null - Error si existe
|
|
213
|
-
refreshUserData, // () => Promise<void> - Actualizar datos
|
|
214
|
-
updateUserProfile, // (data: object) => Promise<CrudifyApiResponse>
|
|
215
|
-
isReady // boolean - Si está listo
|
|
216
|
-
} = useCrudifyUser();
|
|
217
|
-
|
|
218
|
-
if (loading) return <div>Cargando perfil...</div>;
|
|
219
|
-
if (error) return <div>Error: {error}</div>;
|
|
220
|
-
|
|
221
|
-
return (
|
|
222
|
-
<div>
|
|
223
|
-
<h2>Perfil de Usuario</h2>
|
|
224
|
-
<p>Nombre: {user?.name}</p>
|
|
225
|
-
<p>Email: {user?.email}</p>
|
|
226
|
-
<button onClick={refreshUserData}>Actualizar Datos</button>
|
|
227
|
-
</div>
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### ⚙️ **useCrudifyConfig** - Acceso a Configuración
|
|
233
|
-
|
|
234
|
-
Hook para acceder a la configuración actual:
|
|
235
|
-
|
|
236
|
-
```tsx
|
|
237
|
-
import { useCrudifyConfig } from '@nocios/crudify-ui';
|
|
81
|
+
const { userData, sessionData, isLoading } = useUserData();
|
|
238
82
|
|
|
239
|
-
|
|
240
|
-
const {
|
|
241
|
-
config, // CrudifyConfig - Configuración actual
|
|
242
|
-
updateConfig, // (newConfig: Partial<CrudifyConfig>) => void
|
|
243
|
-
isConfigured, // boolean - Si está configurado
|
|
244
|
-
environment // string - Entorno actual (dev, prod, etc.)
|
|
245
|
-
} = useCrudifyConfig();
|
|
83
|
+
if (isLoading) return <div>Cargando...</div>;
|
|
246
84
|
|
|
247
85
|
return (
|
|
248
86
|
<div>
|
|
249
|
-
<
|
|
250
|
-
<p>
|
|
251
|
-
<
|
|
252
|
-
<p>Configurado: {isConfigured ? 'Sí' : 'No'}</p>
|
|
87
|
+
<h2>{userData?.fullName}</h2>
|
|
88
|
+
<p>Email: {sessionData?.email}</p>
|
|
89
|
+
<img src={userData?.avatar} alt="Avatar" />
|
|
253
90
|
</div>
|
|
254
91
|
);
|
|
255
92
|
}
|
|
256
93
|
```
|
|
257
94
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
### Props del Provider
|
|
95
|
+
### useData - Operaciones CRUD
|
|
261
96
|
|
|
262
97
|
```tsx
|
|
263
|
-
|
|
264
|
-
children: React.ReactNode;
|
|
265
|
-
|
|
266
|
-
// Configuración básica
|
|
267
|
-
publicApiKey?: string; // Clave de API de Crudify
|
|
268
|
-
env?: 'dev' | 'stg' | 'api' | 'prod'; // Entorno
|
|
269
|
-
appName?: string; // Nombre de la aplicación
|
|
270
|
-
|
|
271
|
-
// Configuración visual
|
|
272
|
-
logo?: string; // URL del logo
|
|
273
|
-
colors?: {
|
|
274
|
-
primaryColor?: string; // Color principal
|
|
275
|
-
bgColor?: string; // Color de fondo
|
|
276
|
-
[key: string]: string | undefined;
|
|
277
|
-
};
|
|
98
|
+
import { useData } from '@nocios/crudify-ui';
|
|
278
99
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
autoInitialize?: boolean; // Inicializar automáticamente (default: true)
|
|
282
|
-
persistToken?: boolean; // Persistir token entre sesiones (default: true)
|
|
100
|
+
function ProductManager() {
|
|
101
|
+
const { create, read, update, remove } = useData();
|
|
283
102
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
// Callbacks opcionales
|
|
288
|
-
onInitialized?: () => void; // Callback cuando se inicializa
|
|
289
|
-
onError?: (error: string) => void; // Callback para errores
|
|
290
|
-
onTokenChange?: (token: string | null) => void; // Callback cuando cambia el token
|
|
291
|
-
}
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
### Ejemplo de Configuración Completa
|
|
103
|
+
const createProduct = async () => {
|
|
104
|
+
await create('products', { name: 'Nuevo Producto', price: 99.99 });
|
|
105
|
+
};
|
|
295
106
|
|
|
296
|
-
|
|
297
|
-
|
|
107
|
+
const getProducts = async () => {
|
|
108
|
+
const products = await read('products', { limit: 10 });
|
|
109
|
+
};
|
|
298
110
|
|
|
299
|
-
function App() {
|
|
300
111
|
return (
|
|
301
|
-
<
|
|
302
|
-
// Configuración básica
|
|
303
|
-
publicApiKey={process.env.REACT_APP_CRUDIFY_API_KEY}
|
|
304
|
-
env={process.env.NODE_ENV === 'development' ? 'dev' : 'prod'}
|
|
305
|
-
appName="Mi Aplicación"
|
|
306
|
-
|
|
307
|
-
// Configuración visual
|
|
308
|
-
logo="/logo.png"
|
|
309
|
-
colors={{
|
|
310
|
-
primaryColor: "#1066BA",
|
|
311
|
-
bgColor: "#f5f5f5"
|
|
312
|
-
}}
|
|
313
|
-
|
|
314
|
-
// Configuración avanzada
|
|
315
|
-
autoReadFromCookies={true}
|
|
316
|
-
autoInitialize={true}
|
|
317
|
-
persistToken={true}
|
|
318
|
-
|
|
319
|
-
// Acciones de login disponibles
|
|
320
|
-
loginActions={["forgotPassword", "createUser"]}
|
|
321
|
-
|
|
322
|
-
// Callbacks
|
|
323
|
-
onInitialized={() => console.log('Sistema inicializado')}
|
|
324
|
-
onError={(error) => console.error('Error del sistema:', error)}
|
|
325
|
-
onTokenChange={(token) => console.log('Token actualizado:', !!token)}
|
|
326
|
-
>
|
|
327
|
-
<AppContent />
|
|
328
|
-
</CrudifyDataProvider>
|
|
112
|
+
<button onClick={createProduct}>Crear Producto</button>
|
|
329
113
|
);
|
|
330
114
|
}
|
|
331
115
|
```
|
|
332
116
|
|
|
333
|
-
## 🎨
|
|
334
|
-
|
|
335
|
-
### Uso Standalone (Sistema Legacy)
|
|
117
|
+
## 🎨 Componentes UI
|
|
336
118
|
|
|
337
|
-
|
|
119
|
+
### CrudifyLogin - Login Completo
|
|
338
120
|
|
|
339
121
|
```tsx
|
|
340
122
|
import { CrudifyLogin } from '@nocios/crudify-ui';
|
|
341
123
|
|
|
342
|
-
function
|
|
343
|
-
const handleLoginSuccess = (token: string, redirectUrl?: string) => {
|
|
344
|
-
// ⚠️ Importante: Sincronizar con el sistema global
|
|
345
|
-
sessionStorage.setItem("authToken", token);
|
|
346
|
-
|
|
347
|
-
// Si usas CrudifyDataProvider, también sincronizar ahí
|
|
348
|
-
// setToken(token); // usando useCrudifyAuth
|
|
349
|
-
|
|
350
|
-
window.location.href = redirectUrl || "/dashboard";
|
|
351
|
-
};
|
|
352
|
-
|
|
124
|
+
function AuthPage() {
|
|
353
125
|
return (
|
|
354
126
|
<CrudifyLogin
|
|
355
127
|
config={{
|
|
356
|
-
|
|
357
|
-
env: "prod",
|
|
358
|
-
appName: "Mi Aplicación",
|
|
128
|
+
appName: "Mi App",
|
|
359
129
|
logo: "/logo.png",
|
|
360
|
-
colors: {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
loginActions: ["forgotPassword", "createUser"]
|
|
130
|
+
colors: { primaryColor: "#1976d2" }
|
|
131
|
+
}}
|
|
132
|
+
onLoginSuccess={(userData) => {
|
|
133
|
+
console.log('Login exitoso:', userData);
|
|
365
134
|
}}
|
|
366
|
-
onLoginSuccess={handleLoginSuccess}
|
|
367
|
-
onError={(error) => console.error('Login error:', error)}
|
|
368
|
-
redirectUrl="/dashboard"
|
|
369
135
|
language="es"
|
|
370
136
|
/>
|
|
371
137
|
);
|
|
372
138
|
}
|
|
373
139
|
```
|
|
374
140
|
|
|
375
|
-
###
|
|
141
|
+
### ProtectedRoute - Protección de Rutas
|
|
376
142
|
|
|
377
143
|
```tsx
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
onExternalNavigate?: (path: string) => void;
|
|
387
|
-
|
|
388
|
-
// Navegación
|
|
389
|
-
initialScreen?: 'login' | 'forgotPassword' | 'resetPassword' | 'checkCode';
|
|
390
|
-
redirectUrl?: string;
|
|
391
|
-
|
|
392
|
-
// Personalización
|
|
393
|
-
language?: string;
|
|
394
|
-
translations?: CrudifyLoginTranslations;
|
|
395
|
-
translationsUrl?: string;
|
|
396
|
-
|
|
397
|
-
// Opciones
|
|
398
|
-
autoReadFromCookies?: boolean;
|
|
144
|
+
import { ProtectedRoute } from '@nocios/crudify-ui';
|
|
145
|
+
|
|
146
|
+
function App() {
|
|
147
|
+
return (
|
|
148
|
+
<ProtectedRoute fallback={<LoginPage />}>
|
|
149
|
+
<Dashboard />
|
|
150
|
+
</ProtectedRoute>
|
|
151
|
+
);
|
|
399
152
|
}
|
|
400
153
|
```
|
|
401
154
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
```tsx
|
|
405
|
-
<CrudifyLogin
|
|
406
|
-
config={{
|
|
407
|
-
appName: "Mi App",
|
|
408
|
-
logo: "/mi-logo.png",
|
|
409
|
-
colors: {
|
|
410
|
-
primaryColor: "#1066BA",
|
|
411
|
-
bgColor: "#f5f5f5",
|
|
412
|
-
textColor: "#333333",
|
|
413
|
-
buttonTextColor: "#ffffff"
|
|
414
|
-
},
|
|
415
|
-
}}
|
|
416
|
-
/>
|
|
417
|
-
```
|
|
155
|
+
## 🔄 API de Crudify Browser
|
|
418
156
|
|
|
419
|
-
|
|
157
|
+
Re-exporta completamente `@nocios/crudify-browser`:
|
|
420
158
|
|
|
421
159
|
```tsx
|
|
422
|
-
|
|
423
|
-
"login.title": "Iniciar Sesión",
|
|
424
|
-
"login.usernameLabel": "Usuario o Email",
|
|
425
|
-
"login.passwordLabel": "Contraseña",
|
|
426
|
-
"login.loginButton": "Entrar",
|
|
427
|
-
"login.forgotPasswordLink": "¿Olvidaste tu contraseña?",
|
|
428
|
-
"forgotPassword.title": "Recuperar Contraseña",
|
|
429
|
-
"forgotPassword.emailLabel": "Email",
|
|
430
|
-
"forgotPassword.sendButton": "Enviar Código"
|
|
431
|
-
};
|
|
160
|
+
import { crudify } from '@nocios/crudify-ui';
|
|
432
161
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
/>;
|
|
162
|
+
// Usar directamente la API de crudify
|
|
163
|
+
const result = await crudify.create('users', userData);
|
|
164
|
+
const users = await crudify.read('users');
|
|
437
165
|
```
|
|
438
166
|
|
|
439
|
-
## 🛠️
|
|
440
|
-
|
|
441
|
-
### 🔑 **Acceso Directo a la Instancia**
|
|
442
|
-
|
|
443
|
-
Para casos avanzados donde necesitas acceso directo a la instancia de crudify:
|
|
444
|
-
|
|
445
|
-
```tsx
|
|
446
|
-
import { getCrudifyInstanceAsync, getCrudifyInstanceSync } from '@nocios/crudify-ui';
|
|
447
|
-
|
|
448
|
-
// Método asíncrono (recomendado) - espera a que se inicialice
|
|
449
|
-
async function loadData() {
|
|
450
|
-
try {
|
|
451
|
-
const crudifyInstance = await getCrudifyInstanceAsync();
|
|
452
|
-
const response = await crudifyInstance.readItems('products', {});
|
|
453
|
-
return response;
|
|
454
|
-
} catch (error) {
|
|
455
|
-
console.error('Error:', error);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Método síncrono - lanza error si no está listo
|
|
460
|
-
function quickOperation() {
|
|
461
|
-
try {
|
|
462
|
-
const crudifyInstance = await getCrudifyInstanceSync();
|
|
463
|
-
// Usar solo si estás seguro de que ya está inicializado
|
|
464
|
-
return crudifyInstance.readItems('products', {});
|
|
465
|
-
} catch (error) {
|
|
466
|
-
console.error('Crudify not ready:', error);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
```
|
|
167
|
+
## 🛠️ Utilidades
|
|
470
168
|
|
|
471
|
-
###
|
|
169
|
+
### JWT Utils
|
|
472
170
|
|
|
473
171
|
```tsx
|
|
474
|
-
import {
|
|
475
|
-
getCurrentUserEmail,
|
|
476
|
-
decodeJwtSafely,
|
|
477
|
-
isTokenExpired,
|
|
478
|
-
getTokenPayload
|
|
479
|
-
} from '@nocios/crudify-ui';
|
|
172
|
+
import { decodeJwtSafely, getCurrentUserEmail } from '@nocios/crudify-ui';
|
|
480
173
|
|
|
481
|
-
// Obtener email del usuario actual
|
|
482
|
-
const userEmail = getCurrentUserEmail();
|
|
483
|
-
|
|
484
|
-
// Decodificar token JWT de forma segura
|
|
485
174
|
const payload = decodeJwtSafely(token);
|
|
486
|
-
|
|
487
|
-
// Verificar si token ha expirado
|
|
488
|
-
const expired = isTokenExpired(token);
|
|
489
|
-
|
|
490
|
-
// Obtener payload completo con validación
|
|
491
|
-
const tokenData = getTokenPayload(token);
|
|
492
|
-
console.log('Usuario:', tokenData?.email);
|
|
493
|
-
console.log('Expira:', new Date(tokenData?.exp * 1000));
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
### 🔒 **Storage Seguro**
|
|
497
|
-
|
|
498
|
-
```tsx
|
|
499
|
-
import {
|
|
500
|
-
secureSessionStorage,
|
|
501
|
-
secureLocalStorage,
|
|
502
|
-
TokenManager
|
|
503
|
-
} from '@nocios/crudify-ui';
|
|
504
|
-
|
|
505
|
-
// Uso básico del storage seguro
|
|
506
|
-
secureSessionStorage.setToken(token);
|
|
507
|
-
const currentToken = secureSessionStorage.getToken();
|
|
508
|
-
secureSessionStorage.clear();
|
|
509
|
-
|
|
510
|
-
// Uso avanzado con TokenManager
|
|
511
|
-
const tokenManager = TokenManager.getInstance();
|
|
512
|
-
|
|
513
|
-
// Establecer token con sincronización automática
|
|
514
|
-
await tokenManager.setToken(token);
|
|
515
|
-
|
|
516
|
-
// Obtener token válido (verifica expiración)
|
|
517
|
-
const validToken = tokenManager.getValidToken();
|
|
518
|
-
|
|
519
|
-
// Limpiar todo
|
|
520
|
-
tokenManager.clearToken();
|
|
521
|
-
|
|
522
|
-
// Verificar si hay token válido
|
|
523
|
-
const hasValidToken = tokenManager.hasValidToken();
|
|
175
|
+
const email = getCurrentUserEmail();
|
|
524
176
|
```
|
|
525
177
|
|
|
526
|
-
###
|
|
178
|
+
### Error Handler
|
|
527
179
|
|
|
528
180
|
```tsx
|
|
529
|
-
import {
|
|
530
|
-
handleCrudifyError,
|
|
531
|
-
ERROR_CODES,
|
|
532
|
-
getErrorMessage,
|
|
533
|
-
CrudifyErrorHandler
|
|
534
|
-
} from '@nocios/crudify-ui';
|
|
181
|
+
import { handleCrudifyError, getErrorMessage } from '@nocios/crudify-ui';
|
|
535
182
|
|
|
536
|
-
// Manejo básico de errores
|
|
537
183
|
try {
|
|
538
|
-
|
|
184
|
+
// operación
|
|
539
185
|
} catch (error) {
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
console.log(`Error ${err.code}: ${err.message} (Severidad: ${err.severity})`);
|
|
543
|
-
|
|
544
|
-
// Diferentes acciones según el código de error
|
|
545
|
-
switch (err.code) {
|
|
546
|
-
case ERROR_CODES.INVALID_CREDENTIALS:
|
|
547
|
-
// Redirigir a login
|
|
548
|
-
break;
|
|
549
|
-
case ERROR_CODES.NETWORK_ERROR:
|
|
550
|
-
// Mostrar mensaje de conectividad
|
|
551
|
-
break;
|
|
552
|
-
case ERROR_CODES.TOO_MANY_REQUESTS:
|
|
553
|
-
// Mostrar mensaje de espera
|
|
554
|
-
break;
|
|
555
|
-
}
|
|
556
|
-
});
|
|
186
|
+
const parsedError = handleCrudifyError(error);
|
|
187
|
+
const userMessage = getErrorMessage(parsedError.code);
|
|
557
188
|
}
|
|
558
|
-
|
|
559
|
-
// Manejo avanzado con categorías
|
|
560
|
-
const errorHandler = new CrudifyErrorHandler();
|
|
561
|
-
errorHandler.setErrorCallback('authentication', (error) => {
|
|
562
|
-
// Manejar errores de autenticación
|
|
563
|
-
console.log('Auth error:', error);
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
errorHandler.setErrorCallback('validation', (error) => {
|
|
567
|
-
// Manejar errores de validación
|
|
568
|
-
console.log('Validation error:', error);
|
|
569
|
-
});
|
|
570
189
|
```
|
|
571
190
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
Para migrar desde versiones anteriores:
|
|
191
|
+
## 🚀 Ejemplo Completo
|
|
575
192
|
|
|
576
193
|
```tsx
|
|
577
|
-
|
|
578
|
-
import {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const { userProfile, loading, error, refreshProfile } = useUserProfile();
|
|
194
|
+
import React from 'react';
|
|
195
|
+
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
|
196
|
+
import {
|
|
197
|
+
SessionProvider,
|
|
198
|
+
ProtectedRoute,
|
|
199
|
+
CrudifyLogin,
|
|
200
|
+
useSessionContext
|
|
201
|
+
} from '@nocios/crudify-ui';
|
|
586
202
|
|
|
587
|
-
|
|
588
|
-
|
|
203
|
+
function Dashboard() {
|
|
204
|
+
const { sessionData, logout } = useSessionContext();
|
|
589
205
|
|
|
590
206
|
return (
|
|
591
207
|
<div>
|
|
592
|
-
<h1>
|
|
593
|
-
<p>
|
|
594
|
-
<button onClick={
|
|
208
|
+
<h1>Dashboard</h1>
|
|
209
|
+
<p>Bienvenido, {sessionData?.email}</p>
|
|
210
|
+
<button onClick={logout}>Cerrar Sesión</button>
|
|
595
211
|
</div>
|
|
596
212
|
);
|
|
597
213
|
}
|
|
598
214
|
|
|
599
|
-
function
|
|
600
|
-
// Hook legacy para configuración
|
|
601
|
-
const { config } = useCrudifyLogin({
|
|
602
|
-
publicApiKey: "mi-api-key",
|
|
603
|
-
env: "prod",
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
return <div>App: {config.appName}</div>;
|
|
607
|
-
}
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
## 📱 Flujos de Autenticacion
|
|
611
|
-
|
|
612
|
-
### 1. Login Estandar
|
|
613
|
-
|
|
614
|
-
```
|
|
615
|
-
Usuario ingresa credenciales → Validacion → Token JWT → Redireccion
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
### 2. Recuperacion de Contrasena
|
|
619
|
-
|
|
620
|
-
```
|
|
621
|
-
Email → Codigo por email → Validacion → Nueva contrasena → Login automatico
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
### 3. Navegacion entre Pantallas
|
|
625
|
-
|
|
626
|
-
```tsx
|
|
627
|
-
const handleScreenChange = (screen, params) => {
|
|
628
|
-
console.log(`Cambiando a: ${screen}`, params);
|
|
629
|
-
// Logica personalizada de navegacion
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
<CrudifyLogin onScreenChange={handleScreenChange} />;
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
## 🔐 Integracion con Cookies
|
|
636
|
-
|
|
637
|
-
El componente puede leer configuracion automaticamente desde cookies:
|
|
638
|
-
|
|
639
|
-
```javascript
|
|
640
|
-
// Configurar desde JavaScript
|
|
641
|
-
document.cookie = "publicApiKey=tu-api-key; path=/";
|
|
642
|
-
document.cookie = "environment=prod; path=/";
|
|
643
|
-
document.cookie = "appName=Mi App; path=/";
|
|
644
|
-
document.cookie = 'loginActions=["forgotPassword","createUser"]; path=/';
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
## 🌍 Internacionalizacion
|
|
648
|
-
|
|
649
|
-
### Idiomas Soportados
|
|
650
|
-
|
|
651
|
-
- **English** (`en`) - Default
|
|
652
|
-
- **Espanol** (`es`)
|
|
653
|
-
- **Personalizado** (via props)
|
|
654
|
-
|
|
655
|
-
### Cargar traducciones desde URL
|
|
656
|
-
|
|
657
|
-
```tsx
|
|
658
|
-
<CrudifyLogin translationsUrl="/api/translations/es.json" language="es" />
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
## 📊 Integracion con Crudify API
|
|
662
|
-
|
|
663
|
-
El componente incluye acceso completo a la API de Crudify:
|
|
664
|
-
|
|
665
|
-
```tsx
|
|
666
|
-
import { crudify } from "@nocios/crudify-ui";
|
|
667
|
-
|
|
668
|
-
// Usar directamente en tu app
|
|
669
|
-
const usuarios = await crudify.users.list();
|
|
670
|
-
const perfil = await crudify.users.profile();
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
## 🚨 Manejo de Errores
|
|
674
|
-
|
|
675
|
-
### Codigos de Error Estandar
|
|
676
|
-
|
|
677
|
-
| Codigo | Descripcion | Severidad |
|
|
678
|
-
| ----------------------- | ------------------------ | --------- |
|
|
679
|
-
| `INVALID_CREDENTIALS` | Credenciales incorrectas | warning |
|
|
680
|
-
| `TOO_MANY_REQUESTS` | Demasiados intentos | warning |
|
|
681
|
-
| `NETWORK_ERROR` | Error de conexion | error |
|
|
682
|
-
| `INTERNAL_SERVER_ERROR` | Error del servidor | error |
|
|
683
|
-
|
|
684
|
-
### Callback de Errores
|
|
685
|
-
|
|
686
|
-
```tsx
|
|
687
|
-
const handleError = (error: string) => {
|
|
688
|
-
// Notificar al usuario
|
|
689
|
-
toast.error(error);
|
|
690
|
-
|
|
691
|
-
// Log para debugging
|
|
692
|
-
console.error("Login error:", error);
|
|
693
|
-
|
|
694
|
-
// Analytics
|
|
695
|
-
analytics.track("login_error", { error });
|
|
696
|
-
};
|
|
697
|
-
|
|
698
|
-
<CrudifyLogin onError={handleError} />;
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
## 🔄 Estados del Componente
|
|
702
|
-
|
|
703
|
-
### Estados de Carga
|
|
704
|
-
|
|
705
|
-
- Login en progreso
|
|
706
|
-
- Validacion de codigo
|
|
707
|
-
- Envio de email de recuperacion
|
|
708
|
-
- Reseteo de contrasena
|
|
709
|
-
|
|
710
|
-
### Estados de Error
|
|
711
|
-
|
|
712
|
-
- Errores por campo especifico
|
|
713
|
-
- Errores globales
|
|
714
|
-
- Errores de red/servidor
|
|
715
|
-
|
|
716
|
-
## 📱 Ejemplo Avanzado
|
|
717
|
-
|
|
718
|
-
```tsx
|
|
719
|
-
import React from "react";
|
|
720
|
-
import { CrudifyLogin, useUserProfile, secureSessionStorage, handleCrudifyError } from "@nocios/crudify-ui";
|
|
721
|
-
|
|
722
|
-
function AuthenticatedApp() {
|
|
723
|
-
const { userProfile, loading } = useUserProfile();
|
|
724
|
-
|
|
725
|
-
// Si hay usuario, mostrar app
|
|
726
|
-
if (userProfile) {
|
|
727
|
-
return <MainApp user={userProfile} />;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
// Si esta cargando, mostrar spinner
|
|
731
|
-
if (loading) {
|
|
732
|
-
return <div>Verificando autenticacion...</div>;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// Mostrar login
|
|
215
|
+
function LoginPage() {
|
|
736
216
|
return (
|
|
737
|
-
<div
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
minHeight: "100vh",
|
|
743
|
-
backgroundColor: "#f5f5f5",
|
|
744
|
-
}}
|
|
745
|
-
>
|
|
746
|
-
<div style={{ width: "400px", padding: "2rem" }}>
|
|
747
|
-
<CrudifyLogin
|
|
748
|
-
config={{
|
|
749
|
-
publicApiKey: process.env.REACT_APP_CRUDIFY_API_KEY,
|
|
750
|
-
env: process.env.NODE_ENV || "dev",
|
|
751
|
-
appName: "Mi Aplicacion",
|
|
752
|
-
logo: "/logo.svg",
|
|
753
|
-
colors: {
|
|
754
|
-
primaryColor: "#1066BA",
|
|
755
|
-
},
|
|
756
|
-
loginActions: ["forgotPassword", "createUser"],
|
|
757
|
-
}}
|
|
758
|
-
onLoginSuccess={(token, redirectUrl) => {
|
|
759
|
-
secureSessionStorage.setToken(token);
|
|
760
|
-
// Force re-render para activar useUserProfile
|
|
761
|
-
window.location.reload();
|
|
762
|
-
}}
|
|
763
|
-
onError={(error) => {
|
|
764
|
-
console.error("Authentication error:", error);
|
|
765
|
-
// Mostrar notificacion de error
|
|
766
|
-
}}
|
|
767
|
-
onExternalNavigate={(path) => {
|
|
768
|
-
window.location.href = path;
|
|
769
|
-
}}
|
|
770
|
-
language="es"
|
|
771
|
-
redirectUrl="/dashboard"
|
|
772
|
-
autoReadFromCookies={true}
|
|
773
|
-
/>
|
|
774
|
-
</div>
|
|
217
|
+
<div style={{ maxWidth: '400px', margin: '0 auto' }}>
|
|
218
|
+
<CrudifyLogin
|
|
219
|
+
config={{ appName: "Mi App" }}
|
|
220
|
+
onLoginSuccess={(userData) => console.log('Login:', userData)}
|
|
221
|
+
/>
|
|
775
222
|
</div>
|
|
776
223
|
);
|
|
777
224
|
}
|
|
778
225
|
|
|
779
|
-
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
## 🏗️ Implementación con UnifiedProvider (Recomendado)
|
|
783
|
-
|
|
784
|
-
### Patrón Unificado para Apps Completas
|
|
785
|
-
|
|
786
|
-
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:
|
|
787
|
-
|
|
788
|
-
```tsx
|
|
789
|
-
// src/contexts/UnifiedProvider.tsx
|
|
790
|
-
import React, { createContext, useContext, useMemo, useState, useCallback, useEffect, type ReactNode } from "react"
|
|
791
|
-
import { createTheme } from "@mui/material"
|
|
792
|
-
import { crudify, type CrudifyEnvType } from "@nocios/crudify-ui"
|
|
793
|
-
|
|
794
|
-
interface AppContextValue {
|
|
795
|
-
logo: string | null
|
|
796
|
-
setLogo: (logo: string) => void
|
|
797
|
-
publicApiKey: string | null
|
|
798
|
-
setPublicApiKey: (key: string | null) => void
|
|
799
|
-
env: CrudifyEnvType
|
|
800
|
-
setEnv: (env: CrudifyEnvType) => void
|
|
801
|
-
appName: string | null
|
|
802
|
-
setAppName: (name: string | null) => void
|
|
803
|
-
colors: { primaryColor: string; [key: string]: string }
|
|
804
|
-
setColors: (colors: any) => void
|
|
805
|
-
theme: any
|
|
806
|
-
setTheme: (theme: any) => void
|
|
807
|
-
configLoading: boolean
|
|
808
|
-
configError: string | null
|
|
809
|
-
loginActions: string[]
|
|
810
|
-
setLoginActions: (actions: string[]) => void
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
interface DataContextValue {
|
|
814
|
-
token: string | null
|
|
815
|
-
setToken: (token: string | null) => void
|
|
816
|
-
user: Record<string, any> | null
|
|
817
|
-
isInitialized: boolean
|
|
818
|
-
initializationError: string | null
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
const AppContext = createContext<AppContextValue | null>(null)
|
|
822
|
-
const DataContext = createContext<DataContextValue | null>(null)
|
|
823
|
-
|
|
824
|
-
const UnifiedProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
|
825
|
-
// Estado para token y usuario
|
|
826
|
-
const [token, setTokenState] = useState<string | null>(null)
|
|
827
|
-
const [user, setUser] = useState<Record<string, any> | null>(null)
|
|
828
|
-
const [isFullyInitialized, setIsFullyInitialized] = useState(false)
|
|
829
|
-
|
|
830
|
-
// Configuración desde variables de entorno
|
|
831
|
-
const config = useMemo(() => ({
|
|
832
|
-
publicApiKey: import.meta.env.VITE_TEST_PUBLIC_API_KEY || 'default-key',
|
|
833
|
-
env: import.meta.env.VITE_TEST_ENV || 'prod',
|
|
834
|
-
appName: 'Mi Aplicación'
|
|
835
|
-
}), [])
|
|
836
|
-
|
|
837
|
-
const handleSetToken = useCallback((newToken: string | null) => {
|
|
838
|
-
setTokenState(newToken)
|
|
839
|
-
if (newToken) {
|
|
840
|
-
sessionStorage.setItem('crud_token', newToken)
|
|
841
|
-
} else {
|
|
842
|
-
sessionStorage.removeItem('crud_token')
|
|
843
|
-
}
|
|
844
|
-
}, [])
|
|
845
|
-
|
|
846
|
-
// Inicializar token desde sessionStorage
|
|
847
|
-
useEffect(() => {
|
|
848
|
-
const savedToken = sessionStorage.getItem('crud_token')
|
|
849
|
-
if (savedToken) {
|
|
850
|
-
setTokenState(savedToken)
|
|
851
|
-
}
|
|
852
|
-
}, [])
|
|
853
|
-
|
|
854
|
-
// Inicialización de Crudify cuando hay token
|
|
855
|
-
useEffect(() => {
|
|
856
|
-
if (!token) {
|
|
857
|
-
setIsFullyInitialized(false)
|
|
858
|
-
return
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
const initializeCrudify = async () => {
|
|
862
|
-
if (isFullyInitialized) return
|
|
863
|
-
|
|
864
|
-
try {
|
|
865
|
-
await crudify.config(config.env)
|
|
866
|
-
await crudify.init(config.publicApiKey, "none")
|
|
867
|
-
crudify.setToken(token)
|
|
868
|
-
setIsFullyInitialized(true)
|
|
869
|
-
} catch (error) {
|
|
870
|
-
console.error('Error initializing Crudify:', error)
|
|
871
|
-
setIsFullyInitialized(true) // Evitar bucles
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
initializeCrudify()
|
|
876
|
-
}, [token, config.env, config.publicApiKey, isFullyInitialized])
|
|
877
|
-
|
|
878
|
-
// Contexto de App
|
|
879
|
-
const appContextValue: AppContextValue = {
|
|
880
|
-
logo: null,
|
|
881
|
-
setLogo: () => {},
|
|
882
|
-
publicApiKey: config.publicApiKey,
|
|
883
|
-
setPublicApiKey: () => {},
|
|
884
|
-
env: config.env as CrudifyEnvType,
|
|
885
|
-
setEnv: () => {},
|
|
886
|
-
appName: config.appName,
|
|
887
|
-
setAppName: () => {},
|
|
888
|
-
colors: { primaryColor: "#1066BA" },
|
|
889
|
-
setColors: () => {},
|
|
890
|
-
theme: createTheme({ palette: { primary: { main: "#1066BA" } } }),
|
|
891
|
-
setTheme: () => {},
|
|
892
|
-
configLoading: false,
|
|
893
|
-
configError: null,
|
|
894
|
-
loginActions: [],
|
|
895
|
-
setLoginActions: () => {}
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// Contexto de Data
|
|
899
|
-
const dataContextValue: DataContextValue = {
|
|
900
|
-
token,
|
|
901
|
-
setToken: handleSetToken,
|
|
902
|
-
user,
|
|
903
|
-
isInitialized: !!token && isFullyInitialized,
|
|
904
|
-
initializationError: null
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
return (
|
|
908
|
-
<AppContext.Provider value={appContextValue}>
|
|
909
|
-
<DataContext.Provider value={dataContextValue}>
|
|
910
|
-
{children}
|
|
911
|
-
</DataContext.Provider>
|
|
912
|
-
</AppContext.Provider>
|
|
913
|
-
)
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
export const useApp = (): AppContextValue => {
|
|
917
|
-
const context = useContext(AppContext)
|
|
918
|
-
if (!context) throw new Error("useApp should be used within a UnifiedProvider")
|
|
919
|
-
return context
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
export const useData = (): DataContextValue => {
|
|
923
|
-
const context = useContext(DataContext)
|
|
924
|
-
if (!context) throw new Error("useData should be used within a UnifiedProvider")
|
|
925
|
-
return context
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
export { UnifiedProvider }
|
|
929
|
-
```
|
|
930
|
-
|
|
931
|
-
### Implementación del AppStatusHandler
|
|
932
|
-
|
|
933
|
-
```tsx
|
|
934
|
-
// src/components/AppStatusHandler/index.tsx
|
|
935
|
-
import { type ReactNode } from "react"
|
|
936
|
-
import { useLocation } from "react-router-dom"
|
|
937
|
-
import { useApp, useData } from "@contexts/UnifiedProvider"
|
|
938
|
-
import LoadingApp from "@components/LoadingApp"
|
|
939
|
-
import { CssBaseline, ThemeProvider } from "@mui/material"
|
|
940
|
-
import InitError from "@components/InitError"
|
|
941
|
-
|
|
942
|
-
interface AppStatusHandlerProps {
|
|
943
|
-
children: ReactNode
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
const AppStatusHandler = ({ children }: AppStatusHandlerProps) => {
|
|
947
|
-
const location = useLocation()
|
|
948
|
-
const { theme, configLoading, configError } = useApp()
|
|
949
|
-
const { isInitialized, initializationError } = useData()
|
|
950
|
-
|
|
951
|
-
// Rutas que no requieren inicialización completa
|
|
952
|
-
const authRoutes = ['/login', '/login/forgotPassword', '/login/checkCode', '/login/resetPassword']
|
|
953
|
-
const isAuthRoute = authRoutes.some(route => location.pathname.startsWith(route))
|
|
954
|
-
|
|
955
|
-
if (configLoading) {
|
|
956
|
-
return <LoadingApp stage="config" />
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
if (!theme) {
|
|
960
|
-
return <LoadingApp stage="theme" />
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// Solo requerir inicialización para rutas protegidas
|
|
964
|
-
if (!isInitialized && !isAuthRoute) {
|
|
965
|
-
return <LoadingApp stage="initializing" />
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
if (configError || initializationError) {
|
|
969
|
-
return <InitError configError={configError} initializationError={Boolean(initializationError)} />
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
return (
|
|
973
|
-
<ThemeProvider theme={theme}>
|
|
974
|
-
<CssBaseline />
|
|
975
|
-
{children}
|
|
976
|
-
</ThemeProvider>
|
|
977
|
-
)
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
export default AppStatusHandler
|
|
981
|
-
```
|
|
982
|
-
|
|
983
|
-
### Implementación de ProtectedRoute
|
|
984
|
-
|
|
985
|
-
```tsx
|
|
986
|
-
// src/components/Auth/ProtectedRoute/index.tsx
|
|
987
|
-
import { type ReactNode } from "react"
|
|
988
|
-
import { Navigate, useLocation } from "react-router-dom"
|
|
989
|
-
import { useData } from "@contexts/UnifiedProvider"
|
|
990
|
-
|
|
991
|
-
interface ProtectedRouteProps {
|
|
992
|
-
children: ReactNode
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
|
996
|
-
const { token } = useData()
|
|
997
|
-
const location = useLocation()
|
|
998
|
-
|
|
999
|
-
if (!token) {
|
|
1000
|
-
const fullPath = location.pathname + location.search
|
|
1001
|
-
const redirectPath = encodeURIComponent(fullPath)
|
|
1002
|
-
return <Navigate to={`/login?redirect=${redirectPath}`} replace />
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
return children
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
export default ProtectedRoute
|
|
1009
|
-
```
|
|
1010
|
-
|
|
1011
|
-
### Configuración de la App Principal
|
|
1012
|
-
|
|
1013
|
-
```tsx
|
|
1014
|
-
// src/main.tsx
|
|
1015
|
-
import React from 'react'
|
|
1016
|
-
import ReactDOM from 'react-dom/client'
|
|
1017
|
-
import { BrowserRouter } from 'react-router-dom'
|
|
1018
|
-
import { UnifiedProvider } from '@contexts/UnifiedProvider'
|
|
1019
|
-
import { GlobalNotificationProvider } from '@contexts/GlobalNotificationProvider'
|
|
1020
|
-
import AppStatusHandler from '@components/AppStatusHandler'
|
|
1021
|
-
import App from './App'
|
|
1022
|
-
|
|
1023
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
1024
|
-
<React.StrictMode>
|
|
1025
|
-
<BrowserRouter>
|
|
1026
|
-
<UnifiedProvider>
|
|
1027
|
-
<GlobalNotificationProvider>
|
|
1028
|
-
<AppStatusHandler>
|
|
1029
|
-
<App />
|
|
1030
|
-
</AppStatusHandler>
|
|
1031
|
-
</GlobalNotificationProvider>
|
|
1032
|
-
</UnifiedProvider>
|
|
1033
|
-
</BrowserRouter>
|
|
1034
|
-
</React.StrictMode>,
|
|
1035
|
-
)
|
|
1036
|
-
```
|
|
1037
|
-
|
|
1038
|
-
### Uso en Componentes
|
|
1039
|
-
|
|
1040
|
-
```tsx
|
|
1041
|
-
// Cualquier componente de la app
|
|
1042
|
-
import { useApp, useData } from '@contexts/UnifiedProvider'
|
|
1043
|
-
|
|
1044
|
-
function Dashboard() {
|
|
1045
|
-
const { appName, theme } = useApp()
|
|
1046
|
-
const { user, token, isInitialized } = useData()
|
|
1047
|
-
|
|
1048
|
-
if (!isInitialized) {
|
|
1049
|
-
return <div>Inicializando...</div>
|
|
1050
|
-
}
|
|
1051
|
-
|
|
226
|
+
function App() {
|
|
1052
227
|
return (
|
|
1053
|
-
<
|
|
1054
|
-
<
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
228
|
+
<SessionProvider>
|
|
229
|
+
<BrowserRouter>
|
|
230
|
+
<Routes>
|
|
231
|
+
<Route path="/login" element={<LoginPage />} />
|
|
232
|
+
<Route
|
|
233
|
+
path="/dashboard"
|
|
234
|
+
element={
|
|
235
|
+
<ProtectedRoute fallback={<LoginPage />}>
|
|
236
|
+
<Dashboard />
|
|
237
|
+
</ProtectedRoute>
|
|
238
|
+
}
|
|
239
|
+
/>
|
|
240
|
+
</Routes>
|
|
241
|
+
</BrowserRouter>
|
|
242
|
+
</SessionProvider>
|
|
243
|
+
);
|
|
1059
244
|
}
|
|
1060
245
|
```
|
|
1061
246
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
```env
|
|
1065
|
-
# .env.development
|
|
1066
|
-
VITE_TEST_PUBLIC_API_KEY=tu-clave-de-api-de-desarrollo
|
|
1067
|
-
VITE_TEST_ENV=dev
|
|
1068
|
-
|
|
1069
|
-
# .env.production
|
|
1070
|
-
VITE_TEST_PUBLIC_API_KEY=tu-clave-de-api-de-produccion
|
|
1071
|
-
VITE_TEST_ENV=prod
|
|
1072
|
-
```
|
|
1073
|
-
|
|
1074
|
-
### Ventajas de este Patrón
|
|
1075
|
-
|
|
1076
|
-
✅ **Inicialización automática**: Crudify se inicializa automáticamente cuando hay token
|
|
1077
|
-
✅ **Gestión unificada**: Un solo provider para configuración y autenticación
|
|
1078
|
-
✅ **Rutas protegidas**: Manejo automático de redirecciones según autenticación
|
|
1079
|
-
✅ **Estados de carga**: Diferentes estados para config, theme e inicialización
|
|
1080
|
-
✅ **Variables de entorno**: Configuración centralizada desde .env
|
|
1081
|
-
✅ **Compatibilidad completa**: Mantiene la API de providers anteriores
|
|
1082
|
-
✅ **Sin recursión**: Evita problemas de bucles infinitos en la inicialización
|
|
1083
|
-
|
|
1084
|
-
---
|
|
1085
|
-
|
|
1086
|
-
## 🚀 **Mejores Prácticas**
|
|
1087
|
-
|
|
1088
|
-
### ✅ **DO - Recomendaciones**
|
|
1089
|
-
|
|
1090
|
-
```tsx
|
|
1091
|
-
// ✅ Usar el sistema unificado
|
|
1092
|
-
<CrudifyDataProvider publicApiKey="..." env="prod">
|
|
1093
|
-
<App />
|
|
1094
|
-
</CrudifyDataProvider>
|
|
1095
|
-
|
|
1096
|
-
// ✅ Verificar estado antes de operaciones
|
|
1097
|
-
const { isReady } = useCrudifyData();
|
|
1098
|
-
if (isReady) {
|
|
1099
|
-
await readItems('products', {});
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
// ✅ Manejar errores apropiadamente
|
|
1103
|
-
try {
|
|
1104
|
-
const response = await createItem('products', data);
|
|
1105
|
-
if (response.success) {
|
|
1106
|
-
// Manejar éxito
|
|
1107
|
-
}
|
|
1108
|
-
} catch (error) {
|
|
1109
|
-
const errors = handleCrudifyError(error);
|
|
1110
|
-
// Mostrar errores al usuario
|
|
1111
|
-
}
|
|
247
|
+
## 📱 TypeScript
|
|
1112
248
|
|
|
1113
|
-
|
|
1114
|
-
const { isAuthenticated } = useCrudifyAuth();
|
|
1115
|
-
const { readItems } = useCrudifyData();
|
|
1116
|
-
const { user } = useCrudifyUser();
|
|
1117
|
-
```
|
|
1118
|
-
|
|
1119
|
-
### ❌ **DON'T - Evitar**
|
|
249
|
+
Tipos completos incluidos:
|
|
1120
250
|
|
|
1121
251
|
```tsx
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
<CrudifyDataProvider> // Conflicto
|
|
1129
|
-
<App />
|
|
1130
|
-
</CrudifyDataProvider>
|
|
1131
|
-
</AnotherProvider>
|
|
1132
|
-
</CrudifyProvider>
|
|
1133
|
-
|
|
1134
|
-
// ❌ No ignorar el estado isReady
|
|
1135
|
-
const { readItems } = useCrudifyData();
|
|
1136
|
-
await readItems('products', {}); // Puede fallar si no está listo
|
|
1137
|
-
|
|
1138
|
-
// ❌ No manejar tokens manualmente cuando usas el provider
|
|
1139
|
-
sessionStorage.setItem('token', token); // El provider lo maneja
|
|
252
|
+
import type {
|
|
253
|
+
CrudifyLoginProps,
|
|
254
|
+
UseAuthReturn,
|
|
255
|
+
UserProfile,
|
|
256
|
+
TokenData
|
|
257
|
+
} from '@nocios/crudify-ui';
|
|
1140
258
|
```
|
|
1141
259
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
Si tienes código existente, puedes migrar gradualmente:
|
|
260
|
+
## 🌐 Internacionalización
|
|
1145
261
|
|
|
1146
262
|
```tsx
|
|
1147
|
-
//
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
function OldApp() {
|
|
1151
|
-
const { userProfile } = useUserProfile();
|
|
1152
|
-
return userProfile ? <Dashboard /> : <CrudifyLogin />;
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
// Después (sistema unificado)
|
|
1156
|
-
import { CrudifyDataProvider, useCrudifyAuth, CrudifyLogin } from '@nocios/crudify-ui';
|
|
1157
|
-
|
|
1158
|
-
function NewApp() {
|
|
1159
|
-
return (
|
|
1160
|
-
<CrudifyDataProvider publicApiKey="..." env="prod">
|
|
1161
|
-
<AppContent />
|
|
1162
|
-
</CrudifyDataProvider>
|
|
1163
|
-
);
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
function AppContent() {
|
|
1167
|
-
const { isAuthenticated, user, setToken } = useCrudifyAuth();
|
|
1168
|
-
|
|
1169
|
-
return isAuthenticated ? (
|
|
1170
|
-
<Dashboard user={user} />
|
|
1171
|
-
) : (
|
|
1172
|
-
<CrudifyLogin onLoginSuccess={setToken} />
|
|
1173
|
-
);
|
|
1174
|
-
}
|
|
1175
|
-
```
|
|
1176
|
-
|
|
1177
|
-
### 🌍 **Variables de Entorno**
|
|
1178
|
-
|
|
1179
|
-
Configura estas variables en tu proyecto:
|
|
1180
|
-
|
|
1181
|
-
```bash
|
|
1182
|
-
# .env.development
|
|
1183
|
-
REACT_APP_CRUDIFY_API_KEY=tu-clave-de-desarrollo
|
|
1184
|
-
REACT_APP_CRUDIFY_ENV=dev
|
|
263
|
+
// Idiomas soportados: 'en' | 'es'
|
|
264
|
+
<CrudifyLogin language="es" />
|
|
1185
265
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
// Uso en la aplicación
|
|
1193
|
-
<CrudifyDataProvider
|
|
1194
|
-
publicApiKey={process.env.REACT_APP_CRUDIFY_API_KEY}
|
|
1195
|
-
env={process.env.REACT_APP_CRUDIFY_ENV || 'prod'}
|
|
1196
|
-
appName="Mi Aplicación"
|
|
1197
|
-
>
|
|
1198
|
-
<App />
|
|
1199
|
-
</CrudifyDataProvider>
|
|
266
|
+
// Traducciones personalizadas
|
|
267
|
+
<CrudifyLogin
|
|
268
|
+
translations={{
|
|
269
|
+
login: { title: "Iniciar Sesión" }
|
|
270
|
+
}}
|
|
271
|
+
/>
|
|
1200
272
|
```
|
|
1201
273
|
|
|
1202
|
-
|
|
274
|
+
## 🔒 Seguridad
|
|
1203
275
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
<CrudifyDataProvider
|
|
1209
|
-
publicApiKey="..."
|
|
1210
|
-
onInitialized={() => isDevelopment && console.log('✅ Crudify inicializado')}
|
|
1211
|
-
onError={(error) => isDevelopment && console.error('❌ Error:', error)}
|
|
1212
|
-
onTokenChange={(token) => isDevelopment && console.log('🔑 Token:', !!token)}
|
|
1213
|
-
>
|
|
1214
|
-
<App />
|
|
1215
|
-
</CrudifyDataProvider>
|
|
1216
|
-
```
|
|
276
|
+
- ✅ Encriptación de tokens
|
|
277
|
+
- ✅ Refresh Token Pattern
|
|
278
|
+
- ✅ Validación automática
|
|
279
|
+
- ✅ Limpieza automática de tokens expirados
|
|
1217
280
|
|
|
1218
|
-
|
|
281
|
+
## 📚 Documentación Completa
|
|
1219
282
|
|
|
1220
|
-
|
|
283
|
+
Para documentación detallada, ejemplos avanzados y guías completas, consulta [README_DEPTH.md](./README_DEPTH.md).
|
|
1221
284
|
|
|
1222
|
-
|
|
1223
|
-
- ✅ **Nuevo**: Sistema unificado `CrudifyDataProvider`
|
|
1224
|
-
- ✅ **Nuevo**: Hooks especializados (`useCrudifyAuth`, `useCrudifyData`, `useCrudifyUser`)
|
|
1225
|
-
- ✅ **Nuevo**: Inicialización robusta con singleton thread-safe
|
|
1226
|
-
- ✅ **Nuevo**: Gestión automática de tokens con sincronización cross-tab
|
|
1227
|
-
- ✅ **Nuevo**: Acceso directo a instancia con `getCrudifyInstanceAsync`
|
|
1228
|
-
- 🔄 **Mejorado**: Sistema de manejo de errores
|
|
1229
|
-
- 🔄 **Mejorado**: TypeScript con tipos más precisos
|
|
1230
|
-
- ⚠️ **Legacy**: Hooks anteriores mantienen compatibilidad
|
|
285
|
+
## 📄 Licencia
|
|
1231
286
|
|
|
1232
|
-
|
|
1233
|
-
1. Envolver la aplicación con `CrudifyDataProvider`
|
|
1234
|
-
2. Reemplazar hooks individuales con el sistema unificado
|
|
1235
|
-
3. Actualizar manejo de tokens para usar `setToken` del hook
|
|
1236
|
-
4. (Opcional) Migrar configuración a props del provider
|
|
287
|
+
MIT © [Nocios](https://github.com/nocios)
|
|
1237
288
|
|
|
1238
289
|
---
|
|
1239
290
|
|
|
1240
|
-
|
|
1241
|
-
**Mantenido por:** Equipo Crudify
|
|
1242
|
-
**Repositorio:** [GitHub](https://github.com/nocios/crudify-ui)
|
|
1243
|
-
**Documentación:** [Docs](https://docs.crudify.com)
|
|
1244
|
-
**Soporte:** [Issues](https://github.com/nocios/crudify-ui/issues)
|
|
291
|
+
**¿Necesitas ayuda?** Consulta [README_DEPTH.md](./README_DEPTH.md) para documentación completa.
|