spaps 0.2.5 → 0.2.7

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.
@@ -0,0 +1,763 @@
1
+ /**
2
+ * SPAPS Local Server Documentation HTML Generator
3
+ * Generates comprehensive documentation page
4
+ */
5
+
6
+ function generateDocsHTML(port = 3300) {
7
+ return `<!DOCTYPE html>
8
+ <html lang="en">
9
+ <head>
10
+ <meta charset="UTF-8">
11
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
+ <title>SPAPS Documentation - Local Development Mode</title>
13
+ <style>
14
+ * {
15
+ margin: 0;
16
+ padding: 0;
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
22
+ line-height: 1.6;
23
+ color: #333;
24
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
25
+ min-height: 100vh;
26
+ }
27
+
28
+ .container {
29
+ max-width: 1200px;
30
+ margin: 0 auto;
31
+ padding: 2rem;
32
+ }
33
+
34
+ .header {
35
+ background: white;
36
+ border-radius: 12px;
37
+ padding: 2rem;
38
+ margin-bottom: 2rem;
39
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
40
+ }
41
+
42
+ .header h1 {
43
+ color: #764ba2;
44
+ font-size: 2.5rem;
45
+ margin-bottom: 0.5rem;
46
+ }
47
+
48
+ .header .subtitle {
49
+ color: #666;
50
+ font-size: 1.1rem;
51
+ }
52
+
53
+ .status-badge {
54
+ display: inline-block;
55
+ background: #48bb78;
56
+ color: white;
57
+ padding: 0.25rem 0.75rem;
58
+ border-radius: 20px;
59
+ font-size: 0.9rem;
60
+ margin-top: 1rem;
61
+ }
62
+
63
+ .nav {
64
+ background: white;
65
+ border-radius: 12px;
66
+ padding: 1rem;
67
+ margin-bottom: 2rem;
68
+ box-shadow: 0 5px 15px rgba(0,0,0,0.08);
69
+ position: sticky;
70
+ top: 1rem;
71
+ z-index: 100;
72
+ }
73
+
74
+ .nav ul {
75
+ list-style: none;
76
+ display: flex;
77
+ gap: 2rem;
78
+ overflow-x: auto;
79
+ padding: 0.5rem;
80
+ }
81
+
82
+ .nav a {
83
+ color: #764ba2;
84
+ text-decoration: none;
85
+ font-weight: 500;
86
+ white-space: nowrap;
87
+ transition: color 0.3s;
88
+ }
89
+
90
+ .nav a:hover {
91
+ color: #667eea;
92
+ }
93
+
94
+ .content {
95
+ background: white;
96
+ border-radius: 12px;
97
+ padding: 2rem;
98
+ margin-bottom: 2rem;
99
+ box-shadow: 0 5px 15px rgba(0,0,0,0.08);
100
+ }
101
+
102
+ h2 {
103
+ color: #764ba2;
104
+ margin-top: 2rem;
105
+ margin-bottom: 1rem;
106
+ padding-bottom: 0.5rem;
107
+ border-bottom: 2px solid #f0f0f0;
108
+ }
109
+
110
+ h3 {
111
+ color: #667eea;
112
+ margin-top: 1.5rem;
113
+ margin-bottom: 0.75rem;
114
+ }
115
+
116
+ pre {
117
+ background: #f7f7f7;
118
+ border: 1px solid #e0e0e0;
119
+ border-radius: 8px;
120
+ padding: 1rem;
121
+ overflow-x: auto;
122
+ margin: 1rem 0;
123
+ position: relative;
124
+ }
125
+
126
+ code {
127
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', monospace;
128
+ font-size: 0.9rem;
129
+ background: #f7f7f7;
130
+ padding: 0.2rem 0.4rem;
131
+ border-radius: 4px;
132
+ }
133
+
134
+ pre code {
135
+ background: none;
136
+ padding: 0;
137
+ }
138
+
139
+ .endpoint {
140
+ background: #f9f9f9;
141
+ border-left: 4px solid #667eea;
142
+ padding: 1rem;
143
+ margin: 1rem 0;
144
+ border-radius: 4px;
145
+ }
146
+
147
+ .endpoint strong {
148
+ color: #764ba2;
149
+ font-family: 'SF Mono', Monaco, monospace;
150
+ font-size: 0.95rem;
151
+ }
152
+
153
+ .endpoint .method {
154
+ display: inline-block;
155
+ padding: 0.2rem 0.5rem;
156
+ border-radius: 4px;
157
+ font-weight: bold;
158
+ font-size: 0.85rem;
159
+ margin-right: 0.5rem;
160
+ }
161
+
162
+ .endpoint .method.get { background: #48bb78; color: white; }
163
+ .endpoint .method.post { background: #4299e1; color: white; }
164
+ .endpoint .method.put { background: #ed8936; color: white; }
165
+ .endpoint .method.delete { background: #f56565; color: white; }
166
+
167
+ .tabs {
168
+ display: flex;
169
+ gap: 1rem;
170
+ margin-bottom: 1rem;
171
+ border-bottom: 2px solid #f0f0f0;
172
+ }
173
+
174
+ .tab {
175
+ padding: 0.5rem 1rem;
176
+ cursor: pointer;
177
+ border: none;
178
+ background: none;
179
+ color: #666;
180
+ font-size: 1rem;
181
+ transition: all 0.3s;
182
+ }
183
+
184
+ .tab.active {
185
+ color: #764ba2;
186
+ border-bottom: 2px solid #764ba2;
187
+ margin-bottom: -2px;
188
+ }
189
+
190
+ .tab-content {
191
+ display: none;
192
+ }
193
+
194
+ .tab-content.active {
195
+ display: block;
196
+ }
197
+
198
+ .feature-grid {
199
+ display: grid;
200
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
201
+ gap: 1.5rem;
202
+ margin: 2rem 0;
203
+ }
204
+
205
+ .feature-card {
206
+ background: #f9f9f9;
207
+ padding: 1.5rem;
208
+ border-radius: 8px;
209
+ transition: transform 0.3s;
210
+ }
211
+
212
+ .feature-card:hover {
213
+ transform: translateY(-2px);
214
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
215
+ }
216
+
217
+ .feature-card h3 {
218
+ margin-top: 0;
219
+ }
220
+
221
+ .copy-button {
222
+ position: absolute;
223
+ top: 0.5rem;
224
+ right: 0.5rem;
225
+ background: #667eea;
226
+ color: white;
227
+ border: none;
228
+ padding: 0.25rem 0.75rem;
229
+ border-radius: 4px;
230
+ cursor: pointer;
231
+ font-size: 0.85rem;
232
+ transition: background 0.3s;
233
+ }
234
+
235
+ .copy-button:hover {
236
+ background: #764ba2;
237
+ }
238
+
239
+ .copy-button.copied {
240
+ background: #48bb78;
241
+ }
242
+
243
+ .alert {
244
+ background: #fef5e7;
245
+ border: 1px solid #f9e79f;
246
+ border-radius: 8px;
247
+ padding: 1rem;
248
+ margin: 1rem 0;
249
+ }
250
+
251
+ .alert.info {
252
+ background: #e8f4fd;
253
+ border-color: #bee5eb;
254
+ }
255
+
256
+ .alert.success {
257
+ background: #d4edda;
258
+ border-color: #c3e6cb;
259
+ }
260
+
261
+ .footer {
262
+ text-align: center;
263
+ color: white;
264
+ padding: 2rem;
265
+ }
266
+
267
+ .footer a {
268
+ color: white;
269
+ text-decoration: underline;
270
+ }
271
+ </style>
272
+ </head>
273
+ <body>
274
+ <div class="container">
275
+ <div class="header">
276
+ <h1>🍠 SPAPS Documentation</h1>
277
+ <p class="subtitle">Sweet Potato Authentication & Payment Service</p>
278
+ <span class="status-badge">✅ Local Mode Active - Port ${port}</span>
279
+ </div>
280
+
281
+ <nav class="nav">
282
+ <ul>
283
+ <li><a href="#quickstart">Quick Start</a></li>
284
+ <li><a href="#sdk">SDK Setup</a></li>
285
+ <li><a href="#authentication">Authentication</a></li>
286
+ <li><a href="#payments">Payments</a></li>
287
+ <li><a href="#endpoints">API Endpoints</a></li>
288
+ <li><a href="#examples">Examples</a></li>
289
+ <li><a href="#testing">Testing</a></li>
290
+ </ul>
291
+ </nav>
292
+
293
+ <div class="content">
294
+ <div class="alert info">
295
+ <strong>🚀 Local Development Mode</strong><br>
296
+ You're running in local mode. No API keys, database, or external services required!
297
+ All responses are mocked for rapid development.
298
+ </div>
299
+
300
+ <section id="quickstart">
301
+ <h2>Quick Start</h2>
302
+
303
+ <h3>1. Install the SDK</h3>
304
+ <pre><code>npm install spaps-sdk
305
+ # or
306
+ yarn add spaps-sdk</code></pre>
307
+
308
+ <h3>2. Initialize the Client</h3>
309
+ <div class="tabs">
310
+ <button class="tab active" onclick="showTab(event, 'js-init')">JavaScript</button>
311
+ <button class="tab" onclick="showTab(event, 'ts-init')">TypeScript</button>
312
+ <button class="tab" onclick="showTab(event, 'react-init')">React</button>
313
+ </div>
314
+
315
+ <div id="js-init" class="tab-content active">
316
+ <pre><code>const { SPAPSClient } = require('spaps-sdk');
317
+
318
+ // Auto-detects local mode - no config needed!
319
+ const spaps = new SPAPSClient();
320
+
321
+ // Login
322
+ const { data } = await spaps.login('user@example.com', 'password');
323
+ console.log('User:', data.user);
324
+ console.log('Token:', data.access_token);</code></pre>
325
+ </div>
326
+
327
+ <div id="ts-init" class="tab-content">
328
+ <pre><code>import { SPAPSClient } from 'spaps-sdk';
329
+
330
+ // TypeScript types included
331
+ const spaps = new SPAPSClient({
332
+ apiUrl: 'http://localhost:${port}'
333
+ });
334
+
335
+ // Fully typed responses
336
+ const { data } = await spaps.login('user@example.com', 'password');
337
+ console.log('User ID:', data.user.id);</code></pre>
338
+ </div>
339
+
340
+ <div id="react-init" class="tab-content">
341
+ <pre><code>import { SPAPSClient } from 'spaps-sdk';
342
+ import { createContext, useContext } from 'react';
343
+
344
+ const spaps = new SPAPSClient();
345
+ const SpapsContext = createContext(spaps);
346
+
347
+ export function SpapsProvider({ children }) {
348
+ return (
349
+ &lt;SpapsContext.Provider value={spaps}&gt;
350
+ {children}
351
+ &lt;/SpapsContext.Provider&gt;
352
+ );
353
+ }
354
+
355
+ export const useSpaps = () => useContext(SpapsContext);</code></pre>
356
+ </div>
357
+ </section>
358
+
359
+ <section id="sdk">
360
+ <h2>SDK Setup & Configuration</h2>
361
+
362
+ <h3>Installation</h3>
363
+ <pre><code>npm install spaps-sdk</code></pre>
364
+
365
+ <h3>Configuration Options</h3>
366
+ <pre><code>const spaps = new SPAPSClient({
367
+ // API endpoint (auto-detected from env)
368
+ apiUrl: 'http://localhost:${port}',
369
+
370
+ // API key (not needed for localhost)
371
+ apiKey: 'spaps_live_abc123...',
372
+
373
+ // Request timeout in milliseconds
374
+ timeout: 10000,
375
+
376
+ // Auto-detect local mode (default: true)
377
+ autoDetect: true
378
+ });</code></pre>
379
+
380
+ <h3>Environment Variables</h3>
381
+ <pre><code># .env
382
+ SPAPS_API_URL=http://localhost:${port}
383
+ SPAPS_API_KEY=your_api_key_here
384
+
385
+ # Next.js
386
+ NEXT_PUBLIC_SPAPS_API_URL=http://localhost:${port}</code></pre>
387
+
388
+ <div class="feature-grid">
389
+ <div class="feature-card">
390
+ <h3>🔌 Auto-Detection</h3>
391
+ <p>SDK automatically detects local mode when URL contains localhost or 127.0.0.1</p>
392
+ </div>
393
+ <div class="feature-card">
394
+ <h3>🔑 No API Key</h3>
395
+ <p>Local mode doesn't require API keys - perfect for rapid development</p>
396
+ </div>
397
+ <div class="feature-card">
398
+ <h3>📦 TypeScript</h3>
399
+ <p>Full TypeScript support with type definitions included</p>
400
+ </div>
401
+ <div class="feature-card">
402
+ <h3>🔄 Auto-Refresh</h3>
403
+ <p>Tokens automatically refresh when expired</p>
404
+ </div>
405
+ </div>
406
+ </section>
407
+
408
+ <section id="authentication">
409
+ <h2>Authentication</h2>
410
+
411
+ <h3>Email/Password Authentication</h3>
412
+ <pre><code>// Register new user
413
+ const { data } = await spaps.register('user@example.com', 'password');
414
+ console.log('New user:', data.user);
415
+
416
+ // Login existing user
417
+ const { data } = await spaps.login('user@example.com', 'password');
418
+ console.log('Access token:', data.access_token);
419
+
420
+ // Get current user
421
+ const user = await spaps.getUser();
422
+ console.log('Current user:', user.data);
423
+
424
+ // Logout
425
+ await spaps.logout();</code></pre>
426
+
427
+ <h3>Wallet Authentication</h3>
428
+ <pre><code>// Solana wallet
429
+ await spaps.walletSignIn(
430
+ walletAddress,
431
+ signature,
432
+ message,
433
+ 'solana'
434
+ );
435
+
436
+ // Ethereum wallet
437
+ await spaps.walletSignIn(
438
+ walletAddress,
439
+ signature,
440
+ message,
441
+ 'ethereum'
442
+ );</code></pre>
443
+
444
+ <h3>Token Management</h3>
445
+ <pre><code>// Check if authenticated
446
+ if (spaps.isAuthenticated()) {
447
+ console.log('User is logged in');
448
+ }
449
+
450
+ // Get access token
451
+ const token = spaps.getAccessToken();
452
+
453
+ // Set token manually
454
+ spaps.setAccessToken(token);
455
+
456
+ // Refresh token
457
+ await spaps.refresh();</code></pre>
458
+ </section>
459
+
460
+ <section id="payments">
461
+ <h2>Payment Integration</h2>
462
+
463
+ <h3>Stripe Checkout</h3>
464
+ <pre><code>// Create checkout session
465
+ const session = await spaps.createCheckoutSession(
466
+ 'price_123abc', // Stripe price ID
467
+ 'http://localhost:3000/success', // Success URL
468
+ 'http://localhost:3000/cancel' // Cancel URL (optional)
469
+ );
470
+
471
+ // Redirect to Stripe
472
+ window.location.href = session.data.url;</code></pre>
473
+
474
+ <h3>Subscription Management</h3>
475
+ <pre><code>// Get current subscription
476
+ const subscription = await spaps.getSubscription();
477
+ console.log('Status:', subscription.data.status);
478
+ console.log('Plan:', subscription.data.plan);
479
+
480
+ // Cancel subscription
481
+ await spaps.cancelSubscription();</code></pre>
482
+
483
+ <h3>Usage Tracking</h3>
484
+ <pre><code>// Check balance
485
+ const balance = await spaps.getUsageBalance();
486
+ console.log('Credits:', balance.data.balance);
487
+
488
+ // Record usage
489
+ await spaps.recordUsage('api-call', 1);
490
+ await spaps.recordUsage('image-generation', 10);</code></pre>
491
+ </section>
492
+
493
+ <section id="endpoints">
494
+ <h2>API Endpoints</h2>
495
+
496
+ <h3>Authentication</h3>
497
+ <div class="endpoint">
498
+ <span class="method post">POST</span>
499
+ <strong>/api/auth/register</strong> - Register new user
500
+ </div>
501
+ <div class="endpoint">
502
+ <span class="method post">POST</span>
503
+ <strong>/api/auth/login</strong> - Login with email/password
504
+ </div>
505
+ <div class="endpoint">
506
+ <span class="method post">POST</span>
507
+ <strong>/api/auth/wallet-sign-in</strong> - Wallet authentication
508
+ </div>
509
+ <div class="endpoint">
510
+ <span class="method post">POST</span>
511
+ <strong>/api/auth/refresh</strong> - Refresh access token
512
+ </div>
513
+ <div class="endpoint">
514
+ <span class="method post">POST</span>
515
+ <strong>/api/auth/logout</strong> - Logout user
516
+ </div>
517
+ <div class="endpoint">
518
+ <span class="method get">GET</span>
519
+ <strong>/api/auth/user</strong> - Get current user
520
+ </div>
521
+
522
+ <h3>Payments</h3>
523
+ <div class="endpoint">
524
+ <span class="method post">POST</span>
525
+ <strong>/api/stripe/create-checkout-session</strong> - Create Stripe checkout
526
+ </div>
527
+ <div class="endpoint">
528
+ <span class="method get">GET</span>
529
+ <strong>/api/stripe/subscription</strong> - Get subscription status
530
+ </div>
531
+ <div class="endpoint">
532
+ <span class="method delete">DELETE</span>
533
+ <strong>/api/stripe/subscription</strong> - Cancel subscription
534
+ </div>
535
+
536
+ <h3>Usage</h3>
537
+ <div class="endpoint">
538
+ <span class="method get">GET</span>
539
+ <strong>/api/usage/balance</strong> - Get usage balance
540
+ </div>
541
+ <div class="endpoint">
542
+ <span class="method post">POST</span>
543
+ <strong>/api/usage/record</strong> - Record usage event
544
+ </div>
545
+
546
+ <h3>Health</h3>
547
+ <div class="endpoint">
548
+ <span class="method get">GET</span>
549
+ <strong>/health</strong> - Health check
550
+ </div>
551
+ <div class="endpoint">
552
+ <span class="method get">GET</span>
553
+ <strong>/health/local-mode</strong> - Local mode status
554
+ </div>
555
+ </section>
556
+
557
+ <section id="examples">
558
+ <h2>Complete Examples</h2>
559
+
560
+ <h3>React Login Component</h3>
561
+ <pre><code>import { useState } from 'react';
562
+ import { useSpaps } from './contexts/SpapsContext';
563
+
564
+ function LoginForm() {
565
+ const spaps = useSpaps();
566
+ const [email, setEmail] = useState('');
567
+ const [password, setPassword] = useState('');
568
+ const [loading, setLoading] = useState(false);
569
+
570
+ const handleSubmit = async (e) => {
571
+ e.preventDefault();
572
+ setLoading(true);
573
+
574
+ try {
575
+ const { data } = await spaps.login(email, password);
576
+ console.log('Logged in:', data.user);
577
+ // Redirect to dashboard
578
+ } catch (error) {
579
+ console.error('Login failed:', error);
580
+ } finally {
581
+ setLoading(false);
582
+ }
583
+ };
584
+
585
+ return (
586
+ &lt;form onSubmit={handleSubmit}&gt;
587
+ &lt;input
588
+ type="email"
589
+ value={email}
590
+ onChange={(e) => setEmail(e.target.value)}
591
+ required
592
+ /&gt;
593
+ &lt;input
594
+ type="password"
595
+ value={password}
596
+ onChange={(e) => setPassword(e.target.value)}
597
+ required
598
+ /&gt;
599
+ &lt;button type="submit" disabled={loading}&gt;
600
+ {loading ? 'Logging in...' : 'Login'}
601
+ &lt;/button&gt;
602
+ &lt;/form&gt;
603
+ );
604
+ }</code></pre>
605
+
606
+ <h3>Express.js Middleware</h3>
607
+ <pre><code>const express = require('express');
608
+ const { SPAPSClient } = require('spaps-sdk');
609
+
610
+ const app = express();
611
+ const spaps = new SPAPSClient();
612
+
613
+ // Auth middleware
614
+ async function requireAuth(req, res, next) {
615
+ const token = req.headers.authorization?.split(' ')[1];
616
+
617
+ if (!token) {
618
+ return res.status(401).json({ error: 'No token' });
619
+ }
620
+
621
+ try {
622
+ spaps.setAccessToken(token);
623
+ const { data } = await spaps.getUser();
624
+ req.user = data;
625
+ next();
626
+ } catch (error) {
627
+ res.status(401).json({ error: 'Invalid token' });
628
+ }
629
+ }
630
+
631
+ // Protected route
632
+ app.get('/api/profile', requireAuth, (req, res) => {
633
+ res.json(req.user);
634
+ });</code></pre>
635
+
636
+ <h3>Next.js Server Action</h3>
637
+ <pre><code>'use server';
638
+
639
+ import { SPAPSClient } from 'spaps-sdk';
640
+ import { cookies } from 'next/headers';
641
+
642
+ const spaps = new SPAPSClient();
643
+
644
+ export async function loginAction(email: string, password: string) {
645
+ try {
646
+ const { data } = await spaps.login(email, password);
647
+
648
+ // Store token in cookie
649
+ cookies().set('spaps_token', data.access_token, {
650
+ httpOnly: true,
651
+ secure: process.env.NODE_ENV === 'production',
652
+ sameSite: 'lax',
653
+ maxAge: 60 * 60 * 24 * 7 // 1 week
654
+ });
655
+
656
+ return { success: true, user: data.user };
657
+ } catch (error) {
658
+ return { success: false, error: 'Invalid credentials' };
659
+ }
660
+ }</code></pre>
661
+ </section>
662
+
663
+ <section id="testing">
664
+ <h2>Testing</h2>
665
+
666
+ <h3>Test with cURL</h3>
667
+ <pre><code># Health check
668
+ curl http://localhost:${port}/health
669
+
670
+ # Login
671
+ curl -X POST http://localhost:${port}/api/auth/login \\
672
+ -H "Content-Type: application/json" \\
673
+ -d '{"email":"test@example.com","password":"password"}'
674
+
675
+ # Get user (with token)
676
+ curl http://localhost:${port}/api/auth/user \\
677
+ -H "Authorization: Bearer YOUR_TOKEN"</code></pre>
678
+
679
+ <h3>Test with SDK</h3>
680
+ <pre><code>// test.js
681
+ const { SPAPSClient } = require('spaps-sdk');
682
+
683
+ async function test() {
684
+ const spaps = new SPAPSClient();
685
+
686
+ // Test login
687
+ const { data } = await spaps.login('test@example.com', 'password');
688
+ console.log('✅ Login successful:', data.user);
689
+
690
+ // Test authenticated request
691
+ const user = await spaps.getUser();
692
+ console.log('✅ Got user:', user.data);
693
+
694
+ // Test Stripe
695
+ const session = await spaps.createCheckoutSession(
696
+ 'price_123',
697
+ 'http://localhost:3000/success'
698
+ );
699
+ console.log('✅ Checkout URL:', session.data.url);
700
+ }
701
+
702
+ test().catch(console.error);</code></pre>
703
+
704
+ <div class="alert success">
705
+ <strong>💡 Pro Tip:</strong> In local mode, all endpoints return successful mock responses.
706
+ Use any email/password combination for testing.
707
+ </div>
708
+ </section>
709
+ </div>
710
+
711
+ <div class="footer">
712
+ <p>
713
+ <strong>SPAPS v0.2.6</strong> |
714
+ <a href="https://github.com/yourusername/sweet-potato">GitHub</a> |
715
+ <a href="https://sweetpotato.dev">Website</a> |
716
+ <a href="https://discord.gg/sweetpotato">Discord</a>
717
+ </p>
718
+ <p>Made with 🍠 by the Sweet Potato team</p>
719
+ </div>
720
+ </div>
721
+
722
+ <script>
723
+ function showTab(event, tabId) {
724
+ // Hide all tab contents
725
+ const contents = document.querySelectorAll('.tab-content');
726
+ contents.forEach(content => content.classList.remove('active'));
727
+
728
+ // Remove active from all tabs
729
+ const tabs = document.querySelectorAll('.tab');
730
+ tabs.forEach(tab => tab.classList.remove('active'));
731
+
732
+ // Show selected tab
733
+ document.getElementById(tabId).classList.add('active');
734
+ event.target.classList.add('active');
735
+ }
736
+
737
+ // Add copy buttons to code blocks
738
+ document.addEventListener('DOMContentLoaded', () => {
739
+ const codeBlocks = document.querySelectorAll('pre');
740
+ codeBlocks.forEach(block => {
741
+ const button = document.createElement('button');
742
+ button.className = 'copy-button';
743
+ button.textContent = 'Copy';
744
+ button.onclick = () => {
745
+ const code = block.querySelector('code').textContent;
746
+ navigator.clipboard.writeText(code).then(() => {
747
+ button.textContent = 'Copied!';
748
+ button.classList.add('copied');
749
+ setTimeout(() => {
750
+ button.textContent = 'Copy';
751
+ button.classList.remove('copied');
752
+ }, 2000);
753
+ });
754
+ };
755
+ block.appendChild(button);
756
+ });
757
+ });
758
+ </script>
759
+ </body>
760
+ </html>`;
761
+ }
762
+
763
+ module.exports = { generateDocsHTML };