An in-browser wisp.place site explorer
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 >
110 ✕
111 </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}