@nocios/crudify-components 2.0.42 → 2.0.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +70 -0
- package/.husky/pre-commit +26 -0
- package/.husky/pre-push +30 -0
- package/.prettierignore +18 -0
- package/.prettierrc +9 -0
- package/.scannerwork/.sonar_lock +0 -0
- package/.scannerwork/report-task.txt +6 -0
- package/README.md +44 -36
- package/README_DEPTH.md +148 -141
- package/coverage/coverage-final.json +83 -83
- package/coverage/index.html +175 -175
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +686 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +22388 -0
- package/dist/{CrudiaMarkdownField-CXAuu-v2.d.ts → CrudiaMarkdownField-CkiBwG-U.d.ts} +8 -8
- package/dist/{CrudiaMarkdownField-TNyMLb-h.d.mts → CrudiaMarkdownField-D-DqiXMQ.d.mts} +8 -8
- package/dist/{GlobalNotificationProvider-Zq18OkpI.d.ts → GlobalNotificationProvider-CdwdNv_8.d.mts} +4 -4
- package/dist/{GlobalNotificationProvider-Zq18OkpI.d.mts → GlobalNotificationProvider-CdwdNv_8.d.ts} +4 -4
- package/dist/chunk-2WAUZ6KI.js +1 -0
- package/dist/chunk-3IGZNZCT.mjs +1 -0
- package/dist/chunk-43L2PP77.mjs +1 -0
- package/dist/chunk-6VS5OT3A.mjs +1 -0
- package/dist/chunk-BWJTTMKS.js +1 -0
- package/dist/chunk-EMPPCCVU.js +1 -0
- package/dist/chunk-J43UPGBE.js +1 -0
- package/dist/chunk-K6ZRXOJ7.mjs +1 -0
- package/dist/chunk-RYQQZTEP.js +1 -0
- package/dist/chunk-VTMSOK4V.mjs +1 -0
- package/dist/components.d.mts +3 -3
- package/dist/components.d.ts +3 -3
- package/dist/components.js +1 -1
- package/dist/components.mjs +1 -1
- package/dist/{errorTranslation-D-Y7uNN_.d.mts → errorTranslation-BcX8AaK7.d.mts} +5 -5
- package/dist/{errorTranslation-DDlAXpMl.d.ts → errorTranslation-CF-5JClP.d.ts} +5 -5
- package/dist/hooks.d.mts +3 -3
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +1 -1
- package/dist/hooks.mjs +1 -1
- package/dist/{index-U--xRr8A.d.mts → index-D06kTP0C.d.mts} +18 -18
- package/dist/{index-dXVRVcEB.d.ts → index-DEDnmsdO.d.ts} +18 -18
- package/dist/index.d.mts +362 -362
- package/dist/index.d.ts +362 -362
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{tenantConfig-DqJqQkoR.d.mts → tenantConfig-CYnS9TPV.d.mts} +14 -8
- package/dist/{tenantConfig-DqJqQkoR.d.ts → tenantConfig-CYnS9TPV.d.ts} +14 -8
- package/dist/utils.d.mts +3 -3
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +1 -1
- package/dist/utils.mjs +1 -1
- package/eslint.config.mjs +140 -0
- package/package.json +32 -3
- package/scripts/bump-version.cjs +23 -0
- package/sonar-project.properties +23 -0
- package/tests/helpers/testUtils.tsx +89 -0
- package/tests/hooks/useSession/testUtils.tsx +212 -0
- package/tests/setup.ts +139 -0
- package/tests/vitest.d.ts +1 -0
- package/vitest.config.ts +20 -9
- package/.github/workflows/test.yml +0 -59
- package/dist/chunk-2IOB6HHT.js +0 -1
- package/dist/chunk-2WHCDP7V.mjs +0 -1
- package/dist/chunk-37WOWEJG.js +0 -1
- package/dist/chunk-44VU4TSP.mjs +0 -1
- package/dist/chunk-4LMFQECS.js +0 -1
- package/dist/chunk-BXFEQ6KP.js +0 -1
- package/dist/chunk-MFQKGZI4.js +0 -1
- package/dist/chunk-NJERBWND.mjs +0 -1
- package/dist/chunk-WMLIOPUC.mjs +0 -1
- package/dist/chunk-XMEZUDF6.mjs +0 -1
package/README_DEPTH.md
CHANGED
|
@@ -43,8 +43,8 @@ npm install @mui/material @mui/icons-material react react-dom
|
|
|
43
43
|
El `SessionProvider` es el punto de entrada principal para manejar la autenticación y sesiones en tu aplicación:
|
|
44
44
|
|
|
45
45
|
```tsx
|
|
46
|
-
import React from
|
|
47
|
-
import { SessionProvider } from
|
|
46
|
+
import React from 'react';
|
|
47
|
+
import { SessionProvider } from '@nocios/crudify-components';
|
|
48
48
|
|
|
49
49
|
function App() {
|
|
50
50
|
return (
|
|
@@ -52,12 +52,12 @@ function App() {
|
|
|
52
52
|
options={{
|
|
53
53
|
// Configuración automática basada en variables de entorno
|
|
54
54
|
// o configuración manual si es necesario
|
|
55
|
-
storageType:
|
|
55
|
+
storageType: 'localStorage', // 'localStorage' | 'sessionStorage'
|
|
56
56
|
onSessionExpired: () => {
|
|
57
|
-
console.log(
|
|
57
|
+
console.log('Sesión expirada');
|
|
58
58
|
},
|
|
59
59
|
onSessionRestored: (tokens) => {
|
|
60
|
-
console.log(
|
|
60
|
+
console.log('Sesión restaurada:', tokens);
|
|
61
61
|
},
|
|
62
62
|
}}
|
|
63
63
|
>
|
|
@@ -84,17 +84,17 @@ REACT_APP_CRUDIFY_ENV=dev # dev | stg | api | prod
|
|
|
84
84
|
Hook principal para manejo completo de autenticación:
|
|
85
85
|
|
|
86
86
|
```tsx
|
|
87
|
-
import { useAuth } from
|
|
87
|
+
import { useAuth } from '@nocios/crudify-components';
|
|
88
88
|
|
|
89
89
|
function LoginPage() {
|
|
90
90
|
const { login, logout, isAuthenticated, isLoading, error, clearError } = useAuth();
|
|
91
91
|
|
|
92
92
|
const handleLogin = async () => {
|
|
93
93
|
try {
|
|
94
|
-
const result = await login(
|
|
95
|
-
console.log(
|
|
94
|
+
const result = await login('user@example.com', 'password');
|
|
95
|
+
console.log('Login exitoso:', result);
|
|
96
96
|
} catch (error) {
|
|
97
|
-
console.error(
|
|
97
|
+
console.error('Error de login:', error);
|
|
98
98
|
}
|
|
99
99
|
};
|
|
100
100
|
|
|
@@ -108,7 +108,7 @@ function LoginPage() {
|
|
|
108
108
|
<button onClick={handleLogout}>Cerrar Sesión</button>
|
|
109
109
|
) : (
|
|
110
110
|
<button onClick={handleLogin} disabled={isLoading}>
|
|
111
|
-
{isLoading ?
|
|
111
|
+
{isLoading ? 'Iniciando...' : 'Iniciar Sesión'}
|
|
112
112
|
</button>
|
|
113
113
|
)}
|
|
114
114
|
{error && <div>Error: {error}</div>}
|
|
@@ -130,7 +130,7 @@ function LoginPage() {
|
|
|
130
130
|
Hook para obtener y manejar los datos completos del usuario:
|
|
131
131
|
|
|
132
132
|
```tsx
|
|
133
|
-
import { useUserData } from
|
|
133
|
+
import { useUserData } from '@nocios/crudify-components';
|
|
134
134
|
|
|
135
135
|
function UserProfile() {
|
|
136
136
|
const { userData, sessionData, isLoading, error, refetch } = useUserData({
|
|
@@ -176,54 +176,54 @@ function UserProfile() {
|
|
|
176
176
|
Hook para realizar operaciones CRUD type-safe:
|
|
177
177
|
|
|
178
178
|
```tsx
|
|
179
|
-
import { useData } from
|
|
179
|
+
import { useData } from '@nocios/crudify-components';
|
|
180
180
|
|
|
181
181
|
function ProductManager() {
|
|
182
182
|
const { create, read, update, remove, isInitialized } = useData();
|
|
183
183
|
|
|
184
184
|
const createProduct = async () => {
|
|
185
185
|
try {
|
|
186
|
-
const result = await create(
|
|
187
|
-
name:
|
|
186
|
+
const result = await create('products', {
|
|
187
|
+
name: 'Nuevo Producto',
|
|
188
188
|
price: 99.99,
|
|
189
|
-
category:
|
|
189
|
+
category: 'electronics',
|
|
190
190
|
});
|
|
191
|
-
console.log(
|
|
191
|
+
console.log('Producto creado:', result);
|
|
192
192
|
} catch (error) {
|
|
193
|
-
console.error(
|
|
193
|
+
console.error('Error creando producto:', error);
|
|
194
194
|
}
|
|
195
195
|
};
|
|
196
196
|
|
|
197
197
|
const getProducts = async () => {
|
|
198
198
|
try {
|
|
199
|
-
const products = await read(
|
|
200
|
-
filters: { category:
|
|
199
|
+
const products = await read('products', {
|
|
200
|
+
filters: { category: 'electronics' },
|
|
201
201
|
limit: 10,
|
|
202
202
|
offset: 0,
|
|
203
203
|
});
|
|
204
|
-
console.log(
|
|
204
|
+
console.log('Productos:', products);
|
|
205
205
|
} catch (error) {
|
|
206
|
-
console.error(
|
|
206
|
+
console.error('Error obteniendo productos:', error);
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
209
|
|
|
210
210
|
const updateProduct = async (productId: string) => {
|
|
211
211
|
try {
|
|
212
|
-
const result = await update(
|
|
212
|
+
const result = await update('products', productId, {
|
|
213
213
|
price: 89.99,
|
|
214
214
|
});
|
|
215
|
-
console.log(
|
|
215
|
+
console.log('Producto actualizado:', result);
|
|
216
216
|
} catch (error) {
|
|
217
|
-
console.error(
|
|
217
|
+
console.error('Error actualizando producto:', error);
|
|
218
218
|
}
|
|
219
219
|
};
|
|
220
220
|
|
|
221
221
|
const deleteProduct = async (productId: string) => {
|
|
222
222
|
try {
|
|
223
|
-
await remove(
|
|
224
|
-
console.log(
|
|
223
|
+
await remove('products', productId);
|
|
224
|
+
console.log('Producto eliminado');
|
|
225
225
|
} catch (error) {
|
|
226
|
-
console.error(
|
|
226
|
+
console.error('Error eliminando producto:', error);
|
|
227
227
|
}
|
|
228
228
|
};
|
|
229
229
|
|
|
@@ -233,8 +233,8 @@ function ProductManager() {
|
|
|
233
233
|
<div>
|
|
234
234
|
<button onClick={createProduct}>Crear Producto</button>
|
|
235
235
|
<button onClick={getProducts}>Obtener Productos</button>
|
|
236
|
-
<button onClick={() => updateProduct(
|
|
237
|
-
<button onClick={() => deleteProduct(
|
|
236
|
+
<button onClick={() => updateProduct('123')}>Actualizar</button>
|
|
237
|
+
<button onClick={() => deleteProduct('123')}>Eliminar</button>
|
|
238
238
|
</div>
|
|
239
239
|
);
|
|
240
240
|
}
|
|
@@ -252,22 +252,23 @@ function ProductManager() {
|
|
|
252
252
|
Hook de bajo nivel para control directo de sesiones:
|
|
253
253
|
|
|
254
254
|
```tsx
|
|
255
|
-
import { useSession } from
|
|
255
|
+
import { useSession } from '@nocios/crudify-components';
|
|
256
256
|
|
|
257
257
|
function SessionManager() {
|
|
258
|
-
const { isAuthenticated, isLoading, tokens, error, login, logout, refreshTokens, isExpiringSoon, expiresIn } =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
258
|
+
const { isAuthenticated, isLoading, tokens, error, login, logout, refreshTokens, isExpiringSoon, expiresIn } =
|
|
259
|
+
useSession({
|
|
260
|
+
onSessionExpired: () => console.log('Sesión expirada'),
|
|
261
|
+
onSessionRestored: (tokens) => console.log('Sesión restaurada', tokens),
|
|
262
|
+
onTokensRefreshed: (tokens) => console.log('Tokens renovados', tokens),
|
|
263
|
+
});
|
|
263
264
|
|
|
264
265
|
return (
|
|
265
266
|
<div>
|
|
266
267
|
<h3>Estado de Sesión</h3>
|
|
267
|
-
<p>Autenticado: {isAuthenticated ?
|
|
268
|
-
<p>Cargando: {isLoading ?
|
|
268
|
+
<p>Autenticado: {isAuthenticated ? 'Sí' : 'No'}</p>
|
|
269
|
+
<p>Cargando: {isLoading ? 'Sí' : 'No'}</p>
|
|
269
270
|
<p>Expira en: {Math.floor(expiresIn / 60)} minutos</p>
|
|
270
|
-
<p>Expira pronto: {isExpiringSoon ?
|
|
271
|
+
<p>Expira pronto: {isExpiringSoon ? 'Sí' : 'No'}</p>
|
|
271
272
|
|
|
272
273
|
{isExpiringSoon && <button onClick={() => refreshTokens()}>Renovar Tokens</button>}
|
|
273
274
|
|
|
@@ -287,7 +288,7 @@ function SessionManager() {
|
|
|
287
288
|
Hook para acceder al contexto global de sesión:
|
|
288
289
|
|
|
289
290
|
```tsx
|
|
290
|
-
import { useSessionContext } from
|
|
291
|
+
import { useSessionContext } from '@nocios/crudify-components';
|
|
291
292
|
|
|
292
293
|
function NavigationBar() {
|
|
293
294
|
const { isAuthenticated, sessionData, logout, getTokenInfo } = useSessionContext();
|
|
@@ -311,26 +312,26 @@ function NavigationBar() {
|
|
|
311
312
|
Componente completo de autenticación con múltiples pantallas:
|
|
312
313
|
|
|
313
314
|
```tsx
|
|
314
|
-
import { CrudifyLogin } from
|
|
315
|
-
import type { CrudifyLoginConfig } from
|
|
315
|
+
import { CrudifyLogin } from '@nocios/crudify-components';
|
|
316
|
+
import type { CrudifyLoginConfig } from '@nocios/crudify-components';
|
|
316
317
|
|
|
317
318
|
function AuthPage() {
|
|
318
319
|
const config: CrudifyLoginConfig = {
|
|
319
|
-
appName:
|
|
320
|
-
logo:
|
|
320
|
+
appName: 'Mi Aplicación',
|
|
321
|
+
logo: '/logo.png',
|
|
321
322
|
colors: {
|
|
322
|
-
primaryColor:
|
|
323
|
-
bgColor:
|
|
323
|
+
primaryColor: '#1066BA',
|
|
324
|
+
bgColor: '#f5f5f5',
|
|
324
325
|
},
|
|
325
326
|
};
|
|
326
327
|
|
|
327
328
|
const handleLoginSuccess = (userData, redirectUrl) => {
|
|
328
|
-
console.log(
|
|
329
|
+
console.log('Login exitoso:', userData);
|
|
329
330
|
// Redireccionar o actualizar estado
|
|
330
331
|
};
|
|
331
332
|
|
|
332
333
|
const handleError = (error: string) => {
|
|
333
|
-
console.error(
|
|
334
|
+
console.error('Error de autenticación:', error);
|
|
334
335
|
};
|
|
335
336
|
|
|
336
337
|
return (
|
|
@@ -367,7 +368,7 @@ function AuthPage() {
|
|
|
367
368
|
Componente para mostrar información del perfil del usuario:
|
|
368
369
|
|
|
369
370
|
```tsx
|
|
370
|
-
import { UserProfileDisplay } from
|
|
371
|
+
import { UserProfileDisplay } from '@nocios/crudify-components';
|
|
371
372
|
|
|
372
373
|
function ProfilePage() {
|
|
373
374
|
return (
|
|
@@ -384,7 +385,7 @@ function ProfilePage() {
|
|
|
384
385
|
Componente para proteger rutas que requieren autenticación:
|
|
385
386
|
|
|
386
387
|
```tsx
|
|
387
|
-
import { ProtectedRoute } from
|
|
388
|
+
import { ProtectedRoute } from '@nocios/crudify-components';
|
|
388
389
|
|
|
389
390
|
function App() {
|
|
390
391
|
return (
|
|
@@ -410,7 +411,7 @@ function App() {
|
|
|
410
411
|
Componente útil para desarrollo y debug:
|
|
411
412
|
|
|
412
413
|
```tsx
|
|
413
|
-
import { SessionDebugInfo } from
|
|
414
|
+
import { SessionDebugInfo } from '@nocios/crudify-components';
|
|
414
415
|
|
|
415
416
|
function DevPage() {
|
|
416
417
|
return (
|
|
@@ -427,32 +428,32 @@ function DevPage() {
|
|
|
427
428
|
Componente completo para configurar políticas de acceso a datos públicos:
|
|
428
429
|
|
|
429
430
|
```tsx
|
|
430
|
-
import { Policies, POLICY_ACTIONS, PREFERRED_POLICY_ORDER, PolicyAction } from
|
|
431
|
-
import { useState } from
|
|
431
|
+
import { Policies, POLICY_ACTIONS, PREFERRED_POLICY_ORDER, PolicyAction } from '@nocios/crudify-components';
|
|
432
|
+
import { useState } from 'react';
|
|
432
433
|
|
|
433
434
|
function ModuleConfiguration() {
|
|
434
435
|
const [policies, setPolicies] = useState([
|
|
435
436
|
{
|
|
436
|
-
id:
|
|
437
|
-
action:
|
|
437
|
+
id: '1',
|
|
438
|
+
action: 'read',
|
|
438
439
|
fields: {
|
|
439
|
-
allow: [
|
|
440
|
-
owner_allow: [
|
|
441
|
-
deny: [
|
|
440
|
+
allow: ['name', 'email'],
|
|
441
|
+
owner_allow: ['phone'],
|
|
442
|
+
deny: ['password', 'internal_notes'],
|
|
442
443
|
},
|
|
443
444
|
},
|
|
444
445
|
{
|
|
445
|
-
id:
|
|
446
|
-
action:
|
|
446
|
+
id: '2',
|
|
447
|
+
action: 'create',
|
|
447
448
|
fields: {
|
|
448
|
-
allow: [
|
|
449
|
+
allow: ['name', 'email'],
|
|
449
450
|
owner_allow: [],
|
|
450
|
-
deny: [
|
|
451
|
+
deny: ['status', 'verified'],
|
|
451
452
|
},
|
|
452
453
|
},
|
|
453
454
|
]);
|
|
454
455
|
|
|
455
|
-
const availableFields = [
|
|
456
|
+
const availableFields = ['name', 'email', 'phone', 'address', 'status', 'verified', 'password', 'internal_notes'];
|
|
456
457
|
|
|
457
458
|
const handlePolicyChange = (newPolicies) => {
|
|
458
459
|
setPolicies(newPolicies);
|
|
@@ -472,7 +473,7 @@ function ModuleConfiguration() {
|
|
|
472
473
|
/>
|
|
473
474
|
|
|
474
475
|
{/* Mostrar acciones disponibles */}
|
|
475
|
-
<div style={{ marginTop:
|
|
476
|
+
<div style={{ marginTop: '20px' }}>
|
|
476
477
|
<h3>Acciones Disponibles:</h3>
|
|
477
478
|
<ul>
|
|
478
479
|
{POLICY_ACTIONS.map((action) => (
|
|
@@ -518,11 +519,11 @@ type Policy = {
|
|
|
518
519
|
|
|
519
520
|
```tsx
|
|
520
521
|
// Acciones de política disponibles
|
|
521
|
-
export const POLICY_ACTIONS = [
|
|
522
|
+
export const POLICY_ACTIONS = ['create', 'read', 'update', 'delete'] as const;
|
|
522
523
|
export type PolicyAction = (typeof POLICY_ACTIONS)[number];
|
|
523
524
|
|
|
524
525
|
// Orden preferido para mostrar las políticas
|
|
525
|
-
export const PREFERRED_POLICY_ORDER: PolicyAction[] = [
|
|
526
|
+
export const PREFERRED_POLICY_ORDER: PolicyAction[] = ['create', 'read', 'update', 'delete'];
|
|
526
527
|
```
|
|
527
528
|
|
|
528
529
|
#### Casos de Uso Comunes
|
|
@@ -532,12 +533,12 @@ export const PREFERRED_POLICY_ORDER: PolicyAction[] = ["create", "read", "update
|
|
|
532
533
|
```tsx
|
|
533
534
|
// Permitir lectura pública de ciertos campos
|
|
534
535
|
const publicReadPolicy = {
|
|
535
|
-
id:
|
|
536
|
-
action:
|
|
536
|
+
id: 'public-read',
|
|
537
|
+
action: 'read',
|
|
537
538
|
fields: {
|
|
538
|
-
allow: [
|
|
539
|
-
owner_allow: [
|
|
540
|
-
deny: [
|
|
539
|
+
allow: ['name', 'description', 'published_at'],
|
|
540
|
+
owner_allow: ['views', 'stats'],
|
|
541
|
+
deny: ['internal_notes', 'admin_flags'],
|
|
541
542
|
},
|
|
542
543
|
};
|
|
543
544
|
```
|
|
@@ -547,12 +548,12 @@ const publicReadPolicy = {
|
|
|
547
548
|
```tsx
|
|
548
549
|
// Permitir creación pero restringir ciertos campos
|
|
549
550
|
const createPolicy = {
|
|
550
|
-
id:
|
|
551
|
-
action:
|
|
551
|
+
id: 'public-create',
|
|
552
|
+
action: 'create',
|
|
552
553
|
fields: {
|
|
553
|
-
allow: [
|
|
554
|
+
allow: ['title', 'content', 'category'],
|
|
554
555
|
owner_allow: [],
|
|
555
|
-
deny: [
|
|
556
|
+
deny: ['status', 'featured', 'admin_verified'],
|
|
556
557
|
},
|
|
557
558
|
};
|
|
558
559
|
```
|
|
@@ -562,9 +563,9 @@ const createPolicy = {
|
|
|
562
563
|
```tsx
|
|
563
564
|
// Solo el propietario puede eliminar
|
|
564
565
|
const deletePolicy = {
|
|
565
|
-
id:
|
|
566
|
-
action:
|
|
567
|
-
permission:
|
|
566
|
+
id: 'owner-delete',
|
|
567
|
+
action: 'delete',
|
|
568
|
+
permission: 'owner', // '*' = todos, 'deny' = nadie, 'owner' = solo propietario
|
|
568
569
|
};
|
|
569
570
|
```
|
|
570
571
|
|
|
@@ -582,12 +583,12 @@ function PolicyManager() {
|
|
|
582
583
|
// Error de campo específico
|
|
583
584
|
if (error.fieldErrors) {
|
|
584
585
|
setErrors({
|
|
585
|
-
_error:
|
|
586
|
+
_error: 'Hay errores en la configuración',
|
|
586
587
|
...error.fieldErrors,
|
|
587
588
|
});
|
|
588
589
|
} else {
|
|
589
590
|
// Error general
|
|
590
|
-
setErrors(
|
|
591
|
+
setErrors('Error al guardar las políticas');
|
|
591
592
|
}
|
|
592
593
|
}
|
|
593
594
|
};
|
|
@@ -646,19 +647,19 @@ El componente usa react-i18next para internacionalización. Las claves de traduc
|
|
|
646
647
|
Utilidades para trabajar con tokens JWT:
|
|
647
648
|
|
|
648
649
|
```tsx
|
|
649
|
-
import { decodeJwtSafely, getCurrentUserEmail, isTokenExpired } from
|
|
650
|
+
import { decodeJwtSafely, getCurrentUserEmail, isTokenExpired } from '@nocios/crudify-components';
|
|
650
651
|
|
|
651
652
|
// Decodificar JWT de forma segura
|
|
652
653
|
const payload = decodeJwtSafely(token);
|
|
653
|
-
console.log(
|
|
654
|
+
console.log('Usuario ID:', payload?.sub);
|
|
654
655
|
|
|
655
656
|
// Obtener email del usuario actual
|
|
656
657
|
const email = getCurrentUserEmail();
|
|
657
|
-
console.log(
|
|
658
|
+
console.log('Email:', email);
|
|
658
659
|
|
|
659
660
|
// Verificar si un token está expirado
|
|
660
661
|
const expired = isTokenExpired(token);
|
|
661
|
-
console.log(
|
|
662
|
+
console.log('Token expirado:', expired);
|
|
662
663
|
```
|
|
663
664
|
|
|
664
665
|
### Token Storage
|
|
@@ -666,19 +667,19 @@ console.log("Token expirado:", expired);
|
|
|
666
667
|
Sistema de almacenamiento seguro para tokens:
|
|
667
668
|
|
|
668
669
|
```tsx
|
|
669
|
-
import { TokenStorage } from
|
|
670
|
-
import type { TokenData, StorageType } from
|
|
670
|
+
import { TokenStorage } from '@nocios/crudify-components';
|
|
671
|
+
import type { TokenData, StorageType } from '@nocios/crudify-components';
|
|
671
672
|
|
|
672
673
|
// Crear instancia de storage
|
|
673
674
|
const storage = new TokenStorage({
|
|
674
|
-
type:
|
|
675
|
-
encryptionKey:
|
|
675
|
+
type: 'localStorage', // 'localStorage' | 'sessionStorage'
|
|
676
|
+
encryptionKey: 'mi-clave-secreta',
|
|
676
677
|
});
|
|
677
678
|
|
|
678
679
|
// Guardar tokens
|
|
679
680
|
const tokens: TokenData = {
|
|
680
|
-
accessToken:
|
|
681
|
-
refreshToken:
|
|
681
|
+
accessToken: 'access_token_here',
|
|
682
|
+
refreshToken: 'refresh_token_here',
|
|
682
683
|
expiresIn: 3600,
|
|
683
684
|
refreshExpiresIn: 86400,
|
|
684
685
|
};
|
|
@@ -687,14 +688,14 @@ await storage.setTokens(tokens);
|
|
|
687
688
|
|
|
688
689
|
// Obtener tokens
|
|
689
690
|
const storedTokens = await storage.getTokens();
|
|
690
|
-
console.log(
|
|
691
|
+
console.log('Tokens almacenados:', storedTokens);
|
|
691
692
|
|
|
692
693
|
// Limpiar tokens
|
|
693
694
|
await storage.clearTokens();
|
|
694
695
|
|
|
695
696
|
// Verificar validez
|
|
696
697
|
const isValid = await storage.isValid();
|
|
697
|
-
console.log(
|
|
698
|
+
console.log('Tokens válidos:', isValid);
|
|
698
699
|
```
|
|
699
700
|
|
|
700
701
|
### Error Handler
|
|
@@ -702,8 +703,14 @@ console.log("Tokens válidos:", isValid);
|
|
|
702
703
|
Sistema completo de manejo de errores:
|
|
703
704
|
|
|
704
705
|
```tsx
|
|
705
|
-
import {
|
|
706
|
-
|
|
706
|
+
import {
|
|
707
|
+
handleCrudifyError,
|
|
708
|
+
parseApiError,
|
|
709
|
+
parseTransactionError,
|
|
710
|
+
getErrorMessage,
|
|
711
|
+
ERROR_CODES,
|
|
712
|
+
} from '@nocios/crudify-components';
|
|
713
|
+
import type { ParsedError, ErrorCode } from '@nocios/crudify-components';
|
|
707
714
|
|
|
708
715
|
// Manejo universal de errores
|
|
709
716
|
try {
|
|
@@ -711,10 +718,10 @@ try {
|
|
|
711
718
|
} catch (error) {
|
|
712
719
|
const parsedError: ParsedError = handleCrudifyError(error);
|
|
713
720
|
|
|
714
|
-
console.log(
|
|
715
|
-
console.log(
|
|
716
|
-
console.log(
|
|
717
|
-
console.log(
|
|
721
|
+
console.log('Tipo:', parsedError.type);
|
|
722
|
+
console.log('Código:', parsedError.code);
|
|
723
|
+
console.log('Mensaje:', parsedError.message);
|
|
724
|
+
console.log('Severidad:', parsedError.severity);
|
|
718
725
|
|
|
719
726
|
// Mostrar mensaje apropiado al usuario
|
|
720
727
|
const userMessage = getErrorMessage(parsedError.code as ErrorCode);
|
|
@@ -728,7 +735,7 @@ const apiError = parseApiError(response);
|
|
|
728
735
|
const transactionError = parseTransactionError(response);
|
|
729
736
|
|
|
730
737
|
// Códigos de error disponibles
|
|
731
|
-
console.log(
|
|
738
|
+
console.log('Códigos disponibles:', ERROR_CODES);
|
|
732
739
|
```
|
|
733
740
|
|
|
734
741
|
## 🔄 API de Crudify Browser
|
|
@@ -736,12 +743,12 @@ console.log("Códigos disponibles:", ERROR_CODES);
|
|
|
736
743
|
Esta librería re-exporta completamente `@nocios/crudify-sdk`, proporcionando una API unificada:
|
|
737
744
|
|
|
738
745
|
```tsx
|
|
739
|
-
import { crudify } from
|
|
746
|
+
import { crudify } from '@nocios/crudify-components';
|
|
740
747
|
// Equivale a: import crudify from '@nocios/crudify-sdk';
|
|
741
748
|
|
|
742
749
|
// Todas las funcionalidades de crudify-browser están disponibles
|
|
743
|
-
const result = await crudify.create(
|
|
744
|
-
const users = await crudify.read(
|
|
750
|
+
const result = await crudify.create('users', userData);
|
|
751
|
+
const users = await crudify.read('users');
|
|
745
752
|
```
|
|
746
753
|
|
|
747
754
|
Esto significa que puedes:
|
|
@@ -778,7 +785,7 @@ import type {
|
|
|
778
785
|
TokenData,
|
|
779
786
|
ParsedError,
|
|
780
787
|
ErrorCode,
|
|
781
|
-
} from
|
|
788
|
+
} from '@nocios/crudify-components';
|
|
782
789
|
|
|
783
790
|
// Ejemplo con tipos
|
|
784
791
|
const handleLogin = async (credentials: LoginRequest): Promise<CrudifyApiResponse<LoginResponse>> => {
|
|
@@ -848,33 +855,33 @@ import { CrudifyLogin } from '@nocios/crudify-components';
|
|
|
848
855
|
### SessionProvider con opciones completas
|
|
849
856
|
|
|
850
857
|
```tsx
|
|
851
|
-
import { SessionProvider } from
|
|
852
|
-
import type { UseSessionOptions } from
|
|
858
|
+
import { SessionProvider } from '@nocios/crudify-components';
|
|
859
|
+
import type { UseSessionOptions } from '@nocios/crudify-components';
|
|
853
860
|
|
|
854
861
|
const sessionOptions: UseSessionOptions = {
|
|
855
862
|
// Tipo de almacenamiento
|
|
856
|
-
storageType:
|
|
863
|
+
storageType: 'localStorage', // 'localStorage' | 'sessionStorage'
|
|
857
864
|
|
|
858
865
|
// Callbacks del ciclo de vida
|
|
859
866
|
onSessionExpired: () => {
|
|
860
|
-
console.log(
|
|
861
|
-
window.location.href =
|
|
867
|
+
console.log('Sesión expirada - redirigir a login');
|
|
868
|
+
window.location.href = '/login';
|
|
862
869
|
},
|
|
863
870
|
|
|
864
871
|
onSessionRestored: (tokens) => {
|
|
865
|
-
console.log(
|
|
872
|
+
console.log('Sesión restaurada exitosamente', tokens);
|
|
866
873
|
},
|
|
867
874
|
|
|
868
875
|
onTokensRefreshed: (tokens) => {
|
|
869
|
-
console.log(
|
|
876
|
+
console.log('Tokens renovados', tokens);
|
|
870
877
|
},
|
|
871
878
|
|
|
872
879
|
onLogout: () => {
|
|
873
|
-
console.log(
|
|
880
|
+
console.log('Usuario cerró sesión');
|
|
874
881
|
},
|
|
875
882
|
|
|
876
883
|
onError: (error) => {
|
|
877
|
-
console.error(
|
|
884
|
+
console.error('Error de sesión:', error);
|
|
878
885
|
},
|
|
879
886
|
};
|
|
880
887
|
|
|
@@ -894,13 +901,13 @@ function App() {
|
|
|
894
901
|
Si necesitas configuración manual en lugar de variables de entorno:
|
|
895
902
|
|
|
896
903
|
```tsx
|
|
897
|
-
import { crudify } from
|
|
904
|
+
import { crudify } from '@nocios/crudify-components';
|
|
898
905
|
|
|
899
906
|
// Configurar manualmente antes de usar
|
|
900
907
|
crudify.configure({
|
|
901
|
-
publicApiKey:
|
|
902
|
-
environment:
|
|
903
|
-
baseUrl:
|
|
908
|
+
publicApiKey: 'tu-api-key',
|
|
909
|
+
environment: 'production', // 'dev' | 'stg' | 'api' | 'prod'
|
|
910
|
+
baseUrl: 'https://api.tudominio.com', // opcional
|
|
904
911
|
});
|
|
905
912
|
```
|
|
906
913
|
|
|
@@ -909,9 +916,9 @@ crudify.configure({
|
|
|
909
916
|
### Aplicación básica con autenticación
|
|
910
917
|
|
|
911
918
|
```tsx
|
|
912
|
-
import React from
|
|
913
|
-
import { BrowserRouter, Routes, Route, Navigate } from
|
|
914
|
-
import { SessionProvider, ProtectedRoute, CrudifyLogin, useSessionContext } from
|
|
919
|
+
import React from 'react';
|
|
920
|
+
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
|
921
|
+
import { SessionProvider, ProtectedRoute, CrudifyLogin, useSessionContext } from '@nocios/crudify-components';
|
|
915
922
|
|
|
916
923
|
// Componente Dashboard
|
|
917
924
|
function Dashboard() {
|
|
@@ -929,14 +936,14 @@ function Dashboard() {
|
|
|
929
936
|
// Componente Login
|
|
930
937
|
function LoginPage() {
|
|
931
938
|
return (
|
|
932
|
-
<div style={{ maxWidth:
|
|
939
|
+
<div style={{ maxWidth: '400px', margin: '0 auto', padding: '2rem' }}>
|
|
933
940
|
<CrudifyLogin
|
|
934
941
|
config={{
|
|
935
|
-
appName:
|
|
936
|
-
colors: { primaryColor:
|
|
942
|
+
appName: 'Mi App',
|
|
943
|
+
colors: { primaryColor: '#1976d2' },
|
|
937
944
|
}}
|
|
938
945
|
onLoginSuccess={(userData) => {
|
|
939
|
-
console.log(
|
|
946
|
+
console.log('Login exitoso:', userData);
|
|
940
947
|
// La navegación se maneja automáticamente
|
|
941
948
|
}}
|
|
942
949
|
/>
|
|
@@ -972,8 +979,8 @@ export default App;
|
|
|
972
979
|
### Aplicación con operaciones CRUD
|
|
973
980
|
|
|
974
981
|
```tsx
|
|
975
|
-
import React, { useState, useEffect } from
|
|
976
|
-
import { useData, useUserData } from
|
|
982
|
+
import React, { useState, useEffect } from 'react';
|
|
983
|
+
import { useData, useUserData } from '@nocios/crudify-components';
|
|
977
984
|
|
|
978
985
|
function ProductManager() {
|
|
979
986
|
const [products, setProducts] = useState([]);
|
|
@@ -990,13 +997,13 @@ function ProductManager() {
|
|
|
990
997
|
const loadProducts = async () => {
|
|
991
998
|
setLoading(true);
|
|
992
999
|
try {
|
|
993
|
-
const result = await read(
|
|
1000
|
+
const result = await read('products', {
|
|
994
1001
|
filters: { userId: userData?.id },
|
|
995
1002
|
limit: 20,
|
|
996
1003
|
});
|
|
997
1004
|
setProducts(result.data || []);
|
|
998
1005
|
} catch (error) {
|
|
999
|
-
console.error(
|
|
1006
|
+
console.error('Error cargando productos:', error);
|
|
1000
1007
|
} finally {
|
|
1001
1008
|
setLoading(false);
|
|
1002
1009
|
}
|
|
@@ -1004,31 +1011,31 @@ function ProductManager() {
|
|
|
1004
1011
|
|
|
1005
1012
|
const createProduct = async (productData) => {
|
|
1006
1013
|
try {
|
|
1007
|
-
await create(
|
|
1014
|
+
await create('products', {
|
|
1008
1015
|
...productData,
|
|
1009
1016
|
userId: userData?.id,
|
|
1010
1017
|
});
|
|
1011
1018
|
await loadProducts(); // Recargar lista
|
|
1012
1019
|
} catch (error) {
|
|
1013
|
-
console.error(
|
|
1020
|
+
console.error('Error creando producto:', error);
|
|
1014
1021
|
}
|
|
1015
1022
|
};
|
|
1016
1023
|
|
|
1017
1024
|
const updateProduct = async (productId, updates) => {
|
|
1018
1025
|
try {
|
|
1019
|
-
await update(
|
|
1026
|
+
await update('products', productId, updates);
|
|
1020
1027
|
await loadProducts(); // Recargar lista
|
|
1021
1028
|
} catch (error) {
|
|
1022
|
-
console.error(
|
|
1029
|
+
console.error('Error actualizando producto:', error);
|
|
1023
1030
|
}
|
|
1024
1031
|
};
|
|
1025
1032
|
|
|
1026
1033
|
const deleteProduct = async (productId) => {
|
|
1027
1034
|
try {
|
|
1028
|
-
await remove(
|
|
1035
|
+
await remove('products', productId);
|
|
1029
1036
|
await loadProducts(); // Recargar lista
|
|
1030
1037
|
} catch (error) {
|
|
1031
|
-
console.error(
|
|
1038
|
+
console.error('Error eliminando producto:', error);
|
|
1032
1039
|
}
|
|
1033
1040
|
};
|
|
1034
1041
|
|
|
@@ -1041,7 +1048,7 @@ function ProductManager() {
|
|
|
1041
1048
|
<button
|
|
1042
1049
|
onClick={() =>
|
|
1043
1050
|
createProduct({
|
|
1044
|
-
name:
|
|
1051
|
+
name: 'Nuevo Producto',
|
|
1045
1052
|
price: 99.99,
|
|
1046
1053
|
})
|
|
1047
1054
|
}
|
|
@@ -1091,7 +1098,7 @@ function App() {
|
|
|
1091
1098
|
return (
|
|
1092
1099
|
<SessionProvider
|
|
1093
1100
|
options={{
|
|
1094
|
-
storageType:
|
|
1101
|
+
storageType: 'localStorage', // Más persistente
|
|
1095
1102
|
onSessionExpired: handleSessionExpired,
|
|
1096
1103
|
}}
|
|
1097
1104
|
>
|
|
@@ -1109,7 +1116,7 @@ function App() {
|
|
|
1109
1116
|
const { error, clearError } = useAuth();
|
|
1110
1117
|
useEffect(() => {
|
|
1111
1118
|
if (error) {
|
|
1112
|
-
console.error(
|
|
1119
|
+
console.error('Error de auth:', error);
|
|
1113
1120
|
// Mostrar notificación al usuario
|
|
1114
1121
|
clearError();
|
|
1115
1122
|
}
|
|
@@ -1121,8 +1128,8 @@ useEffect(() => {
|
|
|
1121
1128
|
// Opcional: limpiar datos sensibles
|
|
1122
1129
|
};
|
|
1123
1130
|
|
|
1124
|
-
window.addEventListener(
|
|
1125
|
-
return () => window.removeEventListener(
|
|
1131
|
+
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
1132
|
+
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
|
1126
1133
|
}, []);
|
|
1127
1134
|
```
|
|
1128
1135
|
|
|
@@ -1156,7 +1163,7 @@ useEffect(() => {
|
|
|
1156
1163
|
```tsx
|
|
1157
1164
|
// Verificar configuración de environment
|
|
1158
1165
|
crudify.configure({
|
|
1159
|
-
environment:
|
|
1166
|
+
environment: 'dev', // Asegurar environment correcto
|
|
1160
1167
|
publicApiKey: process.env.REACT_APP_CRUDIFY_PUBLIC_API_KEY,
|
|
1161
1168
|
});
|
|
1162
1169
|
```
|
|
@@ -1165,11 +1172,11 @@ crudify.configure({
|
|
|
1165
1172
|
|
|
1166
1173
|
```tsx
|
|
1167
1174
|
// Usar SessionDebugInfo para desarrollo
|
|
1168
|
-
import { SessionDebugInfo } from
|
|
1175
|
+
import { SessionDebugInfo } from '@nocios/crudify-components';
|
|
1169
1176
|
|
|
1170
1177
|
function DevPanel() {
|
|
1171
|
-
return process.env.NODE_ENV ===
|
|
1172
|
-
<div style={{ position:
|
|
1178
|
+
return process.env.NODE_ENV === 'development' ? (
|
|
1179
|
+
<div style={{ position: 'fixed', bottom: 0, right: 0, background: 'white', padding: '1rem' }}>
|
|
1173
1180
|
<SessionDebugInfo />
|
|
1174
1181
|
</div>
|
|
1175
1182
|
) : null;
|