github-to-mcp-monorepo 1.0.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/.env.example +8 -0
- package/.github/CODEOWNERS +6 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +7 -0
- package/.vscode/settings.json +4 -0
- package/ARCHITECTURE.md +1429 -0
- package/CHANGELOG.md +167 -0
- package/CONTRIBUTING.md +327 -0
- package/LICENSE +201 -0
- package/README.md +1028 -0
- package/SECURITY.md +248 -0
- package/VISUAL_GUIDE.md +437 -0
- package/apps/vscode/IMPLEMENTATION.md +480 -0
- package/apps/vscode/README.md +248 -0
- package/apps/vscode/package.json +381 -0
- package/apps/vscode/resources/icon.png +0 -0
- package/apps/vscode/resources/icon.svg +5 -0
- package/apps/vscode/src/commands/browseRegistry.ts +211 -0
- package/apps/vscode/src/commands/configureClaudeDesktop.ts +332 -0
- package/apps/vscode/src/commands/convert.ts +82 -0
- package/apps/vscode/src/commands/convertCurrentRepo.ts +109 -0
- package/apps/vscode/src/commands/convertFromUrl.ts +138 -0
- package/apps/vscode/src/commands/index.ts +121 -0
- package/apps/vscode/src/commands/validate.ts +197 -0
- package/apps/vscode/src/extension.ts +464 -0
- package/apps/vscode/src/global.d.ts +36 -0
- package/apps/vscode/src/test/extension.test.ts +73 -0
- package/apps/vscode/src/utils/file-generator.ts +529 -0
- package/apps/vscode/src/utils/github-api.ts +335 -0
- package/apps/vscode/src/utils/index.ts +29 -0
- package/apps/vscode/src/utils/mcp-config.ts +334 -0
- package/apps/vscode/src/utils/storage.ts +87 -0
- package/apps/vscode/src/views/McpServersTreeView.ts +160 -0
- package/apps/vscode/src/views/OutputChannelView.ts +195 -0
- package/apps/vscode/src/views/StatusBarItem.ts +251 -0
- package/apps/vscode/src/views/ToolsExplorerView.ts +314 -0
- package/apps/vscode/src/views/historyProvider.ts +75 -0
- package/apps/vscode/src/views/index.ts +12 -0
- package/apps/vscode/src/views/resultsPanel.ts +330 -0
- package/apps/vscode/src/webviews/ConversionPanel.ts +350 -0
- package/apps/vscode/src/webviews/ToolDetailsPanel.ts +448 -0
- package/apps/vscode/src/webviews/index.ts +9 -0
- package/apps/vscode/src/webviews/webview-ui/styles.ts +492 -0
- package/apps/vscode/tsconfig.json +20 -0
- package/apps/web/PLAYGROUND_GUIDE.md +499 -0
- package/apps/web/README.md +505 -0
- package/apps/web/app/api/convert/route.ts +100 -0
- package/apps/web/app/api/convert/stream/route.ts +198 -0
- package/apps/web/app/api/deploy/route.ts +157 -0
- package/apps/web/app/api/edge/route.ts +308 -0
- package/apps/web/app/api/export-docker/route.ts +284 -0
- package/apps/web/app/api/generate-openapi/route.ts +119 -0
- package/apps/web/app/api/mcp/[serverId]/route.ts +263 -0
- package/apps/web/app/api/playground/connect/route.ts +143 -0
- package/apps/web/app/api/playground/disconnect/route.ts +78 -0
- package/apps/web/app/api/playground/execute/route.ts +135 -0
- package/apps/web/app/api/playground/sessions/route.ts +103 -0
- package/apps/web/app/api/playground/tools/route.ts +117 -0
- package/apps/web/app/api/playground/v2/connect/route.ts +96 -0
- package/apps/web/app/api/playground/v2/disconnect/route.ts +88 -0
- package/apps/web/app/api/playground/v2/health/route.ts +80 -0
- package/apps/web/app/api/playground/v2/prompts/route.ts +160 -0
- package/apps/web/app/api/playground/v2/resources/route.ts +159 -0
- package/apps/web/app/api/playground/v2/sessions/route.ts +184 -0
- package/apps/web/app/api/playground/v2/tools/route.ts +167 -0
- package/apps/web/app/api/stream/route.ts +232 -0
- package/apps/web/app/batch/BatchConvertClient.tsx +190 -0
- package/apps/web/app/batch/page.tsx +37 -0
- package/apps/web/app/convert/page.tsx +269 -0
- package/apps/web/app/dashboard/page.tsx +380 -0
- package/apps/web/app/globals.css +622 -0
- package/apps/web/app/layout.tsx +120 -0
- package/apps/web/app/manifest.ts +31 -0
- package/apps/web/app/opengraph-image.tsx +112 -0
- package/apps/web/app/page.old.tsx +924 -0
- package/apps/web/app/page.tsx +77 -0
- package/apps/web/app/playground/page.tsx +306 -0
- package/apps/web/app/playground/v2/error.tsx +163 -0
- package/apps/web/app/playground/v2/layout.tsx +58 -0
- package/apps/web/app/playground/v2/loading.tsx +152 -0
- package/apps/web/app/playground/v2/page.tsx +644 -0
- package/apps/web/app/playground/v2/providers.tsx +214 -0
- package/apps/web/app/playground/v2/use-shortcuts.ts +209 -0
- package/apps/web/app/playground/v2/use-url-state.ts +296 -0
- package/apps/web/app/providers.tsx +22 -0
- package/apps/web/app/sitemap.ts +32 -0
- package/apps/web/app/twitter-image.tsx +112 -0
- package/apps/web/components/BranchSelector.tsx +401 -0
- package/apps/web/components/ClaudeConfigExport.tsx +226 -0
- package/apps/web/components/Features.tsx +84 -0
- package/apps/web/components/Footer.tsx +119 -0
- package/apps/web/components/GenerationProgress.tsx +248 -0
- package/apps/web/components/GithubUrlInput.tsx +483 -0
- package/apps/web/components/Header.tsx +175 -0
- package/apps/web/components/Hero.tsx +117 -0
- package/apps/web/components/HowItWorks.tsx +119 -0
- package/apps/web/components/InstallBanner.tsx +158 -0
- package/apps/web/components/Logo.tsx +116 -0
- package/apps/web/components/ParticleBackground.tsx +105 -0
- package/apps/web/components/Playground.tsx +472 -0
- package/apps/web/components/PlaygroundToolTester.tsx +410 -0
- package/apps/web/components/ProductCards.tsx +179 -0
- package/apps/web/components/SplitView.tsx +194 -0
- package/apps/web/components/ToolFilter.tsx +260 -0
- package/apps/web/components/ToolList.tsx +325 -0
- package/apps/web/components/batch/BatchConvert.tsx +785 -0
- package/apps/web/components/batch/index.ts +7 -0
- package/apps/web/components/convert/ConfigTabs.tsx +230 -0
- package/apps/web/components/convert/ConversionResult.tsx +482 -0
- package/apps/web/components/convert/InlinePlayground.tsx +259 -0
- package/apps/web/components/convert/LoadingSteps.tsx +311 -0
- package/apps/web/components/convert/OneClickInstall.tsx +224 -0
- package/apps/web/components/convert/ToolCard.tsx +189 -0
- package/apps/web/components/convert/TryInPlayground.tsx +242 -0
- package/apps/web/components/convert/index.ts +12 -0
- package/apps/web/components/deploy/DeployButton.tsx +369 -0
- package/apps/web/components/deploy/index.ts +7 -0
- package/apps/web/components/docker/DockerExport.tsx +690 -0
- package/apps/web/components/docker/index.ts +7 -0
- package/apps/web/components/install/OneClickInstall.tsx +676 -0
- package/apps/web/components/install/index.ts +7 -0
- package/apps/web/components/playground/CapabilityTabs.tsx +150 -0
- package/apps/web/components/playground/ConnectionStatusV2.tsx +322 -0
- package/apps/web/components/playground/EmptyStates.tsx +305 -0
- package/apps/web/components/playground/ExecutionLog.tsx +260 -0
- package/apps/web/components/playground/ExecutionLogV2.tsx +378 -0
- package/apps/web/components/playground/JsonViewer.tsx +388 -0
- package/apps/web/components/playground/PlaygroundLayout.tsx +244 -0
- package/apps/web/components/playground/PromptsPanel.tsx +385 -0
- package/apps/web/components/playground/ResourcesPanel.tsx +378 -0
- package/apps/web/components/playground/SchemaForm.tsx +477 -0
- package/apps/web/components/playground/ServerStatus.tsx +151 -0
- package/apps/web/components/playground/ShareButton.tsx +239 -0
- package/apps/web/components/playground/ToolsPanel.tsx +309 -0
- package/apps/web/components/playground/TransportConfigurator.tsx +563 -0
- package/apps/web/components/playground/index.ts +74 -0
- package/apps/web/components/playground/types.ts +202 -0
- package/apps/web/components/streaming/StreamingProgress.tsx +441 -0
- package/apps/web/components/streaming/index.ts +7 -0
- package/apps/web/components/ui/badge.tsx +42 -0
- package/apps/web/components/ui/button.tsx +88 -0
- package/apps/web/components/ui/card.tsx +75 -0
- package/apps/web/components/ui/code-block.tsx +122 -0
- package/apps/web/components/ui/index.ts +12 -0
- package/apps/web/components/ui/input.tsx +55 -0
- package/apps/web/components/ui/tabs.tsx +61 -0
- package/apps/web/hooks/index.ts +85 -0
- package/apps/web/hooks/types.ts +1173 -0
- package/apps/web/hooks/use-conversion.ts +133 -0
- package/apps/web/hooks/use-execution-history.ts +376 -0
- package/apps/web/hooks/use-generation-progress.ts +147 -0
- package/apps/web/hooks/use-local-storage.ts +88 -0
- package/apps/web/hooks/use-mcp-client.ts +623 -0
- package/apps/web/hooks/use-mcp-connection.ts +500 -0
- package/apps/web/hooks/use-mcp-execution.ts +282 -0
- package/apps/web/hooks/use-mcp-prompts.ts +441 -0
- package/apps/web/hooks/use-mcp-resources.ts +430 -0
- package/apps/web/hooks/use-mcp-tools.ts +540 -0
- package/apps/web/hooks/use-playground-store.ts +299 -0
- package/apps/web/hooks/use-playground.ts +184 -0
- package/apps/web/hooks/use-streaming-conversion.ts +227 -0
- package/apps/web/hooks/useBatchConversion.ts +271 -0
- package/apps/web/hooks/useDockerConfig.ts +161 -0
- package/apps/web/hooks/usePlatformDetection.ts +80 -0
- package/apps/web/hooks/useStreaming.ts +199 -0
- package/apps/web/lib/api/errors.ts +386 -0
- package/apps/web/lib/api/index.ts +137 -0
- package/apps/web/lib/api/logger.ts +187 -0
- package/apps/web/lib/api/middleware.ts +364 -0
- package/apps/web/lib/api/openapi.ts +977 -0
- package/apps/web/lib/api/session-manager.ts +594 -0
- package/apps/web/lib/api/types.ts +433 -0
- package/apps/web/lib/api/validation.ts +523 -0
- package/apps/web/lib/constants.ts +114 -0
- package/apps/web/lib/mcp/client.ts +1137 -0
- package/apps/web/lib/mcp/events.ts +651 -0
- package/apps/web/lib/mcp/index.ts +347 -0
- package/apps/web/lib/mcp/logger.ts +428 -0
- package/apps/web/lib/mcp/metrics.ts +703 -0
- package/apps/web/lib/mcp/retry.ts +616 -0
- package/apps/web/lib/mcp/session-manager.ts +779 -0
- package/apps/web/lib/mcp/transports.ts +988 -0
- package/apps/web/lib/mcp/types.ts +594 -0
- package/apps/web/lib/mcp-client-enhanced.ts +871 -0
- package/apps/web/lib/mcp-client.ts +778 -0
- package/apps/web/lib/mcp-errors.ts +489 -0
- package/apps/web/lib/mcp-sandbox.ts +593 -0
- package/apps/web/lib/mcp-testing.ts +428 -0
- package/apps/web/lib/mcp-types.ts +448 -0
- package/apps/web/lib/playground-store.tsx +1147 -0
- package/apps/web/lib/utils.ts +439 -0
- package/apps/web/next-env.d.ts +5 -0
- package/apps/web/next.config.js +23 -0
- package/apps/web/package.json +55 -0
- package/apps/web/postcss.config.js +6 -0
- package/apps/web/public/.well-known/ai-plugin.json +17 -0
- package/apps/web/public/logo.svg +6 -0
- package/apps/web/public/robots.txt +22 -0
- package/apps/web/public/schema.json +27 -0
- package/apps/web/tailwind.config.js +26 -0
- package/apps/web/tailwind.config.ts +123 -0
- package/apps/web/tsconfig.json +20 -0
- package/apps/web/types/deploy.ts +139 -0
- package/apps/web/types/index.ts +247 -0
- package/apps/web/vercel.json +39 -0
- package/eslint.config.mjs +23 -0
- package/llms.txt +102 -0
- package/mkdocs/docs/api/core.md +318 -0
- package/mkdocs/docs/api/index.md +128 -0
- package/mkdocs/docs/api/mcp-server.md +301 -0
- package/mkdocs/docs/api/openapi-parser.md +254 -0
- package/mkdocs/docs/assets/logo.svg +7 -0
- package/mkdocs/docs/changelog.md +118 -0
- package/mkdocs/docs/cli/generate.md +148 -0
- package/mkdocs/docs/cli/index.md +52 -0
- package/mkdocs/docs/cli/inspect.md +164 -0
- package/mkdocs/docs/cli/serve.md +136 -0
- package/mkdocs/docs/concepts/classification.md +254 -0
- package/mkdocs/docs/concepts/how-it-works.md +299 -0
- package/mkdocs/docs/concepts/index.md +77 -0
- package/mkdocs/docs/concepts/mcp-protocol.md +362 -0
- package/mkdocs/docs/concepts/tool-types.md +382 -0
- package/mkdocs/docs/contributing/architecture.md +262 -0
- package/mkdocs/docs/contributing/development.md +245 -0
- package/mkdocs/docs/contributing/index.md +73 -0
- package/mkdocs/docs/contributing/testing.md +320 -0
- package/mkdocs/docs/getting-started/configuration.md +235 -0
- package/mkdocs/docs/getting-started/index.md +54 -0
- package/mkdocs/docs/getting-started/installation.md +145 -0
- package/mkdocs/docs/getting-started/quickstart.md +160 -0
- package/mkdocs/docs/guides/batch.md +375 -0
- package/mkdocs/docs/guides/claude-desktop.md +227 -0
- package/mkdocs/docs/guides/cursor.md +188 -0
- package/mkdocs/docs/guides/custom-tools.md +367 -0
- package/mkdocs/docs/guides/index.md +78 -0
- package/mkdocs/docs/guides/private-repos.md +221 -0
- package/mkdocs/docs/guides/vscode.md +247 -0
- package/mkdocs/docs/index.md +175 -0
- package/mkdocs/docs/reference/config.md +223 -0
- package/mkdocs/docs/reference/env.md +192 -0
- package/mkdocs/docs/reference/index.md +102 -0
- package/mkdocs/docs/reference/tools.md +309 -0
- package/mkdocs/docs/stylesheets/extra.css +231 -0
- package/mkdocs/mkdocs.yml +204 -0
- package/mkdocs/overrides/.gitkeep +1 -0
- package/mkdocs/overrides/main.html +7 -0
- package/mkdocs/python-deps.txt +7 -0
- package/mkdocs/vercel.json +11 -0
- package/package.json +63 -0
- package/packages/core/package.json +61 -0
- package/packages/core/src/__tests__/bitbucket-client.test.ts +366 -0
- package/packages/core/src/__tests__/cli.test.ts +235 -0
- package/packages/core/src/__tests__/code-extractor.test.ts +378 -0
- package/packages/core/src/__tests__/docker-generator.test.ts +255 -0
- package/packages/core/src/__tests__/github-client.test.ts +390 -0
- package/packages/core/src/__tests__/gitlab-client.test.ts +319 -0
- package/packages/core/src/__tests__/go-extractor.test.ts +351 -0
- package/packages/core/src/__tests__/graphql-extractor.test.ts +330 -0
- package/packages/core/src/__tests__/java-extractor.test.ts +497 -0
- package/packages/core/src/__tests__/plugins.test.ts +467 -0
- package/packages/core/src/__tests__/readme-extractor.test.ts +258 -0
- package/packages/core/src/__tests__/redis-cache.test.ts +307 -0
- package/packages/core/src/__tests__/rust-extractor.test.ts +252 -0
- package/packages/core/src/__tests__/streaming.test.ts +251 -0
- package/packages/core/src/additional-extractors.ts +333 -0
- package/packages/core/src/cache/cache-interface.ts +179 -0
- package/packages/core/src/cache/index.ts +210 -0
- package/packages/core/src/cache/redis-cache.ts +291 -0
- package/packages/core/src/cache/upstash-cache.ts +379 -0
- package/packages/core/src/cache.ts +251 -0
- package/packages/core/src/cli.ts +822 -0
- package/packages/core/src/code-extractor.ts +696 -0
- package/packages/core/src/docker-generator.ts +470 -0
- package/packages/core/src/edge-compatible.ts +491 -0
- package/packages/core/src/extractors/go-extractor.ts +791 -0
- package/packages/core/src/extractors/index.ts +9 -0
- package/packages/core/src/extractors/java-extractor.ts +937 -0
- package/packages/core/src/extractors/rust-extractor.ts +744 -0
- package/packages/core/src/github-client.ts +319 -0
- package/packages/core/src/go-generator.ts +356 -0
- package/packages/core/src/graphql-extractor.ts +358 -0
- package/packages/core/src/index.ts +797 -0
- package/packages/core/src/langchain-exporter.ts +617 -0
- package/packages/core/src/language-parsers.ts +1114 -0
- package/packages/core/src/mcp-introspector.ts +279 -0
- package/packages/core/src/monorepo-detector.ts +378 -0
- package/packages/core/src/plugins/index.ts +370 -0
- package/packages/core/src/plugins/registry.ts +404 -0
- package/packages/core/src/plugins/types.ts +215 -0
- package/packages/core/src/providers/base-provider.ts +246 -0
- package/packages/core/src/providers/bitbucket-client.ts +464 -0
- package/packages/core/src/providers/gitlab-client.ts +388 -0
- package/packages/core/src/providers/index.ts +176 -0
- package/packages/core/src/python-generator.ts +260 -0
- package/packages/core/src/queue/index.ts +100 -0
- package/packages/core/src/queue/memory-queue.ts +445 -0
- package/packages/core/src/queue/redis-queue.ts +578 -0
- package/packages/core/src/queue/types.ts +251 -0
- package/packages/core/src/readme-extractor.ts +409 -0
- package/packages/core/src/schema-generator.ts +638 -0
- package/packages/core/src/streaming.ts +999 -0
- package/packages/core/src/types.ts +289 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/core/tsup.config.ts +25 -0
- package/packages/mcp-server/README.md +297 -0
- package/packages/mcp-server/package.json +55 -0
- package/packages/mcp-server/src/__tests__/mcp-server.test.ts +177 -0
- package/packages/mcp-server/src/__tests__/tools.test.ts +217 -0
- package/packages/mcp-server/src/index.ts +1206 -0
- package/packages/mcp-server/src/prompts/index.ts +601 -0
- package/packages/mcp-server/src/tools/export-docker.ts +362 -0
- package/packages/mcp-server/src/tools/generate-openapi.ts +162 -0
- package/packages/mcp-server/src/tools/monitor-mcp-server.ts +448 -0
- package/packages/mcp-server/src/tools/stream-convert.ts +398 -0
- package/packages/mcp-server/src/tools/test-mcp-tool.ts +531 -0
- package/packages/mcp-server/tsconfig.json +12 -0
- package/packages/mcp-server/tsup.config.ts +14 -0
- package/packages/openapi-parser/package-lock.json +3028 -0
- package/packages/openapi-parser/package.json +41 -0
- package/packages/openapi-parser/src/analyzer.ts +700 -0
- package/packages/openapi-parser/src/asyncapi-parser.ts +475 -0
- package/packages/openapi-parser/src/cli.ts +302 -0
- package/packages/openapi-parser/src/generator.ts +570 -0
- package/packages/openapi-parser/src/generators/express-analyzer.ts +649 -0
- package/packages/openapi-parser/src/generators/fastapi-analyzer.ts +960 -0
- package/packages/openapi-parser/src/generators/index.ts +200 -0
- package/packages/openapi-parser/src/generators/nextjs-analyzer.ts +768 -0
- package/packages/openapi-parser/src/generators/openapi-builder.ts +527 -0
- package/packages/openapi-parser/src/generators/types.ts +298 -0
- package/packages/openapi-parser/src/graphql-parser.ts +462 -0
- package/packages/openapi-parser/src/grpc-parser.ts +649 -0
- package/packages/openapi-parser/src/har-parser.ts +723 -0
- package/packages/openapi-parser/src/index.ts +635 -0
- package/packages/openapi-parser/src/insomnia-parser.ts +614 -0
- package/packages/openapi-parser/src/parser.ts +231 -0
- package/packages/openapi-parser/src/postman-parser.ts +611 -0
- package/packages/openapi-parser/src/ref-resolver.ts +313 -0
- package/packages/openapi-parser/src/transformer.ts +459 -0
- package/packages/openapi-parser/tests/generators/express.test.ts +209 -0
- package/packages/openapi-parser/tests/generators/fastapi.test.ts +236 -0
- package/packages/openapi-parser/tests/generators/nextjs.test.ts +273 -0
- package/packages/openapi-parser/tests/parsers.test.ts +847 -0
- package/packages/openapi-parser/tsconfig.json +9 -0
- package/packages/openapi-parser/tsup.config.ts +11 -0
- package/packages/registry/package.json +59 -0
- package/packages/registry/src/cli.ts +456 -0
- package/packages/registry/src/index.ts +44 -0
- package/packages/registry/src/popular/github.json +47 -0
- package/packages/registry/src/popular/index.ts +55 -0
- package/packages/registry/src/popular/linear.json +42 -0
- package/packages/registry/src/popular/notion.json +42 -0
- package/packages/registry/src/popular/openai.json +40 -0
- package/packages/registry/src/popular/resend.json +38 -0
- package/packages/registry/src/popular/slack.json +42 -0
- package/packages/registry/src/popular/stripe.json +163 -0
- package/packages/registry/src/popular/supabase.json +42 -0
- package/packages/registry/src/popular/twilio.json +40 -0
- package/packages/registry/src/popular/vercel.json +40 -0
- package/packages/registry/src/registry.ts +492 -0
- package/packages/registry/src/storage.ts +334 -0
- package/packages/registry/src/types.ts +275 -0
- package/packages/registry/src/updater.ts +208 -0
- package/packages/registry/tsconfig.json +10 -0
- package/packages/registry/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +3 -0
- package/scripts/build-docs.sh +16 -0
- package/server.json +9 -0
- package/templates/Dockerfile.python.template +60 -0
- package/templates/Dockerfile.typescript.template +60 -0
- package/templates/docker-compose.template.yml +68 -0
- package/tests/fixtures/express-app/index.js +34 -0
- package/tests/fixtures/express-app/routes/posts.js +43 -0
- package/tests/fixtures/express-app/routes/users.js +58 -0
- package/tests/fixtures/fastapi-app/main.py +125 -0
- package/tests/fixtures/fastapi-app/routes/admin.py +42 -0
- package/tests/fixtures/graphql/simple-schema.graphql +65 -0
- package/tests/fixtures/mocks/github-api-responses.json +63 -0
- package/tests/fixtures/nextjs-app/app/api/posts/route.ts +55 -0
- package/tests/fixtures/nextjs-app/app/api/users/[id]/route.ts +63 -0
- package/tests/fixtures/nextjs-app/app/api/users/route.ts +44 -0
- package/tests/fixtures/nextjs-app/pages/api/health.ts +28 -0
- package/tests/fixtures/openapi/petstore.yaml +179 -0
- package/tests/integration/langchain-export.test.ts +405 -0
- package/tests/integration/openapi-conversion.test.ts +221 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +32 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback, useEffect, useMemo } from 'react'
|
|
4
|
+
import { motion, AnimatePresence } from 'framer-motion'
|
|
5
|
+
import { Search, ArrowRight, AlertCircle, X, Loader2, GitBranch, CheckCircle2, Lightbulb } from 'lucide-react'
|
|
6
|
+
import BranchSelector, { type GitRef } from './BranchSelector'
|
|
7
|
+
|
|
8
|
+
interface GithubUrlInputProps {
|
|
9
|
+
onSubmit: (url: string, ref?: GitRef | null) => void
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
initialValue?: string
|
|
12
|
+
showBranchSelector?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ValidationResult {
|
|
16
|
+
isValid: boolean
|
|
17
|
+
error?: string
|
|
18
|
+
suggestion?: string
|
|
19
|
+
correctedUrl?: string
|
|
20
|
+
hint?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Comprehensive GitHub URL validation with auto-correction and helpful suggestions
|
|
25
|
+
*/
|
|
26
|
+
function validateGithubUrl(input: string): ValidationResult {
|
|
27
|
+
const trimmed = input.trim()
|
|
28
|
+
|
|
29
|
+
// Empty check
|
|
30
|
+
if (!trimmed) {
|
|
31
|
+
return {
|
|
32
|
+
isValid: false,
|
|
33
|
+
error: 'Please enter a GitHub URL',
|
|
34
|
+
hint: 'Example: github.com/facebook/react'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check for common non-GitHub URLs and provide specific guidance
|
|
39
|
+
const nonGithubPatterns = [
|
|
40
|
+
{ pattern: /gitlab\.com/i, name: 'GitLab', suggestion: 'This tool only supports GitHub repositories. GitLab support coming soon!' },
|
|
41
|
+
{ pattern: /bitbucket\.org/i, name: 'Bitbucket', suggestion: 'This tool only supports GitHub repositories.' },
|
|
42
|
+
{ pattern: /npmjs\.com|npm\.im/i, name: 'npm', suggestion: 'Try entering the GitHub repository URL instead. Check the npm page for the repo link.' },
|
|
43
|
+
{ pattern: /pypi\.org/i, name: 'PyPI', suggestion: 'Try entering the GitHub repository URL instead. Check the PyPI page for the repo link.' },
|
|
44
|
+
{ pattern: /stackoverflow\.com|stackexchange\.com/i, name: 'Stack Overflow', suggestion: 'Please enter a GitHub repository URL, not a Q&A link.' },
|
|
45
|
+
{ pattern: /google\.com|bing\.com|duckduckgo\.com/i, name: 'Search engine', suggestion: 'Please enter a GitHub repository URL directly.' },
|
|
46
|
+
{ pattern: /youtube\.com|youtu\.be/i, name: 'YouTube', suggestion: 'Please enter a GitHub repository URL, not a video link.' },
|
|
47
|
+
{ pattern: /twitter\.com|x\.com/i, name: 'Twitter/X', suggestion: 'Please enter a GitHub repository URL, not a social media link.' },
|
|
48
|
+
{ pattern: /medium\.com|dev\.to/i, name: 'Blog', suggestion: 'Please enter a GitHub repository URL, not a blog post.' },
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
for (const { pattern, name, suggestion } of nonGithubPatterns) {
|
|
52
|
+
if (pattern.test(trimmed)) {
|
|
53
|
+
return {
|
|
54
|
+
isValid: false,
|
|
55
|
+
error: `This looks like a ${name} URL`,
|
|
56
|
+
suggestion,
|
|
57
|
+
hint: 'Example: github.com/owner/repo'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Try to normalize and parse the URL
|
|
63
|
+
let normalizedUrl = trimmed
|
|
64
|
+
|
|
65
|
+
// Auto-correct common mistakes
|
|
66
|
+
const corrections: Array<{ pattern: RegExp; replacement: string; description: string }> = [
|
|
67
|
+
// Fix github.com typos
|
|
68
|
+
{ pattern: /^(https?:\/\/)?(www\.)?githib\.com/i, replacement: 'https://github.com', description: 'Fixed typo: githib → github' },
|
|
69
|
+
{ pattern: /^(https?:\/\/)?(www\.)?gihub\.com/i, replacement: 'https://github.com', description: 'Fixed typo: gihub → github' },
|
|
70
|
+
{ pattern: /^(https?:\/\/)?(www\.)?guthub\.com/i, replacement: 'https://github.com', description: 'Fixed typo: guthub → github' },
|
|
71
|
+
{ pattern: /^(https?:\/\/)?(www\.)?githuub\.com/i, replacement: 'https://github.com', description: 'Fixed typo: githuub → github' },
|
|
72
|
+
// Remove trailing .git
|
|
73
|
+
{ pattern: /\.git\/?$/i, replacement: '', description: 'Removed .git suffix' },
|
|
74
|
+
// Fix double slashes (except after protocol)
|
|
75
|
+
{ pattern: /([^:])\/\//g, replacement: '$1/', description: 'Fixed double slashes' },
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
let wasAutoCorreted = false
|
|
79
|
+
let correctionDescription = ''
|
|
80
|
+
|
|
81
|
+
for (const { pattern, replacement, description } of corrections) {
|
|
82
|
+
if (pattern.test(normalizedUrl)) {
|
|
83
|
+
normalizedUrl = normalizedUrl.replace(pattern, replacement)
|
|
84
|
+
wasAutoCorreted = true
|
|
85
|
+
correctionDescription = description
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Add https:// if missing
|
|
90
|
+
if (!normalizedUrl.startsWith('http://') && !normalizedUrl.startsWith('https://')) {
|
|
91
|
+
normalizedUrl = 'https://' + normalizedUrl
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Try to parse as URL
|
|
95
|
+
let parsedUrl: URL
|
|
96
|
+
try {
|
|
97
|
+
parsedUrl = new URL(normalizedUrl)
|
|
98
|
+
} catch {
|
|
99
|
+
return {
|
|
100
|
+
isValid: false,
|
|
101
|
+
error: 'Invalid URL format',
|
|
102
|
+
hint: 'Try: github.com/owner/repo'
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check hostname
|
|
107
|
+
const validHostnames = ['github.com', 'www.github.com']
|
|
108
|
+
if (!validHostnames.includes(parsedUrl.hostname.toLowerCase())) {
|
|
109
|
+
// Check if it might be a raw GitHub content URL
|
|
110
|
+
if (parsedUrl.hostname === 'raw.githubusercontent.com') {
|
|
111
|
+
const parts = parsedUrl.pathname.split('/').filter(Boolean)
|
|
112
|
+
if (parts.length >= 2) {
|
|
113
|
+
const correctedUrl = `https://github.com/${parts[0]}/${parts[1]}`
|
|
114
|
+
return {
|
|
115
|
+
isValid: false,
|
|
116
|
+
error: 'This is a raw content URL',
|
|
117
|
+
suggestion: 'Use the repository URL instead',
|
|
118
|
+
correctedUrl,
|
|
119
|
+
hint: `Did you mean: ${correctedUrl}?`
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check for gist URLs
|
|
125
|
+
if (parsedUrl.hostname === 'gist.github.com') {
|
|
126
|
+
return {
|
|
127
|
+
isValid: false,
|
|
128
|
+
error: 'Gist URLs are not supported',
|
|
129
|
+
suggestion: 'Please enter a GitHub repository URL, not a Gist.',
|
|
130
|
+
hint: 'Example: github.com/owner/repo'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
isValid: false,
|
|
136
|
+
error: 'Not a GitHub URL',
|
|
137
|
+
suggestion: 'Enter a URL starting with github.com',
|
|
138
|
+
hint: 'Example: github.com/owner/repo'
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Parse path parts
|
|
143
|
+
const pathParts = parsedUrl.pathname.split('/').filter(Boolean)
|
|
144
|
+
|
|
145
|
+
// Check for owner/repo pattern
|
|
146
|
+
if (pathParts.length === 0) {
|
|
147
|
+
return {
|
|
148
|
+
isValid: false,
|
|
149
|
+
error: 'Missing repository path',
|
|
150
|
+
suggestion: 'Add the owner and repository name',
|
|
151
|
+
hint: 'Format: github.com/owner/repo'
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (pathParts.length === 1) {
|
|
156
|
+
// Only owner provided
|
|
157
|
+
return {
|
|
158
|
+
isValid: false,
|
|
159
|
+
error: 'Missing repository name',
|
|
160
|
+
suggestion: `Add the repository name after ${pathParts[0]}`,
|
|
161
|
+
hint: `Example: github.com/${pathParts[0]}/repository-name`
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const owner = pathParts[0]
|
|
166
|
+
let repo = pathParts[1]
|
|
167
|
+
|
|
168
|
+
// Validate owner format (GitHub usernames: alphanumeric and hyphens, can't start/end with hyphen)
|
|
169
|
+
if (!/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/.test(owner) || owner.length > 39) {
|
|
170
|
+
return {
|
|
171
|
+
isValid: false,
|
|
172
|
+
error: 'Invalid owner/organization name',
|
|
173
|
+
hint: 'Owner names can only contain letters, numbers, and hyphens'
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Validate repo format (can contain letters, numbers, hyphens, underscores, dots)
|
|
178
|
+
// Remove any extra path parts (like /tree/main)
|
|
179
|
+
repo = repo.replace(/\.git$/, '')
|
|
180
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(repo) || repo.length > 100) {
|
|
181
|
+
return {
|
|
182
|
+
isValid: false,
|
|
183
|
+
error: 'Invalid repository name',
|
|
184
|
+
hint: 'Repository names can only contain letters, numbers, dots, hyphens, and underscores'
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Build the canonical URL
|
|
189
|
+
const canonicalUrl = `https://github.com/${owner}/${repo}`
|
|
190
|
+
|
|
191
|
+
// Success!
|
|
192
|
+
if (wasAutoCorreted) {
|
|
193
|
+
return {
|
|
194
|
+
isValid: true,
|
|
195
|
+
correctedUrl: canonicalUrl,
|
|
196
|
+
suggestion: correctionDescription
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return { isValid: true, correctedUrl: canonicalUrl }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function parseGithubUrl(urlString: string): { owner: string; repo: string } | null {
|
|
204
|
+
const result = validateGithubUrl(urlString)
|
|
205
|
+
if (!result.isValid || !result.correctedUrl) return null
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const url = new URL(result.correctedUrl)
|
|
209
|
+
const pathParts = url.pathname.split('/').filter(Boolean)
|
|
210
|
+
if (pathParts.length >= 2) {
|
|
211
|
+
return { owner: pathParts[0], repo: pathParts[1].replace(/\.git$/, '') }
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// ignore
|
|
215
|
+
}
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export default function GithubUrlInput({ onSubmit, disabled = false, initialValue = '', showBranchSelector = false }: GithubUrlInputProps) {
|
|
220
|
+
const [url, setUrl] = useState(initialValue)
|
|
221
|
+
const [isFocused, setIsFocused] = useState(false)
|
|
222
|
+
const [error, setError] = useState<string | null>(null)
|
|
223
|
+
const [suggestion, setSuggestion] = useState<string | null>(null)
|
|
224
|
+
const [hint, setHint] = useState<string | null>(null)
|
|
225
|
+
const [correctedUrl, setCorrectedUrl] = useState<string | null>(null)
|
|
226
|
+
const [selectedRef, setSelectedRef] = useState<GitRef | null>(null)
|
|
227
|
+
|
|
228
|
+
// Parse owner/repo from URL for BranchSelector
|
|
229
|
+
const repoInfo = useMemo(() => parseGithubUrl(url), [url])
|
|
230
|
+
|
|
231
|
+
// Live validation as user types (debounced feel with useMemo)
|
|
232
|
+
const liveValidation = useMemo(() => {
|
|
233
|
+
if (!url.trim()) return null
|
|
234
|
+
return validateGithubUrl(url)
|
|
235
|
+
}, [url])
|
|
236
|
+
|
|
237
|
+
// Update url when initialValue changes
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
if (initialValue) {
|
|
240
|
+
setUrl(initialValue)
|
|
241
|
+
}
|
|
242
|
+
}, [initialValue])
|
|
243
|
+
|
|
244
|
+
const handleSubmit = useCallback((e: React.FormEvent) => {
|
|
245
|
+
e.preventDefault()
|
|
246
|
+
|
|
247
|
+
const validation = validateGithubUrl(url)
|
|
248
|
+
|
|
249
|
+
if (!validation.isValid) {
|
|
250
|
+
setError(validation.error || 'Invalid URL')
|
|
251
|
+
setSuggestion(validation.suggestion || null)
|
|
252
|
+
setHint(validation.hint || null)
|
|
253
|
+
setCorrectedUrl(validation.correctedUrl || null)
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Clear errors and submit with the corrected URL
|
|
258
|
+
setError(null)
|
|
259
|
+
setSuggestion(null)
|
|
260
|
+
setHint(null)
|
|
261
|
+
setCorrectedUrl(null)
|
|
262
|
+
onSubmit(validation.correctedUrl || url.trim(), selectedRef)
|
|
263
|
+
}, [url, onSubmit, selectedRef])
|
|
264
|
+
|
|
265
|
+
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
266
|
+
setUrl(e.target.value)
|
|
267
|
+
// Clear errors on change
|
|
268
|
+
if (error) {
|
|
269
|
+
setError(null)
|
|
270
|
+
setSuggestion(null)
|
|
271
|
+
setHint(null)
|
|
272
|
+
setCorrectedUrl(null)
|
|
273
|
+
}
|
|
274
|
+
}, [error])
|
|
275
|
+
|
|
276
|
+
const handleClear = useCallback(() => {
|
|
277
|
+
setUrl('')
|
|
278
|
+
setError(null)
|
|
279
|
+
setSuggestion(null)
|
|
280
|
+
setHint(null)
|
|
281
|
+
setCorrectedUrl(null)
|
|
282
|
+
setSelectedRef(null)
|
|
283
|
+
}, [])
|
|
284
|
+
|
|
285
|
+
const handleUseCorrectedUrl = useCallback(() => {
|
|
286
|
+
if (correctedUrl) {
|
|
287
|
+
setUrl(correctedUrl)
|
|
288
|
+
setError(null)
|
|
289
|
+
setSuggestion(null)
|
|
290
|
+
setHint(null)
|
|
291
|
+
setCorrectedUrl(null)
|
|
292
|
+
}
|
|
293
|
+
}, [correctedUrl])
|
|
294
|
+
|
|
295
|
+
// Show valid indicator
|
|
296
|
+
const showValidIndicator = liveValidation?.isValid && url.trim().length > 0
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
<div className="relative">
|
|
300
|
+
{/* Glow effect */}
|
|
301
|
+
<div
|
|
302
|
+
className={`absolute -inset-2 bg-white/10 rounded-2xl blur-xl transition-opacity duration-500 ${isFocused ? 'opacity-100' : 'opacity-0'}`}
|
|
303
|
+
aria-hidden="true"
|
|
304
|
+
/>
|
|
305
|
+
|
|
306
|
+
<form onSubmit={handleSubmit} className="relative">
|
|
307
|
+
{/* Mobile-first layout: stacked on small screens, inline on larger */}
|
|
308
|
+
<div className="flex flex-col sm:flex-row gap-3 sm:gap-0 relative">
|
|
309
|
+
{/* Input container */}
|
|
310
|
+
<div className="relative flex-1">
|
|
311
|
+
<div className="absolute left-4 top-1/2 -translate-y-1/2 pointer-events-none">
|
|
312
|
+
<Search className="w-5 h-5 text-neutral-500" aria-hidden="true" />
|
|
313
|
+
</div>
|
|
314
|
+
<input
|
|
315
|
+
type="text"
|
|
316
|
+
value={url}
|
|
317
|
+
onChange={handleChange}
|
|
318
|
+
onFocus={() => setIsFocused(true)}
|
|
319
|
+
onBlur={() => setIsFocused(false)}
|
|
320
|
+
placeholder="Enter GitHub repo URL (e.g., github.com/owner/repo)"
|
|
321
|
+
aria-label="GitHub Repository URL"
|
|
322
|
+
aria-describedby={error ? 'url-error' : undefined}
|
|
323
|
+
aria-invalid={error ? 'true' : 'false'}
|
|
324
|
+
disabled={disabled}
|
|
325
|
+
className={`w-full pl-12 pr-12 sm:pr-36 py-4 bg-black border rounded-xl text-white placeholder:text-neutral-500 focus:outline-none focus:ring-2 transition-all text-base sm:text-lg min-h-[56px] disabled:opacity-50 disabled:cursor-not-allowed ${
|
|
326
|
+
error
|
|
327
|
+
? 'border-red-500 focus:border-red-500 focus:ring-red-500/20'
|
|
328
|
+
: showValidIndicator
|
|
329
|
+
? 'border-green-500 focus:border-green-500 focus:ring-green-500/20'
|
|
330
|
+
: 'border-neutral-700 focus:border-neutral-500 focus:ring-white/10'
|
|
331
|
+
}`}
|
|
332
|
+
/>
|
|
333
|
+
{/* Valid indicator / Clear button container */}
|
|
334
|
+
<div className="absolute right-3 sm:right-28 top-1/2 -translate-y-1/2 flex items-center gap-1">
|
|
335
|
+
{/* Valid indicator */}
|
|
336
|
+
{showValidIndicator && !error && (
|
|
337
|
+
<motion.div
|
|
338
|
+
initial={{ scale: 0 }}
|
|
339
|
+
animate={{ scale: 1 }}
|
|
340
|
+
className="p-1 text-green-500"
|
|
341
|
+
title="Valid GitHub URL"
|
|
342
|
+
>
|
|
343
|
+
<CheckCircle2 className="w-4 h-4" />
|
|
344
|
+
</motion.div>
|
|
345
|
+
)}
|
|
346
|
+
{/* Clear button */}
|
|
347
|
+
{url && (
|
|
348
|
+
<button
|
|
349
|
+
type="button"
|
|
350
|
+
onClick={handleClear}
|
|
351
|
+
className="p-1.5 text-neutral-500 hover:text-white transition-colors rounded-md focus:outline-none focus:ring-2 focus:ring-white/20"
|
|
352
|
+
aria-label="Clear input"
|
|
353
|
+
>
|
|
354
|
+
<X className="w-4 h-4" />
|
|
355
|
+
</button>
|
|
356
|
+
)}
|
|
357
|
+
</div>
|
|
358
|
+
{/* Desktop submit button (inside input) */}
|
|
359
|
+
<button
|
|
360
|
+
type="submit"
|
|
361
|
+
disabled={!url.trim() || disabled}
|
|
362
|
+
className="hidden sm:flex absolute right-2 top-1/2 -translate-y-1/2 px-5 py-2.5 bg-white rounded-lg font-semibold text-black items-center gap-2 hover:bg-neutral-200 transition-all disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-white/50 min-h-[44px]"
|
|
363
|
+
>
|
|
364
|
+
{disabled ? (
|
|
365
|
+
<>
|
|
366
|
+
<Loader2 className="w-4 h-4 animate-spin" aria-hidden="true" />
|
|
367
|
+
Converting...
|
|
368
|
+
</>
|
|
369
|
+
) : (
|
|
370
|
+
<>
|
|
371
|
+
Convert
|
|
372
|
+
<ArrowRight className="w-4 h-4" aria-hidden="true" />
|
|
373
|
+
</>
|
|
374
|
+
)}
|
|
375
|
+
</button>
|
|
376
|
+
</div>
|
|
377
|
+
|
|
378
|
+
{/* Mobile submit button (separate, full width) */}
|
|
379
|
+
<button
|
|
380
|
+
type="submit"
|
|
381
|
+
disabled={!url.trim() || disabled}
|
|
382
|
+
className="sm:hidden flex items-center justify-center gap-2 w-full px-6 py-4 bg-white rounded-xl font-semibold text-black hover:bg-neutral-200 transition-all disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none focus:ring-2 focus:ring-white/50 min-h-[56px] text-base"
|
|
383
|
+
>
|
|
384
|
+
{disabled ? (
|
|
385
|
+
<>
|
|
386
|
+
<Loader2 className="w-5 h-5 animate-spin" aria-hidden="true" />
|
|
387
|
+
Converting...
|
|
388
|
+
</>
|
|
389
|
+
) : (
|
|
390
|
+
<>
|
|
391
|
+
Convert to MCP
|
|
392
|
+
<ArrowRight className="w-5 h-5" aria-hidden="true" />
|
|
393
|
+
</>
|
|
394
|
+
)}
|
|
395
|
+
</button>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
{/* Branch selector */}
|
|
399
|
+
{showBranchSelector && repoInfo && (
|
|
400
|
+
<motion.div
|
|
401
|
+
initial={{ opacity: 0, y: -10 }}
|
|
402
|
+
animate={{ opacity: 1, y: 0 }}
|
|
403
|
+
className="mt-3 flex items-center gap-2"
|
|
404
|
+
>
|
|
405
|
+
<GitBranch className="w-4 h-4 text-neutral-500" />
|
|
406
|
+
<span className="text-sm text-neutral-500">Branch/Tag:</span>
|
|
407
|
+
<BranchSelector
|
|
408
|
+
owner={repoInfo.owner}
|
|
409
|
+
repo={repoInfo.repo}
|
|
410
|
+
selectedRef={selectedRef}
|
|
411
|
+
onRefChange={setSelectedRef}
|
|
412
|
+
disabled={disabled}
|
|
413
|
+
/>
|
|
414
|
+
</motion.div>
|
|
415
|
+
)}
|
|
416
|
+
|
|
417
|
+
{/* Enhanced error message with suggestions */}
|
|
418
|
+
<AnimatePresence>
|
|
419
|
+
{(error || suggestion || hint || correctedUrl) && (
|
|
420
|
+
<motion.div
|
|
421
|
+
id="url-error"
|
|
422
|
+
role="alert"
|
|
423
|
+
initial={{ opacity: 0, y: -5, height: 0 }}
|
|
424
|
+
animate={{ opacity: 1, y: 0, height: 'auto' }}
|
|
425
|
+
exit={{ opacity: 0, y: -5, height: 0 }}
|
|
426
|
+
transition={{ duration: 0.2 }}
|
|
427
|
+
className="mt-3 p-3 bg-neutral-900 border border-neutral-800 rounded-lg"
|
|
428
|
+
>
|
|
429
|
+
{/* Error message */}
|
|
430
|
+
{error && (
|
|
431
|
+
<div className="flex items-center gap-2 text-red-400 text-sm">
|
|
432
|
+
<AlertCircle className="w-4 h-4 flex-shrink-0" aria-hidden="true" />
|
|
433
|
+
<span className="font-medium">{error}</span>
|
|
434
|
+
</div>
|
|
435
|
+
)}
|
|
436
|
+
|
|
437
|
+
{/* Suggestion */}
|
|
438
|
+
{suggestion && (
|
|
439
|
+
<p className="mt-1.5 text-neutral-400 text-sm pl-6">
|
|
440
|
+
{suggestion}
|
|
441
|
+
</p>
|
|
442
|
+
)}
|
|
443
|
+
|
|
444
|
+
{/* Hint with example */}
|
|
445
|
+
{hint && (
|
|
446
|
+
<div className="mt-2 pl-6 flex items-center gap-2 text-neutral-500 text-sm">
|
|
447
|
+
<Lightbulb className="w-3.5 h-3.5 flex-shrink-0 text-yellow-500/70" />
|
|
448
|
+
<span>{hint}</span>
|
|
449
|
+
</div>
|
|
450
|
+
)}
|
|
451
|
+
|
|
452
|
+
{/* Corrected URL suggestion button */}
|
|
453
|
+
{correctedUrl && (
|
|
454
|
+
<button
|
|
455
|
+
type="button"
|
|
456
|
+
onClick={handleUseCorrectedUrl}
|
|
457
|
+
className="mt-2 ml-6 px-3 py-1.5 bg-white/10 hover:bg-white/20 text-white text-sm rounded-md transition-colors flex items-center gap-2"
|
|
458
|
+
>
|
|
459
|
+
<span>Use: </span>
|
|
460
|
+
<code className="text-green-400">{correctedUrl}</code>
|
|
461
|
+
</button>
|
|
462
|
+
)}
|
|
463
|
+
</motion.div>
|
|
464
|
+
)}
|
|
465
|
+
</AnimatePresence>
|
|
466
|
+
|
|
467
|
+
{/* Live validation hint (when typing, before submit) */}
|
|
468
|
+
<AnimatePresence>
|
|
469
|
+
{!error && liveValidation?.correctedUrl && liveValidation.correctedUrl !== url && url.trim().length > 0 && (
|
|
470
|
+
<motion.div
|
|
471
|
+
initial={{ opacity: 0, y: -5 }}
|
|
472
|
+
animate={{ opacity: 1, y: 0 }}
|
|
473
|
+
exit={{ opacity: 0, y: -5 }}
|
|
474
|
+
className="mt-2 text-neutral-500 text-xs pl-1"
|
|
475
|
+
>
|
|
476
|
+
Will convert as: <span className="text-neutral-400">{liveValidation.correctedUrl}</span>
|
|
477
|
+
</motion.div>
|
|
478
|
+
)}
|
|
479
|
+
</AnimatePresence>
|
|
480
|
+
</form>
|
|
481
|
+
</div>
|
|
482
|
+
)
|
|
483
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { motion, AnimatePresence } from 'framer-motion'
|
|
5
|
+
import { Menu, X, Terminal, Layers, BookOpen, Cloud, Zap, Sparkles } from 'lucide-react'
|
|
6
|
+
import Link from 'next/link'
|
|
7
|
+
import { Logo } from './Logo'
|
|
8
|
+
|
|
9
|
+
const NAV_ITEMS = [
|
|
10
|
+
{ href: '/#how-it-works', label: 'How it works', icon: Zap },
|
|
11
|
+
{ href: '/#features', label: 'Features', icon: Sparkles },
|
|
12
|
+
{ href: '/playground/v2', label: 'Playground', icon: Terminal, badge: 'v2' },
|
|
13
|
+
{ href: '/batch', label: 'Batch Convert', icon: Layers },
|
|
14
|
+
{ href: '/dashboard', label: 'Dashboard', icon: Cloud },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
export default function Header() {
|
|
18
|
+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
|
19
|
+
|
|
20
|
+
// Close mobile menu on escape key
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const handleEscape = (e: KeyboardEvent) => {
|
|
23
|
+
if (e.key === 'Escape') setMobileMenuOpen(false)
|
|
24
|
+
}
|
|
25
|
+
document.addEventListener('keydown', handleEscape)
|
|
26
|
+
return () => document.removeEventListener('keydown', handleEscape)
|
|
27
|
+
}, [])
|
|
28
|
+
|
|
29
|
+
// Close mobile menu when clicking outside
|
|
30
|
+
const handleOverlayClick = useCallback(() => {
|
|
31
|
+
setMobileMenuOpen(false)
|
|
32
|
+
}, [])
|
|
33
|
+
|
|
34
|
+
// Prevent body scroll when menu is open
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (mobileMenuOpen) {
|
|
37
|
+
document.body.style.overflow = 'hidden'
|
|
38
|
+
} else {
|
|
39
|
+
document.body.style.overflow = ''
|
|
40
|
+
}
|
|
41
|
+
return () => {
|
|
42
|
+
document.body.style.overflow = ''
|
|
43
|
+
}
|
|
44
|
+
}, [mobileMenuOpen])
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<motion.header
|
|
48
|
+
initial={{ y: -100 }}
|
|
49
|
+
animate={{ y: 0 }}
|
|
50
|
+
className="fixed top-0 left-0 right-0 z-50 backdrop-blur-xl bg-black/80 border-b border-neutral-800"
|
|
51
|
+
>
|
|
52
|
+
<div className="container mx-auto px-4">
|
|
53
|
+
<div className="flex items-center justify-between h-16">
|
|
54
|
+
{/* Logo */}
|
|
55
|
+
<Link
|
|
56
|
+
href="/"
|
|
57
|
+
className="flex items-center group focus:outline-none focus:ring-2 focus:ring-white/20 rounded-lg p-1 -ml-1"
|
|
58
|
+
aria-label="github-to-mcp home"
|
|
59
|
+
>
|
|
60
|
+
<Logo size="md" />
|
|
61
|
+
</Link>
|
|
62
|
+
|
|
63
|
+
{/* Desktop Nav */}
|
|
64
|
+
<nav className="hidden md:flex items-center gap-8 text-sm" aria-label="Main navigation">
|
|
65
|
+
{NAV_ITEMS.map((item) => (
|
|
66
|
+
<Link
|
|
67
|
+
key={item.href}
|
|
68
|
+
href={item.href}
|
|
69
|
+
className="flex items-center gap-1.5 text-neutral-400 hover:text-white transition-colors focus:outline-none focus:ring-2 focus:ring-white/20 rounded px-2 py-1 -mx-2 -my-1"
|
|
70
|
+
>
|
|
71
|
+
{item.icon && <item.icon className="w-4 h-4" />}
|
|
72
|
+
{item.label}
|
|
73
|
+
{'badge' in item && item.badge && (
|
|
74
|
+
<span className="ml-1 px-1.5 py-0.5 text-[10px] font-medium bg-green-500/20 text-green-400 rounded-full">
|
|
75
|
+
{item.badge}
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
</Link>
|
|
79
|
+
))}
|
|
80
|
+
</nav>
|
|
81
|
+
|
|
82
|
+
{/* Right side - Desktop */}
|
|
83
|
+
<div className="hidden md:flex items-center gap-3">
|
|
84
|
+
<a
|
|
85
|
+
href="https://github.com/nirholas/github-to-mcp"
|
|
86
|
+
target="_blank"
|
|
87
|
+
rel="noopener noreferrer"
|
|
88
|
+
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-neutral-400 hover:text-white bg-transparent border border-neutral-700 rounded-lg hover:border-neutral-600 hover:bg-white/5 transition-colors focus:outline-none focus:ring-2 focus:ring-white/20"
|
|
89
|
+
aria-label="View on GitHub"
|
|
90
|
+
>
|
|
91
|
+
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
|
92
|
+
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/>
|
|
93
|
+
</svg>
|
|
94
|
+
<span>GitHub</span>
|
|
95
|
+
</a>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{/* Mobile menu button */}
|
|
99
|
+
<button
|
|
100
|
+
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
|
101
|
+
className="md:hidden flex items-center justify-center w-11 h-11 -mr-2 text-neutral-400 hover:text-white transition-colors focus:outline-none focus:ring-2 focus:ring-white/20 rounded-lg"
|
|
102
|
+
aria-expanded={mobileMenuOpen}
|
|
103
|
+
aria-controls="mobile-menu"
|
|
104
|
+
aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
|
|
105
|
+
>
|
|
106
|
+
{mobileMenuOpen ? (
|
|
107
|
+
<X className="w-6 h-6" />
|
|
108
|
+
) : (
|
|
109
|
+
<Menu className="w-6 h-6" />
|
|
110
|
+
)}
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* Mobile menu overlay */}
|
|
116
|
+
<AnimatePresence>
|
|
117
|
+
{mobileMenuOpen && (
|
|
118
|
+
<>
|
|
119
|
+
{/* Backdrop */}
|
|
120
|
+
<motion.div
|
|
121
|
+
initial={{ opacity: 0 }}
|
|
122
|
+
animate={{ opacity: 1 }}
|
|
123
|
+
exit={{ opacity: 0 }}
|
|
124
|
+
transition={{ duration: 0.2 }}
|
|
125
|
+
className="fixed inset-0 top-16 bg-black/60 backdrop-blur-sm md:hidden"
|
|
126
|
+
onClick={handleOverlayClick}
|
|
127
|
+
aria-hidden="true"
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
{/* Menu panel */}
|
|
131
|
+
<motion.div
|
|
132
|
+
id="mobile-menu"
|
|
133
|
+
initial={{ opacity: 0, y: -10 }}
|
|
134
|
+
animate={{ opacity: 1, y: 0 }}
|
|
135
|
+
exit={{ opacity: 0, y: -10 }}
|
|
136
|
+
transition={{ duration: 0.2 }}
|
|
137
|
+
className="absolute top-16 left-0 right-0 bg-neutral-900 border-b border-neutral-800 md:hidden"
|
|
138
|
+
>
|
|
139
|
+
<nav className="container mx-auto px-4 py-4" aria-label="Mobile navigation">
|
|
140
|
+
<ul className="space-y-1">
|
|
141
|
+
{NAV_ITEMS.map((item) => (
|
|
142
|
+
<li key={item.href}>
|
|
143
|
+
<Link
|
|
144
|
+
href={item.href}
|
|
145
|
+
onClick={() => setMobileMenuOpen(false)}
|
|
146
|
+
className="flex items-center gap-3 px-4 py-3 text-base text-neutral-300 hover:text-white hover:bg-white/5 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white/20"
|
|
147
|
+
>
|
|
148
|
+
{item.icon && <item.icon className="w-5 h-5" />}
|
|
149
|
+
{item.label}
|
|
150
|
+
</Link>
|
|
151
|
+
</li>
|
|
152
|
+
))}
|
|
153
|
+
<li className="pt-3 border-t border-neutral-800 mt-3">
|
|
154
|
+
<a
|
|
155
|
+
href="https://github.com/nirholas/github-to-mcp"
|
|
156
|
+
target="_blank"
|
|
157
|
+
rel="noopener noreferrer"
|
|
158
|
+
onClick={() => setMobileMenuOpen(false)}
|
|
159
|
+
className="flex items-center gap-3 px-4 py-3 text-base text-neutral-300 hover:text-white hover:bg-white/5 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white/20"
|
|
160
|
+
>
|
|
161
|
+
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
|
|
162
|
+
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/>
|
|
163
|
+
</svg>
|
|
164
|
+
<span>GitHub</span>
|
|
165
|
+
</a>
|
|
166
|
+
</li>
|
|
167
|
+
</ul>
|
|
168
|
+
</nav>
|
|
169
|
+
</motion.div>
|
|
170
|
+
</>
|
|
171
|
+
)}
|
|
172
|
+
</AnimatePresence>
|
|
173
|
+
</motion.header>
|
|
174
|
+
)
|
|
175
|
+
}
|