An in-browser wisp.place site explorer
at main 160 lines 5.0 kB view raw
1/** 2 * ErrorDisplay component 3 * 4 * Displays error messages with icons and actionable options. 5 */ 6 7export interface ErrorDisplayProps { 8 error: string; 9 type?: 'not-found' | 'network' | 'cors' | 'general' | 'no-wisp-records'; 10 onRetry?: () => void; 11 onBack?: () => void; 12 showProxySuggestion?: boolean; 13} 14 15const errorIcons: Record<Exclude<ErrorDisplayProps['type'], undefined>, { icon: string; color: string }> = { 16 'not-found': { icon: '🔍', color: 'text-amber-500' }, 17 'network': { icon: '🌐', color: 'text-red-500' }, 18 'cors': { icon: '🔒', color: 'text-orange-500' }, 19 'general': { icon: '⚠️', color: 'text-gray-500' }, 20 'no-wisp-records': { icon: '📁', color: 'text-amber-500' }, 21}; 22 23const errorTitles: Record<Exclude<ErrorDisplayProps['type'], undefined>, string> = { 24 'not-found': 'Not Found', 25 'network': 'Network Error', 26 'cors': 'Connection Error', 27 'general': 'Error', 28 'no-wisp-records': 'No Site Found', 29}; 30 31export function ErrorDisplay({ 32 error, 33 type = 'general', 34 onRetry, 35 onBack, 36 showProxySuggestion = false, 37}: ErrorDisplayProps) { 38 const { icon, color } = errorIcons[type]; 39 const title = errorTitles[type]; 40 41 return ( 42 <div className="bg-white rounded-lg shadow-lg p-8 max-w-md mx-auto"> 43 {/* Icon */} 44 <div className="flex justify-center mb-4"> 45 <span className="text-4xl">{icon}</span> 46 </div> 47 48 {/* Title and Message */} 49 <div className="text-center mb-6"> 50 <h3 className={`text-xl font-semibold ${color} mb-2`}>{title}</h3> 51 <p className="text-gray-600 text-sm leading-relaxed">{error}</p> 52 </div> 53 54 {/* Proxy suggestion for CORS errors */} 55 {showProxySuggestion && ( 56 <div className="bg-amber-50 border border-amber-200 rounded-lg p-3 mb-6"> 57 <p className="text-amber-800 text-xs"> 58 <strong>Note:</strong> Some PDS servers don't support direct browser access. 59 Consider using a CORS proxy for this operation. 60 </p> 61 </div> 62 )} 63 64 {/* Action buttons */} 65 <div className="flex flex-col sm:flex-row gap-3"> 66 {onRetry && ( 67 <button 68 onClick={onRetry} 69 className="flex-1 px-4 py-2 bg-sky-500 text-white rounded-lg hover:bg-sky-600 transition-colors font-medium" 70 > 71 Try Again 72 </button> 73 )} 74 {onBack && ( 75 <button 76 onClick={onBack} 77 className={`px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors font-medium ${ 78 onRetry ? 'flex-1' : 'w-full' 79 }`} 80 > 81 Back to Resolver 82 </button> 83 )} 84 </div> 85 </div> 86 ); 87} 88 89/** 90 * Compact inline error display 91 */ 92export interface InlineErrorProps { 93 error: string; 94 onDismiss?: () => void; 95} 96 97export function InlineError({ error, onDismiss }: InlineErrorProps) { 98 return ( 99 <div className="bg-red-50 border border-red-200 rounded-lg p-3 flex items-start gap-2"> 100 <span className="text-red-500 mt-0.5">⚠️</span> 101 <div className="flex-1"> 102 <p className="text-red-800 text-sm">{error}</p> 103 </div> 104 {onDismiss && ( 105 <button 106 onClick={onDismiss} 107 className="text-red-400 hover:text-red-600 transition-colors" 108 aria-label="Dismiss" 109 > 110111 </button> 112 )} 113 </div> 114 ); 115} 116 117/** 118 * Error boundary fallback component 119 */ 120export interface ErrorFallbackProps { 121 error: Error; 122 resetError?: () => void; 123} 124 125export function ErrorFallback({ error, resetError }: ErrorFallbackProps) { 126 return ( 127 <div className="min-h-screen bg-gradient-to-br from-red-50 to-orange-50 flex items-center justify-center p-4"> 128 <div className="bg-white rounded-lg shadow-lg p-8 max-w-lg w-full"> 129 <div className="text-center mb-6"> 130 <span className="text-5xl">💥</span> 131 <h2 className="text-2xl font-bold text-gray-800 mt-4 mb-2">Something went wrong</h2> 132 <p className="text-gray-600">An unexpected error occurred while loading the page.</p> 133 </div> 134 135 {error.message && ( 136 <div className="bg-gray-100 rounded-lg p-4 mb-6"> 137 <p className="font-mono text-sm text-gray-700 break-all">{error.message}</p> 138 </div> 139 )} 140 141 {resetError && ( 142 <div className="flex gap-3"> 143 <button 144 onClick={resetError} 145 className="flex-1 px-4 py-2 bg-sky-500 text-white rounded-lg hover:bg-sky-600 transition-colors font-medium" 146 > 147 Try Again 148 </button> 149 <button 150 onClick={() => window.location.href = window.location.pathname} 151 className="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors font-medium" 152 > 153 Reset 154 </button> 155 </div> 156 )} 157 </div> 158 </div> 159 ); 160}