vue · 6744 bytes Raw Blame History
1 <template>
2 <div v-if="hasError" class="error-boundary">
3 <div class="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center px-4">
4 <div class="max-w-md w-full">
5 <!-- Error icon -->
6 <div class="text-center mb-6">
7 <ExclamationTriangleIcon class="w-16 h-16 text-red-500 mx-auto mb-4" />
8 <h1 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">
9 Something went wrong
10 </h1>
11 <p class="text-gray-600 dark:text-gray-400">
12 We encountered an unexpected error. This has been reported to our team.
13 </p>
14 </div>
15
16 <!-- Error details (development only) -->
17 <div v-if="showDetails && errorInfo" class="mb-6 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md">
18 <details class="text-sm">
19 <summary class="font-medium text-red-800 dark:text-red-200 cursor-pointer">
20 Error Details
21 </summary>
22 <div class="mt-2 text-red-700 dark:text-red-300 space-y-2">
23 <div>
24 <strong>Message:</strong> {{ errorInfo.message }}
25 </div>
26 <div v-if="errorInfo.stack">
27 <strong>Stack trace:</strong>
28 <pre class="mt-1 text-xs bg-red-100 dark:bg-red-900/30 p-2 rounded overflow-x-auto">{{ errorInfo.stack }}</pre>
29 </div>
30 <div>
31 <strong>Timestamp:</strong> {{ new Date(errorInfo.timestamp).toLocaleString() }}
32 </div>
33 <div v-if="errorInfo.context">
34 <strong>Context:</strong>
35 <pre class="mt-1 text-xs bg-red-100 dark:bg-red-900/30 p-2 rounded overflow-x-auto">{{ JSON.stringify(errorInfo.context, null, 2) }}</pre>
36 </div>
37 </div>
38 </details>
39 </div>
40
41 <!-- Actions -->
42 <div class="flex flex-col sm:flex-row gap-3">
43 <button
44 @click="handleReload"
45 class="btn btn-primary flex-1"
46 >
47 <ArrowPathIcon class="w-4 h-4 mr-2" />
48 Reload Page
49 </button>
50
51 <button
52 @click="handleReset"
53 class="btn btn-outline flex-1"
54 >
55 Reset Application
56 </button>
57 </div>
58
59 <!-- Additional actions -->
60 <div class="mt-4 text-center space-y-2">
61 <button
62 v-if="errorInfo?.retryable"
63 @click="handleRetry"
64 class="text-sm text-primary-600 hover:text-primary-700 dark:text-primary-400"
65 >
66 Try Last Action Again
67 </button>
68
69 <div class="text-xs text-gray-500 dark:text-gray-400">
70 Error ID: {{ errorInfo?.id || 'unknown' }}
71 </div>
72 </div>
73
74 <!-- Report issue link -->
75 <div class="mt-6 text-center">
76 <a
77 href="https://github.com/anthropics/claude-code/issues"
78 target="_blank"
79 rel="noopener noreferrer"
80 class="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
81 >
82 Report this issue on GitHub
83 </a>
84 </div>
85 </div>
86 </div>
87 </div>
88
89 <slot v-else />
90 </template>
91
92 <script setup lang="ts">
93 import { ref, onErrorCaptured, onMounted } from 'vue'
94 import { ExclamationTriangleIcon, ArrowPathIcon } from '@heroicons/vue/24/outline'
95 import { useErrorHandler, type ErrorInfo } from '@/composables/useErrorHandler'
96
97 const hasError = ref(false)
98 const errorInfo = ref<ErrorInfo | null>(null)
99 const showDetails = ref(import.meta.env.DEV)
100 const lastAction = ref<Function | null>(null)
101
102 const { handleError } = useErrorHandler()
103
104 // Capture Vue component errors
105 onErrorCaptured((error: Error, instance: any, info: string) => {
106 const errorDetails = handleError(error, {
107 component: instance?.$options.name || 'Unknown',
108 errorInfo: info,
109 timestamp: Date.now(),
110 })
111
112 hasError.value = true
113 errorInfo.value = {
114 ...errorDetails,
115 stack: error.stack,
116 context: {
117 ...errorDetails.context,
118 vueInfo: info,
119 },
120 }
121
122 // Prevent the error from propagating further
123 return false
124 })
125
126 // Handle global errors
127 onMounted(() => {
128 const originalOnError = window.onerror
129 const originalOnUnhandledRejection = window.onunhandledrejection
130
131 window.onerror = (message, source, lineno, colno, error) => {
132 if (error) {
133 const errorDetails = handleError(error, {
134 source,
135 lineno,
136 colno,
137 timestamp: Date.now(),
138 })
139
140 hasError.value = true
141 errorInfo.value = {
142 ...errorDetails,
143 stack: error.stack,
144 }
145 }
146
147 // Call original handler
148 if (originalOnError) {
149 return originalOnError(message, source, lineno, colno, error)
150 }
151 return false
152 }
153
154 window.onunhandledrejection = (event) => {
155 const errorDetails = handleError(event.reason, {
156 type: 'unhandled_promise_rejection',
157 timestamp: Date.now(),
158 })
159
160 hasError.value = true
161 errorInfo.value = errorDetails
162
163 // Call original handler
164 if (originalOnUnhandledRejection) {
165 return originalOnUnhandledRejection(event)
166 }
167 }
168 })
169
170 function handleReload() {
171 window.location.reload()
172 }
173
174 function handleReset() {
175 // Clear all localStorage/sessionStorage
176 localStorage.clear()
177 sessionStorage.clear()
178
179 // Clear any cached data
180 if ('caches' in window) {
181 caches.keys().then(names => {
182 names.forEach(name => {
183 caches.delete(name)
184 })
185 })
186 }
187
188 // Reload the page
189 window.location.reload()
190 }
191
192 function handleRetry() {
193 if (lastAction.value) {
194 hasError.value = false
195 errorInfo.value = null
196
197 try {
198 lastAction.value()
199 } catch (error) {
200 // If retry fails, show error again
201 setTimeout(() => {
202 const errorDetails = handleError(error, {
203 retry: true,
204 timestamp: Date.now(),
205 })
206 hasError.value = true
207 errorInfo.value = errorDetails
208 }, 100)
209 }
210 }
211 }
212
213 // Expose method to manually trigger error boundary
214 function triggerError(error: Error, action?: Function) {
215 const errorDetails = handleError(error, {
216 manual: true,
217 timestamp: Date.now(),
218 })
219
220 hasError.value = true
221 errorInfo.value = errorDetails
222 lastAction.value = action || null
223 }
224
225 defineExpose({
226 triggerError,
227 })
228 </script>
229
230 <style scoped>
231 .error-boundary {
232 position: fixed;
233 inset: 0;
234 z-index: 9999;
235 background: rgba(0, 0, 0, 0.1);
236 backdrop-filter: blur(4px);
237 }
238
239 pre {
240 font-family: 'JetBrains Mono', 'Menlo', 'Monaco', monospace;
241 font-size: 10px;
242 line-height: 1.4;
243 }
244
245 details[open] summary {
246 margin-bottom: 8px;
247 }
248 </style>