@geenius/tools 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.env.example +2 -0
  3. package/.github/CODEOWNERS +1 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
  6. package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
  7. package/.github/dependabot.yml +11 -0
  8. package/.github/workflows/ci.yml +23 -0
  9. package/.github/workflows/release.yml +29 -0
  10. package/.node-version +1 -0
  11. package/.nvmrc +1 -0
  12. package/.prettierrc +7 -0
  13. package/.project/ACCOUNT.yaml +4 -0
  14. package/.project/IDEAS.yaml +7 -0
  15. package/.project/PROJECT.yaml +11 -0
  16. package/.project/ROADMAP.yaml +15 -0
  17. package/CHANGELOG.md +16 -0
  18. package/CODE_OF_CONDUCT.md +26 -0
  19. package/CONTRIBUTING.md +69 -0
  20. package/LICENSE +21 -0
  21. package/README.md +1 -0
  22. package/SECURITY.md +18 -0
  23. package/SUPPORT.md +14 -0
  24. package/package.json +75 -0
  25. package/packages/convex/shared/README.md +1 -0
  26. package/packages/convex/shared/package.json +42 -0
  27. package/packages/convex/shared/src/audit/index.ts +5 -0
  28. package/packages/convex/shared/src/audit/presets.ts +165 -0
  29. package/packages/convex/shared/src/audit/schema.ts +85 -0
  30. package/packages/convex/shared/src/audit/write.ts +102 -0
  31. package/packages/convex/shared/src/extract.ts +75 -0
  32. package/packages/convex/shared/src/index.ts +41 -0
  33. package/packages/convex/shared/src/messages.ts +45 -0
  34. package/packages/convex/shared/src/security.ts +112 -0
  35. package/packages/convex/shared/src/throw.ts +184 -0
  36. package/packages/convex/shared/src/types.ts +57 -0
  37. package/packages/convex/shared/src/utils.ts +58 -0
  38. package/packages/convex/shared/tsconfig.json +28 -0
  39. package/packages/convex/shared/tsup.config.ts +12 -0
  40. package/packages/devtools/package.json +27 -0
  41. package/packages/devtools/react/README.md +1 -0
  42. package/packages/devtools/react/package.json +53 -0
  43. package/packages/devtools/react/src/components/DesignPreview.tsx +59 -0
  44. package/packages/devtools/react/src/components/DesignSwitcherDropdown.tsx +99 -0
  45. package/packages/devtools/react/src/components/DevSidebar.tsx +247 -0
  46. package/packages/devtools/react/src/components/DevToolbar.tsx +242 -0
  47. package/packages/devtools/react/src/components/GitHubIssueDialog.tsx +402 -0
  48. package/packages/devtools/react/src/components/InspectorOverlay.tsx +312 -0
  49. package/packages/devtools/react/src/components/PageLoadWaterfall.tsx +144 -0
  50. package/packages/devtools/react/src/components/PerformancePanel.tsx +330 -0
  51. package/packages/devtools/react/src/context/DevModeContext.tsx +226 -0
  52. package/packages/devtools/react/src/context/PerformanceContext.tsx +143 -0
  53. package/packages/devtools/react/src/data/designs.ts +13 -0
  54. package/packages/devtools/react/src/hooks/useGitHubLabels.ts +47 -0
  55. package/packages/devtools/react/src/hooks/useVirtualList.ts +124 -0
  56. package/packages/devtools/react/src/index.ts +77 -0
  57. package/packages/devtools/react/src/panels/ConvexSpy.tsx +130 -0
  58. package/packages/devtools/react/src/panels/DatabaseSeeder.tsx +116 -0
  59. package/packages/devtools/react/src/panels/DevModePhase2.tsx +191 -0
  60. package/packages/devtools/react/src/panels/DevModePhase3.tsx +234 -0
  61. package/packages/devtools/react/src/panels/FeatureFlagsToggle.tsx +104 -0
  62. package/packages/devtools/react/src/panels/QuickRouteJump.tsx +152 -0
  63. package/packages/devtools/react/src/services/github-service.ts +247 -0
  64. package/packages/devtools/react/tsconfig.json +31 -0
  65. package/packages/devtools/react/tsup.config.ts +18 -0
  66. package/packages/devtools/solidjs/README.md +1 -0
  67. package/packages/devtools/solidjs/package.json +49 -0
  68. package/packages/devtools/solidjs/src/components/DesignPreview.tsx +51 -0
  69. package/packages/devtools/solidjs/src/components/DesignSwitcherDropdown.tsx +95 -0
  70. package/packages/devtools/solidjs/src/components/DevSidebar.tsx +247 -0
  71. package/packages/devtools/solidjs/src/components/DevToolbar.tsx +242 -0
  72. package/packages/devtools/solidjs/src/components/GitHubIssueDialog.tsx +400 -0
  73. package/packages/devtools/solidjs/src/components/InspectorOverlay.tsx +311 -0
  74. package/packages/devtools/solidjs/src/components/PageLoadWaterfall.tsx +144 -0
  75. package/packages/devtools/solidjs/src/components/PerformancePanel.tsx +330 -0
  76. package/packages/devtools/solidjs/src/context/DevModeContext.tsx +216 -0
  77. package/packages/devtools/solidjs/src/context/PerformanceContext.tsx +135 -0
  78. package/packages/devtools/solidjs/src/data/designs.ts +13 -0
  79. package/packages/devtools/solidjs/src/hooks/createGitHubLabels.ts +47 -0
  80. package/packages/devtools/solidjs/src/index.ts +64 -0
  81. package/packages/devtools/solidjs/src/services/github-service.ts +247 -0
  82. package/packages/devtools/solidjs/tsconfig.json +21 -0
  83. package/packages/devtools/src/index.ts +377 -0
  84. package/packages/devtools/tsup.config.ts +12 -0
  85. package/packages/env/package.json +30 -0
  86. package/packages/env/src/index.ts +264 -0
  87. package/packages/env/tsup.config.ts +12 -0
  88. package/packages/errors/package.json +27 -0
  89. package/packages/errors/react/README.md +1 -0
  90. package/packages/errors/react/package.json +72 -0
  91. package/packages/errors/react/src/analytics.ts +16 -0
  92. package/packages/errors/react/src/components/ErrorBoundary.tsx +248 -0
  93. package/packages/errors/react/src/components/ErrorDisplay.tsx +328 -0
  94. package/packages/errors/react/src/components/ValidationErrors.tsx +102 -0
  95. package/packages/errors/react/src/config.ts +199 -0
  96. package/packages/errors/react/src/constants.ts +74 -0
  97. package/packages/errors/react/src/hooks/useErrorBoundary.ts +92 -0
  98. package/packages/errors/react/src/hooks/useErrorHandler.ts +87 -0
  99. package/packages/errors/react/src/index.ts +96 -0
  100. package/packages/errors/react/src/types.ts +102 -0
  101. package/packages/errors/react/src/utils/errorMessages.ts +35 -0
  102. package/packages/errors/react/src/utils/errorPolicy.ts +139 -0
  103. package/packages/errors/react/src/utils/extractAppError.ts +174 -0
  104. package/packages/errors/react/src/utils/formatError.ts +112 -0
  105. package/packages/errors/react/tsconfig.json +25 -0
  106. package/packages/errors/react/tsup.config.ts +24 -0
  107. package/packages/errors/solidjs/README.md +1 -0
  108. package/packages/errors/solidjs/package.json +46 -0
  109. package/packages/errors/solidjs/src/components/ErrorDisplay.tsx +179 -0
  110. package/packages/errors/solidjs/src/config.ts +98 -0
  111. package/packages/errors/solidjs/src/hooks/createErrorHandler.ts +107 -0
  112. package/packages/errors/solidjs/src/index.ts +61 -0
  113. package/packages/errors/solidjs/src/types.ts +34 -0
  114. package/packages/errors/solidjs/src/utils/errorPolicy.ts +56 -0
  115. package/packages/errors/solidjs/src/utils/extractAppError.ts +94 -0
  116. package/packages/errors/solidjs/src/utils/formatError.ts +33 -0
  117. package/packages/errors/solidjs/tsconfig.json +26 -0
  118. package/packages/errors/solidjs/tsup.config.ts +21 -0
  119. package/packages/errors/src/index.ts +320 -0
  120. package/packages/errors/tsup.config.ts +12 -0
  121. package/packages/logger/package.json +27 -0
  122. package/packages/logger/react/README.md +1 -0
  123. package/packages/logger/react/package.json +46 -0
  124. package/packages/logger/react/src/index.ts +4 -0
  125. package/packages/logger/react/src/useMetrics.ts +42 -0
  126. package/packages/logger/react/src/usePerformanceLog.ts +61 -0
  127. package/packages/logger/react/tsconfig.json +31 -0
  128. package/packages/logger/react/tsup.config.ts +12 -0
  129. package/packages/logger/solidjs/README.md +1 -0
  130. package/packages/logger/solidjs/package.json +45 -0
  131. package/packages/logger/solidjs/src/createMetrics.ts +37 -0
  132. package/packages/logger/solidjs/src/createPerformanceLog.ts +58 -0
  133. package/packages/logger/solidjs/src/index.ts +4 -0
  134. package/packages/logger/solidjs/tsconfig.json +32 -0
  135. package/packages/logger/solidjs/tsup.config.ts +12 -0
  136. package/packages/logger/src/index.ts +363 -0
  137. package/packages/logger/tsup.config.ts +12 -0
  138. package/packages/perf/package.json +27 -0
  139. package/packages/perf/react/README.md +1 -0
  140. package/packages/perf/react/package.json +59 -0
  141. package/packages/perf/react/src/components/PerformanceDashboard.tsx +257 -0
  142. package/packages/perf/react/src/hooks/useMonitoredQuery.ts +89 -0
  143. package/packages/perf/react/src/hooks/usePerformanceMetrics.ts +78 -0
  144. package/packages/perf/react/src/index.ts +33 -0
  145. package/packages/perf/react/src/services/PerformanceMonitor.ts +313 -0
  146. package/packages/perf/react/src/types.ts +77 -0
  147. package/packages/perf/react/tsconfig.json +25 -0
  148. package/packages/perf/react/tsup.config.ts +19 -0
  149. package/packages/perf/solidjs/README.md +1 -0
  150. package/packages/perf/solidjs/package.json +41 -0
  151. package/packages/perf/solidjs/src/components/PerformanceDashboard.tsx +207 -0
  152. package/packages/perf/solidjs/src/hooks/createPerformanceMetrics.ts +73 -0
  153. package/packages/perf/solidjs/src/index.ts +31 -0
  154. package/packages/perf/solidjs/src/services/PerformanceMonitor.ts +134 -0
  155. package/packages/perf/solidjs/src/types.ts +78 -0
  156. package/packages/perf/solidjs/tsconfig.json +26 -0
  157. package/packages/perf/solidjs/tsup.config.ts +14 -0
  158. package/packages/perf/src/index.ts +410 -0
  159. package/packages/perf/tsup.config.ts +12 -0
  160. package/pnpm-workspace.yaml +2 -0
@@ -0,0 +1 @@
1
+ # ✦ @geenius-tools/errors-react\n\n> React error handling UI — boundaries, displays, analytics dashboard, hooks & utilities\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius-tools/errors-react\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-tools/errors-react';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@geenius-tools/errors-react",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "React error handling UI \u2014 boundaries, displays, analytics dashboard, hooks & utilities",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./analytics": {
16
+ "types": "./dist/analytics.d.ts",
17
+ "import": "./dist/analytics.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "dev": "tsup --watch",
27
+ "typecheck": "tsc --noEmit"
28
+ },
29
+ "dependencies": {
30
+ "@geenius-tools/convex": "workspace:*"
31
+ },
32
+ "devDependencies": {
33
+ "@geenius-ui/react": "workspace:*",
34
+ "@tanstack/react-query": "^5.91.3",
35
+ "@tanstack/react-router": "^1.168.1",
36
+ "@types/react": "^19.0.0",
37
+ "@types/react-dom": "^19.0.0",
38
+ "convex": "^1.34.0",
39
+ "lucide-react": "^0.577.0",
40
+ "react": "^19.2.4",
41
+ "react-dom": "^19.2.4",
42
+ "tsup": "^8.5.1",
43
+ "typescript": "~5.9.3"
44
+ },
45
+ "peerDependencies": {
46
+ "lucide-react": ">=0.300.0",
47
+ "react": "^18.0.0 || ^19.0.0",
48
+ "react-dom": "^18.0.0 || ^19.0.0"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@geenius-ui/react": {
52
+ "optional": true
53
+ },
54
+ "@tanstack/react-router": {
55
+ "optional": true
56
+ },
57
+ "@tanstack/react-query": {
58
+ "optional": true
59
+ },
60
+ "convex": {
61
+ "optional": true
62
+ }
63
+ },
64
+ "author": "Antigravity HQ",
65
+ "license": "MIT",
66
+ "engines": {
67
+ "node": ">=20.0.0"
68
+ },
69
+ "publishConfig": {
70
+ "access": "public"
71
+ }
72
+ }
@@ -0,0 +1,16 @@
1
+ // @geenius-tools/errors-react/analytics
2
+ // Convex-dependent error analytics (separate entry point)
3
+ // Requires: convex, @tanstack/react-query as peer deps
4
+
5
+ // Re-export analytics types
6
+ export type {
7
+ ErrorAnalyticsFilters,
8
+ ErrorAnalyticsEntry,
9
+ ErrorMute,
10
+ ErrorTrend,
11
+ } from './types'
12
+
13
+ // Note: ErrorAnalyticsService, useErrorAnalytics hooks, and ErrorAnalyticsDashboard
14
+ // are tightly bound to a specific Convex API shape (api.library.system.errorAnalytics).
15
+ // They will be added here once we define a generic analytics adapter pattern.
16
+ // For now, consumers should copy these from the boilerplate and adapt to their Convex schema.
@@ -0,0 +1,248 @@
1
+ // @geenius-tools/errors-react — src/components/ErrorBoundary.tsx
2
+
3
+ import { Component, ReactNode, ErrorInfo } from 'react'
4
+ import { AlertTriangle } from 'lucide-react'
5
+
6
+ export interface ErrorBoundaryProps {
7
+ children: ReactNode
8
+ fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode)
9
+ onError?: (error: Error, errorInfo: ErrorInfo) => void
10
+ name?: string
11
+ }
12
+
13
+ interface ErrorBoundaryState {
14
+ hasError: boolean
15
+ error: Error | null
16
+ }
17
+
18
+ /**
19
+ * Error Boundary Component
20
+ *
21
+ * React error boundary for catching component-level errors.
22
+ * Prevents entire app from crashing when a component throws an error.
23
+ *
24
+ * @example
25
+ * <ErrorBoundary name="Dashboard">
26
+ * <Dashboard />
27
+ * </ErrorBoundary>
28
+ */
29
+ export class ErrorBoundary extends Component<
30
+ ErrorBoundaryProps,
31
+ ErrorBoundaryState
32
+ > {
33
+ constructor(props: ErrorBoundaryProps) {
34
+ super(props)
35
+ this.state = {
36
+ hasError: false,
37
+ error: null,
38
+ }
39
+ }
40
+
41
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
42
+ return { hasError: true, error }
43
+ }
44
+
45
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
46
+ try {
47
+ if (typeof globalThis !== 'undefined' && (globalThis as any).process?.env?.NODE_ENV === 'development') {
48
+ console.error('ErrorBoundary caught error:', error, errorInfo)
49
+ }
50
+ } catch { /* ignore */ }
51
+ this.props.onError?.(error, errorInfo)
52
+ }
53
+
54
+ reset = () => {
55
+ this.setState({ hasError: false, error: null })
56
+ }
57
+
58
+ render() {
59
+ if (this.state.hasError && this.state.error) {
60
+ if (this.props.fallback) {
61
+ if (typeof this.props.fallback === 'function') {
62
+ return this.props.fallback(this.state.error, this.reset)
63
+ }
64
+ return this.props.fallback
65
+ }
66
+
67
+ return (
68
+ <DefaultErrorFallback
69
+ error={this.state.error}
70
+ reset={this.reset}
71
+ boundaryName={this.props.name}
72
+ />
73
+ )
74
+ }
75
+
76
+ return this.props.children
77
+ }
78
+ }
79
+
80
+ function DefaultErrorFallback({
81
+ error,
82
+ reset,
83
+ boundaryName,
84
+ }: {
85
+ error: Error
86
+ reset: () => void
87
+ boundaryName?: string
88
+ }) {
89
+ let isDevelopment = false
90
+ try {
91
+ isDevelopment = typeof globalThis !== 'undefined' && (globalThis as any).process?.env?.NODE_ENV === 'development'
92
+ } catch { /* ignore */ }
93
+
94
+ return (
95
+ <div style={{
96
+ display: 'flex',
97
+ alignItems: 'center',
98
+ justifyContent: 'center',
99
+ minHeight: '400px',
100
+ padding: '24px',
101
+ }}>
102
+ <div style={{
103
+ maxWidth: '28rem',
104
+ width: '100%',
105
+ borderRadius: '8px',
106
+ border: '1px solid var(--border, #e5e7eb)',
107
+ backgroundColor: 'var(--panel, #fff)',
108
+ padding: '24px',
109
+ display: 'flex',
110
+ flexDirection: 'column',
111
+ gap: '16px',
112
+ }}>
113
+ <div style={{ display: 'flex', alignItems: 'flex-start', gap: '16px' }}>
114
+ <div style={{
115
+ height: '48px',
116
+ width: '48px',
117
+ display: 'flex',
118
+ alignItems: 'center',
119
+ justifyContent: 'center',
120
+ borderRadius: '12px',
121
+ backgroundColor: 'rgba(239, 68, 68, 0.1)',
122
+ flexShrink: 0,
123
+ }}>
124
+ <AlertTriangle
125
+ style={{ height: '24px', width: '24px', color: '#dc2626' }}
126
+ aria-hidden
127
+ />
128
+ </div>
129
+
130
+ <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: '8px' }}>
131
+ <div>
132
+ <p style={{
133
+ fontSize: '12px',
134
+ fontWeight: 600,
135
+ textTransform: 'uppercase',
136
+ letterSpacing: '0.18em',
137
+ color: '#dc2626',
138
+ margin: 0,
139
+ }}>
140
+ Component Error
141
+ </p>
142
+ {boundaryName && (
143
+ <p style={{
144
+ fontSize: '12px',
145
+ color: 'var(--muted-foreground, #6b7280)',
146
+ marginTop: '4px',
147
+ margin: '4px 0 0',
148
+ }}>
149
+ in {boundaryName}
150
+ </p>
151
+ )}
152
+ </div>
153
+ <h2 style={{
154
+ fontSize: '20px',
155
+ fontWeight: 700,
156
+ color: 'var(--foreground, #111)',
157
+ margin: 0,
158
+ }}>
159
+ Something went wrong
160
+ </h2>
161
+ <p style={{
162
+ fontSize: '14px',
163
+ color: 'var(--muted-foreground, #6b7280)',
164
+ margin: 0,
165
+ }}>
166
+ An error occurred in this component. Try again or contact support.
167
+ </p>
168
+ </div>
169
+ </div>
170
+
171
+ {isDevelopment && (
172
+ <div style={{
173
+ borderRadius: '8px',
174
+ border: '1px solid var(--border, #e5e7eb)',
175
+ backgroundColor: 'var(--bg-muted, #f9fafb)',
176
+ padding: '12px',
177
+ }}>
178
+ <p style={{
179
+ fontSize: '12px',
180
+ fontWeight: 600,
181
+ margin: '0 0 8px',
182
+ opacity: 0.8,
183
+ }}>
184
+ Error details (development only)
185
+ </p>
186
+ <p style={{
187
+ fontSize: '12px',
188
+ fontFamily: 'monospace',
189
+ color: '#dc2626',
190
+ margin: '0 0 8px',
191
+ }}>
192
+ {error.message}
193
+ </p>
194
+ {error.stack && (
195
+ <pre style={{
196
+ fontSize: '12px',
197
+ fontFamily: 'monospace',
198
+ color: 'var(--muted-foreground, #6b7280)',
199
+ whiteSpace: 'pre-wrap',
200
+ wordBreak: 'break-word',
201
+ maxHeight: '160px',
202
+ overflow: 'auto',
203
+ margin: 0,
204
+ }}>
205
+ {error.stack}
206
+ </pre>
207
+ )}
208
+ </div>
209
+ )}
210
+
211
+ <div style={{ display: 'flex', gap: '12px' }}>
212
+ <button
213
+ onClick={reset}
214
+ style={{
215
+ flex: 1,
216
+ padding: '8px 16px',
217
+ borderRadius: '6px',
218
+ border: 'none',
219
+ backgroundColor: 'var(--primary, #3b82f6)',
220
+ color: '#fff',
221
+ fontWeight: 500,
222
+ cursor: 'pointer',
223
+ fontSize: '14px',
224
+ }}
225
+ >
226
+ Try Again
227
+ </button>
228
+ <button
229
+ onClick={() => window.location.reload()}
230
+ style={{
231
+ flex: 1,
232
+ padding: '8px 16px',
233
+ borderRadius: '6px',
234
+ border: '1px solid var(--border, #e5e7eb)',
235
+ backgroundColor: 'transparent',
236
+ color: 'var(--foreground, #111)',
237
+ fontWeight: 500,
238
+ cursor: 'pointer',
239
+ fontSize: '14px',
240
+ }}
241
+ >
242
+ Reload Page
243
+ </button>
244
+ </div>
245
+ </div>
246
+ </div>
247
+ )
248
+ }
@@ -0,0 +1,328 @@
1
+ // @geenius-tools/errors-react — src/components/ErrorDisplay.tsx
2
+
3
+ import { FC } from 'react'
4
+ import { RefreshCw } from 'lucide-react'
5
+ import {
6
+ extractAppError,
7
+ extractRequestId,
8
+ normalizeErrorMessage,
9
+ } from '../utils/extractAppError'
10
+ import {
11
+ getErrorTitle,
12
+ getFallbackMessage,
13
+ getErrorAction,
14
+ } from '../utils/errorMessages'
15
+ import {
16
+ ERROR_CODE_ICONS,
17
+ ERROR_CODE_BG_COLORS,
18
+ ERROR_CODE_COLORS,
19
+ } from '../constants'
20
+ import { ValidationErrors } from './ValidationErrors'
21
+ import type { ErrorDisplayVariant } from '../types'
22
+
23
+ export interface ErrorDisplayProps {
24
+ error: unknown
25
+ onRetry?: () => void
26
+ variant?: ErrorDisplayVariant
27
+ compact?: boolean
28
+ showRequestId?: boolean
29
+ customAction?: {
30
+ label: string
31
+ onClick?: () => void
32
+ href?: string
33
+ }
34
+ className?: string
35
+ }
36
+
37
+ /**
38
+ * Error Display Component
39
+ *
40
+ * Displays errors with appropriate styling based on error code.
41
+ * Integrates tightly with backend error structure.
42
+ *
43
+ * @example
44
+ * <ErrorDisplay
45
+ * error={error}
46
+ * onRetry={() => refetch()}
47
+ * showRequestId
48
+ * />
49
+ */
50
+ export const ErrorDisplay: FC<ErrorDisplayProps> = ({
51
+ error,
52
+ onRetry,
53
+ variant = 'card',
54
+ compact = false,
55
+ showRequestId = false,
56
+ customAction,
57
+ className = '',
58
+ }) => {
59
+ const appError = extractAppError(error)
60
+ const requestId = extractRequestId(error)
61
+
62
+ if (!appError) {
63
+ return (
64
+ <GenericErrorDisplay
65
+ error={error}
66
+ onRetry={onRetry}
67
+ compact={compact}
68
+ />
69
+ )
70
+ }
71
+
72
+ const Icon = ERROR_CODE_ICONS[appError.code]
73
+ const colorClass = ERROR_CODE_COLORS[appError.code]
74
+ const bgClass = ERROR_CODE_BG_COLORS[appError.code]
75
+ const title = getErrorTitle(appError.code)
76
+ const message = appError.message ?? getFallbackMessage(appError.code)
77
+ const action = getErrorAction(appError.code)
78
+
79
+ if (variant === 'inline') {
80
+ return (
81
+ <div className={className} style={{
82
+ display: 'flex',
83
+ alignItems: 'center',
84
+ gap: '8px',
85
+ padding: compact ? '8px' : '12px',
86
+ borderRadius: '8px',
87
+ border: '1px solid var(--border, #e5e7eb)',
88
+ }}>
89
+ {Icon && <Icon style={{ height: '16px', width: '16px', flexShrink: 0 }} className={colorClass} />}
90
+ <span style={{ fontSize: '14px', color: 'var(--foreground, #111)' }}>
91
+ {message}
92
+ </span>
93
+ {onRetry && (
94
+ <button
95
+ onClick={onRetry}
96
+ style={{
97
+ marginLeft: 'auto',
98
+ padding: '4px',
99
+ border: 'none',
100
+ background: 'none',
101
+ cursor: 'pointer',
102
+ borderRadius: '4px',
103
+ }}
104
+ title="Retry"
105
+ >
106
+ <RefreshCw style={{ height: '14px', width: '14px' }} />
107
+ </button>
108
+ )}
109
+ </div>
110
+ )
111
+ }
112
+
113
+ // Card variant (default)
114
+ return (
115
+ <div className={className} style={{
116
+ borderRadius: '8px',
117
+ border: '1px solid var(--border, #e5e7eb)',
118
+ backgroundColor: 'var(--panel, #fff)',
119
+ overflow: 'hidden',
120
+ }}>
121
+ <div style={{
122
+ padding: compact ? '16px' : '24px',
123
+ display: 'flex',
124
+ flexDirection: 'column',
125
+ gap: '16px',
126
+ }}>
127
+ <div style={{ display: 'flex', alignItems: 'flex-start', gap: '16px' }}>
128
+ {Icon && (
129
+ <div
130
+ className={bgClass}
131
+ style={{
132
+ height: compact ? '32px' : '40px',
133
+ width: compact ? '32px' : '40px',
134
+ display: 'flex',
135
+ alignItems: 'center',
136
+ justifyContent: 'center',
137
+ borderRadius: '10px',
138
+ flexShrink: 0,
139
+ }}
140
+ >
141
+ <Icon
142
+ className={colorClass}
143
+ style={{
144
+ height: compact ? '16px' : '20px',
145
+ width: compact ? '16px' : '20px',
146
+ }}
147
+ />
148
+ </div>
149
+ )}
150
+
151
+ <div style={{ flex: 1, minWidth: 0 }}>
152
+ <h3 style={{
153
+ fontSize: compact ? '14px' : '16px',
154
+ fontWeight: 600,
155
+ color: 'var(--foreground, #111)',
156
+ margin: '0 0 4px',
157
+ }}>
158
+ {title}
159
+ </h3>
160
+ <p style={{
161
+ fontSize: compact ? '13px' : '14px',
162
+ color: 'var(--muted-foreground, #6b7280)',
163
+ margin: 0,
164
+ }}>
165
+ {message}
166
+ </p>
167
+ </div>
168
+ </div>
169
+
170
+ {appError.validationErrors && appError.validationErrors.length > 0 && (
171
+ <ValidationErrors errors={appError.validationErrors} />
172
+ )}
173
+
174
+ {showRequestId && requestId && (
175
+ <p style={{
176
+ fontSize: '12px',
177
+ fontFamily: 'monospace',
178
+ color: 'var(--muted-foreground, #6b7280)',
179
+ margin: 0,
180
+ }}>
181
+ Request ID: {requestId}
182
+ </p>
183
+ )}
184
+
185
+ <div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
186
+ {onRetry && (
187
+ <button
188
+ onClick={onRetry}
189
+ style={{
190
+ display: 'flex',
191
+ alignItems: 'center',
192
+ gap: '6px',
193
+ padding: '6px 12px',
194
+ borderRadius: '6px',
195
+ border: '1px solid var(--border, #e5e7eb)',
196
+ backgroundColor: 'transparent',
197
+ cursor: 'pointer',
198
+ fontSize: '13px',
199
+ fontWeight: 500,
200
+ }}
201
+ >
202
+ <RefreshCw style={{ height: '14px', width: '14px' }} />
203
+ Retry
204
+ </button>
205
+ )}
206
+ {customAction && (
207
+ customAction.href ? (
208
+ <a
209
+ href={customAction.href}
210
+ style={{
211
+ padding: '6px 12px',
212
+ borderRadius: '6px',
213
+ border: '1px solid var(--border, #e5e7eb)',
214
+ backgroundColor: 'transparent',
215
+ textDecoration: 'none',
216
+ fontSize: '13px',
217
+ fontWeight: 500,
218
+ color: 'var(--foreground, #111)',
219
+ }}
220
+ >
221
+ {customAction.label}
222
+ </a>
223
+ ) : (
224
+ <button
225
+ onClick={customAction.onClick}
226
+ style={{
227
+ padding: '6px 12px',
228
+ borderRadius: '6px',
229
+ border: '1px solid var(--border, #e5e7eb)',
230
+ backgroundColor: 'transparent',
231
+ cursor: 'pointer',
232
+ fontSize: '13px',
233
+ fontWeight: 500,
234
+ }}
235
+ >
236
+ {customAction.label}
237
+ </button>
238
+ )
239
+ )}
240
+ {!onRetry && !customAction && (
241
+ <p style={{
242
+ fontSize: '13px',
243
+ color: 'var(--muted-foreground, #6b7280)',
244
+ margin: 0,
245
+ }}>
246
+ {action}
247
+ </p>
248
+ )}
249
+ </div>
250
+ </div>
251
+ </div>
252
+ )
253
+ }
254
+
255
+ /**
256
+ * Generic error display for non-app errors
257
+ */
258
+ function GenericErrorDisplay({
259
+ error,
260
+ onRetry,
261
+ compact,
262
+ }: {
263
+ error: unknown
264
+ onRetry?: () => void
265
+ compact: boolean
266
+ }) {
267
+ const message = normalizeErrorMessage(error)
268
+
269
+ return (
270
+ <div style={{
271
+ borderRadius: '8px',
272
+ border: '1px solid var(--border, #e5e7eb)',
273
+ backgroundColor: 'var(--panel, #fff)',
274
+ padding: compact ? '16px' : '24px',
275
+ }}>
276
+ <div style={{ display: 'flex', alignItems: 'flex-start', gap: '12px' }}>
277
+ <div style={{
278
+ height: '32px',
279
+ width: '32px',
280
+ display: 'flex',
281
+ alignItems: 'center',
282
+ justifyContent: 'center',
283
+ borderRadius: '8px',
284
+ backgroundColor: 'rgba(239, 68, 68, 0.1)',
285
+ flexShrink: 0,
286
+ }}>
287
+ <RefreshCw style={{ height: '16px', width: '16px', color: '#dc2626' }} />
288
+ </div>
289
+ <div style={{ flex: 1 }}>
290
+ <h3 style={{
291
+ fontSize: '14px',
292
+ fontWeight: 600,
293
+ color: 'var(--foreground, #111)',
294
+ margin: '0 0 4px',
295
+ }}>
296
+ Error
297
+ </h3>
298
+ <p style={{
299
+ fontSize: '14px',
300
+ color: 'var(--muted-foreground, #6b7280)',
301
+ margin: 0,
302
+ }}>
303
+ {message}
304
+ </p>
305
+ </div>
306
+ {onRetry && (
307
+ <button
308
+ onClick={onRetry}
309
+ style={{
310
+ display: 'flex',
311
+ alignItems: 'center',
312
+ gap: '6px',
313
+ padding: '6px 12px',
314
+ borderRadius: '6px',
315
+ border: '1px solid var(--border, #e5e7eb)',
316
+ backgroundColor: 'transparent',
317
+ cursor: 'pointer',
318
+ fontSize: '13px',
319
+ }}
320
+ >
321
+ <RefreshCw style={{ height: '14px', width: '14px' }} />
322
+ Retry
323
+ </button>
324
+ )}
325
+ </div>
326
+ </div>
327
+ )
328
+ }