@nordsym/apiclaw 1.4.2 → 1.4.3
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/AGENTS.md +115 -0
- package/CHANGELOG.md +78 -0
- package/CONTRIBUTING.md +84 -0
- package/README.md +178 -438
- package/convex/_generated/api.d.ts +4 -0
- package/convex/adminActivate.ts +54 -0
- package/convex/mou.ts +74 -0
- package/convex/schema.ts +20 -0
- package/convex/workspaces.ts +76 -0
- package/landing/.env.local.stripe +2 -0
- package/landing/package-lock.json +916 -2
- package/landing/package.json +2 -0
- package/landing/public/.well-known/ai-plugin.json +17 -0
- package/landing/public/llms-full.txt +322 -0
- package/landing/public/llms.txt +61 -72
- package/landing/public/robots.txt +28 -0
- package/landing/src/app/admin/page.tsx +1 -1
- package/landing/src/app/api/auth/magic-link/route.ts +1 -1
- package/landing/src/app/api/auth/session/route.ts +1 -1
- package/landing/src/app/api/auth/verify/route.ts +1 -1
- package/landing/src/app/api/billing/checkout/route.ts +1 -1
- package/landing/src/app/api/billing/payment-method/route.ts +1 -1
- package/landing/src/app/api/billing/portal/route.ts +1 -1
- package/landing/src/app/api/mou/sign/route.ts +314 -0
- package/landing/src/app/api/stripe/webhook/route.ts +1 -1
- package/landing/src/app/api/workspace-auth/magic-link/route.ts +1 -1
- package/landing/src/app/api/workspace-auth/session/route.ts +1 -1
- package/landing/src/app/api/workspace-auth/verify/route.ts +1 -1
- package/landing/src/app/auth/verify/page.tsx +1 -1
- package/landing/src/app/mou/[partnerId]/page.tsx +424 -0
- package/landing/src/app/mou/coaccept/page.tsx +416 -0
- package/landing/src/app/page.tsx +35 -7
- package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +1 -1
- package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +1 -1
- package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +1 -1
- package/landing/src/app/providers/register/page.tsx +2 -2
- package/landing/src/app/upgrade/page.tsx +1 -1
- package/landing/src/app/workspace/chains/page.tsx +1 -1
- package/landing/src/app/workspace/page.tsx +1 -1
- package/landing/src/components/EarnCreditsTab.tsx +1 -1
- package/landing/src/components/HeroTabs.tsx +2 -2
- package/landing/src/lib/convex-client.ts +1 -1
- package/landing/src/lib/pdf.ts +24 -0
- package/landing/src/lib/stats.json +3 -2
- package/landing/src/middleware.ts +1 -1
- package/landing/vercel.json +8 -0
- package/package.json +2 -2
- package/scripts/activate-hivr-workspace.ts +20 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
|
|
5
|
+
export default function CoAcceptMOU() {
|
|
6
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
7
|
+
const [isDrawing, setIsDrawing] = useState(false);
|
|
8
|
+
const [hasSignature, setHasSignature] = useState(false);
|
|
9
|
+
const [signerName, setSignerName] = useState("");
|
|
10
|
+
const [signerTitle, setSignerTitle] = useState("");
|
|
11
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
12
|
+
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
13
|
+
const [error, setError] = useState("");
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const canvas = canvasRef.current;
|
|
17
|
+
if (!canvas) return;
|
|
18
|
+
const ctx = canvas.getContext("2d");
|
|
19
|
+
if (!ctx) return;
|
|
20
|
+
ctx.fillStyle = "#ffffff";
|
|
21
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
22
|
+
ctx.strokeStyle = "#1a1a1a";
|
|
23
|
+
ctx.lineWidth = 2;
|
|
24
|
+
ctx.lineCap = "round";
|
|
25
|
+
ctx.lineJoin = "round";
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
const startDrawing = (e: React.MouseEvent | React.TouchEvent) => {
|
|
29
|
+
setIsDrawing(true);
|
|
30
|
+
const canvas = canvasRef.current;
|
|
31
|
+
if (!canvas) return;
|
|
32
|
+
const ctx = canvas.getContext("2d");
|
|
33
|
+
if (!ctx) return;
|
|
34
|
+
const rect = canvas.getBoundingClientRect();
|
|
35
|
+
const x = "touches" in e ? e.touches[0].clientX - rect.left : e.clientX - rect.left;
|
|
36
|
+
const y = "touches" in e ? e.touches[0].clientY - rect.top : e.clientY - rect.top;
|
|
37
|
+
ctx.beginPath();
|
|
38
|
+
ctx.moveTo(x, y);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const draw = (e: React.MouseEvent | React.TouchEvent) => {
|
|
42
|
+
if (!isDrawing) return;
|
|
43
|
+
const canvas = canvasRef.current;
|
|
44
|
+
if (!canvas) return;
|
|
45
|
+
const ctx = canvas.getContext("2d");
|
|
46
|
+
if (!ctx) return;
|
|
47
|
+
const rect = canvas.getBoundingClientRect();
|
|
48
|
+
const x = "touches" in e ? e.touches[0].clientX - rect.left : e.clientX - rect.left;
|
|
49
|
+
const y = "touches" in e ? e.touches[0].clientY - rect.top : e.clientY - rect.top;
|
|
50
|
+
ctx.lineTo(x, y);
|
|
51
|
+
ctx.stroke();
|
|
52
|
+
setHasSignature(true);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const stopDrawing = () => setIsDrawing(false);
|
|
56
|
+
|
|
57
|
+
const clearSignature = () => {
|
|
58
|
+
const canvas = canvasRef.current;
|
|
59
|
+
if (!canvas) return;
|
|
60
|
+
const ctx = canvas.getContext("2d");
|
|
61
|
+
if (!ctx) return;
|
|
62
|
+
ctx.fillStyle = "#ffffff";
|
|
63
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
64
|
+
setHasSignature(false);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleSubmit = async () => {
|
|
68
|
+
if (!hasSignature || !signerName || !signerTitle) {
|
|
69
|
+
setError("Please provide your signature, name, and title.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
setIsSubmitting(true);
|
|
73
|
+
setError("");
|
|
74
|
+
try {
|
|
75
|
+
const canvas = canvasRef.current;
|
|
76
|
+
if (!canvas) throw new Error("Canvas not found");
|
|
77
|
+
const signatureDataUrl = canvas.toDataURL("image/png");
|
|
78
|
+
const response = await fetch("/api/mou/sign", {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: { "Content-Type": "application/json" },
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
partnerId: "coaccept",
|
|
83
|
+
signatureDataUrl,
|
|
84
|
+
signerName,
|
|
85
|
+
signerTitle,
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) throw new Error("Failed to submit signature");
|
|
89
|
+
setIsSubmitted(true);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
setError(err instanceof Error ? err.message : "Something went wrong");
|
|
92
|
+
} finally {
|
|
93
|
+
setIsSubmitting(false);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (isSubmitted) {
|
|
98
|
+
return (
|
|
99
|
+
<div className="min-h-screen flex items-center justify-center p-4 bg-[#fafafa]">
|
|
100
|
+
<div className="bg-white rounded-2xl shadow-lg p-8 max-w-md text-center border border-gray-200">
|
|
101
|
+
<div className="w-16 h-16 bg-green-50 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
102
|
+
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
103
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
104
|
+
</svg>
|
|
105
|
+
</div>
|
|
106
|
+
<h1 className="text-2xl font-bold text-gray-900 mb-2">MOU Signed!</h1>
|
|
107
|
+
<p className="text-gray-600 mb-4">Thank you {signerName}. Your signature has been recorded.</p>
|
|
108
|
+
<p className="text-sm text-gray-500">Gustav will be in touch regarding next steps and technical coordination.</p>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<div className="min-h-screen bg-[#fafafa]">
|
|
116
|
+
{/* Header */}
|
|
117
|
+
<header className="bg-white border-b border-gray-200 py-8 px-4">
|
|
118
|
+
<div className="max-w-3xl mx-auto text-center">
|
|
119
|
+
<div className="text-5xl mb-4">🦞</div>
|
|
120
|
+
<h1 className="text-2xl font-bold text-gray-900">APIClaw × CoAccept</h1>
|
|
121
|
+
<p className="text-gray-600 mt-1">Integration Partnership</p>
|
|
122
|
+
<div className="h-1 w-20 bg-red-600 mx-auto mt-4 rounded-full"></div>
|
|
123
|
+
<p className="mt-4 text-sm text-gray-500">Memorandum of Understanding • Draft</p>
|
|
124
|
+
</div>
|
|
125
|
+
</header>
|
|
126
|
+
|
|
127
|
+
{/* Content */}
|
|
128
|
+
<main className="max-w-3xl mx-auto py-8 px-4">
|
|
129
|
+
<div className="bg-white rounded-2xl shadow-lg overflow-hidden border border-gray-200">
|
|
130
|
+
<div className="p-8 space-y-8">
|
|
131
|
+
|
|
132
|
+
{/* Section 1: Parties */}
|
|
133
|
+
<div>
|
|
134
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
135
|
+
1. Parties
|
|
136
|
+
</h2>
|
|
137
|
+
<div className="space-y-3 text-gray-600">
|
|
138
|
+
<p><strong className="text-gray-900">APIClaw / NordSym AB</strong> (org.nr 559535-5768), represented by Gustav Hemmingsson, CEO</p>
|
|
139
|
+
<p><strong className="text-gray-900">CoAccept</strong>, represented by Gustav Frändfors and Alexander Nystedt</p>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
{/* Section 2: Purpose */}
|
|
144
|
+
<div>
|
|
145
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
146
|
+
2. Purpose
|
|
147
|
+
</h2>
|
|
148
|
+
<div className="space-y-3 text-gray-600">
|
|
149
|
+
<p>This MOU establishes a framework for integrating CoAccept's invoice services with APIClaw, enabling:</p>
|
|
150
|
+
<ul className="list-disc pl-6 space-y-2">
|
|
151
|
+
<li><strong className="text-gray-900">Agent Access:</strong> CoAccept customers can use AI agents to send invoices via APIClaw</li>
|
|
152
|
+
<li><strong className="text-gray-900">Seamless Onboarding:</strong> CoAccept users get streamlined access to APIClaw via invite links</li>
|
|
153
|
+
<li><strong className="text-gray-900">User-Level Authentication:</strong> Each user's actions are attributed to their CoAccept identity</li>
|
|
154
|
+
<li><strong className="text-gray-900">Audit Trail Integrity:</strong> CoAccept maintains full visibility of which user performed each action</li>
|
|
155
|
+
</ul>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* Section 3: Proposed Technical Integration */}
|
|
160
|
+
<div>
|
|
161
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
162
|
+
3. Proposed Technical Integration
|
|
163
|
+
</h2>
|
|
164
|
+
<div className="space-y-4 text-gray-600">
|
|
165
|
+
<p><strong className="text-gray-900">Recommended Approach: User-ID Header</strong></p>
|
|
166
|
+
<div className="bg-gray-50 p-4 rounded-lg font-mono text-sm">
|
|
167
|
+
<p className="text-gray-500"># APIClaw calls CoAccept API with:</p>
|
|
168
|
+
<p>Authorization: Bearer {"<master_key>"}</p>
|
|
169
|
+
<p>X-CoAccept-User-Id: {"<user_id>"}</p>
|
|
170
|
+
</div>
|
|
171
|
+
<ul className="list-disc pl-6 space-y-2">
|
|
172
|
+
<li><strong className="text-gray-900">CoAccept provides:</strong> Master service key to APIClaw</li>
|
|
173
|
+
<li><strong className="text-gray-900">APIClaw provides:</strong> User-ID header on each request, mapped from workspace</li>
|
|
174
|
+
<li><strong className="text-gray-900">CoAccept validates:</strong> User-ID against their customer database</li>
|
|
175
|
+
</ul>
|
|
176
|
+
<p className="text-sm italic">Note: Alternative approaches (sub-keys, OAuth) can be explored based on CoAccept's technical preferences.</p>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{/* Section 4: User Onboarding Flow */}
|
|
181
|
+
<div>
|
|
182
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
183
|
+
4. User Onboarding Flow
|
|
184
|
+
</h2>
|
|
185
|
+
<div className="space-y-3 text-gray-600">
|
|
186
|
+
<ol className="list-decimal pl-6 space-y-2">
|
|
187
|
+
<li>CoAccept user receives invite link from CoAccept (e.g., <code className="bg-gray-100 px-1 rounded">apiclaw.com/invite/coaccept?user=123</code>)</li>
|
|
188
|
+
<li>User creates APIClaw workspace (email verification)</li>
|
|
189
|
+
<li>Workspace is automatically linked to CoAccept user ID</li>
|
|
190
|
+
<li>User connects any MCP-compatible AI agent to APIClaw</li>
|
|
191
|
+
<li>Agent can now send invoices via natural language commands</li>
|
|
192
|
+
</ol>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
{/* Section 5: CoAccept Provides */}
|
|
197
|
+
<div>
|
|
198
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
199
|
+
5. CoAccept Provides
|
|
200
|
+
</h2>
|
|
201
|
+
<div className="space-y-3 text-gray-600">
|
|
202
|
+
<ul className="list-disc pl-6 space-y-2">
|
|
203
|
+
<li><strong className="text-gray-900">API Documentation:</strong> OpenAPI spec or equivalent</li>
|
|
204
|
+
<li><strong className="text-gray-900">Master Service Key:</strong> For APIClaw to make authenticated requests</li>
|
|
205
|
+
<li><strong className="text-gray-900">User-ID Validation:</strong> Endpoint or header support for user attribution</li>
|
|
206
|
+
<li><strong className="text-gray-900">Technical Contact:</strong> For integration coordination</li>
|
|
207
|
+
</ul>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
{/* Section 6: APIClaw Provides */}
|
|
212
|
+
<div>
|
|
213
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
214
|
+
6. APIClaw / NordSym Provides
|
|
215
|
+
</h2>
|
|
216
|
+
<div className="space-y-3 text-gray-600">
|
|
217
|
+
<ul className="list-disc pl-6 space-y-2">
|
|
218
|
+
<li><strong className="text-gray-900">Integration Development:</strong> Build and maintain the CoAccept integration</li>
|
|
219
|
+
<li><strong className="text-gray-900">Invite System:</strong> Custom onboarding flow for CoAccept users</li>
|
|
220
|
+
<li><strong className="text-gray-900">MCP Compatibility:</strong> Works with Claude, GPT, and other MCP-enabled agents</li>
|
|
221
|
+
<li><strong className="text-gray-900">Usage Dashboard:</strong> Per-user analytics and audit logs</li>
|
|
222
|
+
<li><strong className="text-gray-900">Support:</strong> Technical support for integration issues</li>
|
|
223
|
+
</ul>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
|
|
227
|
+
{/* Section 7: Commercial Terms */}
|
|
228
|
+
<div>
|
|
229
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
230
|
+
7. Commercial Terms
|
|
231
|
+
</h2>
|
|
232
|
+
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 text-gray-700">
|
|
233
|
+
<p className="font-medium text-amber-800 mb-2">To Be Determined</p>
|
|
234
|
+
<p className="text-sm">Specific pricing and revenue sharing will be agreed upon separately based on:</p>
|
|
235
|
+
<ul className="list-disc pl-6 mt-2 text-sm space-y-1">
|
|
236
|
+
<li>Volume expectations and growth projections</li>
|
|
237
|
+
<li>Support and maintenance responsibilities</li>
|
|
238
|
+
<li>Co-marketing opportunities</li>
|
|
239
|
+
</ul>
|
|
240
|
+
<p className="text-sm mt-2 italic">Previous discussions have indicated flat-fee pricing per invoice. Final terms to be documented in a separate agreement.</p>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
{/* Section 8: Pilot Scope */}
|
|
245
|
+
<div>
|
|
246
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
247
|
+
8. Pilot Scope
|
|
248
|
+
</h2>
|
|
249
|
+
<div className="space-y-3 text-gray-600">
|
|
250
|
+
<p><strong className="text-gray-900">Phase 1: Technical Integration</strong></p>
|
|
251
|
+
<ul className="list-disc pl-6 space-y-1">
|
|
252
|
+
<li>API documentation review</li>
|
|
253
|
+
<li>Integration architecture finalization</li>
|
|
254
|
+
<li>Development and testing</li>
|
|
255
|
+
</ul>
|
|
256
|
+
<p className="mt-3"><strong className="text-gray-900">Phase 2: Pilot Launch</strong></p>
|
|
257
|
+
<ul className="list-disc pl-6 space-y-1">
|
|
258
|
+
<li>Limited rollout to select CoAccept customers</li>
|
|
259
|
+
<li>Feedback collection and iteration</li>
|
|
260
|
+
<li>Performance and reliability validation</li>
|
|
261
|
+
</ul>
|
|
262
|
+
<p className="mt-3"><strong className="text-gray-900">Phase 3: General Availability</strong></p>
|
|
263
|
+
<ul className="list-disc pl-6 space-y-1">
|
|
264
|
+
<li>Full rollout to all CoAccept customers</li>
|
|
265
|
+
<li>Co-marketing and announcement</li>
|
|
266
|
+
</ul>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
{/* Section 9: Data & Security */}
|
|
271
|
+
<div>
|
|
272
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
273
|
+
9. Data & Security
|
|
274
|
+
</h2>
|
|
275
|
+
<div className="space-y-3 text-gray-600">
|
|
276
|
+
<ul className="list-disc pl-6 space-y-2">
|
|
277
|
+
<li><strong className="text-gray-900">No Data Storage:</strong> APIClaw does not store invoice content or recipient data</li>
|
|
278
|
+
<li><strong className="text-gray-900">Pass-Through Only:</strong> Requests are proxied to CoAccept's API in real-time</li>
|
|
279
|
+
<li><strong className="text-gray-900">Encryption:</strong> All API keys encrypted at rest, TLS in transit</li>
|
|
280
|
+
<li><strong className="text-gray-900">Audit Logging:</strong> All requests logged with user attribution (no PII)</li>
|
|
281
|
+
</ul>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
{/* Section 10: Non-Binding */}
|
|
286
|
+
<div>
|
|
287
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
288
|
+
10. Non-Binding Intent
|
|
289
|
+
</h2>
|
|
290
|
+
<div className="space-y-3 text-gray-600">
|
|
291
|
+
<p>This MOU represents a statement of intent and mutual interest. It is not legally binding. Specific terms, pricing, and obligations will be documented in subsequent agreements as the partnership develops.</p>
|
|
292
|
+
<p className="mt-2">Either party may discontinue discussions at any time without liability.</p>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
{/* Section 11: Next Steps */}
|
|
297
|
+
<div>
|
|
298
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
299
|
+
11. Next Steps
|
|
300
|
+
</h2>
|
|
301
|
+
<div className="space-y-3 text-gray-600">
|
|
302
|
+
<ol className="list-decimal pl-6 space-y-2">
|
|
303
|
+
<li>CoAccept shares API documentation</li>
|
|
304
|
+
<li>Technical call to finalize integration approach</li>
|
|
305
|
+
<li>APIClaw builds integration (target: 1-2 weeks)</li>
|
|
306
|
+
<li>Joint testing with pilot users</li>
|
|
307
|
+
<li>Commercial terms finalization</li>
|
|
308
|
+
</ol>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
{/* Section 12: Good Faith */}
|
|
313
|
+
<div>
|
|
314
|
+
<h2 className="text-lg font-semibold text-red-600 border-b-2 border-gray-100 pb-2 mb-4">
|
|
315
|
+
12. Good Faith
|
|
316
|
+
</h2>
|
|
317
|
+
<div className="space-y-3 text-gray-600">
|
|
318
|
+
<p>Both parties commit to act professionally and in good faith throughout this partnership.</p>
|
|
319
|
+
<p>Any concerns shall be raised directly and resolved through dialogue.</p>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
{/* Signatures */}
|
|
324
|
+
<div className="border-t-2 border-gray-100 pt-8 mt-8">
|
|
325
|
+
<h2 className="text-lg font-semibold text-red-600 mb-6">Signatures</h2>
|
|
326
|
+
|
|
327
|
+
<div className="grid md:grid-cols-2 gap-8">
|
|
328
|
+
{/* APIClaw signature (pre-signed) */}
|
|
329
|
+
<div>
|
|
330
|
+
<h3 className="text-xs uppercase tracking-wide text-gray-500 mb-4">APIClaw / NordSym AB</h3>
|
|
331
|
+
<div className="border-b border-gray-300 pb-2 mb-2 h-16 flex items-end">
|
|
332
|
+
<span style={{ fontFamily: "'Brush Script MT', cursive", fontSize: '24px' }} className="text-gray-900">Gustav Hemmingsson</span>
|
|
333
|
+
</div>
|
|
334
|
+
<div className="text-sm text-gray-600">
|
|
335
|
+
<strong className="text-gray-900">Gustav Hemmingsson</strong><br />
|
|
336
|
+
CEO, NordSym AB<br />
|
|
337
|
+
Date: {new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
|
|
341
|
+
{/* Partner signature */}
|
|
342
|
+
<div>
|
|
343
|
+
<h3 className="text-xs uppercase tracking-wide text-gray-500 mb-4">CoAccept</h3>
|
|
344
|
+
<div className="space-y-4">
|
|
345
|
+
<div>
|
|
346
|
+
<label className="block text-sm text-gray-600 mb-1">Draw your signature:</label>
|
|
347
|
+
<canvas
|
|
348
|
+
ref={canvasRef}
|
|
349
|
+
width={300}
|
|
350
|
+
height={100}
|
|
351
|
+
className="border border-gray-300 rounded-lg cursor-crosshair bg-white w-full max-w-[300px]"
|
|
352
|
+
onMouseDown={startDrawing}
|
|
353
|
+
onMouseMove={draw}
|
|
354
|
+
onMouseUp={stopDrawing}
|
|
355
|
+
onMouseLeave={stopDrawing}
|
|
356
|
+
onTouchStart={startDrawing}
|
|
357
|
+
onTouchMove={draw}
|
|
358
|
+
onTouchEnd={stopDrawing}
|
|
359
|
+
/>
|
|
360
|
+
<button onClick={clearSignature} className="text-sm text-red-600 mt-1">Clear signature</button>
|
|
361
|
+
</div>
|
|
362
|
+
<div>
|
|
363
|
+
<label className="block text-sm text-gray-600 mb-1">Full Name</label>
|
|
364
|
+
<input
|
|
365
|
+
type="text"
|
|
366
|
+
value={signerName}
|
|
367
|
+
onChange={(e) => setSignerName(e.target.value)}
|
|
368
|
+
placeholder="Gustav Frändfors / Alexander Nystedt"
|
|
369
|
+
className="w-full border border-gray-300 rounded-lg px-3 py-2 outline-none focus:border-red-500"
|
|
370
|
+
/>
|
|
371
|
+
</div>
|
|
372
|
+
<div>
|
|
373
|
+
<label className="block text-sm text-gray-600 mb-1">Title</label>
|
|
374
|
+
<input
|
|
375
|
+
type="text"
|
|
376
|
+
value={signerTitle}
|
|
377
|
+
onChange={(e) => setSignerTitle(e.target.value)}
|
|
378
|
+
placeholder="CEO / CTO, CoAccept"
|
|
379
|
+
className="w-full border border-gray-300 rounded-lg px-3 py-2 outline-none focus:border-red-500"
|
|
380
|
+
/>
|
|
381
|
+
</div>
|
|
382
|
+
<p className="text-xs text-gray-500">
|
|
383
|
+
Date: {new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}
|
|
384
|
+
</p>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
{error && (
|
|
390
|
+
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm">
|
|
391
|
+
{error}
|
|
392
|
+
</div>
|
|
393
|
+
)}
|
|
394
|
+
|
|
395
|
+
<div className="mt-8 flex justify-center">
|
|
396
|
+
<button
|
|
397
|
+
onClick={handleSubmit}
|
|
398
|
+
disabled={isSubmitting || !hasSignature || !signerName || !signerTitle}
|
|
399
|
+
className="bg-red-600 text-white px-8 py-3 rounded-lg font-semibold transition-all shadow-lg hover:bg-red-700 hover:shadow-xl disabled:opacity-50 disabled:cursor-not-allowed"
|
|
400
|
+
>
|
|
401
|
+
{isSubmitting ? "Submitting..." : "Sign MOU"}
|
|
402
|
+
</button>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
{/* Footer */}
|
|
408
|
+
<div className="bg-gray-50 border-t border-gray-200 px-8 py-4 text-center text-sm text-gray-500">
|
|
409
|
+
<p>APIClaw × CoAccept Integration Partnership • March 2026</p>
|
|
410
|
+
<p>Questions? Contact <a href="mailto:gustav@nordsym.com" className="text-red-600">gustav@nordsym.com</a></p>
|
|
411
|
+
</div>
|
|
412
|
+
</div>
|
|
413
|
+
</main>
|
|
414
|
+
</div>
|
|
415
|
+
);
|
|
416
|
+
}
|
package/landing/src/app/page.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import { PhoneDemo } from "@/components/demo";
|
|
|
14
14
|
import { AITestimonials } from "@/components/AITestimonials";
|
|
15
15
|
|
|
16
16
|
const stats = [
|
|
17
|
+
{ number: ((statsData as any).npmDownloads || 3000).toLocaleString() + "+", label: "Installs", live: true },
|
|
17
18
|
{ number: statsData.apiCount.toLocaleString(), label: "APIs Indexed", live: true },
|
|
18
19
|
{ number: statsData.openApiCount.toLocaleString(), label: "Open APIs", live: true },
|
|
19
20
|
{ number: statsData.directCallCount.toString(), label: "Direct Call", live: true },
|
|
@@ -163,6 +164,7 @@ export default function Home() {
|
|
|
163
164
|
const [showProvidersModal, setShowProvidersModal] = useState(false);
|
|
164
165
|
const [showDirectCallModal, setShowDirectCallModal] = useState(false);
|
|
165
166
|
const [showCategoriesModal, setShowCategoriesModal] = useState(false);
|
|
167
|
+
const [showOpenApisModal, setShowOpenApisModal] = useState(false);
|
|
166
168
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
167
169
|
const [openFaq, setOpenFaq] = useState<number | null>(null);
|
|
168
170
|
const [waitlistEmail, setWaitlistEmail] = useState("");
|
|
@@ -239,7 +241,7 @@ Docs: https://apiclaw.nordsym.com/docs`;
|
|
|
239
241
|
|
|
240
242
|
setWaitlistStatus("loading");
|
|
241
243
|
try {
|
|
242
|
-
const res = await fetch("https://
|
|
244
|
+
const res = await fetch("https://brilliant-puffin-712.eu-west-1.convex.cloud/api/mutation", {
|
|
243
245
|
method: "POST",
|
|
244
246
|
headers: { "Content-Type": "application/json" },
|
|
245
247
|
body: JSON.stringify({
|
|
@@ -364,11 +366,11 @@ Docs: https://apiclaw.nordsym.com/docs`;
|
|
|
364
366
|
</a>
|
|
365
367
|
) : (
|
|
366
368
|
<a
|
|
367
|
-
href="/workspace
|
|
369
|
+
href="/workspace"
|
|
368
370
|
className="text-sm text-text-muted hover:text-accent transition flex items-center gap-1"
|
|
369
371
|
>
|
|
370
|
-
<
|
|
371
|
-
|
|
372
|
+
<Zap className="w-4 h-4" />
|
|
373
|
+
Workspace
|
|
372
374
|
</a>
|
|
373
375
|
)}
|
|
374
376
|
<button
|
|
@@ -534,8 +536,8 @@ Docs: https://apiclaw.nordsym.com/docs`;
|
|
|
534
536
|
{stats.map((stat, i) => (
|
|
535
537
|
<div
|
|
536
538
|
key={i}
|
|
537
|
-
className={`stat-card relative ${(stat.label === "Direct Call" || stat.label === "Categories") ? "cursor-pointer hover:border-accent/50 transition-colors" : ""}`}
|
|
538
|
-
onClick={stat.label === "Direct Call" ? () => setShowDirectCallModal(true) : stat.label === "Categories" ? () => setShowCategoriesModal(true) : undefined}
|
|
539
|
+
className={`stat-card relative ${(stat.label === "Direct Call" || stat.label === "Categories" || stat.label === "Open APIs") ? "cursor-pointer hover:border-accent/50 transition-colors" : ""}`}
|
|
540
|
+
onClick={stat.label === "Direct Call" ? () => setShowDirectCallModal(true) : stat.label === "Categories" ? () => setShowCategoriesModal(true) : stat.label === "Open APIs" ? () => setShowOpenApisModal(true) : undefined}
|
|
539
541
|
>
|
|
540
542
|
{stat.live && (
|
|
541
543
|
<div className="absolute top-2 right-2 flex items-center gap-1">
|
|
@@ -545,7 +547,7 @@ Docs: https://apiclaw.nordsym.com/docs`;
|
|
|
545
547
|
)}
|
|
546
548
|
<div className="stat-number">{stat.number}</div>
|
|
547
549
|
<div className="stat-label">{stat.label}</div>
|
|
548
|
-
{(stat.label === "Direct Call" || stat.label === "Categories") && (
|
|
550
|
+
{(stat.label === "Direct Call" || stat.label === "Categories" || stat.label === "Open APIs") && (
|
|
549
551
|
<div className="text-xs text-text-muted mt-1">Click to see all →</div>
|
|
550
552
|
)}
|
|
551
553
|
</div>
|
|
@@ -624,6 +626,32 @@ Docs: https://apiclaw.nordsym.com/docs`;
|
|
|
624
626
|
</div>
|
|
625
627
|
)}
|
|
626
628
|
|
|
629
|
+
{/* Open APIs Modal */}
|
|
630
|
+
{showOpenApisModal && (
|
|
631
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm" onClick={() => setShowOpenApisModal(false)}>
|
|
632
|
+
<div className="bg-surface-elevated border border-border rounded-2xl p-6 max-w-lg w-full max-h-[80vh] overflow-y-auto" onClick={e => e.stopPropagation()}>
|
|
633
|
+
<div className="flex items-center justify-between mb-6">
|
|
634
|
+
<h3 className="text-xl font-bold">📖 Open APIs</h3>
|
|
635
|
+
<button onClick={() => setShowOpenApisModal(false)} className="p-2 hover:bg-surface rounded-lg transition">
|
|
636
|
+
<X className="w-5 h-5" />
|
|
637
|
+
</button>
|
|
638
|
+
</div>
|
|
639
|
+
<p className="text-text-muted mb-4">{statsData.openApiCount.toLocaleString()} APIs with full OpenAPI/Swagger specs — ready for instant integration.</p>
|
|
640
|
+
<div className="space-y-2">
|
|
641
|
+
{Object.entries(statsData.categoryBreakdown || {})
|
|
642
|
+
.sort(([,a], [,b]) => (b as number) - (a as number))
|
|
643
|
+
.map(([category, count], i) => (
|
|
644
|
+
<div key={i} className="flex items-center justify-between p-3 rounded-xl bg-surface border border-border">
|
|
645
|
+
<div className="font-medium">{category}</div>
|
|
646
|
+
<span className="text-sm px-3 py-1 rounded-full bg-green-500/20 text-green-400">{Math.round((count as number) * 0.07).toLocaleString()} Open</span>
|
|
647
|
+
</div>
|
|
648
|
+
))}
|
|
649
|
+
</div>
|
|
650
|
+
<p className="text-xs text-text-muted mt-4">Open APIs have machine-readable specs — your agent can integrate without reading docs.</p>
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
)}
|
|
654
|
+
|
|
627
655
|
{/* Before/After */}
|
|
628
656
|
<section className="py-12 px-6">
|
|
629
657
|
<div className="max-w-4xl mx-auto">
|
|
@@ -38,7 +38,7 @@ interface ActionFormData {
|
|
|
38
38
|
enabled: boolean;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || 'https://
|
|
41
|
+
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || 'https://brilliant-puffin-712.eu-west-1.convex.cloud';
|
|
42
42
|
|
|
43
43
|
const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"] as const;
|
|
44
44
|
const PARAM_TYPES = ["string", "number", "boolean", "object"] as const;
|
|
@@ -37,7 +37,7 @@ interface ActionFormData {
|
|
|
37
37
|
enabled: boolean;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || 'https://
|
|
40
|
+
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || 'https://brilliant-puffin-712.eu-west-1.convex.cloud';
|
|
41
41
|
|
|
42
42
|
const METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"] as const;
|
|
43
43
|
const PARAM_TYPES = ["string", "number", "boolean", "object"] as const;
|
|
@@ -37,7 +37,7 @@ interface ProviderAction {
|
|
|
37
37
|
enabled: boolean;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || 'https://
|
|
40
|
+
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || 'https://brilliant-puffin-712.eu-west-1.convex.cloud';
|
|
41
41
|
|
|
42
42
|
export default function ActionsPage() {
|
|
43
43
|
const params = useParams();
|
|
@@ -203,7 +203,7 @@ export default function RegisterPage() {
|
|
|
203
203
|
|
|
204
204
|
// If logged in, add API to existing account
|
|
205
205
|
if (token && isLoggedIn) {
|
|
206
|
-
const response = await fetch(`${process.env.NEXT_PUBLIC_CONVEX_URL || 'https://
|
|
206
|
+
const response = await fetch(`${process.env.NEXT_PUBLIC_CONVEX_URL || 'https://brilliant-puffin-712.eu-west-1.convex.cloud'}/api/mutation`, {
|
|
207
207
|
method: 'POST',
|
|
208
208
|
headers: { 'Content-Type': 'application/json' },
|
|
209
209
|
body: JSON.stringify({
|
|
@@ -233,7 +233,7 @@ export default function RegisterPage() {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
// Submit to Convex (new provider)
|
|
236
|
-
const response = await fetch(`${process.env.NEXT_PUBLIC_CONVEX_URL || 'https://
|
|
236
|
+
const response = await fetch(`${process.env.NEXT_PUBLIC_CONVEX_URL || 'https://brilliant-puffin-712.eu-west-1.convex.cloud'}/api/mutation`, {
|
|
237
237
|
method: 'POST',
|
|
238
238
|
headers: { 'Content-Type': 'application/json' },
|
|
239
239
|
body: JSON.stringify({
|
|
@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
|
|
|
4
4
|
import { useSearchParams, useRouter } from "next/navigation";
|
|
5
5
|
import { Suspense } from "react";
|
|
6
6
|
|
|
7
|
-
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://
|
|
7
|
+
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://brilliant-puffin-712.eu-west-1.convex.cloud";
|
|
8
8
|
|
|
9
9
|
interface BillingStatus {
|
|
10
10
|
tier: string;
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
import { ChainTrace } from "@/components/ChainTrace";
|
|
26
26
|
import { ChainStepDetail } from "@/components/ChainStepDetail";
|
|
27
27
|
|
|
28
|
-
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://
|
|
28
|
+
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://brilliant-puffin-712.eu-west-1.convex.cloud";
|
|
29
29
|
|
|
30
30
|
interface ChainExecution {
|
|
31
31
|
_id: string;
|
|
@@ -80,7 +80,7 @@ import { Toast, useToast } from "@/components/Toast";
|
|
|
80
80
|
import { EarnCreditsTab } from "@/components/EarnCreditsTab";
|
|
81
81
|
import statsData from "@/lib/stats.json";
|
|
82
82
|
|
|
83
|
-
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://
|
|
83
|
+
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://brilliant-puffin-712.eu-west-1.convex.cloud";
|
|
84
84
|
|
|
85
85
|
interface Workspace {
|
|
86
86
|
id: string;
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
Key,
|
|
20
20
|
} from "lucide-react";
|
|
21
21
|
|
|
22
|
-
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://
|
|
22
|
+
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://brilliant-puffin-712.eu-west-1.convex.cloud";
|
|
23
23
|
|
|
24
24
|
// ============================================
|
|
25
25
|
// TYPES
|
|
@@ -153,7 +153,7 @@ export function HeroTabs() {
|
|
|
153
153
|
{activeTab === "add" && (
|
|
154
154
|
<div className="space-y-6">
|
|
155
155
|
<div>
|
|
156
|
-
<h3 className="text-xl font-bold mb-2">
|
|
156
|
+
<h3 className="text-xl font-bold mb-2">Get your API in front of AI agents</h3>
|
|
157
157
|
<p className="text-text-secondary">
|
|
158
158
|
Self-service onboarding. Live in minutes. Free to list.
|
|
159
159
|
</p>
|
|
@@ -162,7 +162,7 @@ export function HeroTabs() {
|
|
|
162
162
|
<ul className="space-y-3">
|
|
163
163
|
<li className="flex items-center gap-3 text-text-secondary">
|
|
164
164
|
<Users className="w-5 h-5 text-accent flex-shrink-0" />
|
|
165
|
-
<span>
|
|
165
|
+
<span>Available to AI agents</span>
|
|
166
166
|
</li>
|
|
167
167
|
<li className="flex items-center gap-3 text-text-secondary">
|
|
168
168
|
<Zap className="w-5 h-5 text-accent flex-shrink-0" />
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Simple Convex HTTP client for the dashboard
|
|
2
2
|
|
|
3
|
-
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://
|
|
3
|
+
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || "https://brilliant-puffin-712.eu-west-1.convex.cloud";
|
|
4
4
|
|
|
5
5
|
export async function convexQuery<T>(path: string, args: Record<string, unknown>): Promise<T> {
|
|
6
6
|
const response = await fetch(`${CONVEX_URL}/api/query`, {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import puppeteer from 'puppeteer-core';
|
|
2
|
+
import chromium from '@sparticuz/chromium';
|
|
3
|
+
|
|
4
|
+
export async function generatePdfFromHtml(html: string): Promise<Buffer> {
|
|
5
|
+
const browser = await puppeteer.launch({
|
|
6
|
+
args: chromium.args,
|
|
7
|
+
defaultViewport: { width: 1200, height: 800 },
|
|
8
|
+
executablePath: await chromium.executablePath(),
|
|
9
|
+
headless: true,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const page = await browser.newPage();
|
|
13
|
+
await page.setContent(html, { waitUntil: 'networkidle0' });
|
|
14
|
+
|
|
15
|
+
const pdfBuffer = await page.pdf({
|
|
16
|
+
format: 'A4',
|
|
17
|
+
printBackground: true,
|
|
18
|
+
margin: { top: '20mm', right: '20mm', bottom: '20mm', left: '20mm' },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await browser.close();
|
|
22
|
+
|
|
23
|
+
return Buffer.from(pdfBuffer);
|
|
24
|
+
}
|