UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

281 lines (278 loc) 8.56 kB
'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