@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,58 @@
|
|
|
1
|
+
// @geenius-tools/convex-wrappers — src/utils.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a unique request ID for tracing.
|
|
5
|
+
*/
|
|
6
|
+
export function makeRequestId(): string {
|
|
7
|
+
const timestamp = Date.now().toString(36)
|
|
8
|
+
const random = Math.random().toString(36).substring(2, 8)
|
|
9
|
+
return `req_${timestamp}_${random}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Redact sensitive fields from an object for logging.
|
|
14
|
+
*/
|
|
15
|
+
export function redactForLogging(
|
|
16
|
+
args: Record<string, unknown>,
|
|
17
|
+
config: { sensitiveFields?: string[] },
|
|
18
|
+
): Record<string, unknown> {
|
|
19
|
+
const sensitiveFields = new Set(
|
|
20
|
+
config.sensitiveFields ?? ['password', 'token', 'secret', 'apiKey', 'accessToken'],
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const result: Record<string, unknown> = {}
|
|
24
|
+
for (const [key, value] of Object.entries(args)) {
|
|
25
|
+
if (sensitiveFields.has(key)) {
|
|
26
|
+
result[key] = '[REDACTED]'
|
|
27
|
+
} else {
|
|
28
|
+
result[key] = value
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Extract userId from args (for trusted internal calls).
|
|
36
|
+
* Throws if userId is missing.
|
|
37
|
+
*/
|
|
38
|
+
export function extractRequiredUserId(
|
|
39
|
+
args: Record<string, unknown>,
|
|
40
|
+
context: string,
|
|
41
|
+
): string {
|
|
42
|
+
const userId = args.userId
|
|
43
|
+
if (!userId || typeof userId !== 'string') {
|
|
44
|
+
throw new Error(`Missing userId in ${context}`)
|
|
45
|
+
}
|
|
46
|
+
return userId
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Extract optional userId from args.
|
|
51
|
+
*/
|
|
52
|
+
export function extractOptionalUserId(
|
|
53
|
+
args: Record<string, unknown>,
|
|
54
|
+
): string | undefined {
|
|
55
|
+
const userId = args.userId
|
|
56
|
+
if (!userId || typeof userId !== 'string') return undefined
|
|
57
|
+
return userId
|
|
58
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ES2022"
|
|
8
|
+
],
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"declaration": true,
|
|
16
|
+
"declarationMap": true,
|
|
17
|
+
"sourceMap": true,
|
|
18
|
+
"outDir": "./dist",
|
|
19
|
+
"rootDir": "./src"
|
|
20
|
+
},
|
|
21
|
+
"include": [
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"exclude": [
|
|
25
|
+
"node_modules",
|
|
26
|
+
"dist"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geenius-tools/devtools",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Development tooling utilities for Geenius projects",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"type-check": "tsc --noEmit",
|
|
17
|
+
"clean": "rm -rf dist .turbo"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"typescript": "~6.0.2",
|
|
21
|
+
"tsup": "^8.0.1"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT"
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# ✦ @geenius-tools/devtools-react\n\n> React dev-tools UI — floating toolbar, performance panel, inspector, GitHub issues\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/devtools-react\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-tools/devtools-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,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geenius-tools/devtools-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "React dev-tools UI — floating toolbar, performance panel, inspector, GitHub issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"clean": "rm -rf dist",
|
|
27
|
+
"type-check": "tsc --noEmit",
|
|
28
|
+
"prepublishOnly": "pnpm clean && pnpm build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@geenius-tools/logger": "workspace:*"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@geenius-ui/react": "workspace:*",
|
|
35
|
+
"@types/react": "^19.0.0",
|
|
36
|
+
"@types/react-dom": "^19.0.0",
|
|
37
|
+
"lucide-react": "^0.577.0",
|
|
38
|
+
"react": "^19.2.4",
|
|
39
|
+
"react-dom": "^19.2.4",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"typescript": "~5.9.3"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@geenius-ui/react": "workspace:*",
|
|
45
|
+
"lucide-react": ">=0.300.0",
|
|
46
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
47
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
48
|
+
},
|
|
49
|
+
"author": "Antigravity HQ",
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @geenius-tools/devtools-react — components/DesignPreview.tsx
|
|
2
|
+
import { useRef, useEffect } from 'react'
|
|
3
|
+
import type { Design } from '../data/designs'
|
|
4
|
+
|
|
5
|
+
export type DesignPreviewProps = {
|
|
6
|
+
designNumber: number
|
|
7
|
+
showGrid?: boolean
|
|
8
|
+
theme?: 'light' | 'dark'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* DesignPreview — renders an HTML design file in an iframe with grid overlay + theme control.
|
|
13
|
+
*
|
|
14
|
+
* Uses Tailwind CSS utility classes.
|
|
15
|
+
*/
|
|
16
|
+
export function DesignPreview({
|
|
17
|
+
designNumber,
|
|
18
|
+
showGrid = false,
|
|
19
|
+
theme = 'light',
|
|
20
|
+
}: DesignPreviewProps) {
|
|
21
|
+
const iframeRef = useRef<HTMLIFrameElement>(null)
|
|
22
|
+
|
|
23
|
+
const applyTheme = () => {
|
|
24
|
+
try {
|
|
25
|
+
const iframe = iframeRef.current
|
|
26
|
+
if (iframe?.contentDocument) {
|
|
27
|
+
const action = theme === 'dark' ? 'add' : 'remove'
|
|
28
|
+
iframe.contentDocument.documentElement.classList[action]('dark')
|
|
29
|
+
iframe.contentDocument.body?.classList[action]('dark')
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
// Cross-origin restrictions
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
useEffect(applyTheme, [theme, designNumber])
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="relative w-full h-screen">
|
|
40
|
+
{showGrid && (
|
|
41
|
+
<div className="absolute inset-0 pointer-events-none z-[100] flex justify-between px-4 max-w-[1400px] mx-auto w-full">
|
|
42
|
+
{Array.from({ length: 12 }).map((_, i) => (
|
|
43
|
+
<div
|
|
44
|
+
key={i}
|
|
45
|
+
className="h-full w-[calc(100%/12)] border-x border-red-500/20 bg-red-500/5 mx-2"
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
)}
|
|
50
|
+
<iframe
|
|
51
|
+
ref={iframeRef}
|
|
52
|
+
src={`/designs/design-${designNumber}.html`}
|
|
53
|
+
className="w-full h-full border-0"
|
|
54
|
+
title={`Design ${designNumber} Preview`}
|
|
55
|
+
onLoad={applyTheme}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// @geenius-tools/devtools-react — components/DesignSwitcherDropdown.tsx
|
|
2
|
+
import { useState, useRef, useEffect } from 'react'
|
|
3
|
+
|
|
4
|
+
export type DesignSwitcherDropdownProps = {
|
|
5
|
+
/** Current active path — used to highlight the active design */
|
|
6
|
+
currentPath: string
|
|
7
|
+
/** Called when user selects a design */
|
|
8
|
+
onNavigate: (path: string) => void
|
|
9
|
+
/** Total number of designs */
|
|
10
|
+
designCount?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* DesignSwitcherDropdown — dropdown menu for switching between design previews.
|
|
15
|
+
*
|
|
16
|
+
* Router-agnostic: caller provides currentPath and onNavigate.
|
|
17
|
+
* Uses Tailwind CSS utility classes.
|
|
18
|
+
*/
|
|
19
|
+
export function DesignSwitcherDropdown({
|
|
20
|
+
currentPath,
|
|
21
|
+
onNavigate,
|
|
22
|
+
designCount = 21,
|
|
23
|
+
}: DesignSwitcherDropdownProps) {
|
|
24
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
25
|
+
const dropdownRef = useRef<HTMLDivElement>(null)
|
|
26
|
+
|
|
27
|
+
const getCurrentDesign = () => {
|
|
28
|
+
const match = currentPath.match(/\/dev\/design\/(\d+)/)
|
|
29
|
+
return match ? `Design ${match[1]}` : 'Current Design'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const currentDesign = getCurrentDesign()
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
36
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
|
37
|
+
setIsOpen(false)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
document.addEventListener('mousedown', handleClickOutside)
|
|
41
|
+
return () => document.removeEventListener('mousedown', handleClickOutside)
|
|
42
|
+
}, [])
|
|
43
|
+
|
|
44
|
+
const handleDesignChange = (path: string) => {
|
|
45
|
+
onNavigate(path)
|
|
46
|
+
setIsOpen(false)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const designs = [
|
|
50
|
+
{ label: 'Current Design', path: '/' },
|
|
51
|
+
...Array.from({ length: designCount }, (_, i) => ({
|
|
52
|
+
label: `Design ${i + 1}`,
|
|
53
|
+
path: `/dev/design/${i + 1}`,
|
|
54
|
+
})),
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div ref={dropdownRef} className="relative">
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
62
|
+
aria-label="Switch Design"
|
|
63
|
+
className="flex items-center gap-2 px-3 py-2 rounded-lg text-foreground hover:bg-bg-muted transition-colors text-sm font-medium"
|
|
64
|
+
>
|
|
65
|
+
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}><circle cx="13.5" cy="6.5" r="0.5" /><circle cx="17.5" cy="10.5" r="0.5" /><circle cx="8.5" cy="7.5" r="0.5" /><circle cx="6.5" cy="12" r="0.5" /><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z" /></svg>
|
|
66
|
+
<span className="hidden sm:inline">{currentDesign}</span>
|
|
67
|
+
<svg
|
|
68
|
+
className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
|
|
69
|
+
fill="none"
|
|
70
|
+
stroke="currentColor"
|
|
71
|
+
viewBox="0 0 24 24"
|
|
72
|
+
>
|
|
73
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
74
|
+
</svg>
|
|
75
|
+
</button>
|
|
76
|
+
|
|
77
|
+
{isOpen && (
|
|
78
|
+
<div className="absolute right-0 mt-2 py-2 bg-card border border-border rounded-lg shadow-xl overflow-hidden min-w-[180px] max-h-[400px] overflow-y-auto z-50">
|
|
79
|
+
{designs.map((design) => (
|
|
80
|
+
<button
|
|
81
|
+
key={design.path}
|
|
82
|
+
type="button"
|
|
83
|
+
onClick={() => handleDesignChange(design.path)}
|
|
84
|
+
className={`w-full px-4 py-2.5 text-left text-sm transition-colors ${currentDesign === design.label
|
|
85
|
+
? 'bg-primary/10 text-primary font-medium'
|
|
86
|
+
: 'hover:bg-bg-muted text-foreground'
|
|
87
|
+
}`}
|
|
88
|
+
>
|
|
89
|
+
{design.label}
|
|
90
|
+
{currentDesign === design.label && (
|
|
91
|
+
<span className="ml-2 text-primary">✓</span>
|
|
92
|
+
)}
|
|
93
|
+
</button>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// @geenius-tools/devtools-react — src/components/DevSidebar.tsx
|
|
2
|
+
|
|
3
|
+
import { memo, useState, useEffect, useMemo } from 'react'
|
|
4
|
+
import { cn } from '@geenius-ui/react'
|
|
5
|
+
import {
|
|
6
|
+
Bug,
|
|
7
|
+
Lightbulb,
|
|
8
|
+
ExternalLink,
|
|
9
|
+
Clock,
|
|
10
|
+
X,
|
|
11
|
+
RefreshCw,
|
|
12
|
+
Maximize2,
|
|
13
|
+
Minimize2,
|
|
14
|
+
} from 'lucide-react'
|
|
15
|
+
import { useDevModeOptional } from '../context/DevModeContext'
|
|
16
|
+
import {
|
|
17
|
+
getGitHubIssues,
|
|
18
|
+
getGitHubConfig,
|
|
19
|
+
getIssueType,
|
|
20
|
+
formatRelativeTime,
|
|
21
|
+
type GitHubIssueListItem,
|
|
22
|
+
} from '../services/github-service'
|
|
23
|
+
|
|
24
|
+
interface IssueCardProps {
|
|
25
|
+
issue: GitHubIssueListItem
|
|
26
|
+
expandedView: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const IssueCard = memo(function IssueCard({
|
|
30
|
+
issue,
|
|
31
|
+
expandedView,
|
|
32
|
+
}: IssueCardProps) {
|
|
33
|
+
const issueType = getIssueType(issue)
|
|
34
|
+
|
|
35
|
+
const linkedComponent = useMemo(() => {
|
|
36
|
+
if (!issue.body) return null
|
|
37
|
+
const match = issue.body.match(/\*\*Name:\*\*\s*`([^`]+)`/)
|
|
38
|
+
return match ? match[1] : null
|
|
39
|
+
}, [issue.body])
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<a
|
|
43
|
+
href={issue.html_url}
|
|
44
|
+
target="_blank"
|
|
45
|
+
rel="noopener noreferrer"
|
|
46
|
+
className={cn(
|
|
47
|
+
'block p-3 rounded-lg border transition-all duration-200',
|
|
48
|
+
'hover:bg-white/5 hover:border-white/20',
|
|
49
|
+
'border-white/10 bg-white/[0.02]',
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
<div className="flex items-start gap-3">
|
|
53
|
+
<div
|
|
54
|
+
className={cn(
|
|
55
|
+
'flex-shrink-0 w-8 h-8 rounded-lg flex items-center justify-center',
|
|
56
|
+
issueType === 'bug' && 'bg-red-500/10 text-red-500',
|
|
57
|
+
issueType === 'feature' && 'bg-emerald-500/10 text-emerald-500',
|
|
58
|
+
issueType === 'other' && 'bg-blue-500/10 text-blue-500',
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
{issueType === 'bug' ? (
|
|
62
|
+
<Bug className="w-4 h-4" />
|
|
63
|
+
) : (
|
|
64
|
+
<Lightbulb className="w-4 h-4" />
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div className="flex-1 min-w-0">
|
|
69
|
+
<p className="font-medium text-sm text-white line-clamp-2">
|
|
70
|
+
{issue.title}
|
|
71
|
+
</p>
|
|
72
|
+
|
|
73
|
+
{linkedComponent && (
|
|
74
|
+
<span className="inline-flex items-center gap-1 mt-1 text-[10px] px-1.5 py-0.5 bg-white/5 border border-white/10 rounded text-white/60">
|
|
75
|
+
{linkedComponent}
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
<div className="flex items-center gap-3 mt-2 text-xs text-white/40">
|
|
80
|
+
<span>#{issue.number}</span>
|
|
81
|
+
<span className="flex items-center gap-1">
|
|
82
|
+
<Clock className="w-3 h-3" />
|
|
83
|
+
{formatRelativeTime(issue.updated_at)}
|
|
84
|
+
</span>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{expandedView && issue.body && (
|
|
88
|
+
<div className="mt-3 text-xs text-white/40 border-l-2 border-white/10 pl-2 line-clamp-6 whitespace-pre-line font-mono bg-white/[0.02] p-2 rounded-sm">
|
|
89
|
+
{issue.body.substring(0, 300)}
|
|
90
|
+
{issue.body.length > 300 ? '...' : ''}
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</div>
|
|
94
|
+
<ExternalLink className="w-4 h-4 text-white/30 flex-shrink-0" />
|
|
95
|
+
</div>
|
|
96
|
+
</a>
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
export const DevSidebar = memo(function DevSidebar() {
|
|
101
|
+
const devMode = useDevModeOptional()
|
|
102
|
+
const [issues, setIssues] = useState<GitHubIssueListItem[]>([])
|
|
103
|
+
const [loading, setLoading] = useState(false)
|
|
104
|
+
const [tab, setTab] = useState<'bugs' | 'features'>('bugs')
|
|
105
|
+
const [isExpanded, setIsExpanded] = useState(false)
|
|
106
|
+
|
|
107
|
+
const isSidebarOpen = devMode?.isSidebarOpen ?? false
|
|
108
|
+
const setSidebarOpen = devMode?.setSidebarOpen ?? (() => { })
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (isSidebarOpen && issues.length === 0 && !loading) {
|
|
112
|
+
fetchIssues()
|
|
113
|
+
}
|
|
114
|
+
}, [isSidebarOpen])
|
|
115
|
+
|
|
116
|
+
const fetchIssues = async () => {
|
|
117
|
+
setLoading(true)
|
|
118
|
+
try {
|
|
119
|
+
const result = await getGitHubIssues('all', 'open')
|
|
120
|
+
setIssues(result)
|
|
121
|
+
} catch {
|
|
122
|
+
// Silently fail
|
|
123
|
+
} finally {
|
|
124
|
+
setLoading(false)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const handleRefresh = () => fetchIssues()
|
|
129
|
+
const toggleExpand = () => setIsExpanded((prev) => !prev)
|
|
130
|
+
|
|
131
|
+
const { bugs, features } = useMemo(() => {
|
|
132
|
+
const bugs: GitHubIssueListItem[] = []
|
|
133
|
+
const features: GitHubIssueListItem[] = []
|
|
134
|
+
|
|
135
|
+
issues.forEach((issue) => {
|
|
136
|
+
const type = getIssueType(issue)
|
|
137
|
+
if (type === 'bug') bugs.push(issue)
|
|
138
|
+
else features.push(issue)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
return { bugs, features }
|
|
142
|
+
}, [issues])
|
|
143
|
+
|
|
144
|
+
const currentIssues = tab === 'bugs' ? bugs : features
|
|
145
|
+
|
|
146
|
+
if (!devMode || !isSidebarOpen) return null
|
|
147
|
+
|
|
148
|
+
const config = getGitHubConfig()
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div
|
|
152
|
+
data-dev-tool="true"
|
|
153
|
+
className={cn(
|
|
154
|
+
'fixed top-0 right-0 h-full z-50',
|
|
155
|
+
'flex flex-col',
|
|
156
|
+
'bg-gradient-to-b from-slate-900/98 to-slate-950/98',
|
|
157
|
+
'backdrop-blur-xl border-l border-white/10',
|
|
158
|
+
'shadow-2xl shadow-black/50',
|
|
159
|
+
'transition-all duration-300 ease-in-out',
|
|
160
|
+
isExpanded ? 'w-full sm:max-w-[50vw]' : 'w-full sm:max-w-md',
|
|
161
|
+
'animate-in slide-in-from-right duration-300',
|
|
162
|
+
)}
|
|
163
|
+
>
|
|
164
|
+
{/* Header */}
|
|
165
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-white/10">
|
|
166
|
+
<div className="flex items-center gap-2">
|
|
167
|
+
<Bug className="w-4 h-4 text-primary" />
|
|
168
|
+
<span className="text-sm font-bold text-white">GitHub Issues</span>
|
|
169
|
+
<span className="text-xs text-white/40">
|
|
170
|
+
({config.owner}/{config.repo})
|
|
171
|
+
</span>
|
|
172
|
+
</div>
|
|
173
|
+
<div className="flex items-center gap-1">
|
|
174
|
+
<button
|
|
175
|
+
onClick={handleRefresh}
|
|
176
|
+
disabled={loading}
|
|
177
|
+
className={cn(
|
|
178
|
+
'p-1.5 text-white/40 hover:text-white hover:bg-white/10 rounded-lg transition-colors',
|
|
179
|
+
loading && 'animate-spin',
|
|
180
|
+
)}
|
|
181
|
+
>
|
|
182
|
+
<RefreshCw className="w-3.5 h-3.5" />
|
|
183
|
+
</button>
|
|
184
|
+
<button
|
|
185
|
+
onClick={toggleExpand}
|
|
186
|
+
className="hidden sm:block p-1.5 text-white/40 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
|
|
187
|
+
>
|
|
188
|
+
{isExpanded ? (
|
|
189
|
+
<Minimize2 className="w-3.5 h-3.5" />
|
|
190
|
+
) : (
|
|
191
|
+
<Maximize2 className="w-3.5 h-3.5" />
|
|
192
|
+
)}
|
|
193
|
+
</button>
|
|
194
|
+
<button
|
|
195
|
+
onClick={() => setSidebarOpen(false)}
|
|
196
|
+
className="p-1.5 text-white/40 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
|
|
197
|
+
>
|
|
198
|
+
<X className="w-4 h-4" />
|
|
199
|
+
</button>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
{/* Tabs */}
|
|
204
|
+
<div className="flex border-b border-white/10">
|
|
205
|
+
<button
|
|
206
|
+
onClick={() => setTab('bugs')}
|
|
207
|
+
className={cn(
|
|
208
|
+
'flex-1 px-4 py-2.5 text-xs font-medium transition-colors',
|
|
209
|
+
tab === 'bugs'
|
|
210
|
+
? 'text-red-400 border-b-2 border-red-500'
|
|
211
|
+
: 'text-white/40 hover:text-white/70',
|
|
212
|
+
)}
|
|
213
|
+
>
|
|
214
|
+
🐛 Bugs ({bugs.length})
|
|
215
|
+
</button>
|
|
216
|
+
<button
|
|
217
|
+
onClick={() => setTab('features')}
|
|
218
|
+
className={cn(
|
|
219
|
+
'flex-1 px-4 py-2.5 text-xs font-medium transition-colors',
|
|
220
|
+
tab === 'features'
|
|
221
|
+
? 'text-emerald-400 border-b-2 border-emerald-500'
|
|
222
|
+
: 'text-white/40 hover:text-white/70',
|
|
223
|
+
)}
|
|
224
|
+
>
|
|
225
|
+
✨ Features ({features.length})
|
|
226
|
+
</button>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
{/* Content */}
|
|
230
|
+
<div className="flex-1 overflow-y-auto p-3 space-y-2">
|
|
231
|
+
{loading ? (
|
|
232
|
+
<div className="flex items-center justify-center py-12">
|
|
233
|
+
<div className="w-6 h-6 border-2 border-white/20 border-t-primary rounded-full animate-spin" />
|
|
234
|
+
</div>
|
|
235
|
+
) : currentIssues.length === 0 ? (
|
|
236
|
+
<div className="text-center py-12 text-sm text-white/30">
|
|
237
|
+
No {tab === 'bugs' ? 'bugs' : 'features'} found
|
|
238
|
+
</div>
|
|
239
|
+
) : (
|
|
240
|
+
currentIssues.map((issue) => (
|
|
241
|
+
<IssueCard key={issue.id} issue={issue} expandedView={isExpanded} />
|
|
242
|
+
))
|
|
243
|
+
)}
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
)
|
|
247
|
+
})
|