@nocios/crudify-ui 3.0.48 → 3.0.52

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/dist/index.d.mts CHANGED
@@ -227,6 +227,8 @@ type SessionConfig = {
227
227
  onSessionRestored?: (tokens: TokenData) => void;
228
228
  onLoginSuccess?: (tokens: TokenData) => void;
229
229
  onLogout?: () => void;
230
+ showNotification?: (message: string, severity?: "error" | "info" | "success" | "warning") => void;
231
+ translateFn?: (key: string) => string;
230
232
  };
231
233
  type LoginResult = {
232
234
  success: boolean;
@@ -298,6 +300,10 @@ declare class SessionManager {
298
300
  * Limpiar sesión completamente
299
301
  */
300
302
  clearSession(): void;
303
+ /**
304
+ * Obtener mensaje de sesión expirada traducido
305
+ */
306
+ private getSessionExpiredMessage;
301
307
  private log;
302
308
  private formatError;
303
309
  }
@@ -314,6 +320,8 @@ type UseSessionOptions = {
314
320
  enableLogging?: boolean;
315
321
  onSessionExpired?: () => void;
316
322
  onSessionRestored?: (tokens: TokenData) => void;
323
+ showNotification?: (message: string, severity?: "error" | "info" | "success" | "warning") => void;
324
+ translateFn?: (key: string) => string;
317
325
  };
318
326
  declare function useSession(options?: UseSessionOptions): {
319
327
  login: (email: string, password: string) => Promise<LoginResult>;
@@ -400,10 +408,9 @@ type SessionProviderProps = {
400
408
  notificationOptions?: NotificationOptions;
401
409
  };
402
410
  /**
403
- * Provider de sesión para envolver la aplicación
411
+ * Provider de sesión principal para envolver la aplicación
404
412
  */
405
- declare function SessionProvider({ children, options, config: propConfig, showNotifications, // ✅ Por defecto DESACTIVADO
406
- notificationOptions, }: SessionProviderProps): react_jsx_runtime.JSX.Element;
413
+ declare function SessionProvider(props: SessionProviderProps): react_jsx_runtime.JSX.Element;
407
414
  /**
408
415
  * Hook para usar el contexto de sesión
409
416
  */
package/dist/index.d.ts CHANGED
@@ -227,6 +227,8 @@ type SessionConfig = {
227
227
  onSessionRestored?: (tokens: TokenData) => void;
228
228
  onLoginSuccess?: (tokens: TokenData) => void;
229
229
  onLogout?: () => void;
230
+ showNotification?: (message: string, severity?: "error" | "info" | "success" | "warning") => void;
231
+ translateFn?: (key: string) => string;
230
232
  };
231
233
  type LoginResult = {
232
234
  success: boolean;
@@ -298,6 +300,10 @@ declare class SessionManager {
298
300
  * Limpiar sesión completamente
299
301
  */
300
302
  clearSession(): void;
303
+ /**
304
+ * Obtener mensaje de sesión expirada traducido
305
+ */
306
+ private getSessionExpiredMessage;
301
307
  private log;
302
308
  private formatError;
303
309
  }
@@ -314,6 +320,8 @@ type UseSessionOptions = {
314
320
  enableLogging?: boolean;
315
321
  onSessionExpired?: () => void;
316
322
  onSessionRestored?: (tokens: TokenData) => void;
323
+ showNotification?: (message: string, severity?: "error" | "info" | "success" | "warning") => void;
324
+ translateFn?: (key: string) => string;
317
325
  };
318
326
  declare function useSession(options?: UseSessionOptions): {
319
327
  login: (email: string, password: string) => Promise<LoginResult>;
@@ -400,10 +408,9 @@ type SessionProviderProps = {
400
408
  notificationOptions?: NotificationOptions;
401
409
  };
402
410
  /**
403
- * Provider de sesión para envolver la aplicación
411
+ * Provider de sesión principal para envolver la aplicación
404
412
  */
405
- declare function SessionProvider({ children, options, config: propConfig, showNotifications, // ✅ Por defecto DESACTIVADO
406
- notificationOptions, }: SessionProviderProps): react_jsx_runtime.JSX.Element;
413
+ declare function SessionProvider(props: SessionProviderProps): react_jsx_runtime.JSX.Element;
407
414
  /**
408
415
  * Hook para usar el contexto de sesión
409
416
  */
package/dist/index.js CHANGED
@@ -662,6 +662,162 @@ _TokenStorage.ENCRYPTION_KEY = "crudify_secure_key_v1";
662
662
  _TokenStorage.storageType = "localStorage";
663
663
  var TokenStorage = _TokenStorage;
664
664
 
665
+ // src/utils/errorTranslation.ts
666
+ var ERROR_TRANSLATION_HIERARCHY = [
667
+ "errors.{category}.{code}",
668
+ // errors.auth.INVALID_CREDENTIALS
669
+ "errors.{code}",
670
+ // errors.INVALID_CREDENTIALS
671
+ "login.{code}",
672
+ // login.INVALID_CREDENTIALS (legacy)
673
+ "error.{code}",
674
+ // error.INVALID_CREDENTIALS (singular)
675
+ "messages.{code}",
676
+ // messages.INVALID_CREDENTIALS
677
+ "{code}"
678
+ // INVALID_CREDENTIALS (direct)
679
+ ];
680
+ var ERROR_CATEGORY_MAP = {
681
+ // Authentication errors
682
+ "INVALID_CREDENTIALS": "auth",
683
+ "UNAUTHORIZED": "auth",
684
+ "INVALID_API_KEY": "auth",
685
+ "USER_NOT_FOUND": "auth",
686
+ "USER_NOT_ACTIVE": "auth",
687
+ "NO_PERMISSION": "auth",
688
+ "SESSION_EXPIRED": "auth",
689
+ // Data errors
690
+ "ITEM_NOT_FOUND": "data",
691
+ "NOT_FOUND": "data",
692
+ "IN_USE": "data",
693
+ "DUPLICATE_ENTRY": "data",
694
+ // Validation errors
695
+ "FIELD_ERROR": "validation",
696
+ "BAD_REQUEST": "validation",
697
+ "INVALID_EMAIL": "validation",
698
+ "INVALID_CODE": "validation",
699
+ "REQUIRED_FIELD": "validation",
700
+ // System errors
701
+ "INTERNAL_SERVER_ERROR": "system",
702
+ "DATABASE_CONNECTION_ERROR": "system",
703
+ "INVALID_CONFIGURATION": "system",
704
+ "UNKNOWN_OPERATION": "system",
705
+ "TIMEOUT_ERROR": "system",
706
+ "NETWORK_ERROR": "system",
707
+ // Rate limiting
708
+ "TOO_MANY_REQUESTS": "rate_limit"
709
+ };
710
+ var DEFAULT_ERROR_MESSAGES = {
711
+ "INVALID_CREDENTIALS": "Invalid username or password",
712
+ "UNAUTHORIZED": "You are not authorized to perform this action",
713
+ "SESSION_EXPIRED": "Your session has expired. Please log in again.",
714
+ "USER_NOT_FOUND": "User not found",
715
+ "ITEM_NOT_FOUND": "Item not found",
716
+ "FIELD_ERROR": "Invalid field value",
717
+ "INTERNAL_SERVER_ERROR": "An internal error occurred",
718
+ "NETWORK_ERROR": "Network connection error",
719
+ "TIMEOUT_ERROR": "Request timeout",
720
+ "UNKNOWN_OPERATION": "Unknown operation",
721
+ "INVALID_EMAIL": "Invalid email format",
722
+ "INVALID_CODE": "Invalid code",
723
+ "TOO_MANY_REQUESTS": "Too many requests, please try again later"
724
+ };
725
+ function translateErrorCode(errorCode, config) {
726
+ const { translateFn, currentLanguage, enableDebug } = config;
727
+ if (enableDebug) {
728
+ console.log(`\u{1F50D} [ErrorTranslation] Translating error code: ${errorCode} (lang: ${currentLanguage || "unknown"})`);
729
+ }
730
+ const normalizedCode = errorCode.toUpperCase();
731
+ const category = ERROR_CATEGORY_MAP[normalizedCode];
732
+ const translationKeys = ERROR_TRANSLATION_HIERARCHY.map((pattern) => {
733
+ return pattern.replace("{category}", category || "general").replace("{code}", normalizedCode);
734
+ });
735
+ if (enableDebug) {
736
+ console.log(`\u{1F511} [ErrorTranslation] Searching keys:`, translationKeys);
737
+ }
738
+ for (const key of translationKeys) {
739
+ const translated = translateFn(key);
740
+ if (enableDebug) {
741
+ console.log(`\u{1F50D} [ErrorTranslation] Checking key: "${key}" -> result: "${translated}" (same as key: ${translated === key})`);
742
+ }
743
+ if (translated && translated !== key) {
744
+ if (enableDebug) {
745
+ console.log(`\u2705 [ErrorTranslation] Found translation at key: ${key} = "${translated}"`);
746
+ }
747
+ return translated;
748
+ }
749
+ }
750
+ const defaultMessage = DEFAULT_ERROR_MESSAGES[normalizedCode];
751
+ if (defaultMessage) {
752
+ if (enableDebug) {
753
+ console.log(`\u{1F504} [ErrorTranslation] Using default message: "${defaultMessage}"`);
754
+ }
755
+ return defaultMessage;
756
+ }
757
+ const friendlyCode = normalizedCode.replace(/_/g, " ").toLowerCase().replace(/\b\w/g, (l) => l.toUpperCase());
758
+ if (enableDebug) {
759
+ console.log(`\u26A0\uFE0F [ErrorTranslation] No translation found, using friendly code: "${friendlyCode}"`);
760
+ }
761
+ return friendlyCode;
762
+ }
763
+ function translateErrorCodes(errorCodes, config) {
764
+ return errorCodes.map((code) => translateErrorCode(code, config));
765
+ }
766
+ function translateError(error, config) {
767
+ const { enableDebug } = config;
768
+ if (enableDebug) {
769
+ console.log(`\u{1F50D} [ErrorTranslation] Translating error:`, error);
770
+ }
771
+ const translatedCode = translateErrorCode(error.code, config);
772
+ if (translatedCode !== error.code.toUpperCase() && translatedCode !== error.code) {
773
+ if (enableDebug) {
774
+ console.log(`\u2705 [ErrorTranslation] Using hierarchical translation: "${translatedCode}"`);
775
+ }
776
+ if (error.field) {
777
+ return `${error.field}: ${translatedCode}`;
778
+ }
779
+ return translatedCode;
780
+ }
781
+ if (error.message && !error.message.includes("Error:") && error.message.length > 0 && error.message !== error.code) {
782
+ if (enableDebug) {
783
+ console.log(`\u{1F504} [ErrorTranslation] No hierarchical translation found, using API message: "${error.message}"`);
784
+ }
785
+ return error.message;
786
+ }
787
+ if (enableDebug) {
788
+ console.log(`\u26A0\uFE0F [ErrorTranslation] Using final fallback: "${translatedCode}"`);
789
+ }
790
+ if (error.field) {
791
+ return `${error.field}: ${translatedCode}`;
792
+ }
793
+ return translatedCode;
794
+ }
795
+ function createErrorTranslator(translateFn, options = {}) {
796
+ const config = {
797
+ translateFn,
798
+ currentLanguage: options.currentLanguage,
799
+ enableDebug: options.enableDebug || false
800
+ };
801
+ return {
802
+ translateErrorCode: (code) => translateErrorCode(code, config),
803
+ translateErrorCodes: (codes) => translateErrorCodes(codes, config),
804
+ translateError: (error) => translateError(error, config),
805
+ // Método de conveniencia para errores de API
806
+ translateApiError: (apiResponse) => {
807
+ if (apiResponse?.data?.response?.status) {
808
+ return translateErrorCode(apiResponse.data.response.status, config);
809
+ }
810
+ if (apiResponse?.status) {
811
+ return translateErrorCode(apiResponse.status, config);
812
+ }
813
+ if (apiResponse?.code) {
814
+ return translateErrorCode(apiResponse.code, config);
815
+ }
816
+ return "Unknown error";
817
+ }
818
+ };
819
+ }
820
+
665
821
  // src/core/SessionManager.ts
666
822
  var SessionManager = class _SessionManager {
667
823
  constructor() {
@@ -818,6 +974,7 @@ var SessionManager = class _SessionManager {
818
974
  if (!response.success) {
819
975
  this.log("Token refresh failed:", response.errors);
820
976
  TokenStorage.clearTokens();
977
+ this.config.showNotification?.(this.getSessionExpiredMessage(), "warning");
821
978
  this.config.onSessionExpired?.();
822
979
  return false;
823
980
  }
@@ -833,6 +990,7 @@ var SessionManager = class _SessionManager {
833
990
  } catch (error) {
834
991
  this.log("Token refresh error:", error);
835
992
  TokenStorage.clearTokens();
993
+ this.config.showNotification?.(this.getSessionExpiredMessage(), "warning");
836
994
  this.config.onSessionExpired?.();
837
995
  return false;
838
996
  }
@@ -852,6 +1010,7 @@ var SessionManager = class _SessionManager {
852
1010
  if (authError.isRefreshTokenInvalid || authError.isTokenRefreshFailed) {
853
1011
  this.log("Refresh token invalid or refresh already failed, clearing session");
854
1012
  TokenStorage.clearTokens();
1013
+ this.config.showNotification?.(this.getSessionExpiredMessage(), "warning");
855
1014
  this.config.onSessionExpired?.();
856
1015
  return response;
857
1016
  }
@@ -865,6 +1024,7 @@ var SessionManager = class _SessionManager {
865
1024
  } else {
866
1025
  this.log("Auth error with no valid tokens or irrecoverable error, triggering session expired");
867
1026
  TokenStorage.clearTokens();
1027
+ this.config.showNotification?.(this.getSessionExpiredMessage(), "warning");
868
1028
  this.config.onSessionExpired?.();
869
1029
  }
870
1030
  }
@@ -947,6 +1107,18 @@ var SessionManager = class _SessionManager {
947
1107
  import_crudify_browser2.default.logout();
948
1108
  this.log("Session cleared completely");
949
1109
  }
1110
+ /**
1111
+ * Obtener mensaje de sesión expirada traducido
1112
+ */
1113
+ getSessionExpiredMessage() {
1114
+ if (this.config.translateFn) {
1115
+ return translateErrorCode("SESSION_EXPIRED", {
1116
+ translateFn: this.config.translateFn,
1117
+ enableDebug: this.config.enableLogging
1118
+ });
1119
+ }
1120
+ return "Tu sesi\xF3n ha expirado. Por favor, inicia sesi\xF3n nuevamente.";
1121
+ }
950
1122
  // Métodos privados
951
1123
  log(message, ...args) {
952
1124
  if (this.config.enableLogging) {
@@ -980,6 +1152,8 @@ function useSession(options = {}) {
980
1152
  const config = {
981
1153
  autoRestore: options.autoRestore ?? true,
982
1154
  enableLogging: options.enableLogging ?? false,
1155
+ showNotification: options.showNotification,
1156
+ translateFn: options.translateFn,
983
1157
  onSessionExpired: () => {
984
1158
  setState((prev) => ({
985
1159
  ...prev,
@@ -1356,21 +1530,28 @@ var useGlobalNotification = () => {
1356
1530
  // src/providers/SessionProvider.tsx
1357
1531
  var import_jsx_runtime5 = require("react/jsx-runtime");
1358
1532
  var SessionContext = (0, import_react7.createContext)(void 0);
1359
- function SessionProvider({
1533
+ function InnerSessionProvider({
1360
1534
  children,
1361
1535
  options = {},
1362
1536
  config: propConfig,
1363
1537
  showNotifications = false,
1364
- // ✅ Por defecto DESACTIVADO
1365
1538
  notificationOptions = {}
1366
1539
  }) {
1540
+ let showNotificationFn;
1541
+ try {
1542
+ const { showNotification } = useGlobalNotification();
1543
+ showNotificationFn = showNotification;
1544
+ } catch {
1545
+ }
1367
1546
  const enhancedOptions = import_react7.default.useMemo(() => ({
1368
1547
  ...options,
1548
+ showNotification: showNotificationFn,
1549
+ // TODO: Agregar translateFn cuando esté disponible
1369
1550
  onSessionExpired: () => {
1370
1551
  console.log("\u{1F6A8} SessionProvider - Session expired callback triggered");
1371
1552
  options.onSessionExpired?.();
1372
1553
  }
1373
- }), [options]);
1554
+ }), [options, showNotificationFn]);
1374
1555
  const sessionHook = useSession(enhancedOptions);
1375
1556
  const resolvedConfig = (0, import_react7.useMemo)(() => {
1376
1557
  let publicApiKey;
@@ -1469,7 +1650,16 @@ function SessionProvider({
1469
1650
  defaultAutoHideDuration: notificationOptions.defaultAutoHideDuration || 6e3,
1470
1651
  position: notificationOptions.position || { vertical: "top", horizontal: "right" }
1471
1652
  };
1472
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(GlobalNotificationProvider, { ...notificationConfig, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SessionContext.Provider, { value: contextValue, children }) });
1653
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SessionContext.Provider, { value: contextValue, children });
1654
+ }
1655
+ function SessionProvider(props) {
1656
+ const notificationConfig = {
1657
+ enabled: props.showNotifications,
1658
+ maxNotifications: props.notificationOptions?.maxNotifications || 5,
1659
+ defaultAutoHideDuration: props.notificationOptions?.defaultAutoHideDuration || 6e3,
1660
+ position: props.notificationOptions?.position || { vertical: "top", horizontal: "right" }
1661
+ };
1662
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(GlobalNotificationProvider, { ...notificationConfig, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(InnerSessionProvider, { ...props }) });
1473
1663
  }
1474
1664
  function useSessionContext() {
1475
1665
  const context = (0, import_react7.useContext)(SessionContext);
@@ -1835,161 +2025,6 @@ function handleCrudifyError(error) {
1835
2025
  ];
1836
2026
  }
1837
2027
 
1838
- // src/utils/errorTranslation.ts
1839
- var ERROR_TRANSLATION_HIERARCHY = [
1840
- "errors.{category}.{code}",
1841
- // errors.auth.INVALID_CREDENTIALS
1842
- "errors.{code}",
1843
- // errors.INVALID_CREDENTIALS
1844
- "login.{code}",
1845
- // login.INVALID_CREDENTIALS (legacy)
1846
- "error.{code}",
1847
- // error.INVALID_CREDENTIALS (singular)
1848
- "messages.{code}",
1849
- // messages.INVALID_CREDENTIALS
1850
- "{code}"
1851
- // INVALID_CREDENTIALS (direct)
1852
- ];
1853
- var ERROR_CATEGORY_MAP = {
1854
- // Authentication errors
1855
- "INVALID_CREDENTIALS": "auth",
1856
- "UNAUTHORIZED": "auth",
1857
- "INVALID_API_KEY": "auth",
1858
- "USER_NOT_FOUND": "auth",
1859
- "USER_NOT_ACTIVE": "auth",
1860
- "NO_PERMISSION": "auth",
1861
- "SESSION_EXPIRED": "auth",
1862
- // Data errors
1863
- "ITEM_NOT_FOUND": "data",
1864
- "NOT_FOUND": "data",
1865
- "IN_USE": "data",
1866
- "DUPLICATE_ENTRY": "data",
1867
- // Validation errors
1868
- "FIELD_ERROR": "validation",
1869
- "BAD_REQUEST": "validation",
1870
- "INVALID_EMAIL": "validation",
1871
- "INVALID_CODE": "validation",
1872
- "REQUIRED_FIELD": "validation",
1873
- // System errors
1874
- "INTERNAL_SERVER_ERROR": "system",
1875
- "DATABASE_CONNECTION_ERROR": "system",
1876
- "INVALID_CONFIGURATION": "system",
1877
- "UNKNOWN_OPERATION": "system",
1878
- "TIMEOUT_ERROR": "system",
1879
- "NETWORK_ERROR": "system",
1880
- // Rate limiting
1881
- "TOO_MANY_REQUESTS": "rate_limit"
1882
- };
1883
- var DEFAULT_ERROR_MESSAGES = {
1884
- "INVALID_CREDENTIALS": "Invalid username or password",
1885
- "UNAUTHORIZED": "You are not authorized to perform this action",
1886
- "USER_NOT_FOUND": "User not found",
1887
- "ITEM_NOT_FOUND": "Item not found",
1888
- "FIELD_ERROR": "Invalid field value",
1889
- "INTERNAL_SERVER_ERROR": "An internal error occurred",
1890
- "NETWORK_ERROR": "Network connection error",
1891
- "TIMEOUT_ERROR": "Request timeout",
1892
- "UNKNOWN_OPERATION": "Unknown operation",
1893
- "INVALID_EMAIL": "Invalid email format",
1894
- "INVALID_CODE": "Invalid code",
1895
- "TOO_MANY_REQUESTS": "Too many requests, please try again later"
1896
- };
1897
- function translateErrorCode(errorCode, config) {
1898
- const { translateFn, currentLanguage, enableDebug } = config;
1899
- if (enableDebug) {
1900
- console.log(`\u{1F50D} [ErrorTranslation] Translating error code: ${errorCode} (lang: ${currentLanguage || "unknown"})`);
1901
- }
1902
- const normalizedCode = errorCode.toUpperCase();
1903
- const category = ERROR_CATEGORY_MAP[normalizedCode];
1904
- const translationKeys = ERROR_TRANSLATION_HIERARCHY.map((pattern) => {
1905
- return pattern.replace("{category}", category || "general").replace("{code}", normalizedCode);
1906
- });
1907
- if (enableDebug) {
1908
- console.log(`\u{1F511} [ErrorTranslation] Searching keys:`, translationKeys);
1909
- }
1910
- for (const key of translationKeys) {
1911
- const translated = translateFn(key);
1912
- if (enableDebug) {
1913
- console.log(`\u{1F50D} [ErrorTranslation] Checking key: "${key}" -> result: "${translated}" (same as key: ${translated === key})`);
1914
- }
1915
- if (translated && translated !== key) {
1916
- if (enableDebug) {
1917
- console.log(`\u2705 [ErrorTranslation] Found translation at key: ${key} = "${translated}"`);
1918
- }
1919
- return translated;
1920
- }
1921
- }
1922
- const defaultMessage = DEFAULT_ERROR_MESSAGES[normalizedCode];
1923
- if (defaultMessage) {
1924
- if (enableDebug) {
1925
- console.log(`\u{1F504} [ErrorTranslation] Using default message: "${defaultMessage}"`);
1926
- }
1927
- return defaultMessage;
1928
- }
1929
- const friendlyCode = normalizedCode.replace(/_/g, " ").toLowerCase().replace(/\b\w/g, (l) => l.toUpperCase());
1930
- if (enableDebug) {
1931
- console.log(`\u26A0\uFE0F [ErrorTranslation] No translation found, using friendly code: "${friendlyCode}"`);
1932
- }
1933
- return friendlyCode;
1934
- }
1935
- function translateErrorCodes(errorCodes, config) {
1936
- return errorCodes.map((code) => translateErrorCode(code, config));
1937
- }
1938
- function translateError(error, config) {
1939
- const { enableDebug } = config;
1940
- if (enableDebug) {
1941
- console.log(`\u{1F50D} [ErrorTranslation] Translating error:`, error);
1942
- }
1943
- const translatedCode = translateErrorCode(error.code, config);
1944
- if (translatedCode !== error.code.toUpperCase() && translatedCode !== error.code) {
1945
- if (enableDebug) {
1946
- console.log(`\u2705 [ErrorTranslation] Using hierarchical translation: "${translatedCode}"`);
1947
- }
1948
- if (error.field) {
1949
- return `${error.field}: ${translatedCode}`;
1950
- }
1951
- return translatedCode;
1952
- }
1953
- if (error.message && !error.message.includes("Error:") && error.message.length > 0 && error.message !== error.code) {
1954
- if (enableDebug) {
1955
- console.log(`\u{1F504} [ErrorTranslation] No hierarchical translation found, using API message: "${error.message}"`);
1956
- }
1957
- return error.message;
1958
- }
1959
- if (enableDebug) {
1960
- console.log(`\u26A0\uFE0F [ErrorTranslation] Using final fallback: "${translatedCode}"`);
1961
- }
1962
- if (error.field) {
1963
- return `${error.field}: ${translatedCode}`;
1964
- }
1965
- return translatedCode;
1966
- }
1967
- function createErrorTranslator(translateFn, options = {}) {
1968
- const config = {
1969
- translateFn,
1970
- currentLanguage: options.currentLanguage,
1971
- enableDebug: options.enableDebug || false
1972
- };
1973
- return {
1974
- translateErrorCode: (code) => translateErrorCode(code, config),
1975
- translateErrorCodes: (codes) => translateErrorCodes(codes, config),
1976
- translateError: (error) => translateError(error, config),
1977
- // Método de conveniencia para errores de API
1978
- translateApiError: (apiResponse) => {
1979
- if (apiResponse?.data?.response?.status) {
1980
- return translateErrorCode(apiResponse.data.response.status, config);
1981
- }
1982
- if (apiResponse?.status) {
1983
- return translateErrorCode(apiResponse.status, config);
1984
- }
1985
- if (apiResponse?.code) {
1986
- return translateErrorCode(apiResponse.code, config);
1987
- }
1988
- return "Unknown error";
1989
- }
1990
- };
1991
- }
1992
-
1993
2028
  // src/components/CrudifyLogin/Forms/LoginForm.tsx
1994
2029
  var import_jsx_runtime6 = require("react/jsx-runtime");
1995
2030
  var LoginForm = ({ onScreenChange, onExternalNavigate, onLoginSuccess, onError, redirectUrl = "/" }) => {
package/dist/index.mjs CHANGED
@@ -589,6 +589,162 @@ _TokenStorage.ENCRYPTION_KEY = "crudify_secure_key_v1";
589
589
  _TokenStorage.storageType = "localStorage";
590
590
  var TokenStorage = _TokenStorage;
591
591
 
592
+ // src/utils/errorTranslation.ts
593
+ var ERROR_TRANSLATION_HIERARCHY = [
594
+ "errors.{category}.{code}",
595
+ // errors.auth.INVALID_CREDENTIALS
596
+ "errors.{code}",
597
+ // errors.INVALID_CREDENTIALS
598
+ "login.{code}",
599
+ // login.INVALID_CREDENTIALS (legacy)
600
+ "error.{code}",
601
+ // error.INVALID_CREDENTIALS (singular)
602
+ "messages.{code}",
603
+ // messages.INVALID_CREDENTIALS
604
+ "{code}"
605
+ // INVALID_CREDENTIALS (direct)
606
+ ];
607
+ var ERROR_CATEGORY_MAP = {
608
+ // Authentication errors
609
+ "INVALID_CREDENTIALS": "auth",
610
+ "UNAUTHORIZED": "auth",
611
+ "INVALID_API_KEY": "auth",
612
+ "USER_NOT_FOUND": "auth",
613
+ "USER_NOT_ACTIVE": "auth",
614
+ "NO_PERMISSION": "auth",
615
+ "SESSION_EXPIRED": "auth",
616
+ // Data errors
617
+ "ITEM_NOT_FOUND": "data",
618
+ "NOT_FOUND": "data",
619
+ "IN_USE": "data",
620
+ "DUPLICATE_ENTRY": "data",
621
+ // Validation errors
622
+ "FIELD_ERROR": "validation",
623
+ "BAD_REQUEST": "validation",
624
+ "INVALID_EMAIL": "validation",
625
+ "INVALID_CODE": "validation",
626
+ "REQUIRED_FIELD": "validation",
627
+ // System errors
628
+ "INTERNAL_SERVER_ERROR": "system",
629
+ "DATABASE_CONNECTION_ERROR": "system",
630
+ "INVALID_CONFIGURATION": "system",
631
+ "UNKNOWN_OPERATION": "system",
632
+ "TIMEOUT_ERROR": "system",
633
+ "NETWORK_ERROR": "system",
634
+ // Rate limiting
635
+ "TOO_MANY_REQUESTS": "rate_limit"
636
+ };
637
+ var DEFAULT_ERROR_MESSAGES = {
638
+ "INVALID_CREDENTIALS": "Invalid username or password",
639
+ "UNAUTHORIZED": "You are not authorized to perform this action",
640
+ "SESSION_EXPIRED": "Your session has expired. Please log in again.",
641
+ "USER_NOT_FOUND": "User not found",
642
+ "ITEM_NOT_FOUND": "Item not found",
643
+ "FIELD_ERROR": "Invalid field value",
644
+ "INTERNAL_SERVER_ERROR": "An internal error occurred",
645
+ "NETWORK_ERROR": "Network connection error",
646
+ "TIMEOUT_ERROR": "Request timeout",
647
+ "UNKNOWN_OPERATION": "Unknown operation",
648
+ "INVALID_EMAIL": "Invalid email format",
649
+ "INVALID_CODE": "Invalid code",
650
+ "TOO_MANY_REQUESTS": "Too many requests, please try again later"
651
+ };
652
+ function translateErrorCode(errorCode, config) {
653
+ const { translateFn, currentLanguage, enableDebug } = config;
654
+ if (enableDebug) {
655
+ console.log(`\u{1F50D} [ErrorTranslation] Translating error code: ${errorCode} (lang: ${currentLanguage || "unknown"})`);
656
+ }
657
+ const normalizedCode = errorCode.toUpperCase();
658
+ const category = ERROR_CATEGORY_MAP[normalizedCode];
659
+ const translationKeys = ERROR_TRANSLATION_HIERARCHY.map((pattern) => {
660
+ return pattern.replace("{category}", category || "general").replace("{code}", normalizedCode);
661
+ });
662
+ if (enableDebug) {
663
+ console.log(`\u{1F511} [ErrorTranslation] Searching keys:`, translationKeys);
664
+ }
665
+ for (const key of translationKeys) {
666
+ const translated = translateFn(key);
667
+ if (enableDebug) {
668
+ console.log(`\u{1F50D} [ErrorTranslation] Checking key: "${key}" -> result: "${translated}" (same as key: ${translated === key})`);
669
+ }
670
+ if (translated && translated !== key) {
671
+ if (enableDebug) {
672
+ console.log(`\u2705 [ErrorTranslation] Found translation at key: ${key} = "${translated}"`);
673
+ }
674
+ return translated;
675
+ }
676
+ }
677
+ const defaultMessage = DEFAULT_ERROR_MESSAGES[normalizedCode];
678
+ if (defaultMessage) {
679
+ if (enableDebug) {
680
+ console.log(`\u{1F504} [ErrorTranslation] Using default message: "${defaultMessage}"`);
681
+ }
682
+ return defaultMessage;
683
+ }
684
+ const friendlyCode = normalizedCode.replace(/_/g, " ").toLowerCase().replace(/\b\w/g, (l) => l.toUpperCase());
685
+ if (enableDebug) {
686
+ console.log(`\u26A0\uFE0F [ErrorTranslation] No translation found, using friendly code: "${friendlyCode}"`);
687
+ }
688
+ return friendlyCode;
689
+ }
690
+ function translateErrorCodes(errorCodes, config) {
691
+ return errorCodes.map((code) => translateErrorCode(code, config));
692
+ }
693
+ function translateError(error, config) {
694
+ const { enableDebug } = config;
695
+ if (enableDebug) {
696
+ console.log(`\u{1F50D} [ErrorTranslation] Translating error:`, error);
697
+ }
698
+ const translatedCode = translateErrorCode(error.code, config);
699
+ if (translatedCode !== error.code.toUpperCase() && translatedCode !== error.code) {
700
+ if (enableDebug) {
701
+ console.log(`\u2705 [ErrorTranslation] Using hierarchical translation: "${translatedCode}"`);
702
+ }
703
+ if (error.field) {
704
+ return `${error.field}: ${translatedCode}`;
705
+ }
706
+ return translatedCode;
707
+ }
708
+ if (error.message && !error.message.includes("Error:") && error.message.length > 0 && error.message !== error.code) {
709
+ if (enableDebug) {
710
+ console.log(`\u{1F504} [ErrorTranslation] No hierarchical translation found, using API message: "${error.message}"`);
711
+ }
712
+ return error.message;
713
+ }
714
+ if (enableDebug) {
715
+ console.log(`\u26A0\uFE0F [ErrorTranslation] Using final fallback: "${translatedCode}"`);
716
+ }
717
+ if (error.field) {
718
+ return `${error.field}: ${translatedCode}`;
719
+ }
720
+ return translatedCode;
721
+ }
722
+ function createErrorTranslator(translateFn, options = {}) {
723
+ const config = {
724
+ translateFn,
725
+ currentLanguage: options.currentLanguage,
726
+ enableDebug: options.enableDebug || false
727
+ };
728
+ return {
729
+ translateErrorCode: (code) => translateErrorCode(code, config),
730
+ translateErrorCodes: (codes) => translateErrorCodes(codes, config),
731
+ translateError: (error) => translateError(error, config),
732
+ // Método de conveniencia para errores de API
733
+ translateApiError: (apiResponse) => {
734
+ if (apiResponse?.data?.response?.status) {
735
+ return translateErrorCode(apiResponse.data.response.status, config);
736
+ }
737
+ if (apiResponse?.status) {
738
+ return translateErrorCode(apiResponse.status, config);
739
+ }
740
+ if (apiResponse?.code) {
741
+ return translateErrorCode(apiResponse.code, config);
742
+ }
743
+ return "Unknown error";
744
+ }
745
+ };
746
+ }
747
+
592
748
  // src/core/SessionManager.ts
593
749
  var SessionManager = class _SessionManager {
594
750
  constructor() {
@@ -745,6 +901,7 @@ var SessionManager = class _SessionManager {
745
901
  if (!response.success) {
746
902
  this.log("Token refresh failed:", response.errors);
747
903
  TokenStorage.clearTokens();
904
+ this.config.showNotification?.(this.getSessionExpiredMessage(), "warning");
748
905
  this.config.onSessionExpired?.();
749
906
  return false;
750
907
  }
@@ -760,6 +917,7 @@ var SessionManager = class _SessionManager {
760
917
  } catch (error) {
761
918
  this.log("Token refresh error:", error);
762
919
  TokenStorage.clearTokens();
920
+ this.config.showNotification?.(this.getSessionExpiredMessage(), "warning");
763
921
  this.config.onSessionExpired?.();
764
922
  return false;
765
923
  }
@@ -779,6 +937,7 @@ var SessionManager = class _SessionManager {
779
937
  if (authError.isRefreshTokenInvalid || authError.isTokenRefreshFailed) {
780
938
  this.log("Refresh token invalid or refresh already failed, clearing session");
781
939
  TokenStorage.clearTokens();
940
+ this.config.showNotification?.(this.getSessionExpiredMessage(), "warning");
782
941
  this.config.onSessionExpired?.();
783
942
  return response;
784
943
  }
@@ -792,6 +951,7 @@ var SessionManager = class _SessionManager {
792
951
  } else {
793
952
  this.log("Auth error with no valid tokens or irrecoverable error, triggering session expired");
794
953
  TokenStorage.clearTokens();
954
+ this.config.showNotification?.(this.getSessionExpiredMessage(), "warning");
795
955
  this.config.onSessionExpired?.();
796
956
  }
797
957
  }
@@ -874,6 +1034,18 @@ var SessionManager = class _SessionManager {
874
1034
  crudify2.logout();
875
1035
  this.log("Session cleared completely");
876
1036
  }
1037
+ /**
1038
+ * Obtener mensaje de sesión expirada traducido
1039
+ */
1040
+ getSessionExpiredMessage() {
1041
+ if (this.config.translateFn) {
1042
+ return translateErrorCode("SESSION_EXPIRED", {
1043
+ translateFn: this.config.translateFn,
1044
+ enableDebug: this.config.enableLogging
1045
+ });
1046
+ }
1047
+ return "Tu sesi\xF3n ha expirado. Por favor, inicia sesi\xF3n nuevamente.";
1048
+ }
877
1049
  // Métodos privados
878
1050
  log(message, ...args) {
879
1051
  if (this.config.enableLogging) {
@@ -907,6 +1079,8 @@ function useSession(options = {}) {
907
1079
  const config = {
908
1080
  autoRestore: options.autoRestore ?? true,
909
1081
  enableLogging: options.enableLogging ?? false,
1082
+ showNotification: options.showNotification,
1083
+ translateFn: options.translateFn,
910
1084
  onSessionExpired: () => {
911
1085
  setState((prev) => ({
912
1086
  ...prev,
@@ -1283,21 +1457,28 @@ var useGlobalNotification = () => {
1283
1457
  // src/providers/SessionProvider.tsx
1284
1458
  import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
1285
1459
  var SessionContext = createContext5(void 0);
1286
- function SessionProvider({
1460
+ function InnerSessionProvider({
1287
1461
  children,
1288
1462
  options = {},
1289
1463
  config: propConfig,
1290
1464
  showNotifications = false,
1291
- // ✅ Por defecto DESACTIVADO
1292
1465
  notificationOptions = {}
1293
1466
  }) {
1467
+ let showNotificationFn;
1468
+ try {
1469
+ const { showNotification } = useGlobalNotification();
1470
+ showNotificationFn = showNotification;
1471
+ } catch {
1472
+ }
1294
1473
  const enhancedOptions = React5.useMemo(() => ({
1295
1474
  ...options,
1475
+ showNotification: showNotificationFn,
1476
+ // TODO: Agregar translateFn cuando esté disponible
1296
1477
  onSessionExpired: () => {
1297
1478
  console.log("\u{1F6A8} SessionProvider - Session expired callback triggered");
1298
1479
  options.onSessionExpired?.();
1299
1480
  }
1300
- }), [options]);
1481
+ }), [options, showNotificationFn]);
1301
1482
  const sessionHook = useSession(enhancedOptions);
1302
1483
  const resolvedConfig = useMemo2(() => {
1303
1484
  let publicApiKey;
@@ -1396,7 +1577,16 @@ function SessionProvider({
1396
1577
  defaultAutoHideDuration: notificationOptions.defaultAutoHideDuration || 6e3,
1397
1578
  position: notificationOptions.position || { vertical: "top", horizontal: "right" }
1398
1579
  };
1399
- return /* @__PURE__ */ jsx5(GlobalNotificationProvider, { ...notificationConfig, children: /* @__PURE__ */ jsx5(SessionContext.Provider, { value: contextValue, children }) });
1580
+ return /* @__PURE__ */ jsx5(SessionContext.Provider, { value: contextValue, children });
1581
+ }
1582
+ function SessionProvider(props) {
1583
+ const notificationConfig = {
1584
+ enabled: props.showNotifications,
1585
+ maxNotifications: props.notificationOptions?.maxNotifications || 5,
1586
+ defaultAutoHideDuration: props.notificationOptions?.defaultAutoHideDuration || 6e3,
1587
+ position: props.notificationOptions?.position || { vertical: "top", horizontal: "right" }
1588
+ };
1589
+ return /* @__PURE__ */ jsx5(GlobalNotificationProvider, { ...notificationConfig, children: /* @__PURE__ */ jsx5(InnerSessionProvider, { ...props }) });
1400
1590
  }
1401
1591
  function useSessionContext() {
1402
1592
  const context = useContext5(SessionContext);
@@ -1762,161 +1952,6 @@ function handleCrudifyError(error) {
1762
1952
  ];
1763
1953
  }
1764
1954
 
1765
- // src/utils/errorTranslation.ts
1766
- var ERROR_TRANSLATION_HIERARCHY = [
1767
- "errors.{category}.{code}",
1768
- // errors.auth.INVALID_CREDENTIALS
1769
- "errors.{code}",
1770
- // errors.INVALID_CREDENTIALS
1771
- "login.{code}",
1772
- // login.INVALID_CREDENTIALS (legacy)
1773
- "error.{code}",
1774
- // error.INVALID_CREDENTIALS (singular)
1775
- "messages.{code}",
1776
- // messages.INVALID_CREDENTIALS
1777
- "{code}"
1778
- // INVALID_CREDENTIALS (direct)
1779
- ];
1780
- var ERROR_CATEGORY_MAP = {
1781
- // Authentication errors
1782
- "INVALID_CREDENTIALS": "auth",
1783
- "UNAUTHORIZED": "auth",
1784
- "INVALID_API_KEY": "auth",
1785
- "USER_NOT_FOUND": "auth",
1786
- "USER_NOT_ACTIVE": "auth",
1787
- "NO_PERMISSION": "auth",
1788
- "SESSION_EXPIRED": "auth",
1789
- // Data errors
1790
- "ITEM_NOT_FOUND": "data",
1791
- "NOT_FOUND": "data",
1792
- "IN_USE": "data",
1793
- "DUPLICATE_ENTRY": "data",
1794
- // Validation errors
1795
- "FIELD_ERROR": "validation",
1796
- "BAD_REQUEST": "validation",
1797
- "INVALID_EMAIL": "validation",
1798
- "INVALID_CODE": "validation",
1799
- "REQUIRED_FIELD": "validation",
1800
- // System errors
1801
- "INTERNAL_SERVER_ERROR": "system",
1802
- "DATABASE_CONNECTION_ERROR": "system",
1803
- "INVALID_CONFIGURATION": "system",
1804
- "UNKNOWN_OPERATION": "system",
1805
- "TIMEOUT_ERROR": "system",
1806
- "NETWORK_ERROR": "system",
1807
- // Rate limiting
1808
- "TOO_MANY_REQUESTS": "rate_limit"
1809
- };
1810
- var DEFAULT_ERROR_MESSAGES = {
1811
- "INVALID_CREDENTIALS": "Invalid username or password",
1812
- "UNAUTHORIZED": "You are not authorized to perform this action",
1813
- "USER_NOT_FOUND": "User not found",
1814
- "ITEM_NOT_FOUND": "Item not found",
1815
- "FIELD_ERROR": "Invalid field value",
1816
- "INTERNAL_SERVER_ERROR": "An internal error occurred",
1817
- "NETWORK_ERROR": "Network connection error",
1818
- "TIMEOUT_ERROR": "Request timeout",
1819
- "UNKNOWN_OPERATION": "Unknown operation",
1820
- "INVALID_EMAIL": "Invalid email format",
1821
- "INVALID_CODE": "Invalid code",
1822
- "TOO_MANY_REQUESTS": "Too many requests, please try again later"
1823
- };
1824
- function translateErrorCode(errorCode, config) {
1825
- const { translateFn, currentLanguage, enableDebug } = config;
1826
- if (enableDebug) {
1827
- console.log(`\u{1F50D} [ErrorTranslation] Translating error code: ${errorCode} (lang: ${currentLanguage || "unknown"})`);
1828
- }
1829
- const normalizedCode = errorCode.toUpperCase();
1830
- const category = ERROR_CATEGORY_MAP[normalizedCode];
1831
- const translationKeys = ERROR_TRANSLATION_HIERARCHY.map((pattern) => {
1832
- return pattern.replace("{category}", category || "general").replace("{code}", normalizedCode);
1833
- });
1834
- if (enableDebug) {
1835
- console.log(`\u{1F511} [ErrorTranslation] Searching keys:`, translationKeys);
1836
- }
1837
- for (const key of translationKeys) {
1838
- const translated = translateFn(key);
1839
- if (enableDebug) {
1840
- console.log(`\u{1F50D} [ErrorTranslation] Checking key: "${key}" -> result: "${translated}" (same as key: ${translated === key})`);
1841
- }
1842
- if (translated && translated !== key) {
1843
- if (enableDebug) {
1844
- console.log(`\u2705 [ErrorTranslation] Found translation at key: ${key} = "${translated}"`);
1845
- }
1846
- return translated;
1847
- }
1848
- }
1849
- const defaultMessage = DEFAULT_ERROR_MESSAGES[normalizedCode];
1850
- if (defaultMessage) {
1851
- if (enableDebug) {
1852
- console.log(`\u{1F504} [ErrorTranslation] Using default message: "${defaultMessage}"`);
1853
- }
1854
- return defaultMessage;
1855
- }
1856
- const friendlyCode = normalizedCode.replace(/_/g, " ").toLowerCase().replace(/\b\w/g, (l) => l.toUpperCase());
1857
- if (enableDebug) {
1858
- console.log(`\u26A0\uFE0F [ErrorTranslation] No translation found, using friendly code: "${friendlyCode}"`);
1859
- }
1860
- return friendlyCode;
1861
- }
1862
- function translateErrorCodes(errorCodes, config) {
1863
- return errorCodes.map((code) => translateErrorCode(code, config));
1864
- }
1865
- function translateError(error, config) {
1866
- const { enableDebug } = config;
1867
- if (enableDebug) {
1868
- console.log(`\u{1F50D} [ErrorTranslation] Translating error:`, error);
1869
- }
1870
- const translatedCode = translateErrorCode(error.code, config);
1871
- if (translatedCode !== error.code.toUpperCase() && translatedCode !== error.code) {
1872
- if (enableDebug) {
1873
- console.log(`\u2705 [ErrorTranslation] Using hierarchical translation: "${translatedCode}"`);
1874
- }
1875
- if (error.field) {
1876
- return `${error.field}: ${translatedCode}`;
1877
- }
1878
- return translatedCode;
1879
- }
1880
- if (error.message && !error.message.includes("Error:") && error.message.length > 0 && error.message !== error.code) {
1881
- if (enableDebug) {
1882
- console.log(`\u{1F504} [ErrorTranslation] No hierarchical translation found, using API message: "${error.message}"`);
1883
- }
1884
- return error.message;
1885
- }
1886
- if (enableDebug) {
1887
- console.log(`\u26A0\uFE0F [ErrorTranslation] Using final fallback: "${translatedCode}"`);
1888
- }
1889
- if (error.field) {
1890
- return `${error.field}: ${translatedCode}`;
1891
- }
1892
- return translatedCode;
1893
- }
1894
- function createErrorTranslator(translateFn, options = {}) {
1895
- const config = {
1896
- translateFn,
1897
- currentLanguage: options.currentLanguage,
1898
- enableDebug: options.enableDebug || false
1899
- };
1900
- return {
1901
- translateErrorCode: (code) => translateErrorCode(code, config),
1902
- translateErrorCodes: (codes) => translateErrorCodes(codes, config),
1903
- translateError: (error) => translateError(error, config),
1904
- // Método de conveniencia para errores de API
1905
- translateApiError: (apiResponse) => {
1906
- if (apiResponse?.data?.response?.status) {
1907
- return translateErrorCode(apiResponse.data.response.status, config);
1908
- }
1909
- if (apiResponse?.status) {
1910
- return translateErrorCode(apiResponse.status, config);
1911
- }
1912
- if (apiResponse?.code) {
1913
- return translateErrorCode(apiResponse.code, config);
1914
- }
1915
- return "Unknown error";
1916
- }
1917
- };
1918
- }
1919
-
1920
1955
  // src/components/CrudifyLogin/Forms/LoginForm.tsx
1921
1956
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1922
1957
  var LoginForm = ({ onScreenChange, onExternalNavigate, onLoginSuccess, onError, redirectUrl = "/" }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocios/crudify-ui",
3
- "version": "3.0.48",
3
+ "version": "3.0.52",
4
4
  "description": "Biblioteca de componentes UI para Crudify",
5
5
  "author": "Nocios",
6
6
  "license": "MIT",
@@ -25,7 +25,7 @@
25
25
  "@mui/icons-material": "^7.1.0",
26
26
  "@mui/material": "^7.1.0",
27
27
  "@mui/x-data-grid": "^8.5.1",
28
- "@nocios/crudify-browser": "^2.0.4",
28
+ "@nocios/crudify-browser": "^2.0.5",
29
29
  "@types/uuid": "^10.0.0",
30
30
  "crypto-js": "^4.2.0",
31
31
  "i18next-browser-languagedetector": "^8.1.0",