UNPKG

@ai-growth/nextjs

Version:

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

495 lines (494 loc) 26.5 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { useCallback, useState } from 'react'; import { ErrorBoundary } from './ErrorBoundary'; import { ButtonLoading } from './LoadingComponents'; // ============================================================================ // SHARED COMPONENTS // ============================================================================ const ErrorPageLayout = ({ children, className = '', style = {}, brandName, brandLogo }) => { return (_jsxs("div", { className: `error-page-layout ${className}`, style: { minHeight: '100vh', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', padding: '2rem', fontFamily: 'system-ui, -apple-system, sans-serif', backgroundColor: '#f8f9fa', color: '#212529', textAlign: 'center', ...style, }, children: [(brandName || brandLogo) && (_jsx("div", { style: { marginBottom: '2rem' }, children: brandLogo ? (_jsx("img", { src: brandLogo, alt: brandName || 'Logo', style: { maxHeight: '60px', maxWidth: '200px', marginBottom: '1rem' } })) : (_jsx("h1", { style: { fontSize: '1.5rem', fontWeight: '600', margin: '0 0 1rem 0', color: '#495057' }, children: brandName })) })), _jsx("div", { style: { maxWidth: '600px', width: '100%', backgroundColor: '#fff', padding: '3rem 2rem', borderRadius: '12px', boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)', border: '1px solid #e9ecef', }, children: children })] })); }; const NavigationButtons = ({ links, showRetry, onRetry, isRetrying }) => { const handleRetry = useCallback(async () => { if (onRetry) { await onRetry(); } else { window.location.reload(); } }, [onRetry]); return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '1rem', alignItems: 'center', marginTop: '2rem', }, children: [_jsxs("div", { style: { display: 'flex', gap: '1rem', flexWrap: 'wrap', justifyContent: 'center', }, children: [links.filter(link => link.primary).map((link, index) => (_jsx("a", { href: link.href, style: { display: 'inline-block', padding: '0.75rem 1.5rem', backgroundColor: '#007bff', color: '#fff', textDecoration: 'none', borderRadius: '6px', fontWeight: '500', fontSize: '1rem', transition: 'background-color 0.2s', }, onMouseOver: (e) => { e.target.style.backgroundColor = '#0056b3'; }, onMouseOut: (e) => { e.target.style.backgroundColor = '#007bff'; }, children: link.label }, index))), showRetry && (_jsx("button", { onClick: handleRetry, disabled: isRetrying, style: { display: 'inline-flex', alignItems: 'center', gap: '0.5rem', padding: '0.75rem 1.5rem', backgroundColor: isRetrying ? '#6c757d' : '#28a745', color: '#fff', border: 'none', borderRadius: '6px', fontWeight: '500', fontSize: '1rem', cursor: isRetrying ? 'not-allowed' : 'pointer', transition: 'background-color 0.2s', }, children: isRetrying ? (_jsxs(_Fragment, { children: [_jsx(ButtonLoading, { variant: "spinner", size: "small", color: "#fff" }), "Retrying..."] })) : ('Try Again') }))] }), links.filter(link => !link.primary).length > 0 && (_jsx("div", { style: { display: 'flex', gap: '1rem', flexWrap: 'wrap', justifyContent: 'center', }, children: links.filter(link => !link.primary).map((link, index) => (_jsx("a", { href: link.href, style: { display: 'inline-block', padding: '0.5rem 1rem', color: '#6c757d', textDecoration: 'none', fontSize: '0.875rem', borderRadius: '4px', transition: 'color 0.2s', }, onMouseOver: (e) => { e.target.style.color = '#495057'; }, onMouseOut: (e) => { e.target.style.color = '#6c757d'; }, children: link.label }, index))) }))] })); }; // ============================================================================ // 404 NOT FOUND PAGE // ============================================================================ /** * Comprehensive 404 Not Found page with search and navigation options */ export const NotFoundPage = ({ path, suggestions = [], showSearch = false, onSearch, navigationLinks = [ { label: 'Go Home', href: '/', primary: true }, { label: 'Browse Content', href: '/content' }, { label: 'Contact Support', href: '/support' }, ], showRetry = false, showSupport = true, onRetry, onSupport, brandName, brandLogo, customActions, className = '', style = {}, }) => { const [searchQuery, setSearchQuery] = useState(''); const [isRetrying, setIsRetrying] = useState(false); const handleSearch = useCallback(async (e) => { e.preventDefault(); if (onSearch && searchQuery.trim()) { onSearch(searchQuery.trim()); } }, [onSearch, searchQuery]); const handleRetry = useCallback(async () => { setIsRetrying(true); try { if (onRetry) { await onRetry(); } else { window.location.reload(); } } finally { setTimeout(() => setIsRetrying(false), 1000); } }, [onRetry]); const handleSupport = useCallback(() => { if (onSupport) { onSupport(); } else { window.open('/support', '_blank'); } }, [onSupport]); // Enhanced navigation links with support const enhancedLinks = [ ...navigationLinks, ...(showSupport && !navigationLinks.some(link => link.href.includes('support')) ? [ { label: 'Contact Support', href: '/support' } ] : []) ]; return (_jsxs(ErrorPageLayout, { className: className, style: style, ...(brandName && { brandName }), ...(brandLogo && { brandLogo }), children: [_jsx("div", { style: { fontSize: '6rem', marginBottom: '1.5rem', color: '#ffc107' }, children: "\uD83D\uDD0D" }), _jsx("h1", { style: { fontSize: '3rem', fontWeight: '700', margin: '0 0 1rem 0', color: '#212529', lineHeight: '1.2', }, children: "404" }), _jsx("h2", { style: { fontSize: '1.5rem', fontWeight: '600', margin: '0 0 1rem 0', color: '#495057', }, children: "Page Not Found" }), _jsx("p", { style: { fontSize: '1.1rem', color: '#6c757d', lineHeight: '1.6', margin: '0 0 1.5rem 0', }, children: path ? (_jsxs(_Fragment, { children: ["The page ", _jsx("code", { style: { backgroundColor: '#f8f9fa', padding: '0.2rem 0.4rem', borderRadius: '4px', fontFamily: 'monospace' }, children: path }), " doesn't exist or has been moved."] })) : ("Sorry, we couldn't find the page you're looking for. It might have been moved, deleted, or you entered the wrong URL.") }), showSearch && (_jsx("form", { onSubmit: handleSearch, style: { marginBottom: '2rem' }, children: _jsxs("div", { style: { display: 'flex', gap: '0.5rem', maxWidth: '400px', margin: '0 auto', }, children: [_jsx("input", { type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search for content...", style: { flex: 1, padding: '0.75rem', border: '1px solid #ced4da', borderRadius: '6px', fontSize: '1rem', outline: 'none', }, onFocus: (e) => { e.target.style.borderColor = '#007bff'; e.target.style.boxShadow = '0 0 0 3px rgba(0, 123, 255, 0.1)'; }, onBlur: (e) => { e.target.style.borderColor = '#ced4da'; e.target.style.boxShadow = 'none'; } }), _jsx("button", { type: "submit", disabled: !searchQuery.trim(), style: { padding: '0.75rem 1.5rem', backgroundColor: searchQuery.trim() ? '#007bff' : '#6c757d', color: '#fff', border: 'none', borderRadius: '6px', cursor: searchQuery.trim() ? 'pointer' : 'not-allowed', fontSize: '1rem', fontWeight: '500', }, children: "Search" })] }) })), suggestions.length > 0 && (_jsxs("div", { style: { marginBottom: '2rem' }, children: [_jsx("h3", { style: { fontSize: '1.1rem', fontWeight: '600', margin: '0 0 1rem 0', color: '#495057', }, children: "Try these instead:" }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '0.5rem', }, children: suggestions.map((suggestion, index) => (_jsx("a", { href: suggestion.href, style: { display: 'block', padding: '0.75rem 1rem', backgroundColor: '#f8f9fa', color: '#495057', textDecoration: 'none', borderRadius: '6px', border: '1px solid #e9ecef', transition: 'all 0.2s', }, onMouseOver: (e) => { e.target.style.backgroundColor = '#e9ecef'; e.target.style.borderColor = '#ced4da'; }, onMouseOut: (e) => { e.target.style.backgroundColor = '#f8f9fa'; e.target.style.borderColor = '#e9ecef'; }, children: suggestion.label }, index))) })] })), customActions && (_jsx("div", { style: { marginBottom: '2rem' }, children: customActions })), _jsx(NavigationButtons, { links: enhancedLinks, showRetry: showRetry, onRetry: handleRetry, isRetrying: isRetrying }), _jsx("div", { style: { marginTop: '2rem', padding: '1rem', backgroundColor: '#f8f9fa', borderRadius: '6px', fontSize: '0.875rem', color: '#6c757d', }, children: _jsxs("p", { style: { margin: '0' }, children: ["If you believe this is an error, please", ' ', _jsx("button", { onClick: handleSupport, style: { background: 'none', border: 'none', color: '#007bff', textDecoration: 'underline', cursor: 'pointer', fontSize: 'inherit', padding: '0', }, children: "contact our support team" }), ' ', "and we'll help you find what you're looking for."] }) })] })); }; // ============================================================================ // SERVER ERROR PAGE // ============================================================================ /** * Generic server error page for 500 errors and other server issues */ export const ServerErrorPage = ({ errorCode = 500, errorDetails, showTechnicalDetails = false, incidentId, navigationLinks = [ { label: 'Go Home', href: '/', primary: true }, { label: 'Try Again', href: '', primary: true }, ], showRetry = true, showSupport = true, onRetry, onSupport, brandName, brandLogo, customActions, className = '', style = {}, }) => { const [isRetrying, setIsRetrying] = useState(false); const [showDetails, setShowDetails] = useState(false); const getErrorInfo = () => { switch (errorCode) { case 500: return { title: 'Internal Server Error', message: "We're experiencing some technical difficulties. Our team has been notified and is working to fix this issue.", icon: '⚠️', color: '#dc3545', }; case 502: return { title: 'Bad Gateway', message: "We're having trouble connecting to our servers. Please try again in a few moments.", icon: '🔌', color: '#fd7e14', }; case 503: return { title: 'Service Unavailable', message: "Our service is temporarily unavailable due to maintenance. We'll be back online shortly.", icon: '🔧', color: '#ffc107', }; default: return { title: 'Something Went Wrong', message: "We encountered an unexpected error. Please try again or contact support if the problem persists.", icon: '❌', color: '#dc3545', }; } }; const errorInfo = getErrorInfo(); const handleRetry = useCallback(async () => { setIsRetrying(true); try { if (onRetry) { await onRetry(); } else { window.location.reload(); } } finally { setTimeout(() => setIsRetrying(false), 1000); } }, [onRetry]); const handleSupport = useCallback(() => { if (onSupport) { onSupport(); } else { const supportUrl = incidentId ? `/support?incident=${incidentId}` : '/support'; window.open(supportUrl, '_blank'); } }, [onSupport, incidentId]); // Enhanced navigation links const enhancedLinks = navigationLinks.map(link => { if (link.href === '' && link.label.toLowerCase().includes('try')) { return { ...link, href: '#', primary: false }; // Will use retry handler } return link; }); return (_jsxs(ErrorPageLayout, { className: className, style: style, ...(brandName && { brandName }), ...(brandLogo && { brandLogo }), children: [_jsx("div", { style: { fontSize: '6rem', marginBottom: '1.5rem', color: errorInfo.color }, children: errorInfo.icon }), _jsx("h1", { style: { fontSize: '3rem', fontWeight: '700', margin: '0 0 1rem 0', color: errorInfo.color, lineHeight: '1.2', }, children: errorCode }), _jsx("h2", { style: { fontSize: '1.5rem', fontWeight: '600', margin: '0 0 1rem 0', color: '#495057', }, children: errorInfo.title }), _jsx("p", { style: { fontSize: '1.1rem', color: '#6c757d', lineHeight: '1.6', margin: '0 0 1.5rem 0', }, children: errorInfo.message }), incidentId && (_jsx("div", { style: { marginBottom: '1.5rem', padding: '0.75rem 1rem', backgroundColor: '#f8f9fa', borderRadius: '6px', border: '1px solid #e9ecef', }, children: _jsxs("p", { style: { margin: '0', fontSize: '0.875rem', color: '#6c757d', }, children: [_jsx("strong", { children: "Incident ID:" }), ' ', _jsx("code", { style: { backgroundColor: '#ffffff', padding: '0.2rem 0.4rem', borderRadius: '4px', fontFamily: 'monospace', border: '1px solid #ced4da', }, children: incidentId })] }) })), showTechnicalDetails && errorDetails && (_jsxs("div", { style: { marginBottom: '2rem' }, children: [_jsxs("button", { onClick: () => setShowDetails(!showDetails), style: { background: 'none', border: '1px solid #ced4da', color: '#495057', padding: '0.5rem 1rem', borderRadius: '4px', cursor: 'pointer', fontSize: '0.875rem', marginBottom: '1rem', }, children: [showDetails ? 'Hide' : 'Show', " Technical Details"] }), showDetails && (_jsxs("details", { style: { backgroundColor: '#f8f9fa', border: '1px solid #e9ecef', borderRadius: '6px', padding: '1rem', }, children: [_jsx("summary", { style: { fontWeight: '600', marginBottom: '0.5rem', cursor: 'pointer', }, children: "Error Information" }), _jsx("pre", { style: { backgroundColor: '#ffffff', padding: '0.75rem', border: '1px solid #ced4da', borderRadius: '4px', fontSize: '0.75rem', overflow: 'auto', fontFamily: 'monospace', margin: '0', whiteSpace: 'pre-wrap', }, children: JSON.stringify({ error: errorDetails.error.message, timestamp: errorDetails.timestamp, userAgent: errorDetails.userAgent, url: errorDetails.url, }, null, 2) })] }))] })), customActions && (_jsx("div", { style: { marginBottom: '2rem' }, children: customActions })), _jsx(NavigationButtons, { links: enhancedLinks.filter(link => !link.label.toLowerCase().includes('try')), showRetry: showRetry, onRetry: handleRetry, isRetrying: isRetrying }), showSupport && (_jsxs("div", { style: { marginTop: '2rem', padding: '1.5rem', backgroundColor: '#e7f3ff', borderRadius: '8px', border: '1px solid #b3d7ff', }, children: [_jsx("h3", { style: { fontSize: '1.1rem', fontWeight: '600', margin: '0 0 0.5rem 0', color: '#0c5460', }, children: "Need Help?" }), _jsxs("p", { style: { margin: '0 0 1rem 0', fontSize: '0.95rem', color: '#0c5460', }, children: ["If this problem persists, our support team is here to help.", incidentId && ' Please reference the incident ID above when contacting us.'] }), _jsx("button", { onClick: handleSupport, style: { padding: '0.6rem 1.2rem', backgroundColor: '#0c5460', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer', fontSize: '0.9rem', fontWeight: '500', }, children: "Contact Support" })] }))] })); }; // ============================================================================ // GENERIC ERROR PAGE COMPONENT // ============================================================================ /** * Generic error page that can be customized for any error scenario */ export const ErrorPage = ({ title = 'Oops! Something went wrong', message = "We're sorry, but something unexpected happened. Please try again or contact support if the problem persists.", navigationLinks = [ { label: 'Go Home', href: '/', primary: true }, { label: 'Contact Support', href: '/support' }, ], showRetry = true, showSupport = true, onRetry, onSupport, brandName, brandLogo, customActions, className = '', style = {}, }) => { const [isRetrying, setIsRetrying] = useState(false); const handleRetry = useCallback(async () => { setIsRetrying(true); try { if (onRetry) { await onRetry(); } else { window.location.reload(); } } finally { setTimeout(() => setIsRetrying(false), 1000); } }, [onRetry]); const handleSupport = useCallback(() => { if (onSupport) { onSupport(); } else { window.open('/support', '_blank'); } }, [onSupport]); return (_jsxs(ErrorPageLayout, { className: className, style: style, ...(brandName && { brandName }), ...(brandLogo && { brandLogo }), children: [_jsx("div", { style: { fontSize: '5rem', marginBottom: '1.5rem', color: '#6c757d' }, children: "\uD83D\uDE15" }), _jsx("h1", { style: { fontSize: '2rem', fontWeight: '700', margin: '0 0 1rem 0', color: '#212529', lineHeight: '1.3', }, children: title }), _jsx("p", { style: { fontSize: '1.1rem', color: '#6c757d', lineHeight: '1.6', margin: '0 0 2rem 0', }, children: message }), customActions && (_jsx("div", { style: { marginBottom: '2rem' }, children: customActions })), _jsx(NavigationButtons, { links: navigationLinks, showRetry: showRetry, onRetry: handleRetry, isRetrying: isRetrying }), showSupport && (_jsx("div", { style: { marginTop: '2rem', padding: '1rem', backgroundColor: '#f8f9fa', borderRadius: '6px', fontSize: '0.875rem', color: '#6c757d', }, children: _jsxs("p", { style: { margin: '0' }, children: ["Still having trouble?", ' ', _jsx("button", { onClick: handleSupport, style: { background: 'none', border: 'none', color: '#007bff', textDecoration: 'underline', cursor: 'pointer', fontSize: 'inherit', padding: '0', }, children: "Contact our support team" }), ' ', "for assistance."] }) }))] })); }; // ============================================================================ // ERROR BOUNDARY INTEGRATION // ============================================================================ /** * Error boundary wrapper that uses our error pages */ export const ErrorPageBoundary = ({ children, fallbackComponent: FallbackComponent, brandName, brandLogo, onError }) => { const fallback = FallbackComponent ? ((errorDetails) => (_jsx(FallbackComponent, { error: errorDetails.error, errorDetails: errorDetails }))) : ((errorDetails) => (_jsx(ServerErrorPage, { errorCode: 500, errorDetails: errorDetails, ...(brandName && { brandName }), ...(brandLogo && { brandLogo }), showTechnicalDetails: process.env.NODE_ENV === 'development', incidentId: `err-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` }))); return (_jsx(ErrorBoundary, { fallback: fallback, ...(onError && { onError }), enableRetry: true, maxRetries: 3, children: children })); }; export default ErrorPage;