@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.
- package/.changeset/config.json +11 -0
- package/.env.example +2 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +23 -0
- package/.github/workflows/release.yml +29 -0
- package/.node-version +1 -0
- package/.nvmrc +1 -0
- package/.prettierrc +7 -0
- package/.project/ACCOUNT.yaml +4 -0
- package/.project/IDEAS.yaml +7 -0
- package/.project/PROJECT.yaml +11 -0
- package/.project/ROADMAP.yaml +15 -0
- package/CHANGELOG.md +16 -0
- package/CODE_OF_CONDUCT.md +26 -0
- package/CONTRIBUTING.md +69 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/SECURITY.md +18 -0
- package/SUPPORT.md +14 -0
- package/package.json +75 -0
- package/packages/convex/shared/README.md +1 -0
- package/packages/convex/shared/package.json +42 -0
- package/packages/convex/shared/src/audit/index.ts +5 -0
- package/packages/convex/shared/src/audit/presets.ts +165 -0
- package/packages/convex/shared/src/audit/schema.ts +85 -0
- package/packages/convex/shared/src/audit/write.ts +102 -0
- package/packages/convex/shared/src/extract.ts +75 -0
- package/packages/convex/shared/src/index.ts +41 -0
- package/packages/convex/shared/src/messages.ts +45 -0
- package/packages/convex/shared/src/security.ts +112 -0
- package/packages/convex/shared/src/throw.ts +184 -0
- package/packages/convex/shared/src/types.ts +57 -0
- package/packages/convex/shared/src/utils.ts +58 -0
- package/packages/convex/shared/tsconfig.json +28 -0
- package/packages/convex/shared/tsup.config.ts +12 -0
- package/packages/devtools/package.json +27 -0
- package/packages/devtools/react/README.md +1 -0
- package/packages/devtools/react/package.json +53 -0
- package/packages/devtools/react/src/components/DesignPreview.tsx +59 -0
- package/packages/devtools/react/src/components/DesignSwitcherDropdown.tsx +99 -0
- package/packages/devtools/react/src/components/DevSidebar.tsx +247 -0
- package/packages/devtools/react/src/components/DevToolbar.tsx +242 -0
- package/packages/devtools/react/src/components/GitHubIssueDialog.tsx +402 -0
- package/packages/devtools/react/src/components/InspectorOverlay.tsx +312 -0
- package/packages/devtools/react/src/components/PageLoadWaterfall.tsx +144 -0
- package/packages/devtools/react/src/components/PerformancePanel.tsx +330 -0
- package/packages/devtools/react/src/context/DevModeContext.tsx +226 -0
- package/packages/devtools/react/src/context/PerformanceContext.tsx +143 -0
- package/packages/devtools/react/src/data/designs.ts +13 -0
- package/packages/devtools/react/src/hooks/useGitHubLabels.ts +47 -0
- package/packages/devtools/react/src/hooks/useVirtualList.ts +124 -0
- package/packages/devtools/react/src/index.ts +77 -0
- package/packages/devtools/react/src/panels/ConvexSpy.tsx +130 -0
- package/packages/devtools/react/src/panels/DatabaseSeeder.tsx +116 -0
- package/packages/devtools/react/src/panels/DevModePhase2.tsx +191 -0
- package/packages/devtools/react/src/panels/DevModePhase3.tsx +234 -0
- package/packages/devtools/react/src/panels/FeatureFlagsToggle.tsx +104 -0
- package/packages/devtools/react/src/panels/QuickRouteJump.tsx +152 -0
- package/packages/devtools/react/src/services/github-service.ts +247 -0
- package/packages/devtools/react/tsconfig.json +31 -0
- package/packages/devtools/react/tsup.config.ts +18 -0
- package/packages/devtools/solidjs/README.md +1 -0
- package/packages/devtools/solidjs/package.json +49 -0
- package/packages/devtools/solidjs/src/components/DesignPreview.tsx +51 -0
- package/packages/devtools/solidjs/src/components/DesignSwitcherDropdown.tsx +95 -0
- package/packages/devtools/solidjs/src/components/DevSidebar.tsx +247 -0
- package/packages/devtools/solidjs/src/components/DevToolbar.tsx +242 -0
- package/packages/devtools/solidjs/src/components/GitHubIssueDialog.tsx +400 -0
- package/packages/devtools/solidjs/src/components/InspectorOverlay.tsx +311 -0
- package/packages/devtools/solidjs/src/components/PageLoadWaterfall.tsx +144 -0
- package/packages/devtools/solidjs/src/components/PerformancePanel.tsx +330 -0
- package/packages/devtools/solidjs/src/context/DevModeContext.tsx +216 -0
- package/packages/devtools/solidjs/src/context/PerformanceContext.tsx +135 -0
- package/packages/devtools/solidjs/src/data/designs.ts +13 -0
- package/packages/devtools/solidjs/src/hooks/createGitHubLabels.ts +47 -0
- package/packages/devtools/solidjs/src/index.ts +64 -0
- package/packages/devtools/solidjs/src/services/github-service.ts +247 -0
- package/packages/devtools/solidjs/tsconfig.json +21 -0
- package/packages/devtools/src/index.ts +377 -0
- package/packages/devtools/tsup.config.ts +12 -0
- package/packages/env/package.json +30 -0
- package/packages/env/src/index.ts +264 -0
- package/packages/env/tsup.config.ts +12 -0
- package/packages/errors/package.json +27 -0
- package/packages/errors/react/README.md +1 -0
- package/packages/errors/react/package.json +72 -0
- package/packages/errors/react/src/analytics.ts +16 -0
- package/packages/errors/react/src/components/ErrorBoundary.tsx +248 -0
- package/packages/errors/react/src/components/ErrorDisplay.tsx +328 -0
- package/packages/errors/react/src/components/ValidationErrors.tsx +102 -0
- package/packages/errors/react/src/config.ts +199 -0
- package/packages/errors/react/src/constants.ts +74 -0
- package/packages/errors/react/src/hooks/useErrorBoundary.ts +92 -0
- package/packages/errors/react/src/hooks/useErrorHandler.ts +87 -0
- package/packages/errors/react/src/index.ts +96 -0
- package/packages/errors/react/src/types.ts +102 -0
- package/packages/errors/react/src/utils/errorMessages.ts +35 -0
- package/packages/errors/react/src/utils/errorPolicy.ts +139 -0
- package/packages/errors/react/src/utils/extractAppError.ts +174 -0
- package/packages/errors/react/src/utils/formatError.ts +112 -0
- package/packages/errors/react/tsconfig.json +25 -0
- package/packages/errors/react/tsup.config.ts +24 -0
- package/packages/errors/solidjs/README.md +1 -0
- package/packages/errors/solidjs/package.json +46 -0
- package/packages/errors/solidjs/src/components/ErrorDisplay.tsx +179 -0
- package/packages/errors/solidjs/src/config.ts +98 -0
- package/packages/errors/solidjs/src/hooks/createErrorHandler.ts +107 -0
- package/packages/errors/solidjs/src/index.ts +61 -0
- package/packages/errors/solidjs/src/types.ts +34 -0
- package/packages/errors/solidjs/src/utils/errorPolicy.ts +56 -0
- package/packages/errors/solidjs/src/utils/extractAppError.ts +94 -0
- package/packages/errors/solidjs/src/utils/formatError.ts +33 -0
- package/packages/errors/solidjs/tsconfig.json +26 -0
- package/packages/errors/solidjs/tsup.config.ts +21 -0
- package/packages/errors/src/index.ts +320 -0
- package/packages/errors/tsup.config.ts +12 -0
- package/packages/logger/package.json +27 -0
- package/packages/logger/react/README.md +1 -0
- package/packages/logger/react/package.json +46 -0
- package/packages/logger/react/src/index.ts +4 -0
- package/packages/logger/react/src/useMetrics.ts +42 -0
- package/packages/logger/react/src/usePerformanceLog.ts +61 -0
- package/packages/logger/react/tsconfig.json +31 -0
- package/packages/logger/react/tsup.config.ts +12 -0
- package/packages/logger/solidjs/README.md +1 -0
- package/packages/logger/solidjs/package.json +45 -0
- package/packages/logger/solidjs/src/createMetrics.ts +37 -0
- package/packages/logger/solidjs/src/createPerformanceLog.ts +58 -0
- package/packages/logger/solidjs/src/index.ts +4 -0
- package/packages/logger/solidjs/tsconfig.json +32 -0
- package/packages/logger/solidjs/tsup.config.ts +12 -0
- package/packages/logger/src/index.ts +363 -0
- package/packages/logger/tsup.config.ts +12 -0
- package/packages/perf/package.json +27 -0
- package/packages/perf/react/README.md +1 -0
- package/packages/perf/react/package.json +59 -0
- package/packages/perf/react/src/components/PerformanceDashboard.tsx +257 -0
- package/packages/perf/react/src/hooks/useMonitoredQuery.ts +89 -0
- package/packages/perf/react/src/hooks/usePerformanceMetrics.ts +78 -0
- package/packages/perf/react/src/index.ts +33 -0
- package/packages/perf/react/src/services/PerformanceMonitor.ts +313 -0
- package/packages/perf/react/src/types.ts +77 -0
- package/packages/perf/react/tsconfig.json +25 -0
- package/packages/perf/react/tsup.config.ts +19 -0
- package/packages/perf/solidjs/README.md +1 -0
- package/packages/perf/solidjs/package.json +41 -0
- package/packages/perf/solidjs/src/components/PerformanceDashboard.tsx +207 -0
- package/packages/perf/solidjs/src/hooks/createPerformanceMetrics.ts +73 -0
- package/packages/perf/solidjs/src/index.ts +31 -0
- package/packages/perf/solidjs/src/services/PerformanceMonitor.ts +134 -0
- package/packages/perf/solidjs/src/types.ts +78 -0
- package/packages/perf/solidjs/tsconfig.json +26 -0
- package/packages/perf/solidjs/tsup.config.ts +14 -0
- package/packages/perf/src/index.ts +410 -0
- package/packages/perf/tsup.config.ts +12 -0
- 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
|
+
})
|