@theihtisham/devtools-with-cloud 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.
Files changed (150) hide show
  1. package/.env.example +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +73 -0
  4. package/docker-compose.yml +23 -0
  5. package/jest.config.js +7 -0
  6. package/next-env.d.ts +5 -0
  7. package/next.config.mjs +22 -0
  8. package/package.json +82 -0
  9. package/postcss.config.js +6 -0
  10. package/prisma/schema.prisma +105 -0
  11. package/prisma/seed.ts +211 -0
  12. package/src/app/(app)/ai/page.tsx +122 -0
  13. package/src/app/(app)/collections/page.tsx +155 -0
  14. package/src/app/(app)/environments/page.tsx +96 -0
  15. package/src/app/(app)/history/page.tsx +107 -0
  16. package/src/app/(app)/import/page.tsx +102 -0
  17. package/src/app/(app)/layout.tsx +60 -0
  18. package/src/app/(app)/settings/page.tsx +79 -0
  19. package/src/app/(app)/workspace/page.tsx +284 -0
  20. package/src/app/api/ai/discover/route.ts +17 -0
  21. package/src/app/api/ai/explain/route.ts +29 -0
  22. package/src/app/api/ai/generate-tests/route.ts +37 -0
  23. package/src/app/api/ai/suggest/route.ts +29 -0
  24. package/src/app/api/collections/[id]/route.ts +66 -0
  25. package/src/app/api/collections/route.ts +48 -0
  26. package/src/app/api/environments/route.ts +40 -0
  27. package/src/app/api/export/openapi/route.ts +17 -0
  28. package/src/app/api/export/postman/route.ts +18 -0
  29. package/src/app/api/import/curl/route.ts +18 -0
  30. package/src/app/api/import/har/route.ts +20 -0
  31. package/src/app/api/import/openapi/route.ts +21 -0
  32. package/src/app/api/import/postman/route.ts +21 -0
  33. package/src/app/api/proxy/route.ts +35 -0
  34. package/src/app/api/requests/[id]/execute/route.ts +85 -0
  35. package/src/app/api/requests/[id]/history/route.ts +23 -0
  36. package/src/app/api/requests/[id]/route.ts +66 -0
  37. package/src/app/api/requests/route.ts +49 -0
  38. package/src/app/api/workspaces/route.ts +38 -0
  39. package/src/app/globals.css +99 -0
  40. package/src/app/layout.tsx +24 -0
  41. package/src/app/page.tsx +182 -0
  42. package/src/components/ai/ai-panel.tsx +65 -0
  43. package/src/components/ai/code-explainer.tsx +51 -0
  44. package/src/components/ai/endpoint-discovery.tsx +62 -0
  45. package/src/components/ai/test-generator.tsx +49 -0
  46. package/src/components/collections/collection-actions.tsx +36 -0
  47. package/src/components/collections/collection-tree.tsx +55 -0
  48. package/src/components/collections/folder-creator.tsx +54 -0
  49. package/src/components/landing/comparison.tsx +43 -0
  50. package/src/components/landing/cta.tsx +16 -0
  51. package/src/components/landing/features.tsx +24 -0
  52. package/src/components/landing/hero.tsx +23 -0
  53. package/src/components/response/body-viewer.tsx +33 -0
  54. package/src/components/response/headers-viewer.tsx +23 -0
  55. package/src/components/response/status-badge.tsx +25 -0
  56. package/src/components/response/test-results.tsx +50 -0
  57. package/src/components/response/timing-chart.tsx +39 -0
  58. package/src/components/ui/badge.tsx +24 -0
  59. package/src/components/ui/button.tsx +32 -0
  60. package/src/components/ui/code-editor.tsx +51 -0
  61. package/src/components/ui/dialog.tsx +56 -0
  62. package/src/components/ui/dropdown.tsx +63 -0
  63. package/src/components/ui/input.tsx +22 -0
  64. package/src/components/ui/key-value-editor.tsx +75 -0
  65. package/src/components/ui/select.tsx +24 -0
  66. package/src/components/ui/tabs.tsx +85 -0
  67. package/src/components/ui/textarea.tsx +22 -0
  68. package/src/components/ui/toast.tsx +54 -0
  69. package/src/components/workspace/request-panel.tsx +38 -0
  70. package/src/components/workspace/response-panel.tsx +81 -0
  71. package/src/components/workspace/sidebar.tsx +52 -0
  72. package/src/components/workspace/split-pane.tsx +49 -0
  73. package/src/components/workspace/tabs/auth-tab.tsx +94 -0
  74. package/src/components/workspace/tabs/body-tab.tsx +41 -0
  75. package/src/components/workspace/tabs/headers-tab.tsx +23 -0
  76. package/src/components/workspace/tabs/params-tab.tsx +23 -0
  77. package/src/components/workspace/tabs/pre-request-tab.tsx +26 -0
  78. package/src/components/workspace/url-bar.tsx +53 -0
  79. package/src/hooks/use-ai.ts +115 -0
  80. package/src/hooks/use-collection.ts +71 -0
  81. package/src/hooks/use-environment.ts +73 -0
  82. package/src/hooks/use-request.ts +111 -0
  83. package/src/lib/ai/endpoint-discovery.ts +158 -0
  84. package/src/lib/ai/explainer.ts +127 -0
  85. package/src/lib/ai/suggester.ts +164 -0
  86. package/src/lib/ai/test-generator.ts +161 -0
  87. package/src/lib/auth/api-key.ts +28 -0
  88. package/src/lib/auth/aws-sig.ts +131 -0
  89. package/src/lib/auth/basic.ts +17 -0
  90. package/src/lib/auth/bearer.ts +15 -0
  91. package/src/lib/auth/oauth2.ts +155 -0
  92. package/src/lib/auth/types.ts +16 -0
  93. package/src/lib/db/client.ts +15 -0
  94. package/src/lib/env/manager.ts +32 -0
  95. package/src/lib/env/resolver.ts +30 -0
  96. package/src/lib/exporters/openapi.ts +193 -0
  97. package/src/lib/exporters/postman.ts +140 -0
  98. package/src/lib/graphql/builder.ts +249 -0
  99. package/src/lib/graphql/formatter.ts +147 -0
  100. package/src/lib/graphql/index.ts +43 -0
  101. package/src/lib/graphql/introspection.ts +175 -0
  102. package/src/lib/graphql/types.ts +99 -0
  103. package/src/lib/graphql/validator.ts +216 -0
  104. package/src/lib/http/client.ts +112 -0
  105. package/src/lib/http/proxy.ts +83 -0
  106. package/src/lib/http/request-builder.ts +214 -0
  107. package/src/lib/http/response-parser.ts +106 -0
  108. package/src/lib/http/timing.ts +63 -0
  109. package/src/lib/importers/curl-parser.ts +346 -0
  110. package/src/lib/importers/har-parser.ts +128 -0
  111. package/src/lib/importers/openapi.ts +324 -0
  112. package/src/lib/importers/postman.ts +312 -0
  113. package/src/lib/test-runner/assertions.ts +163 -0
  114. package/src/lib/test-runner/reporter.ts +90 -0
  115. package/src/lib/test-runner/runner.ts +69 -0
  116. package/src/lib/utils/api-response.ts +85 -0
  117. package/src/lib/utils/cn.ts +6 -0
  118. package/src/lib/utils/content-type.ts +123 -0
  119. package/src/lib/utils/download.ts +53 -0
  120. package/src/lib/utils/errors.ts +92 -0
  121. package/src/lib/utils/format.ts +142 -0
  122. package/src/lib/utils/syntax-highlight.ts +108 -0
  123. package/src/lib/utils/validation.ts +231 -0
  124. package/src/lib/websocket/client.ts +182 -0
  125. package/src/lib/websocket/frames.ts +96 -0
  126. package/src/lib/websocket/history.ts +121 -0
  127. package/src/lib/websocket/index.ts +25 -0
  128. package/src/lib/websocket/types.ts +57 -0
  129. package/src/types/ai.ts +28 -0
  130. package/src/types/collection.ts +24 -0
  131. package/src/types/environment.ts +16 -0
  132. package/src/types/request.ts +54 -0
  133. package/src/types/response.ts +37 -0
  134. package/tailwind.config.ts +82 -0
  135. package/tests/lib/env/resolver.test.ts +108 -0
  136. package/tests/lib/graphql/builder.test.ts +349 -0
  137. package/tests/lib/graphql/formatter.test.ts +99 -0
  138. package/tests/lib/http/request-builder.test.ts +160 -0
  139. package/tests/lib/http/response-parser.test.ts +150 -0
  140. package/tests/lib/http/timing.test.ts +188 -0
  141. package/tests/lib/importers/curl-parser.test.ts +245 -0
  142. package/tests/lib/test-runner/assertions.test.ts +342 -0
  143. package/tests/lib/utils/cn.test.ts +46 -0
  144. package/tests/lib/utils/content-type.test.ts +175 -0
  145. package/tests/lib/utils/format.test.ts +188 -0
  146. package/tests/lib/utils/validation.test.ts +237 -0
  147. package/tests/lib/websocket/history.test.ts +186 -0
  148. package/tsconfig.json +29 -0
  149. package/tsconfig.tsbuildinfo +1 -0
  150. package/vitest.config.ts +21 -0
@@ -0,0 +1,182 @@
1
+ import Link from 'next/link';
2
+
3
+ export default function LandingPage() {
4
+ return (
5
+ <div className="min-h-screen bg-background">
6
+ {/* Header */}
7
+ <header className="border-b border-border">
8
+ <div className="container mx-auto flex items-center justify-between px-6 py-4">
9
+ <div className="flex items-center gap-2">
10
+ <div className="h-8 w-8 rounded-lg bg-primary flex items-center justify-center">
11
+ <span className="text-primary-foreground font-bold text-sm">AT</span>
12
+ </div>
13
+ <span className="text-xl font-bold text-foreground">APITester</span>
14
+ </div>
15
+ <nav className="flex items-center gap-6">
16
+ <a href="#features" className="text-muted-foreground hover:text-foreground transition-colors">Features</a>
17
+ <a href="#comparison" className="text-muted-foreground hover:text-foreground transition-colors">Compare</a>
18
+ <Link
19
+ href="/workspace"
20
+ className="bg-primary text-primary-foreground px-4 py-2 rounded-md hover:bg-primary/90 transition-colors"
21
+ >
22
+ Open App
23
+ </Link>
24
+ </nav>
25
+ </div>
26
+ </header>
27
+
28
+ {/* Hero */}
29
+ <section className="container mx-auto px-6 py-24 text-center">
30
+ <h1 className="text-5xl font-bold text-foreground mb-6">
31
+ API Testing,{' '}
32
+ <span className="text-primary">Reimagined</span>
33
+ </h1>
34
+ <p className="text-xl text-muted-foreground max-w-2xl mx-auto mb-8">
35
+ An open-source API testing tool with AI-powered test generation,
36
+ endpoint discovery, and a beautiful interface. Built for modern developers.
37
+ </p>
38
+ <div className="flex gap-4 justify-center">
39
+ <Link
40
+ href="/workspace"
41
+ className="bg-primary text-primary-foreground px-6 py-3 rounded-lg text-lg font-medium hover:bg-primary/90 transition-colors"
42
+ >
43
+ Get Started
44
+ </Link>
45
+ <a
46
+ href="https://github.com/api-tester/api-tester"
47
+ target="_blank"
48
+ rel="noopener noreferrer"
49
+ className="border border-border text-foreground px-6 py-3 rounded-lg text-lg font-medium hover:bg-accent transition-colors"
50
+ >
51
+ GitHub
52
+ </a>
53
+ </div>
54
+ </section>
55
+
56
+ {/* Features */}
57
+ <section id="features" className="container mx-auto px-6 py-16">
58
+ <h2 className="text-3xl font-bold text-center text-foreground mb-12">
59
+ Features That Matter
60
+ </h2>
61
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
62
+ {[
63
+ {
64
+ title: 'AI Test Generation',
65
+ description: 'Automatically generate comprehensive test suites from your API responses. Save hours of manual test writing.',
66
+ icon: 'brain',
67
+ },
68
+ {
69
+ title: 'Endpoint Discovery',
70
+ description: 'Scan your codebase and auto-generate API collections from Express, FastAPI, Next.js, and more.',
71
+ icon: 'search',
72
+ },
73
+ {
74
+ title: 'Beautiful Interface',
75
+ description: 'A modern, responsive UI with syntax highlighting, collapsible JSON, and timing visualizations.',
76
+ icon: 'palette',
77
+ },
78
+ {
79
+ title: 'Import Anything',
80
+ description: 'Import from OpenAPI, Postman, cURL, and HAR. Zero migration cost from your existing tools.',
81
+ icon: 'import',
82
+ },
83
+ {
84
+ title: 'Environment Management',
85
+ description: 'Switch between development, staging, and production with variable interpolation and secrets masking.',
86
+ icon: 'env',
87
+ },
88
+ {
89
+ title: 'Self-Hosted',
90
+ description: 'Run it locally or on your own infrastructure. Your data never leaves your network.',
91
+ icon: 'server',
92
+ },
93
+ ].map((feature) => (
94
+ <div
95
+ key={feature.title}
96
+ className="border border-border rounded-lg p-6 hover:border-primary/50 transition-colors"
97
+ >
98
+ <div className="h-10 w-10 rounded-lg bg-primary/10 flex items-center justify-center mb-4">
99
+ <span className="text-primary text-lg">
100
+ {feature.icon === 'brain' && '{ }'}
101
+ {feature.icon === 'search' && '<>'}
102
+ {feature.icon === 'palette' && '#'}
103
+ {feature.icon === 'import' && '+'}
104
+ {feature.icon === 'env' && '$'}
105
+ {feature.icon === 'server' && '@'}
106
+ </span>
107
+ </div>
108
+ <h3 className="text-lg font-semibold text-foreground mb-2">{feature.title}</h3>
109
+ <p className="text-muted-foreground">{feature.description}</p>
110
+ </div>
111
+ ))}
112
+ </div>
113
+ </section>
114
+
115
+ {/* Comparison */}
116
+ <section id="comparison" className="container mx-auto px-6 py-16">
117
+ <h2 className="text-3xl font-bold text-center text-foreground mb-12">
118
+ Why APITester?
119
+ </h2>
120
+ <div className="overflow-x-auto">
121
+ <table className="w-full border border-border rounded-lg">
122
+ <thead>
123
+ <tr className="border-b border-border bg-muted">
124
+ <th className="px-4 py-3 text-left text-foreground">Feature</th>
125
+ <th className="px-4 py-3 text-center text-primary font-bold">APITester</th>
126
+ <th className="px-4 py-3 text-center text-muted-foreground">Postman</th>
127
+ <th className="px-4 py-3 text-center text-muted-foreground">Insomnia</th>
128
+ </tr>
129
+ </thead>
130
+ <tbody>
131
+ {[
132
+ { feature: 'Open Source', us: true, postman: false, insomnia: true },
133
+ { feature: 'AI Test Generation', us: true, postman: false, insomnia: false },
134
+ { feature: 'Self-Hosted', us: true, postman: false, insomnia: false },
135
+ { feature: 'Unlimited Requests', us: true, postman: true, insomnia: true },
136
+ { feature: 'Free Forever', us: true, postman: false, insomnia: true },
137
+ { feature: 'Import/Export', us: true, postman: true, insomnia: true },
138
+ { feature: 'Environment Variables', us: true, postman: true, insomnia: true },
139
+ ].map((row) => (
140
+ <tr key={row.feature} className="border-b border-border">
141
+ <td className="px-4 py-3 text-foreground">{row.feature}</td>
142
+ <td className="px-4 py-3 text-center">
143
+ {row.us ? <span className="text-green-400">&#10003;</span> : <span className="text-red-400">&#10007;</span>}
144
+ </td>
145
+ <td className="px-4 py-3 text-center">
146
+ {row.postman ? <span className="text-green-400">&#10003;</span> : <span className="text-red-400">&#10007;</span>}
147
+ </td>
148
+ <td className="px-4 py-3 text-center">
149
+ {row.insomnia ? <span className="text-green-400">&#10003;</span> : <span className="text-red-400">&#10007;</span>}
150
+ </td>
151
+ </tr>
152
+ ))}
153
+ </tbody>
154
+ </table>
155
+ </div>
156
+ </section>
157
+
158
+ {/* CTA */}
159
+ <section className="container mx-auto px-6 py-24 text-center">
160
+ <h2 className="text-3xl font-bold text-foreground mb-4">
161
+ Ready to Get Started?
162
+ </h2>
163
+ <p className="text-lg text-muted-foreground mb-8">
164
+ Start testing your APIs in seconds. No account required.
165
+ </p>
166
+ <Link
167
+ href="/workspace"
168
+ className="bg-primary text-primary-foreground px-8 py-3 rounded-lg text-lg font-medium hover:bg-primary/90 transition-colors inline-block"
169
+ >
170
+ Launch APITester
171
+ </Link>
172
+ </section>
173
+
174
+ {/* Footer */}
175
+ <footer className="border-t border-border py-8">
176
+ <div className="container mx-auto px-6 text-center text-muted-foreground">
177
+ <p>APITester - Open Source API Testing Tool</p>
178
+ </div>
179
+ </footer>
180
+ </div>
181
+ );
182
+ }
@@ -0,0 +1,65 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { useAi } from '@/hooks/use-ai';
5
+
6
+ export function AiPanel() {
7
+ const [input, setInput] = useState('');
8
+ const [mode, setMode] = useState<'generate' | 'discover' | 'explain' | 'suggest'>('generate');
9
+ const { loading, result, error, generateTests, discoverEndpoints, explainResponse, suggestImprovements, clear } = useAi();
10
+
11
+ const handleSubmit = async () => {
12
+ if (!input.trim()) return;
13
+ switch (mode) {
14
+ case 'generate':
15
+ await generateTests(input);
16
+ break;
17
+ case 'discover':
18
+ await discoverEndpoints(input);
19
+ break;
20
+ case 'explain':
21
+ await explainResponse(input);
22
+ break;
23
+ case 'suggest':
24
+ await suggestImprovements(input);
25
+ break;
26
+ }
27
+ };
28
+
29
+ return (
30
+ <div className="flex flex-col h-full">
31
+ <div className="flex gap-1 p-2 border-b border-border">
32
+ {(['generate', 'discover', 'explain', 'suggest'] as const).map((m) => (
33
+ <button
34
+ key={m}
35
+ onClick={() => { setMode(m); clear(); }}
36
+ className={`px-3 py-1 text-xs rounded ${mode === m ? 'bg-primary/10 text-primary' : 'text-muted-foreground hover:text-foreground'}`}
37
+ >
38
+ {m}
39
+ </button>
40
+ ))}
41
+ </div>
42
+ <textarea
43
+ value={input}
44
+ onChange={(e) => setInput(e.target.value)}
45
+ className="flex-1 p-2 bg-transparent text-foreground text-sm font-mono resize-none outline-none"
46
+ placeholder={`Enter input for ${mode}...`}
47
+ />
48
+ <div className="p-2 border-t border-border">
49
+ <button
50
+ onClick={handleSubmit}
51
+ disabled={loading}
52
+ className="w-full px-3 py-1.5 bg-primary text-primary-foreground rounded text-sm"
53
+ >
54
+ {loading ? 'Processing...' : 'Run'}
55
+ </button>
56
+ </div>
57
+ {error && <div className="p-2 text-destructive text-xs border-t border-border">{error}</div>}
58
+ {result && (
59
+ <div className="max-h-48 overflow-auto p-2 border-t border-border">
60
+ <pre className="text-xs font-mono text-foreground whitespace-pre-wrap">{result}</pre>
61
+ </div>
62
+ )}
63
+ </div>
64
+ );
65
+ }
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ export function CodeExplainer() {
6
+ const [input, setInput] = useState('');
7
+ const [explanation, setExplanation] = useState('');
8
+ const [loading, setLoading] = useState(false);
9
+
10
+ const explain = async () => {
11
+ setLoading(true);
12
+ try {
13
+ const res = await fetch('/api/ai/explain', {
14
+ method: 'POST',
15
+ headers: { 'Content-Type': 'application/json' },
16
+ body: JSON.stringify({ response: input }),
17
+ });
18
+ const data = await res.json();
19
+ if (data.success && data.data?.explanation) {
20
+ setExplanation(data.data.explanation);
21
+ }
22
+ } catch {
23
+ // Handle error silently
24
+ } finally {
25
+ setLoading(false);
26
+ }
27
+ };
28
+
29
+ return (
30
+ <div>
31
+ <textarea
32
+ value={input}
33
+ onChange={(e) => setInput(e.target.value)}
34
+ placeholder="Paste a response to explain..."
35
+ className="w-full h-40 px-3 py-2 rounded border border-border bg-background text-foreground text-sm font-mono mb-3"
36
+ />
37
+ <button
38
+ onClick={explain}
39
+ disabled={loading || !input.trim()}
40
+ className="px-4 py-2 bg-primary text-primary-foreground rounded text-sm mb-3"
41
+ >
42
+ {loading ? 'Analyzing...' : 'Explain'}
43
+ </button>
44
+ {explanation && (
45
+ <div className="p-3 rounded border border-border bg-muted/30">
46
+ <pre className="text-sm text-foreground whitespace-pre-wrap">{explanation}</pre>
47
+ </div>
48
+ )}
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ interface DiscoveredEndpoint {
6
+ method: string;
7
+ path: string;
8
+ name: string;
9
+ }
10
+
11
+ export function EndpointDiscovery() {
12
+ const [code, setCode] = useState('');
13
+ const [endpoints, setEndpoints] = useState<DiscoveredEndpoint[]>([]);
14
+ const [loading, setLoading] = useState(false);
15
+
16
+ const discover = async () => {
17
+ setLoading(true);
18
+ try {
19
+ const res = await fetch('/api/ai/discover', {
20
+ method: 'POST',
21
+ headers: { 'Content-Type': 'application/json' },
22
+ body: JSON.stringify({ code, framework: 'auto', workspaceId: 'default' }),
23
+ });
24
+ const data = await res.json();
25
+ if (data.success && data.data?.endpoints) {
26
+ setEndpoints(data.data.endpoints);
27
+ }
28
+ } catch {
29
+ // Handle error silently
30
+ } finally {
31
+ setLoading(false);
32
+ }
33
+ };
34
+
35
+ return (
36
+ <div>
37
+ <textarea
38
+ value={code}
39
+ onChange={(e) => setCode(e.target.value)}
40
+ placeholder="Paste source code to discover endpoints..."
41
+ className="w-full h-40 px-3 py-2 rounded border border-border bg-background text-foreground text-sm font-mono mb-3"
42
+ />
43
+ <button
44
+ onClick={discover}
45
+ disabled={loading || !code.trim()}
46
+ className="px-4 py-2 bg-primary text-primary-foreground rounded text-sm mb-3"
47
+ >
48
+ {loading ? 'Scanning...' : 'Discover'}
49
+ </button>
50
+ {endpoints.length > 0 && (
51
+ <div className="space-y-2">
52
+ {endpoints.map((ep, i) => (
53
+ <div key={i} className="flex items-center gap-2 p-2 border border-border rounded text-sm">
54
+ <span className="font-mono text-method-get">{ep.method}</span>
55
+ <span className="font-mono text-foreground">{ep.path}</span>
56
+ </div>
57
+ ))}
58
+ </div>
59
+ )}
60
+ </div>
61
+ );
62
+ }
@@ -0,0 +1,49 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { CodeEditor } from '@/components/ui/code-editor';
5
+
6
+ interface TestGeneratorProps {
7
+ response: string;
8
+ onGenerated: (tests: string) => void;
9
+ }
10
+
11
+ export function TestGenerator({ response, onGenerated }: TestGeneratorProps) {
12
+ const [loading, setLoading] = useState(false);
13
+ const [tests, setTests] = useState('');
14
+
15
+ const generate = async () => {
16
+ setLoading(true);
17
+ try {
18
+ const res = await fetch('/api/ai/generate-tests', {
19
+ method: 'POST',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify({ requestId: 'manual', response }),
22
+ });
23
+ const data = await res.json();
24
+ if (data.success && data.data?.tests) {
25
+ setTests(data.data.tests);
26
+ onGenerated(data.data.tests);
27
+ }
28
+ } catch {
29
+ // Handle error silently
30
+ } finally {
31
+ setLoading(false);
32
+ }
33
+ };
34
+
35
+ return (
36
+ <div>
37
+ <button
38
+ onClick={generate}
39
+ disabled={loading || !response}
40
+ className="px-4 py-2 bg-primary text-primary-foreground rounded text-sm mb-3"
41
+ >
42
+ {loading ? 'Generating...' : 'Generate Tests'}
43
+ </button>
44
+ {tests && (
45
+ <CodeEditor value={tests} onChange={setTests} language="javascript" />
46
+ )}
47
+ </div>
48
+ );
49
+ }
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+
3
+ interface CollectionActionsProps {
4
+ collectionId: string;
5
+ onExport?: (format: string) => void;
6
+ onRunAll?: () => void;
7
+ onDelete?: () => void;
8
+ }
9
+
10
+ export function CollectionActions({ collectionId, onExport, onRunAll, onDelete }: CollectionActionsProps) {
11
+ return (
12
+ <div className="flex items-center gap-2">
13
+ <button
14
+ onClick={onRunAll}
15
+ className="px-3 py-1 text-xs bg-primary text-primary-foreground rounded hover:bg-primary/90 transition-colors"
16
+ >
17
+ Run All
18
+ </button>
19
+ <select
20
+ onChange={(e) => e.target.value && onExport?.(e.target.value)}
21
+ className="px-2 py-1 text-xs border border-border rounded bg-background text-foreground"
22
+ defaultValue=""
23
+ >
24
+ <option value="" disabled>Export</option>
25
+ <option value="openapi">OpenAPI 3.1</option>
26
+ <option value="postman">Postman</option>
27
+ </select>
28
+ <button
29
+ onClick={onDelete}
30
+ className="px-3 py-1 text-xs text-destructive hover:text-destructive/80 transition-colors"
31
+ >
32
+ Delete
33
+ </button>
34
+ </div>
35
+ );
36
+ }
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+
3
+ import { cn } from '@/lib/utils/cn';
4
+
5
+ interface CollectionTreeItem {
6
+ id: string;
7
+ name: string;
8
+ method?: string;
9
+ children?: CollectionTreeItem[];
10
+ }
11
+
12
+ interface CollectionTreeProps {
13
+ items: CollectionTreeItem[];
14
+ selectedId?: string;
15
+ onSelect: (id: string) => void;
16
+ className?: string;
17
+ }
18
+
19
+ export function CollectionTree({ items, selectedId, onSelect, className }: CollectionTreeProps) {
20
+ return (
21
+ <div className={cn('space-y-0.5', className)}>
22
+ {items.map((item) => (
23
+ <div key={item.id}>
24
+ <button
25
+ onClick={() => onSelect(item.id)}
26
+ className={cn(
27
+ 'w-full flex items-center gap-2 px-2 py-1.5 rounded text-sm text-left transition-colors',
28
+ selectedId === item.id ? 'bg-primary/10 text-primary' : 'text-foreground hover:bg-accent',
29
+ )}
30
+ >
31
+ {item.method && (
32
+ <span className={cn(
33
+ 'text-xs font-mono font-medium',
34
+ item.method === 'GET' && 'text-method-get',
35
+ item.method === 'POST' && 'text-method-post',
36
+ item.method === 'PUT' && 'text-method-put',
37
+ item.method === 'DELETE' && 'text-method-delete',
38
+ item.method === 'PATCH' && 'text-method-patch',
39
+ )}>
40
+ {item.method}
41
+ </span>
42
+ )}
43
+ {!item.method && <span className="text-xs text-muted-foreground">#</span>}
44
+ <span className="truncate">{item.name}</span>
45
+ </button>
46
+ {item.children && item.children.length > 0 && (
47
+ <div className="ml-4">
48
+ <CollectionTree items={item.children} selectedId={selectedId} onSelect={onSelect} />
49
+ </div>
50
+ )}
51
+ </div>
52
+ ))}
53
+ </div>
54
+ );
55
+ }
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+
5
+ interface FolderCreatorProps {
6
+ parentId?: string;
7
+ onCreate: (name: string) => void;
8
+ }
9
+
10
+ export function FolderCreator({ parentId, onCreate }: FolderCreatorProps) {
11
+ const [name, setName] = useState('');
12
+ const [show, setShow] = useState(false);
13
+
14
+ if (!show) {
15
+ return (
16
+ <button
17
+ onClick={() => setShow(true)}
18
+ className="text-xs text-primary hover:text-primary/80"
19
+ >
20
+ + New Folder
21
+ </button>
22
+ );
23
+ }
24
+
25
+ return (
26
+ <div className="flex items-center gap-2">
27
+ <input
28
+ type="text"
29
+ value={name}
30
+ onChange={(e) => setName(e.target.value)}
31
+ placeholder="Folder name"
32
+ className="flex-1 px-2 py-1 text-sm border border-border rounded bg-background text-foreground"
33
+ autoFocus
34
+ onKeyDown={(e) => {
35
+ if (e.key === 'Enter' && name.trim()) {
36
+ onCreate(name.trim());
37
+ setName('');
38
+ setShow(false);
39
+ }
40
+ if (e.key === 'Escape') {
41
+ setShow(false);
42
+ setName('');
43
+ }
44
+ }}
45
+ />
46
+ <button
47
+ onClick={() => { setShow(false); setName(''); }}
48
+ className="text-xs text-muted-foreground"
49
+ >
50
+ Cancel
51
+ </button>
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,43 @@
1
+ export function Comparison() {
2
+ const rows = [
3
+ { feature: 'Open Source', us: true, postman: false, insomnia: true },
4
+ { feature: 'AI Test Generation', us: true, postman: false, insomnia: false },
5
+ { feature: 'Self-Hosted', us: true, postman: false, insomnia: false },
6
+ { feature: 'Free Forever', us: true, postman: false, insomnia: true },
7
+ { feature: 'Unlimited Requests', us: true, postman: true, insomnia: true },
8
+ ];
9
+
10
+ return (
11
+ <section className="container mx-auto px-6 py-16">
12
+ <h2 className="text-3xl font-bold text-center text-foreground mb-12">Why APITester?</h2>
13
+ <div className="overflow-x-auto">
14
+ <table className="w-full border border-border rounded-lg">
15
+ <thead>
16
+ <tr className="border-b border-border bg-muted">
17
+ <th className="px-4 py-3 text-left text-foreground">Feature</th>
18
+ <th className="px-4 py-3 text-center text-primary font-bold">APITester</th>
19
+ <th className="px-4 py-3 text-center text-muted-foreground">Postman</th>
20
+ <th className="px-4 py-3 text-center text-muted-foreground">Insomnia</th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ {rows.map((row) => (
25
+ <tr key={row.feature} className="border-b border-border">
26
+ <td className="px-4 py-3 text-foreground">{row.feature}</td>
27
+ <td className="px-4 py-3 text-center">
28
+ {row.us ? <span className="text-green-400">&#10003;</span> : <span className="text-red-400">&#10007;</span>}
29
+ </td>
30
+ <td className="px-4 py-3 text-center">
31
+ {row.postman ? <span className="text-green-400">&#10003;</span> : <span className="text-red-400">&#10007;</span>}
32
+ </td>
33
+ <td className="px-4 py-3 text-center">
34
+ {row.insomnia ? <span className="text-green-400">&#10003;</span> : <span className="text-red-400">&#10007;</span>}
35
+ </td>
36
+ </tr>
37
+ ))}
38
+ </tbody>
39
+ </table>
40
+ </div>
41
+ </section>
42
+ );
43
+ }
@@ -0,0 +1,16 @@
1
+ import Link from 'next/link';
2
+
3
+ export function CTA() {
4
+ return (
5
+ <section className="container mx-auto px-6 py-24 text-center">
6
+ <h2 className="text-3xl font-bold text-foreground mb-4">Ready to Get Started?</h2>
7
+ <p className="text-lg text-muted-foreground mb-8">Start testing your APIs in seconds.</p>
8
+ <Link
9
+ href="/workspace"
10
+ className="bg-primary text-primary-foreground px-8 py-3 rounded-lg text-lg font-medium hover:bg-primary/90 transition-colors inline-block"
11
+ >
12
+ Launch APITester
13
+ </Link>
14
+ </section>
15
+ );
16
+ }
@@ -0,0 +1,24 @@
1
+ const features = [
2
+ { title: 'AI Test Generation', description: 'Automatically generate test suites from API responses.' },
3
+ { title: 'Endpoint Discovery', description: 'Scan codebases to discover API endpoints automatically.' },
4
+ { title: 'Beautiful Interface', description: 'Modern, responsive UI with syntax highlighting.' },
5
+ { title: 'Import Anything', description: 'Import from OpenAPI, Postman, cURL, and HAR.' },
6
+ { title: 'Environment Management', description: 'Switch between dev, staging, and production.' },
7
+ { title: 'Self-Hosted', description: 'Run locally. Your data never leaves your network.' },
8
+ ];
9
+
10
+ export function Features() {
11
+ return (
12
+ <section className="container mx-auto px-6 py-16">
13
+ <h2 className="text-3xl font-bold text-center text-foreground mb-12">Features</h2>
14
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
15
+ {features.map((f) => (
16
+ <div key={f.title} className="border border-border rounded-lg p-6 hover:border-primary/50 transition-colors">
17
+ <h3 className="text-lg font-semibold text-foreground mb-2">{f.title}</h3>
18
+ <p className="text-muted-foreground">{f.description}</p>
19
+ </div>
20
+ ))}
21
+ </div>
22
+ </section>
23
+ );
24
+ }
@@ -0,0 +1,23 @@
1
+ import Link from 'next/link';
2
+
3
+ export function Hero() {
4
+ return (
5
+ <section className="container mx-auto px-6 py-24 text-center">
6
+ <h1 className="text-5xl font-bold text-foreground mb-6">
7
+ API Testing, <span className="text-primary">Reimagined</span>
8
+ </h1>
9
+ <p className="text-xl text-muted-foreground max-w-2xl mx-auto mb-8">
10
+ An open-source API testing tool with AI-powered test generation,
11
+ endpoint discovery, and a beautiful interface.
12
+ </p>
13
+ <div className="flex gap-4 justify-center">
14
+ <Link
15
+ href="/workspace"
16
+ className="bg-primary text-primary-foreground px-6 py-3 rounded-lg text-lg font-medium hover:bg-primary/90 transition-colors"
17
+ >
18
+ Get Started
19
+ </Link>
20
+ </div>
21
+ </section>
22
+ );
23
+ }