spaps-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +76 -0
- package/dist/chunk-GPBTYWDD.js +496 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +9 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +26 -0
- package/dist/resources/SPAPS_SURFACE_CONTRACT.md +125 -0
- package/dist/resources/glossary.md +36 -0
- package/dist/resources/llms.txt +15 -0
- package/dist/wizard/step-01-setup.md +149 -0
- package/dist/wizard/step-02-environment.md +291 -0
- package/dist/wizard/step-03-sdk-init.md +351 -0
- package/dist/wizard/step-04-email-auth.md +311 -0
- package/dist/wizard/step-05-wallet-auth.md +368 -0
- package/dist/wizard/step-06-magic-link.md +560 -0
- package/dist/wizard/step-07-payments.md +529 -0
- package/dist/wizard/step-08-whitelist.md +338 -0
- package/dist/wizard/step-09-admin.md +579 -0
- package/dist/wizard/step-10-errors.md +525 -0
- package/dist/wizard/step-11-ui-polish.md +640 -0
- package/dist/wizard/step-12-testing.md +588 -0
- package/dist/wizard/wizard.lock +67 -0
- package/package.json +66 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# SPAPS Integration Step 3/12: SDK Initialization
|
|
2
|
+
|
|
3
|
+
## Prerequisites Check
|
|
4
|
+
|
|
5
|
+
Before starting, confirm you have completed:
|
|
6
|
+
- ✅ Step 1: Dependencies installed including spaps-sdk
|
|
7
|
+
- ✅ Step 2: Environment variables configured with port 3301
|
|
8
|
+
- ✅ SPAPS running on localhost:3301
|
|
9
|
+
|
|
10
|
+
## Required TodoWrite List
|
|
11
|
+
|
|
12
|
+
Create a TodoWrite with EXACTLY these items:
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
TodoWrite({
|
|
16
|
+
todos: [
|
|
17
|
+
{ content: "Create lib directory in project root", status: "pending", activeForm: "Creating lib directory" },
|
|
18
|
+
{ content: "Create lib/spaps.ts file", status: "pending", activeForm: "Creating SDK initialization file" },
|
|
19
|
+
{ content: "Import SweetPotatoSDK from 'spaps-sdk'", status: "pending", activeForm: "Importing SDK" },
|
|
20
|
+
{ content: "Initialize SDK with localhost:3301", status: "pending", activeForm: "Initializing SDK with correct port" },
|
|
21
|
+
{ content: "Export TokenManager and WalletUtils", status: "pending", activeForm: "Exporting utilities" },
|
|
22
|
+
{ content: "Create type definitions if needed", status: "pending", activeForm: "Creating type definitions" },
|
|
23
|
+
{ content: "Verify imports work correctly", status: "pending", activeForm: "Verifying SDK setup" },
|
|
24
|
+
{ content: "Request step 4 of SPAPS integration wizard", status: "pending", activeForm: "Requesting next step" }
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## SDK Initialization Guide
|
|
30
|
+
|
|
31
|
+
### 1. Create Directory Structure
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# From your project root
|
|
35
|
+
mkdir -p lib
|
|
36
|
+
touch lib/spaps.ts
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Basic SDK Initialization
|
|
40
|
+
|
|
41
|
+
⚠️ **CRITICAL PARAMETER NAME**: The constructor parameter is `apiUrl` (NOT `baseURL`)
|
|
42
|
+
|
|
43
|
+
Create `lib/spaps.ts` with this EXACT content:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// lib/spaps.ts
|
|
47
|
+
import { SweetPotatoSDK, TokenManager, WalletUtils } from 'spaps-sdk';
|
|
48
|
+
|
|
49
|
+
// Initialize the SDK with environment variables
|
|
50
|
+
// ✅ CORRECT: Use 'apiUrl' as the parameter name
|
|
51
|
+
const sdk = new SweetPotatoSDK({
|
|
52
|
+
apiUrl: process.env.NEXT_PUBLIC_SPAPS_API_URL || 'http://localhost:3301',
|
|
53
|
+
apiKey: process.env.SPAPS_API_KEY || 'test_key_local_dev_only'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Export everything needed
|
|
57
|
+
export { sdk, TokenManager, WalletUtils };
|
|
58
|
+
export default sdk;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Common Parameter Naming Mistakes
|
|
62
|
+
|
|
63
|
+
❌ **WRONG** - These parameter names DO NOT WORK:
|
|
64
|
+
```typescript
|
|
65
|
+
// WRONG - 'baseURL' doesn't exist
|
|
66
|
+
const sdk = new SweetPotatoSDK({
|
|
67
|
+
baseURL: 'http://localhost:3301', // NO!
|
|
68
|
+
apiKey: 'your_key'
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// WRONG - 'url' doesn't exist
|
|
72
|
+
const sdk = new SweetPotatoSDK({
|
|
73
|
+
url: 'http://localhost:3301', // NO!
|
|
74
|
+
apiKey: 'your_key'
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// WRONG - 'endpoint' doesn't exist
|
|
78
|
+
const sdk = new SweetPotatoSDK({
|
|
79
|
+
endpoint: 'http://localhost:3301', // NO!
|
|
80
|
+
apiKey: 'your_key'
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
✅ **CORRECT** - Always use `apiUrl`:
|
|
85
|
+
```typescript
|
|
86
|
+
// CORRECT - This is the only parameter name that works
|
|
87
|
+
const sdk = new SweetPotatoSDK({
|
|
88
|
+
apiUrl: 'http://localhost:3301', // YES!
|
|
89
|
+
apiKey: 'your_key'
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Advanced Setup (Optional)
|
|
94
|
+
|
|
95
|
+
For more control, you can create separate client and server instances:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// lib/spaps.ts
|
|
99
|
+
import { SweetPotatoSDK, TokenManager, WalletUtils, SweetPotatoAPIError } from 'spaps-sdk';
|
|
100
|
+
import type { AuthResponse, User } from 'spaps-sdk';
|
|
101
|
+
|
|
102
|
+
// Client-side SDK (for browser)
|
|
103
|
+
const clientSDK = new SweetPotatoSDK({
|
|
104
|
+
apiUrl: process.env.NEXT_PUBLIC_SPAPS_API_URL || 'http://localhost:3301',
|
|
105
|
+
apiKey: 'test_key_local_dev_only' // Public key for client
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Server-side SDK (for API routes/server actions)
|
|
109
|
+
const serverSDK = new SweetPotatoSDK({
|
|
110
|
+
apiUrl: process.env.NEXT_PUBLIC_SPAPS_API_URL || 'http://localhost:3301',
|
|
111
|
+
apiKey: process.env.SPAPS_API_KEY || 'test_key_local_dev_only'
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Determine which SDK to use based on environment
|
|
115
|
+
const sdk = typeof window !== 'undefined' ? clientSDK : serverSDK;
|
|
116
|
+
|
|
117
|
+
// Export everything applications need
|
|
118
|
+
export {
|
|
119
|
+
sdk,
|
|
120
|
+
clientSDK,
|
|
121
|
+
serverSDK,
|
|
122
|
+
TokenManager,
|
|
123
|
+
WalletUtils,
|
|
124
|
+
SweetPotatoAPIError
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Export types
|
|
128
|
+
export type { AuthResponse, User };
|
|
129
|
+
|
|
130
|
+
// Default export
|
|
131
|
+
export default sdk;
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 4. Type Definitions (if needed)
|
|
135
|
+
|
|
136
|
+
If TypeScript can't find types, create:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// types/spaps.d.ts
|
|
140
|
+
declare module 'spaps-sdk' {
|
|
141
|
+
export class SweetPotatoSDK {
|
|
142
|
+
constructor(config: { apiUrl: string; apiKey: string });
|
|
143
|
+
auth: AuthService;
|
|
144
|
+
payments: PaymentsService;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export class TokenManager {
|
|
148
|
+
static storeTokens(tokens: AuthResponse): void;
|
|
149
|
+
static getAccessToken(): string | null;
|
|
150
|
+
static getRefreshToken(): string | null;
|
|
151
|
+
static clearTokens(): void;
|
|
152
|
+
static isTokenExpired(token: string): boolean;
|
|
153
|
+
static autoRefreshToken(sdk: SweetPotatoSDK): Promise<boolean>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export class WalletUtils {
|
|
157
|
+
static detectChainType(address: string): string;
|
|
158
|
+
static validateAddress(address: string, chainType: string): boolean;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export class SweetPotatoAPIError extends Error {
|
|
162
|
+
code?: string;
|
|
163
|
+
status?: number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Add other types as needed
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 5. Test Your Setup
|
|
171
|
+
|
|
172
|
+
Create a test file to verify the SDK works:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// app/api/test-sdk/route.ts
|
|
176
|
+
import { NextResponse } from 'next/server';
|
|
177
|
+
import { sdk } from '@/lib/spaps';
|
|
178
|
+
|
|
179
|
+
export async function GET() {
|
|
180
|
+
try {
|
|
181
|
+
// Test that SDK is initialized
|
|
182
|
+
const sdkInfo = {
|
|
183
|
+
initialized: !!sdk,
|
|
184
|
+
hasAuth: !!sdk.auth,
|
|
185
|
+
hasPayments: !!sdk.payments,
|
|
186
|
+
apiUrl: process.env.NEXT_PUBLIC_SPAPS_API_URL
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return NextResponse.json({
|
|
190
|
+
success: true,
|
|
191
|
+
sdk: sdkInfo
|
|
192
|
+
});
|
|
193
|
+
} catch (error: any) {
|
|
194
|
+
return NextResponse.json({
|
|
195
|
+
success: false,
|
|
196
|
+
error: error.message
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Test it:
|
|
203
|
+
```bash
|
|
204
|
+
curl http://localhost:3000/api/test-sdk
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Important SDK Details
|
|
208
|
+
|
|
209
|
+
### Available SDK Methods
|
|
210
|
+
|
|
211
|
+
The SDK provides these main services:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
sdk.auth // Authentication methods
|
|
215
|
+
sdk.payments // Stripe payment methods
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### What's NOT in the Main SDK
|
|
219
|
+
|
|
220
|
+
Remember these are NOT in the main SDK:
|
|
221
|
+
- ❌ `sdk.whitelist` - Use admin-utils or direct API
|
|
222
|
+
- ❌ `sdk.admin` - Use direct API calls with JWT
|
|
223
|
+
- ❌ `sdk.users` - User management via auth methods
|
|
224
|
+
|
|
225
|
+
### Utility Classes Available
|
|
226
|
+
|
|
227
|
+
These utilities are exported separately:
|
|
228
|
+
- `TokenManager` - Token storage and management
|
|
229
|
+
- `WalletUtils` - Wallet address utilities
|
|
230
|
+
- `SweetPotatoAPIError` - Error handling class
|
|
231
|
+
|
|
232
|
+
## Validation Checklist
|
|
233
|
+
|
|
234
|
+
✅ **File Structure**:
|
|
235
|
+
- `lib/spaps.ts` exists
|
|
236
|
+
- Imports from 'spaps-sdk' (NOT @sweet-potato/sdk)
|
|
237
|
+
- Exports sdk, TokenManager, WalletUtils
|
|
238
|
+
|
|
239
|
+
✅ **SDK Configuration**:
|
|
240
|
+
- Uses `http://localhost:3301` as API URL
|
|
241
|
+
- Port is 3301 (NOT 3000, 3300, or 3456)
|
|
242
|
+
- API key configured correctly
|
|
243
|
+
|
|
244
|
+
✅ **Testing**:
|
|
245
|
+
- Test endpoint returns success
|
|
246
|
+
- SDK methods are accessible
|
|
247
|
+
- No import errors in console
|
|
248
|
+
|
|
249
|
+
## Common Mistakes to Avoid
|
|
250
|
+
|
|
251
|
+
❌ **Wrong package name**:
|
|
252
|
+
```typescript
|
|
253
|
+
// WRONG
|
|
254
|
+
import { SweetPotatoSDK } from 'spaps-sdk';
|
|
255
|
+
import { SweetPotatoSDK } from 'sweetpotato-sdk';
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
❌ **Wrong port**:
|
|
259
|
+
```typescript
|
|
260
|
+
// WRONG
|
|
261
|
+
apiUrl: 'http://localhost:3000' // This is Next.js
|
|
262
|
+
apiUrl: 'http://localhost:3301' // Old port
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
❌ **Not exporting utilities**:
|
|
266
|
+
```typescript
|
|
267
|
+
// WRONG - Forgot to export utilities
|
|
268
|
+
export { sdk };
|
|
269
|
+
// Missing TokenManager and WalletUtils!
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
❌ **Hardcoding values**:
|
|
273
|
+
```typescript
|
|
274
|
+
// WRONG - Should use environment variables
|
|
275
|
+
apiUrl: 'http://localhost:3301', // Hardcoded
|
|
276
|
+
apiKey: 'some-key' // Hardcoded
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Using the SDK
|
|
280
|
+
|
|
281
|
+
After initialization, you can use it like this:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// In your components
|
|
285
|
+
import { sdk, TokenManager } from '@/lib/spaps';
|
|
286
|
+
|
|
287
|
+
// Email authentication
|
|
288
|
+
const auth = await sdk.auth.signInWithPassword({
|
|
289
|
+
email: 'test@example.com',
|
|
290
|
+
password: 'Test123!'
|
|
291
|
+
});
|
|
292
|
+
TokenManager.storeTokens(auth);
|
|
293
|
+
|
|
294
|
+
// Wallet authentication
|
|
295
|
+
const walletAuth = await sdk.auth.authenticateWallet(
|
|
296
|
+
address,
|
|
297
|
+
signatureCallback,
|
|
298
|
+
'ethereum'
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// Payments
|
|
302
|
+
const products = await sdk.payments.listProducts();
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Troubleshooting
|
|
306
|
+
|
|
307
|
+
### Module Not Found
|
|
308
|
+
|
|
309
|
+
If you get "Cannot find module 'spaps-sdk'":
|
|
310
|
+
```bash
|
|
311
|
+
npm list spaps-sdk # Check if installed
|
|
312
|
+
npm install spaps-sdk # Reinstall if needed
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Type Errors
|
|
316
|
+
|
|
317
|
+
If TypeScript complains about types:
|
|
318
|
+
1. Restart TypeScript server in VSCode
|
|
319
|
+
2. Clear Next.js cache: `rm -rf .next`
|
|
320
|
+
3. Check `tsconfig.json` includes your lib folder
|
|
321
|
+
|
|
322
|
+
### Connection Errors
|
|
323
|
+
|
|
324
|
+
If SDK can't connect to SPAPS:
|
|
325
|
+
```bash
|
|
326
|
+
# Check SPAPS is running
|
|
327
|
+
curl http://localhost:3301/health
|
|
328
|
+
|
|
329
|
+
# Check your dev script
|
|
330
|
+
npm run dev # Should start both SPAPS and Next.js
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Next Step
|
|
334
|
+
|
|
335
|
+
When ALL todos are ✅ complete and SDK is initialized:
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
mcp__product-manager__get_agent_instructions({
|
|
339
|
+
category: "spaps-integration",
|
|
340
|
+
project: "spaps-demo",
|
|
341
|
+
step: 4
|
|
342
|
+
})
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Summary
|
|
346
|
+
|
|
347
|
+
SDK initialization is the foundation for all SPAPS features:
|
|
348
|
+
- **Correct package**: `spaps-sdk` (not @sweet-potato/sdk)
|
|
349
|
+
- **Correct port**: 3301 (not 3000, 3300, or 3456)
|
|
350
|
+
- **Export utilities**: TokenManager and WalletUtils
|
|
351
|
+
- **Use environment variables**: Don't hardcode values
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# SPAPS Integration Step 4/12: Email/Password Authentication
|
|
2
|
+
|
|
3
|
+
## Prerequisites Check
|
|
4
|
+
|
|
5
|
+
Before starting, confirm you have completed:
|
|
6
|
+
- ✅ Step 1: Project setup with spaps-sdk installed
|
|
7
|
+
- ✅ Step 2: Environment configuration with port 3301
|
|
8
|
+
- ✅ Step 3: SDK initialization in lib/spaps.ts
|
|
9
|
+
|
|
10
|
+
## Required TodoWrite List
|
|
11
|
+
|
|
12
|
+
Create a TodoWrite with EXACTLY these items:
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
TodoWrite({
|
|
16
|
+
todos: [
|
|
17
|
+
{ content: "Create components/auth directory structure", status: "pending", activeForm: "Creating auth directory" },
|
|
18
|
+
{ content: "Build email/password form with shadcn/ui components", status: "pending", activeForm: "Building authentication form" },
|
|
19
|
+
{ content: "Implement signInWithPassword with OBJECT parameter", status: "pending", activeForm: "Implementing signInWithPassword" },
|
|
20
|
+
{ content: "Add TokenManager.storeTokens for token storage", status: "pending", activeForm: "Adding token storage" },
|
|
21
|
+
{ content: "Setup TokenManager.autoRefreshToken", status: "pending", activeForm: "Setting up auto-refresh" },
|
|
22
|
+
{ content: "Implement SweetPotatoAPIError handling", status: "pending", activeForm: "Implementing error handling" },
|
|
23
|
+
{ content: "Test with test@example.com / Test123!", status: "pending", activeForm: "Testing authentication" },
|
|
24
|
+
{ content: "Request step 5 of SPAPS integration wizard", status: "pending", activeForm: "Requesting next step" }
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Implementation Guide
|
|
30
|
+
|
|
31
|
+
### 1. Create Auth Component Structure
|
|
32
|
+
```bash
|
|
33
|
+
mkdir -p components/auth
|
|
34
|
+
touch components/auth/email-auth-form.tsx
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Import Requirements
|
|
38
|
+
```typescript
|
|
39
|
+
// ✅ CORRECT IMPORTS
|
|
40
|
+
import { SweetPotatoSDK, TokenManager, SweetPotatoAPIError } from 'spaps-sdk';
|
|
41
|
+
import { sdk } from '@/lib/spaps';
|
|
42
|
+
|
|
43
|
+
// UI Components from shadcn/ui
|
|
44
|
+
import { Button } from "@/components/ui/button"
|
|
45
|
+
import { Input } from "@/components/ui/input"
|
|
46
|
+
import { Label } from "@/components/ui/label"
|
|
47
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
48
|
+
import { toast } from "sonner"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Authentication Implementation
|
|
52
|
+
|
|
53
|
+
⚠️ **CRITICAL - CORRECT METHOD SIGNATURE**:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// ✅ CORRECT - Pass an OBJECT with email and password
|
|
57
|
+
const authResponse = await sdk.auth.signInWithPassword({
|
|
58
|
+
email: 'test@example.com',
|
|
59
|
+
password: 'Test123!'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ❌ WRONG - DO NOT pass as separate parameters
|
|
63
|
+
const authResponse = await sdk.auth.signInWithPassword(email, password);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 4. Token Management
|
|
67
|
+
|
|
68
|
+
⚠️ **CRITICAL - CORRECT TOKEN STORAGE**:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// ✅ CORRECT - Pass the auth response object
|
|
72
|
+
TokenManager.storeTokens(authResponse);
|
|
73
|
+
|
|
74
|
+
// Then setup auto-refresh
|
|
75
|
+
TokenManager.autoRefreshToken(sdk);
|
|
76
|
+
|
|
77
|
+
// ❌ WRONG - DO NOT pass tokens separately
|
|
78
|
+
TokenManager.storeTokens(authResponse.access_token, authResponse.refresh_token);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 5. Error Handling
|
|
82
|
+
|
|
83
|
+
Implement specific error handling:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
try {
|
|
87
|
+
const auth = await sdk.auth.signInWithPassword({
|
|
88
|
+
email: email.trim(),
|
|
89
|
+
password: password
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
TokenManager.storeTokens(auth);
|
|
93
|
+
TokenManager.autoRefreshToken(sdk);
|
|
94
|
+
|
|
95
|
+
toast.success('Successfully authenticated!');
|
|
96
|
+
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error instanceof SweetPotatoAPIError) {
|
|
99
|
+
switch (error.code) {
|
|
100
|
+
case 'INVALID_CREDENTIALS':
|
|
101
|
+
toast.error('Invalid email or password');
|
|
102
|
+
break;
|
|
103
|
+
case 'RATE_LIMITED':
|
|
104
|
+
toast.error('Too many attempts. Please wait before trying again.');
|
|
105
|
+
break;
|
|
106
|
+
case 'USER_NOT_FOUND':
|
|
107
|
+
toast.error('No account found with this email');
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
toast.error(error.message || 'Authentication failed');
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
toast.error('An unexpected error occurred');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 6. Complete Component Example
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
'use client'
|
|
122
|
+
|
|
123
|
+
import { useState } from 'react'
|
|
124
|
+
import { Button } from "@/components/ui/button"
|
|
125
|
+
import { Input } from "@/components/ui/input"
|
|
126
|
+
import { Label } from "@/components/ui/label"
|
|
127
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
128
|
+
import { toast } from "sonner"
|
|
129
|
+
import { sdk } from '@/lib/spaps'
|
|
130
|
+
import { SweetPotatoAPIError, TokenManager } from 'spaps-sdk'
|
|
131
|
+
|
|
132
|
+
export function EmailAuthForm() {
|
|
133
|
+
const [email, setEmail] = useState('')
|
|
134
|
+
const [password, setPassword] = useState('')
|
|
135
|
+
const [isLoading, setIsLoading] = useState(false)
|
|
136
|
+
|
|
137
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
138
|
+
e.preventDefault()
|
|
139
|
+
setIsLoading(true)
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
// ✅ CORRECT: Pass object with email and password
|
|
143
|
+
const authResponse = await sdk.auth.signInWithPassword({
|
|
144
|
+
email: email.trim(),
|
|
145
|
+
password: password
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ✅ CORRECT: Store tokens and setup auto-refresh
|
|
149
|
+
TokenManager.storeTokens(authResponse);
|
|
150
|
+
TokenManager.autoRefreshToken(sdk);
|
|
151
|
+
|
|
152
|
+
toast.success('Successfully authenticated!');
|
|
153
|
+
// Handle success (e.g., redirect, update UI)
|
|
154
|
+
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (error instanceof SweetPotatoAPIError) {
|
|
157
|
+
// Specific error handling
|
|
158
|
+
switch (error.code) {
|
|
159
|
+
case 'INVALID_CREDENTIALS':
|
|
160
|
+
toast.error('Invalid email or password');
|
|
161
|
+
break;
|
|
162
|
+
case 'RATE_LIMITED':
|
|
163
|
+
toast.error('Too many attempts. Please wait.');
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
toast.error(error.message);
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
toast.error('Authentication failed');
|
|
170
|
+
}
|
|
171
|
+
} finally {
|
|
172
|
+
setIsLoading(false)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Add test credentials button
|
|
177
|
+
const fillTestCredentials = () => {
|
|
178
|
+
setEmail('test@example.com')
|
|
179
|
+
setPassword('Test123!')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<Card>
|
|
184
|
+
<CardHeader>
|
|
185
|
+
<CardTitle>Sign In</CardTitle>
|
|
186
|
+
<CardDescription>
|
|
187
|
+
Enter your email and password to authenticate
|
|
188
|
+
</CardDescription>
|
|
189
|
+
</CardHeader>
|
|
190
|
+
<CardContent>
|
|
191
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
192
|
+
{/* Form fields */}
|
|
193
|
+
</form>
|
|
194
|
+
</CardContent>
|
|
195
|
+
</Card>
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Validation Checklist
|
|
201
|
+
|
|
202
|
+
✅ **Method Usage**:
|
|
203
|
+
- Uses `signInWithPassword({ email, password })` with object parameter
|
|
204
|
+
- NOT using separate parameters
|
|
205
|
+
|
|
206
|
+
✅ **Token Management**:
|
|
207
|
+
- Uses `TokenManager.storeTokens(authResponse)`
|
|
208
|
+
- Calls `TokenManager.autoRefreshToken(sdk)`
|
|
209
|
+
- NOT storing tokens manually in localStorage
|
|
210
|
+
|
|
211
|
+
✅ **Error Handling**:
|
|
212
|
+
- Imports and uses `SweetPotatoAPIError`
|
|
213
|
+
- Handles `INVALID_CREDENTIALS` specifically
|
|
214
|
+
- Handles `RATE_LIMITED` specifically
|
|
215
|
+
- Shows user-friendly error messages
|
|
216
|
+
|
|
217
|
+
✅ **UI Components**:
|
|
218
|
+
- Uses shadcn/ui components (Button, Input, Card, etc.)
|
|
219
|
+
- Includes loading states
|
|
220
|
+
- Shows toast notifications
|
|
221
|
+
|
|
222
|
+
✅ **Testing**:
|
|
223
|
+
- Can login with test@example.com / Test123!
|
|
224
|
+
- Tokens are stored (check DevTools > Application > Local Storage)
|
|
225
|
+
- Error handling works (try wrong password)
|
|
226
|
+
|
|
227
|
+
## Common Mistakes to Avoid
|
|
228
|
+
|
|
229
|
+
❌ **Method signature**: `signInWithPassword(email, password)` - MUST pass object
|
|
230
|
+
❌ **Token storage**: Using localStorage directly instead of TokenManager
|
|
231
|
+
❌ **Missing imports**: Not importing SweetPotatoAPIError
|
|
232
|
+
❌ **Generic errors**: Not handling specific error codes
|
|
233
|
+
❌ **No loading state**: Form doesn't show loading indicator
|
|
234
|
+
❌ **No auto-refresh**: Forgetting to call autoRefreshToken
|
|
235
|
+
|
|
236
|
+
## Server Contract Note: TOTP MFA Challenge on Login
|
|
237
|
+
|
|
238
|
+
If the user has activated TOTP MFA on their SPAPS account, `POST /api/auth/login`
|
|
239
|
+
returns an MFA challenge payload **instead of tokens**:
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{ "mfa_required": true, "challenge_id": "...", "challenge": "...", "expires_at": "...", "methods": ["totp", "recovery_code"] }
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Handle this branch before assuming tokens exist: echo `challenge_id` +
|
|
246
|
+
`challenge` to `POST /api/auth/mfa/verify` together with the user's 6-digit
|
|
247
|
+
TOTP code (or a single-use recovery code) to receive the standard token
|
|
248
|
+
payload. Challenges expire after 300 seconds and allow at most 5 attempts.
|
|
249
|
+
Accounts that never enrolled in MFA are unaffected — login returns tokens
|
|
250
|
+
directly as shown above. MFA enforcement currently covers email/password
|
|
251
|
+
login only (not magic-link, wallet, OIDC, WebAuthn-assertion, or SMS sign-in).
|
|
252
|
+
|
|
253
|
+
## Other SPAPS Login Methods (server API)
|
|
254
|
+
|
|
255
|
+
Beyond email/password (this step), wallet sign-in (step 5), and magic link
|
|
256
|
+
(step 6), the SPAPS server also supports — via direct API calls; SDK helper
|
|
257
|
+
coverage may lag:
|
|
258
|
+
|
|
259
|
+
- **OIDC social sign-in** — `POST /api/auth/oidc/nonce` then
|
|
260
|
+
`POST /api/auth/oidc/sign-in` with a provider ID token (Google, Apple,
|
|
261
|
+
GitHub-issued OIDC). Pass the RAW nonce to the provider (hashed-nonce
|
|
262
|
+
flows are not matched). Requires the SPAPS operator to seed an
|
|
263
|
+
`auth_provider_configs` row for your application.
|
|
264
|
+
- **WebAuthn passkeys** — `POST /api/auth/webauthn/register/{options,verify}`
|
|
265
|
+
(authenticated) and `POST /api/auth/webauthn/assertion/{options,verify}`
|
|
266
|
+
(sign-in).
|
|
267
|
+
- **SMS OTP** — `POST /api/auth/sms/request` then `POST /api/auth/sms/verify`.
|
|
268
|
+
|
|
269
|
+
All of these return the same token payload shape as `/api/auth/login`, so
|
|
270
|
+
`TokenManager.storeTokens` works unchanged.
|
|
271
|
+
|
|
272
|
+
## Testing Requirements
|
|
273
|
+
|
|
274
|
+
1. **Successful Login**:
|
|
275
|
+
- Email: `test@example.com`
|
|
276
|
+
- Password: `Test123!`
|
|
277
|
+
- Should see success toast
|
|
278
|
+
- Tokens stored in browser
|
|
279
|
+
|
|
280
|
+
2. **Failed Login**:
|
|
281
|
+
- Wrong password: Should show "Invalid credentials"
|
|
282
|
+
- Rate limiting: After 5 attempts, should show rate limit message
|
|
283
|
+
|
|
284
|
+
3. **Token Verification**:
|
|
285
|
+
```javascript
|
|
286
|
+
// In browser console:
|
|
287
|
+
const token = TokenManager.getAccessToken();
|
|
288
|
+
console.log('Token exists:', !!token);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Next Step
|
|
292
|
+
|
|
293
|
+
When ALL todos are ✅ complete and tests pass:
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
mcp__product-manager__get_agent_instructions({
|
|
297
|
+
category: "spaps-integration",
|
|
298
|
+
project: "spaps-demo",
|
|
299
|
+
step: 5
|
|
300
|
+
})
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Troubleshooting
|
|
304
|
+
|
|
305
|
+
If authentication fails:
|
|
306
|
+
1. Check SPAPS is running on port 3301: `curl http://localhost:3301/health`
|
|
307
|
+
2. Verify SDK initialization in lib/spaps.ts
|
|
308
|
+
3. Check browser console for specific errors
|
|
309
|
+
4. Ensure you're using the object parameter format
|
|
310
|
+
|
|
311
|
+
Remember: **The parameter format is critical!** Most failures come from using separate parameters instead of an object.
|