@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,242 @@
1
+ // @geenius-tools/devtools-react — src/components/DevToolbar.tsx
2
+
3
+ import { memo, useState, useRef, useEffect } from 'react'
4
+ import { cn } from '@geenius-ui/react'
5
+ import {
6
+ Power,
7
+ PanelRight,
8
+ Bug,
9
+ Lightbulb,
10
+ Target,
11
+ Activity,
12
+ } from 'lucide-react'
13
+ import { useDevModeOptional, useIsDevAllowed } from '../context/DevModeContext'
14
+ import { usePerformanceContextOptional } from '../context/PerformanceContext'
15
+ import { GitHubIssueDialog } from './GitHubIssueDialog'
16
+ import { PerformancePanel } from './PerformancePanel'
17
+
18
+ interface ToolbarButtonProps {
19
+ icon: React.ReactNode
20
+ label: string
21
+ onClick?: () => void
22
+ active?: boolean
23
+ variant?: 'default' | 'primary' | 'success' | 'danger'
24
+ showLabel?: boolean
25
+ disabled?: boolean
26
+ }
27
+
28
+ const ToolbarButton = memo(function ToolbarButton({
29
+ icon,
30
+ label,
31
+ onClick,
32
+ active = false,
33
+ variant = 'default',
34
+ showLabel = true,
35
+ disabled = false,
36
+ }: ToolbarButtonProps) {
37
+ const variantStyles = {
38
+ default: active
39
+ ? 'bg-white/20 text-white'
40
+ : 'text-white/70 hover:text-white hover:bg-white/10',
41
+ primary: active
42
+ ? 'bg-primary/30 text-primary-foreground'
43
+ : 'text-white/70 hover:text-primary hover:bg-primary/10',
44
+ success: 'text-white/70 hover:text-emerald-400 hover:bg-emerald-500/10',
45
+ danger: 'text-white/70 hover:text-red-400 hover:bg-red-500/10',
46
+ }
47
+
48
+ const content = (
49
+ <>
50
+ {icon}
51
+ {showLabel && (
52
+ <span className="hidden sm:inline text-xs font-medium">{label}</span>
53
+ )}
54
+ </>
55
+ )
56
+
57
+ const className = cn(
58
+ 'flex items-center gap-2 px-3 py-2 rounded-lg transition-all duration-200',
59
+ 'focus:outline-none focus:ring-2 focus:ring-white/20',
60
+ disabled
61
+ ? 'opacity-40 cursor-not-allowed text-white'
62
+ : variantStyles[variant],
63
+ )
64
+
65
+ return (
66
+ <button
67
+ type="button"
68
+ onClick={disabled ? undefined : onClick}
69
+ className={cn(className, !disabled && 'cursor-pointer')}
70
+ title={label}
71
+ disabled={disabled}
72
+ >
73
+ {content}
74
+ </button>
75
+ )
76
+ })
77
+
78
+ interface ExpandableSectionProps {
79
+ show: boolean
80
+ children: React.ReactNode
81
+ }
82
+
83
+ const ExpandableSection = memo(function ExpandableSection({
84
+ show,
85
+ children,
86
+ }: ExpandableSectionProps) {
87
+ const contentRef = useRef<HTMLDivElement>(null)
88
+ const [width, setWidth] = useState(0)
89
+
90
+ useEffect(() => {
91
+ if (contentRef.current) {
92
+ setWidth(show ? contentRef.current.scrollWidth : 0)
93
+ }
94
+ }, [show, children])
95
+
96
+ return (
97
+ <div
98
+ className="overflow-hidden transition-all duration-300 ease-out"
99
+ style={{ width: show ? width : 0, opacity: show ? 1 : 0 }}
100
+ >
101
+ <div
102
+ ref={contentRef}
103
+ className={cn(
104
+ 'flex items-center gap-1 whitespace-nowrap',
105
+ 'transition-transform duration-300 ease-out',
106
+ show ? 'translate-x-0' : '-translate-x-2',
107
+ )}
108
+ >
109
+ {children}
110
+ </div>
111
+ </div>
112
+ )
113
+ })
114
+
115
+ export interface DevToolbarProps {
116
+ /** Whether dev tools are allowed. Default: auto-detects from env */
117
+ isAllowed?: boolean
118
+ }
119
+
120
+ export const DevToolbar = memo(function DevToolbar({
121
+ isAllowed,
122
+ }: DevToolbarProps = {}) {
123
+ const isDevAllowed = useIsDevAllowed(isAllowed)
124
+ const devMode = useDevModeOptional()
125
+ const performanceContext = usePerformanceContextOptional()
126
+
127
+ const [isReady, setIsReady] = useState(false)
128
+ useEffect(() => {
129
+ const timer = setTimeout(() => setIsReady(true), 1000)
130
+ return () => clearTimeout(timer)
131
+ }, [])
132
+
133
+ if (!isDevAllowed || !devMode) return null
134
+
135
+ const {
136
+ isDevMode,
137
+ isSidebarOpen,
138
+ toggleDevMode,
139
+ toggleSidebar,
140
+ openIssueDialog,
141
+ issueDialog,
142
+ closeIssueDialog,
143
+ } = devMode
144
+
145
+ const showContent = isDevMode && isReady
146
+
147
+ return (
148
+ <>
149
+ {/* Floating Toolbar */}
150
+ <div
151
+ data-dev-tool="true"
152
+ className={cn(
153
+ 'fixed bottom-4 left-1/2 -translate-x-1/2 z-50',
154
+ 'flex items-center px-2 py-1',
155
+ showContent ? 'gap-2' : 'gap-0',
156
+ 'rounded-2xl border border-white/10',
157
+ 'bg-gradient-to-r from-slate-900/95 via-slate-800/95 to-slate-900/95',
158
+ 'backdrop-blur-xl shadow-2xl shadow-black/50',
159
+ 'transition-all duration-300 ease-out',
160
+ )}
161
+ >
162
+ {/* Power / Toggle Dev Mode */}
163
+ <ToolbarButton
164
+ icon={<Power className="w-4 h-4" />}
165
+ label={isDevMode ? 'Dev Mode On' : 'Dev Mode Off'}
166
+ onClick={toggleDevMode}
167
+ active={isDevMode}
168
+ variant={isDevMode ? 'primary' : 'default'}
169
+ />
170
+
171
+ <ExpandableSection show={showContent}>
172
+ {/* Separator */}
173
+ <div className="shrink-0 w-px h-6 bg-white/20 mx-1 my-2" />
174
+
175
+ {/* Inspector Mode Toggle */}
176
+ <ToolbarButton
177
+ icon={<Target className="w-4 h-4" />}
178
+ label="Inspector"
179
+ onClick={devMode.toggleInspector}
180
+ active={devMode.isInspectorMode}
181
+ variant={devMode.isInspectorMode ? 'primary' : 'default'}
182
+ />
183
+
184
+ {/* Performance Monitor Toggle */}
185
+ {performanceContext && (
186
+ <ToolbarButton
187
+ icon={<Activity className="w-4 h-4" />}
188
+ label="Perf"
189
+ onClick={performanceContext.togglePerformancePanel}
190
+ active={performanceContext.isPerformancePanelOpen}
191
+ variant={
192
+ performanceContext.isPerformancePanelOpen
193
+ ? 'primary'
194
+ : 'default'
195
+ }
196
+ />
197
+ )}
198
+
199
+ {/* Sidebar Toggle (Desktop only) */}
200
+ <div className="hidden md:block">
201
+ <ToolbarButton
202
+ icon={<PanelRight className="w-4 h-4" />}
203
+ label="Issues"
204
+ onClick={toggleSidebar}
205
+ active={isSidebarOpen}
206
+ />
207
+ </div>
208
+
209
+ {/* Create Bug */}
210
+ <ToolbarButton
211
+ icon={<Bug className="w-4 h-4" />}
212
+ label="Bug"
213
+ onClick={() => openIssueDialog('bug')}
214
+ variant="danger"
215
+ />
216
+
217
+ {/* Create Feature */}
218
+ <ToolbarButton
219
+ icon={<Lightbulb className="w-4 h-4" />}
220
+ label="Feature"
221
+ onClick={() => openIssueDialog('feature')}
222
+ variant="success"
223
+ />
224
+ </ExpandableSection>
225
+ </div>
226
+
227
+ {/* Performance Panel */}
228
+ {performanceContext && <PerformancePanel />}
229
+
230
+ {/* Issue Dialog */}
231
+ <div data-dev-tool="true">
232
+ <GitHubIssueDialog
233
+ open={issueDialog.isOpen}
234
+ onOpenChange={(open) => !open && closeIssueDialog()}
235
+ initialType={issueDialog.type}
236
+ initialDescription={issueDialog.initialDescription}
237
+ componentDetails={issueDialog.componentDetails}
238
+ />
239
+ </div>
240
+ </>
241
+ )
242
+ })
@@ -0,0 +1,402 @@
1
+ // @geenius-tools/devtools-react — src/components/GitHubIssueDialog.tsx
2
+
3
+ import { useState, useCallback, useEffect, memo, useRef } from 'react'
4
+ import { cn } from '@geenius-ui/react'
5
+ import {
6
+ Bug,
7
+ Lightbulb,
8
+ ExternalLink,
9
+ AlertCircle,
10
+ CheckCircle2,
11
+ Github,
12
+ Component,
13
+ } from 'lucide-react'
14
+ import type { ComponentDetails } from '../context/DevModeContext'
15
+ import { useGitHubLabels } from '../hooks/useGitHubLabels'
16
+ import {
17
+ createGitHubIssue,
18
+ isGitHubConfigured,
19
+ type CreateIssueResponse,
20
+ } from '../services/github-service'
21
+
22
+ type IssueType = 'bug' | 'feature'
23
+
24
+ interface FormState {
25
+ type: IssueType
26
+ title: string
27
+ description: string
28
+ selectedLabel: string
29
+ }
30
+
31
+ const initialFormState: FormState = {
32
+ type: 'feature',
33
+ title: '',
34
+ description: '',
35
+ selectedLabel: '',
36
+ }
37
+
38
+ export interface GitHubIssueDialogProps {
39
+ open: boolean
40
+ onOpenChange: (open: boolean) => void
41
+ initialType?: IssueType
42
+ initialDescription?: string
43
+ componentDetails?: ComponentDetails[]
44
+ }
45
+
46
+ export const GitHubIssueDialog = memo(function GitHubIssueDialog({
47
+ open,
48
+ onOpenChange,
49
+ initialType = 'feature',
50
+ initialDescription = '',
51
+ componentDetails,
52
+ }: GitHubIssueDialogProps) {
53
+ const [form, setForm] = useState<FormState>({
54
+ ...initialFormState,
55
+ type: initialType,
56
+ description: initialDescription,
57
+ })
58
+ const [loading, setLoading] = useState(false)
59
+ const [success, setSuccess] = useState<CreateIssueResponse | null>(null)
60
+ const [error, setError] = useState<string | null>(null)
61
+ const { data: labels } = useGitHubLabels()
62
+ const configured = isGitHubConfigured()
63
+ const dialogRef = useRef<HTMLDivElement>(null)
64
+
65
+ useEffect(() => {
66
+ if (open) {
67
+ setForm({
68
+ ...initialFormState,
69
+ type: initialType,
70
+ description: initialDescription,
71
+ })
72
+ setError(null)
73
+ setSuccess(null)
74
+ }
75
+ }, [open, initialType, initialDescription])
76
+
77
+ // Close on Escape
78
+ useEffect(() => {
79
+ if (!open) return
80
+ const handleKeyDown = (e: KeyboardEvent) => {
81
+ if (e.key === 'Escape') onOpenChange(false)
82
+ }
83
+ window.addEventListener('keydown', handleKeyDown)
84
+ return () => window.removeEventListener('keydown', handleKeyDown)
85
+ }, [open, onOpenChange])
86
+
87
+ const handleSubmit = useCallback(async () => {
88
+ if (!form.title.trim()) {
89
+ setError('Please provide a title for the issue.')
90
+ return
91
+ }
92
+
93
+ setLoading(true)
94
+ setError(null)
95
+
96
+ try {
97
+ const typePrefix =
98
+ form.type === 'bug' ? '🐛 Bug Report' : '✨ Feature Request'
99
+
100
+ let componentSection = ''
101
+ if (componentDetails && componentDetails.length > 0) {
102
+ componentSection = `\n### Components\n${componentDetails
103
+ .map((detail, index) => {
104
+ let propsBlock = ''
105
+ if (detail.props) {
106
+ try {
107
+ propsBlock = `\n- **Props:**\n\`\`\`json\n${JSON.stringify(detail.props, null, 2)}\n\`\`\``
108
+ } catch {
109
+ propsBlock = '\n- **Props:** (Complex Object)'
110
+ }
111
+ }
112
+ return `
113
+ **Component ${index + 1}**
114
+ - **Name:** \`${detail.name}\`
115
+ ${detail.key ? `- **Key:** \`${detail.key}\`` : ''}
116
+ ${detail.file ? `- **File:** \`${detail.file}\`` : ''}
117
+ - **Selector:** \`${detail.selector}\`${propsBlock}
118
+ `
119
+ })
120
+ .join('\n')}`
121
+ }
122
+
123
+ const descriptionSection = form.description
124
+ ? `## Description\n${form.description}`
125
+ : ''
126
+
127
+ const timestamp = new Date().toISOString()
128
+ const theme =
129
+ typeof document !== 'undefined' &&
130
+ document.documentElement.classList.contains('dark')
131
+ ? 'dark'
132
+ : 'light'
133
+ const viewport =
134
+ typeof window !== 'undefined'
135
+ ? `${window.innerWidth}x${window.innerHeight}`
136
+ : 'unknown'
137
+ const userAgent =
138
+ typeof navigator !== 'undefined' ? navigator.userAgent : 'unknown'
139
+ const url =
140
+ typeof window !== 'undefined' ? window.location.href : 'unknown'
141
+
142
+ const contextSection = `
143
+ -----
144
+ **Context Information**
145
+ - **URL:** ${url}
146
+ - **Time:** ${timestamp}
147
+ - **Theme:** ${theme}
148
+ - **Viewport:** ${viewport}
149
+ - **User Agent:** ${userAgent}
150
+ `.trim()
151
+
152
+ const body = [
153
+ `# ${typePrefix}`,
154
+ '',
155
+ descriptionSection,
156
+ componentSection,
157
+ '',
158
+ contextSection,
159
+ ]
160
+ .filter(Boolean)
161
+ .join('\n')
162
+
163
+ const issueLabels: string[] = []
164
+ if (form.type === 'bug') issueLabels.push('bug')
165
+ if (form.type === 'feature') issueLabels.push('enhancement')
166
+ if (form.selectedLabel) issueLabels.push(form.selectedLabel)
167
+
168
+ const result = await createGitHubIssue({
169
+ title: `[${form.type === 'bug' ? 'Bug' : 'Feature'}] ${form.title}`,
170
+ body,
171
+ labels: issueLabels,
172
+ })
173
+
174
+ setSuccess(result)
175
+ } catch (err) {
176
+ setError(err instanceof Error ? err.message : 'An error occurred')
177
+ } finally {
178
+ setLoading(false)
179
+ }
180
+ }, [form, componentDetails])
181
+
182
+ const handleFieldChange = useCallback(
183
+ <K extends keyof FormState>(field: K, value: FormState[K]) => {
184
+ setForm((prev) => ({ ...prev, [field]: value }))
185
+ setError(null)
186
+ },
187
+ [],
188
+ )
189
+
190
+ if (!open) return null
191
+
192
+ return (
193
+ <div className="fixed inset-0 z-[10000] flex items-center justify-center" data-dev-tool="true">
194
+ {/* Backdrop */}
195
+ <div
196
+ className="absolute inset-0 bg-black/60 backdrop-blur-sm"
197
+ onClick={() => onOpenChange(false)}
198
+ />
199
+
200
+ {/* Dialog */}
201
+ <div
202
+ ref={dialogRef}
203
+ className={cn(
204
+ 'relative z-10 w-full max-w-[500px] mx-4',
205
+ 'bg-gradient-to-br from-slate-900 to-slate-800',
206
+ 'border border-white/10 rounded-2xl shadow-2xl',
207
+ 'max-h-[90vh] overflow-y-auto',
208
+ 'animate-in fade-in zoom-in-95 duration-200',
209
+ )}
210
+ >
211
+ {/* Header */}
212
+ <div className="flex items-center gap-2 px-6 py-4 border-b border-white/10">
213
+ <Github className="h-5 w-5 text-primary" />
214
+ <h2 className="text-lg font-bold text-white">Create GitHub Issue</h2>
215
+ </div>
216
+
217
+ <div className="px-6 py-4">
218
+ {!configured ? (
219
+ <div className="py-6">
220
+ <div className="text-center space-y-3">
221
+ <div className="mx-auto w-12 h-12 rounded-full bg-yellow-500/10 flex items-center justify-center">
222
+ <AlertCircle className="h-6 w-6 text-yellow-500" />
223
+ </div>
224
+ <p className="text-sm text-white/60">
225
+ GitHub integration is not configured. Call{' '}
226
+ <code className="text-xs bg-white/10 px-1.5 py-0.5 rounded">
227
+ configureGitHub()
228
+ </code>{' '}
229
+ or set VITE_GITHUB_TOKEN to enable issue creation.
230
+ </p>
231
+ </div>
232
+ </div>
233
+ ) : success ? (
234
+ <div className="py-6">
235
+ <div className="text-center space-y-4">
236
+ <CheckCircle2 className="mx-auto h-12 w-12 text-green-500" />
237
+ <div>
238
+ <p className="font-medium text-white">Issue Created!</p>
239
+ <p className="text-sm text-white/60 mt-1">{success.title}</p>
240
+ </div>
241
+ <a
242
+ href={success.html_url}
243
+ target="_blank"
244
+ rel="noopener noreferrer"
245
+ className="inline-flex items-center gap-2 px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary/80 transition-colors"
246
+ >
247
+ <ExternalLink className="h-4 w-4" />
248
+ View on GitHub
249
+ </a>
250
+ </div>
251
+ </div>
252
+ ) : (
253
+ <div className="space-y-4 py-2">
254
+ {/* Issue Type Selector */}
255
+ <div className="grid grid-cols-2 gap-2">
256
+ <button
257
+ type="button"
258
+ onClick={() => handleFieldChange('type', 'feature')}
259
+ className={cn(
260
+ 'flex items-center gap-2 p-3 rounded-lg border-2 transition-all',
261
+ form.type === 'feature'
262
+ ? 'border-emerald-500 bg-emerald-500/10 text-emerald-400'
263
+ : 'border-white/10 text-white/60 hover:border-white/20',
264
+ )}
265
+ >
266
+ <Lightbulb className="h-4 w-4" />
267
+ <span className="font-medium text-sm">Feature</span>
268
+ </button>
269
+ <button
270
+ type="button"
271
+ onClick={() => handleFieldChange('type', 'bug')}
272
+ className={cn(
273
+ 'flex items-center gap-2 p-3 rounded-lg border-2 transition-all',
274
+ form.type === 'bug'
275
+ ? 'border-red-500 bg-red-500/10 text-red-400'
276
+ : 'border-white/10 text-white/60 hover:border-white/20',
277
+ )}
278
+ >
279
+ <Bug className="h-4 w-4" />
280
+ <span className="font-medium text-sm">Bug Report</span>
281
+ </button>
282
+ </div>
283
+
284
+ {/* Component Details */}
285
+ {componentDetails && componentDetails.length > 0 && (
286
+ <div className="p-3 rounded-lg bg-white/5 border border-white/10">
287
+ <div className="flex items-center gap-1.5 text-xs text-white/50 mb-2">
288
+ <Component className="h-3 w-3" />
289
+ <span>
290
+ {componentDetails.length} component
291
+ {componentDetails.length > 1 ? 's' : ''} attached
292
+ </span>
293
+ </div>
294
+ <div className="space-y-1">
295
+ {componentDetails.map((c, i) => (
296
+ <div key={i} className="text-xs font-mono text-white/80">
297
+ {c.name}
298
+ {c.key && (
299
+ <span className="text-white/40 ml-1">
300
+ key={c.key}
301
+ </span>
302
+ )}
303
+ </div>
304
+ ))}
305
+ </div>
306
+ </div>
307
+ )}
308
+
309
+ {/* Title */}
310
+ <div>
311
+ <label className="block text-xs font-medium text-white/60 mb-1">
312
+ Title
313
+ </label>
314
+ <input
315
+ type="text"
316
+ value={form.title}
317
+ onChange={(e) => handleFieldChange('title', e.target.value)}
318
+ placeholder={
319
+ form.type === 'bug'
320
+ ? 'Describe the bug...'
321
+ : 'Feature idea...'
322
+ }
323
+ className="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-sm text-white placeholder:text-white/30 focus:outline-none focus:ring-2 focus:ring-primary/50"
324
+ autoFocus
325
+ />
326
+ </div>
327
+
328
+ {/* Description */}
329
+ <div>
330
+ <label className="block text-xs font-medium text-white/60 mb-1">
331
+ Description
332
+ </label>
333
+ <textarea
334
+ value={form.description}
335
+ onChange={(e) =>
336
+ handleFieldChange('description', e.target.value)
337
+ }
338
+ placeholder="Provide more details..."
339
+ rows={4}
340
+ className="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-sm text-white placeholder:text-white/30 focus:outline-none focus:ring-2 focus:ring-primary/50 resize-none"
341
+ />
342
+ </div>
343
+
344
+ {/* Label Selector */}
345
+ {labels && labels.length > 0 && (
346
+ <div>
347
+ <label className="block text-xs font-medium text-white/60 mb-1">
348
+ Label
349
+ </label>
350
+ <select
351
+ value={form.selectedLabel}
352
+ onChange={(e) =>
353
+ handleFieldChange('selectedLabel', e.target.value)
354
+ }
355
+ className="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-sm text-white focus:outline-none focus:ring-2 focus:ring-primary/50"
356
+ >
357
+ <option value="">No label</option>
358
+ {labels.map((label) => (
359
+ <option key={label.id} value={label.name}>
360
+ {label.name}
361
+ </option>
362
+ ))}
363
+ </select>
364
+ </div>
365
+ )}
366
+
367
+ {/* Error */}
368
+ {error && (
369
+ <div className="flex items-center gap-2 p-3 bg-red-500/10 border border-red-500/20 rounded-lg">
370
+ <AlertCircle className="h-4 w-4 text-red-400 flex-shrink-0" />
371
+ <p className="text-xs text-red-300">{error}</p>
372
+ </div>
373
+ )}
374
+
375
+ {/* Actions */}
376
+ <div className="flex justify-end gap-2 pt-2">
377
+ <button
378
+ onClick={() => onOpenChange(false)}
379
+ className="px-4 py-2 text-sm text-white/60 hover:text-white transition-colors"
380
+ >
381
+ Cancel
382
+ </button>
383
+ <button
384
+ onClick={handleSubmit}
385
+ disabled={loading || !form.title.trim()}
386
+ className={cn(
387
+ 'px-4 py-2 text-sm font-medium rounded-lg transition-all',
388
+ loading || !form.title.trim()
389
+ ? 'bg-white/10 text-white/30 cursor-not-allowed'
390
+ : 'bg-primary text-white hover:bg-primary/80',
391
+ )}
392
+ >
393
+ {loading ? 'Creating...' : 'Create Issue'}
394
+ </button>
395
+ </div>
396
+ </div>
397
+ )}
398
+ </div>
399
+ </div>
400
+ </div>
401
+ )
402
+ })