UNPKG

@ai-growth/nextjs

Version:

Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering

286 lines (285 loc) 14.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.TemplateErrorBoundary = exports.ApiErrorBoundary = exports.CmsErrorBoundary = exports.ErrorBoundary = void 0; exports.withErrorBoundary = withErrorBoundary; exports.useErrorThrower = useErrorThrower; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = __importStar(require("react")); // ============================================================================ // BASE ERROR BOUNDARY COMPONENT // ============================================================================ /** * Base ErrorBoundary component that catches JavaScript errors anywhere in the child component tree * and displays a fallback UI instead of crashing the entire application. * * @example * ```tsx * <ErrorBoundary * fallback={<ErrorFallback />} * onError={(errorDetails) => logError(errorDetails)} * enableRetry={true} * > * <MyComponent /> * </ErrorBoundary> * ``` */ class ErrorBoundary extends react_1.Component { constructor(props) { super(props); this.retryTimeoutId = null; this.handleRetry = () => { const { maxRetries = 3 } = this.props; if (this.state.retryCount < maxRetries) { this.setState(prevState => ({ hasError: false, error: null, errorInfo: null, retryCount: prevState.retryCount + 1, })); } }; this.handleReset = () => { this.setState({ hasError: false, error: null, errorInfo: null, retryCount: 0, errorId: '', }); }; this.state = { hasError: false, error: null, errorInfo: null, retryCount: 0, errorId: '', }; } static getDerivedStateFromError(error) { const errorId = `error-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; return { hasError: true, error, errorId, }; } componentDidCatch(error, errorInfo) { const userAgent = typeof window !== 'undefined' ? window.navigator.userAgent : undefined; const url = typeof window !== 'undefined' ? window.location.href : undefined; const errorDetails = { error, errorInfo, timestamp: new Date(), }; if (userAgent) errorDetails.userAgent = userAgent; if (url) errorDetails.url = url; if (this.props.context) errorDetails.additionalContext = this.props.context; // Update state with error info this.setState({ errorInfo }); // Call error handler if provided if (this.props.onError) { this.props.onError(errorDetails); } // Log error to console in development if (process.env.NODE_ENV === 'development') { console.group('🚨 Error Boundary Caught Error'); console.error('Error:', error); console.error('Error Info:', errorInfo); console.error('Component Stack:', errorInfo.componentStack); console.error('Error Boundary Props:', this.props); console.groupEnd(); } } componentWillUnmount() { if (this.retryTimeoutId) { clearTimeout(this.retryTimeoutId); } } render() { const { children, fallback, enableRetry = false, maxRetries = 3, className, showErrorDetails = process.env.NODE_ENV === 'development' } = this.props; const { hasError, error, errorInfo, retryCount } = this.state; if (hasError && error) { const errorDetails = { error, errorInfo: errorInfo, timestamp: new Date(), }; if (typeof window !== 'undefined' && window.navigator.userAgent) { errorDetails.userAgent = window.navigator.userAgent; } if (typeof window !== 'undefined' && window.location.href) { errorDetails.url = window.location.href; } if (this.props.context) { errorDetails.additionalContext = this.props.context; } // Render custom fallback if provided if (fallback) { const fallbackElement = typeof fallback === 'function' ? fallback(errorDetails) : fallback; return ((0, jsx_runtime_1.jsx)("div", { className: className, children: fallbackElement })); } // Render default error UI return ((0, jsx_runtime_1.jsxs)("div", { className: className, style: { padding: '2rem', margin: '1rem', border: '1px solid #e53e3e', borderRadius: '8px', backgroundColor: '#fed7d7', color: '#742a2a', fontFamily: 'system-ui, sans-serif', }, children: [(0, jsx_runtime_1.jsxs)("div", { style: { marginBottom: '1rem' }, children: [(0, jsx_runtime_1.jsx)("h2", { style: { margin: '0 0 0.5rem 0', fontSize: '1.25rem', fontWeight: 'bold' }, children: "\u26A0\uFE0F Something went wrong" }), (0, jsx_runtime_1.jsx)("p", { style: { margin: '0', opacity: 0.8 }, children: "We encountered an error while rendering this content." })] }), showErrorDetails && ((0, jsx_runtime_1.jsxs)("details", { style: { marginBottom: '1rem' }, children: [(0, jsx_runtime_1.jsx)("summary", { style: { cursor: 'pointer', fontWeight: 'bold' }, children: "Error Details" }), (0, jsx_runtime_1.jsx)("pre", { style: { background: '#fff', padding: '0.5rem', border: '1px solid #ccc', borderRadius: '4px', fontSize: '0.875rem', overflow: 'auto', margin: '0.5rem 0', }, children: error.message }), errorInfo && ((0, jsx_runtime_1.jsx)("pre", { style: { background: '#fff', padding: '0.5rem', border: '1px solid #ccc', borderRadius: '4px', fontSize: '0.75rem', overflow: 'auto', margin: '0.5rem 0', }, children: errorInfo.componentStack }))] })), (0, jsx_runtime_1.jsxs)("div", { style: { display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }, children: [enableRetry && retryCount < maxRetries && ((0, jsx_runtime_1.jsxs)("button", { onClick: this.handleRetry, style: { padding: '0.5rem 1rem', background: '#3182ce', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', fontWeight: 'bold', }, children: ["Retry (", maxRetries - retryCount, " attempts left)"] })), (0, jsx_runtime_1.jsx)("button", { onClick: this.handleReset, style: { padding: '0.5rem 1rem', background: '#718096', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', }, children: "Reset" }), (0, jsx_runtime_1.jsx)("button", { onClick: () => window.location.reload(), style: { padding: '0.5rem 1rem', background: '#38a169', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', }, children: "Reload Page" })] }), retryCount > 0 && ((0, jsx_runtime_1.jsxs)("p", { style: { margin: '1rem 0 0 0', fontSize: '0.875rem', opacity: 0.7 }, children: ["Retry attempts: ", retryCount, "/", maxRetries] }))] })); } return children; } } exports.ErrorBoundary = ErrorBoundary; const CmsErrorBoundary = ({ children, fallback, contentSlug, contentType, context = {}, ...props }) => { const cmsContext = { ...context, contentSlug, contentType, boundaryType: 'cms', }; const cmsFallback = fallback || ((_errorDetails) => ((0, jsx_runtime_1.jsxs)("div", { style: { padding: '2rem', textAlign: 'center', border: '1px dashed #e2e8f0', borderRadius: '8px', backgroundColor: '#f7fafc', color: '#4a5568', }, children: [(0, jsx_runtime_1.jsx)("h3", { style: { margin: '0 0 1rem 0' }, children: "Content Unavailable" }), (0, jsx_runtime_1.jsx)("p", { style: { margin: '0 0 1rem 0' }, children: "We couldn't load the requested content. This might be a temporary issue." }), contentSlug && ((0, jsx_runtime_1.jsxs)("p", { style: { fontSize: '0.875rem', opacity: 0.7, margin: '0' }, children: ["Content: ", contentSlug] }))] }))); return ((0, jsx_runtime_1.jsx)(ErrorBoundary, { ...props, fallback: cmsFallback, context: cmsContext, errorLevel: "error", children: children })); }; exports.CmsErrorBoundary = CmsErrorBoundary; const ApiErrorBoundary = ({ children, fallback, endpoint, operation, context = {}, ...props }) => { const apiContext = { ...context, endpoint, operation, boundaryType: 'api', }; const apiFallback = fallback || ((_errorDetails) => ((0, jsx_runtime_1.jsxs)("div", { style: { padding: '1.5rem', border: '1px solid #fed7d7', borderRadius: '6px', backgroundColor: '#fffaf0', color: '#c53030', }, children: [(0, jsx_runtime_1.jsx)("h4", { style: { margin: '0 0 0.5rem 0' }, children: "\u26A0\uFE0F Service Unavailable" }), (0, jsx_runtime_1.jsx)("p", { style: { margin: '0', fontSize: '0.875rem' }, children: "We're having trouble connecting to our services. Please try again in a moment." })] }))); return ((0, jsx_runtime_1.jsx)(ErrorBoundary, { ...props, fallback: apiFallback, context: apiContext, errorLevel: "warning", enableRetry: true, maxRetries: 2, children: children })); }; exports.ApiErrorBoundary = ApiErrorBoundary; const TemplateErrorBoundary = ({ children, fallback, templateName, contentType, context = {}, ...props }) => { const templateContext = { ...context, templateName, contentType, boundaryType: 'template', }; const templateFallback = fallback || ((_errorDetails) => ((0, jsx_runtime_1.jsxs)("div", { style: { padding: '2rem', border: '2px dashed #e2e8f0', borderRadius: '8px', backgroundColor: '#f8f9fa', color: '#6c757d', textAlign: 'center', }, children: [(0, jsx_runtime_1.jsx)("h3", { style: { margin: '0 0 1rem 0' }, children: "Template Error" }), (0, jsx_runtime_1.jsx)("p", { style: { margin: '0 0 1rem 0' }, children: "There was an issue rendering this template. A fallback template will be used." }), templateName && ((0, jsx_runtime_1.jsxs)("p", { style: { fontSize: '0.875rem', opacity: 0.7, margin: '0' }, children: ["Template: ", templateName] }))] }))); return ((0, jsx_runtime_1.jsx)(ErrorBoundary, { ...props, fallback: templateFallback, context: templateContext, errorLevel: "warning", enableRetry: false, children: children })); }; exports.TemplateErrorBoundary = TemplateErrorBoundary; // ============================================================================ // UTILITY COMPONENTS AND HOOKS // ============================================================================ /** * Higher-order component that wraps a component with an error boundary */ function withErrorBoundary(Component, errorBoundaryProps) { const WrappedComponent = (props) => ((0, jsx_runtime_1.jsx)(ErrorBoundary, { ...errorBoundaryProps, children: (0, jsx_runtime_1.jsx)(Component, { ...props }) })); WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name})`; return WrappedComponent; } /** * Hook to throw an error for testing error boundaries */ function useErrorThrower() { return react_1.default.useCallback((error) => { const errorToThrow = typeof error === 'string' ? new Error(error) : error; throw errorToThrow; }, []); } exports.default = ErrorBoundary;