@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
JavaScript
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;