aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
281 lines (278 loc) • 8.56 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import React, { useState, useRef, useCallback, useEffect } from 'react';
/**
* Enhanced error boundary hook with retry logic and error reporting
*/
function useErrorBoundary(options = {}) {
const {
maxRetries = 3,
resetOnPropsChange = true,
resetTimeout = 5000,
onError,
onReset
} = options;
const [errorState, setErrorState] = useState({
hasError: false,
error: null,
errorInfo: null,
errorId: null,
retryCount: 0
});
const resetTimeoutRef = useRef();
const propsHashRef = useRef("");
// Generate error ID for tracking
const generateErrorId = useCallback(() => {
return `error-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}, []);
// Capture error with enhanced information
const captureError = useCallback((error, errorInfo) => {
const enhancedErrorInfo = {
componentStack: errorInfo?.componentStack || "",
errorBoundary: errorInfo?.errorBoundary || "useErrorBoundary",
timestamp: Date.now()
};
const errorId = generateErrorId();
setErrorState(prev => {
const newErrorState = {
hasError: true,
error,
errorInfo: enhancedErrorInfo,
errorId,
retryCount: prev.retryCount
};
// Report error to external service
onError?.(error, enhancedErrorInfo);
// Log error for development
if (process.env.NODE_ENV === "development") {
console.group(`🚨 Error Boundary: ${errorId}`);
console.error("Error:", error);
console.error("Component Stack:", enhancedErrorInfo.componentStack);
console.error("Timestamp:", new Date(enhancedErrorInfo.timestamp).toISOString());
console.groupEnd();
}
// Auto-reset after timeout if retries available
if (resetTimeout > 0 && prev.retryCount < maxRetries) {
resetTimeoutRef.current = setTimeout(() => {
retry();
}, resetTimeout);
}
return newErrorState;
});
}, [generateErrorId, onError, resetTimeout, maxRetries]);
// Reset error state
const resetError = useCallback(() => {
setErrorState({
hasError: false,
error: null,
errorInfo: null,
errorId: null,
retryCount: 0
});
if (resetTimeoutRef.current) {
clearTimeout(resetTimeoutRef.current);
}
onReset?.();
}, [onReset]);
// Retry with incremented counter
const retry = useCallback(() => {
setErrorState(prev => {
if (prev.retryCount >= maxRetries) {
return prev; // Don't retry if max retries reached
}
return {
hasError: false,
error: null,
errorInfo: null,
errorId: null,
retryCount: prev.retryCount + 1
};
});
if (resetTimeoutRef.current) {
clearTimeout(resetTimeoutRef.current);
}
onReset?.();
}, [maxRetries, onReset]);
// Create error boundary wrapper
const withErrorBoundary = useCallback((Component, fallback) => {
return /*#__PURE__*/React.forwardRef((props, ref) => {
try {
if (errorState.hasError && errorState.error && errorState.errorInfo) {
if (fallback) {
const FallbackComponent = fallback;
return jsx(FallbackComponent, {
error: errorState.error,
errorInfo: errorState.errorInfo,
retry: retry
});
}
// Default fallback UI
return jsxs("div", {
className: "glass-p-4 glass-border glass-border-red-500/20 glass-surface-danger/10 glass-radius-lg",
children: [jsx("h3", {
className: 'text-red-400 font-semibold mb-2',
children: "Something went wrong"
}), jsx("p", {
className: 'text-red-300 glass-text-sm mb-3',
children: errorState.error.message
}), errorState.retryCount < maxRetries && jsxs("button", {
onClick: retry,
className: 'glass-px-3 glass-py-1 glass-surface-danger/20 text-red-300 glass-radius hover:bg-red-500/30 transition-colors glass-focus glass-touch-target glass-contrast-guard',
children: ["Try Again (", maxRetries - errorState.retryCount, " attempts left)"]
})]
});
}
return jsx(Component, {
...props,
ref: ref
});
} catch (error) {
captureError(error);
return null;
}
});
}, [errorState, retry, maxRetries, captureError]);
// Reset on props change (if enabled)
useEffect(() => {
if (!resetOnPropsChange) return;
const currentPropsHash = JSON.stringify(arguments);
if (propsHashRef.current !== currentPropsHash && errorState.hasError) {
resetError();
}
propsHashRef.current = currentPropsHash;
}, [resetOnPropsChange, errorState.hasError, resetError]);
// Cleanup timeouts
useEffect(() => {
return () => {
if (resetTimeoutRef.current) {
clearTimeout(resetTimeoutRef.current);
}
};
}, []);
return {
...errorState,
captureError,
resetError,
retry,
withErrorBoundary,
canRetry: errorState.retryCount < maxRetries
};
}
/**
* Hook for async error handling with retry logic
*/
function useAsyncError() {
const [error, setError] = useState(null);
const [isRetrying, setIsRetrying] = useState(false);
const retryCountRef = useRef(0);
const captureAsyncError = useCallback(error => {
setError(error);
// Log async error
if (process.env.NODE_ENV === "development") {
console.error("Async Error:", error);
}
}, []);
const retryAsync = useCallback(async (asyncFn, maxRetries = 3) => {
setIsRetrying(true);
setError(null);
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const result = await asyncFn();
setIsRetrying(false);
retryCountRef.current = 0;
return result;
} catch (error) {
if (attempt === maxRetries) {
captureAsyncError(error);
setIsRetrying(false);
retryCountRef.current = attempt + 1;
throw error;
}
// Wait before retry with exponential backoff
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
}, [captureAsyncError]);
const clearError = useCallback(() => {
setError(null);
retryCountRef.current = 0;
}, []);
return {
error,
isRetrying,
retryCount: retryCountRef.current,
captureAsyncError,
retryAsync,
clearError
};
}
/**
* Hook for graceful degradation
*/
function useGracefulDegradation(primaryFeature, fallbackFeature, testCondition) {
const [result, setResult] = useState(() => {
try {
return testCondition() ? primaryFeature() : fallbackFeature();
} catch {
return fallbackFeature();
}
});
useEffect(() => {
try {
const shouldUsePrimary = testCondition();
setResult(shouldUsePrimary ? primaryFeature() : fallbackFeature());
} catch (error) {
if (process.env.NODE_ENV === "development") {
console.warn("Feature test failed, using fallback:", error);
}
setResult(fallbackFeature());
}
}, [primaryFeature, fallbackFeature, testCondition]);
return result;
}
/**
* Hook for error reporting and analytics
*/
function useErrorReporting(options = {}) {
const {
enableReporting = false,
endpoint,
apiKey,
userId,
sessionId
} = options;
const reportError = useCallback(async (error, context = {}) => {
if (!enableReporting || !endpoint) return;
try {
const errorReport = {
message: error.message,
stack: error.stack,
timestamp: Date.now(),
url: window.location.href,
userAgent: navigator.userAgent,
userId,
sessionId,
context
};
await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(apiKey && {
Authorization: `Bearer ${apiKey}`
})
},
body: JSON.stringify(errorReport)
});
} catch (reportingError) {
if (process.env.NODE_ENV === "development") {
console.warn("Failed to report error:", reportingError);
}
}
}, [enableReporting, endpoint, apiKey, userId, sessionId]);
return {
reportError
};
}
export { useAsyncError, useErrorBoundary, useErrorReporting, useGracefulDegradation };
//# sourceMappingURL=useErrorBoundary.js.map