@23blocks/react 6.0.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +638 -0
- package/dist/index.esm.js +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
# @23blocks/react
|
|
2
|
+
|
|
3
|
+
React bindings for the 23blocks SDK - hooks and context providers.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@23blocks/react)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @23blocks/react
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
This package provides React-specific bindings for the 23blocks SDK:
|
|
17
|
+
|
|
18
|
+
- **Context Providers** - Configure blocks at the app level
|
|
19
|
+
- **Hooks** - React hooks for all SDK functionality
|
|
20
|
+
- **Token Management** - Automatic token storage and refresh
|
|
21
|
+
- **TypeScript** - Full type safety
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Wrap your app
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
// app/layout.tsx (Next.js App Router)
|
|
29
|
+
'use client';
|
|
30
|
+
|
|
31
|
+
import { Provider } from '@23blocks/react';
|
|
32
|
+
|
|
33
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
34
|
+
return (
|
|
35
|
+
<html lang="en">
|
|
36
|
+
<body>
|
|
37
|
+
<Provider
|
|
38
|
+
apiKey={process.env.NEXT_PUBLIC_API_KEY!}
|
|
39
|
+
urls={{
|
|
40
|
+
authentication: process.env.NEXT_PUBLIC_AUTH_URL!,
|
|
41
|
+
// Add other service URLs as needed
|
|
42
|
+
// products: process.env.NEXT_PUBLIC_PRODUCTS_URL,
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
{children}
|
|
46
|
+
</Provider>
|
|
47
|
+
</body>
|
|
48
|
+
</html>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Use the hooks
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
'use client';
|
|
57
|
+
|
|
58
|
+
import { useAuth } from '@23blocks/react';
|
|
59
|
+
import { useState } from 'react';
|
|
60
|
+
|
|
61
|
+
export function LoginForm() {
|
|
62
|
+
const { signIn, signOut, isAuthenticated } = useAuth();
|
|
63
|
+
const [email, setEmail] = useState('');
|
|
64
|
+
const [password, setPassword] = useState('');
|
|
65
|
+
|
|
66
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
const { user } = await signIn({ email, password });
|
|
69
|
+
console.log('Welcome', user.email);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (isAuthenticated()) {
|
|
73
|
+
return (
|
|
74
|
+
<div>
|
|
75
|
+
<p>You're logged in!</p>
|
|
76
|
+
<button onClick={signOut}>Sign Out</button>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<form onSubmit={handleSubmit}>
|
|
83
|
+
<input
|
|
84
|
+
type="email"
|
|
85
|
+
value={email}
|
|
86
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
87
|
+
placeholder="Email"
|
|
88
|
+
/>
|
|
89
|
+
<input
|
|
90
|
+
type="password"
|
|
91
|
+
value={password}
|
|
92
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
93
|
+
placeholder="Password"
|
|
94
|
+
/>
|
|
95
|
+
<button type="submit">Sign In</button>
|
|
96
|
+
</form>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 3. Access any block
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
'use client';
|
|
105
|
+
|
|
106
|
+
import { useClient } from '@23blocks/react';
|
|
107
|
+
import { useEffect, useState } from 'react';
|
|
108
|
+
|
|
109
|
+
export function ProductList() {
|
|
110
|
+
const client = useClient();
|
|
111
|
+
const [products, setProducts] = useState([]);
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
client.products.products.list({ limit: 20 })
|
|
115
|
+
.then((response) => setProducts(response.data));
|
|
116
|
+
}, [client]);
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div>
|
|
120
|
+
{products.map((product) => (
|
|
121
|
+
<div key={product.id}>{product.name}</div>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Configuration Options
|
|
129
|
+
|
|
130
|
+
### Provider Props
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<Provider
|
|
134
|
+
// Required: Your API key
|
|
135
|
+
apiKey="your-api-key"
|
|
136
|
+
|
|
137
|
+
// Required: Service URLs (only configure what you need)
|
|
138
|
+
urls={{
|
|
139
|
+
authentication: 'https://auth.yourapp.com',
|
|
140
|
+
products: 'https://products.yourapp.com',
|
|
141
|
+
crm: 'https://crm.yourapp.com',
|
|
142
|
+
}}
|
|
143
|
+
|
|
144
|
+
// Optional: Tenant ID for multi-tenant setups
|
|
145
|
+
tenantId="tenant-123"
|
|
146
|
+
|
|
147
|
+
// Optional: Authentication mode (default: 'token')
|
|
148
|
+
authMode="token" // 'token' | 'cookie'
|
|
149
|
+
|
|
150
|
+
// Optional: Token storage (default: 'localStorage')
|
|
151
|
+
storage="localStorage" // 'localStorage' | 'sessionStorage' | 'memory'
|
|
152
|
+
>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Token Mode (Default)
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
<Provider
|
|
159
|
+
apiKey="your-api-key"
|
|
160
|
+
urls={{ authentication: 'https://auth.yourapp.com' }}
|
|
161
|
+
authMode="token" // default
|
|
162
|
+
storage="localStorage" // default
|
|
163
|
+
>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Cookie Mode (Recommended for Security)
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<Provider
|
|
170
|
+
apiKey="your-api-key"
|
|
171
|
+
urls={{ authentication: 'https://auth.yourapp.com' }}
|
|
172
|
+
authMode="cookie"
|
|
173
|
+
>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Multi-Tenant Setup
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
<Provider
|
|
180
|
+
apiKey="your-api-key"
|
|
181
|
+
urls={{ authentication: 'https://auth.yourapp.com' }}
|
|
182
|
+
tenantId="tenant-123"
|
|
183
|
+
>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Available Hooks
|
|
187
|
+
|
|
188
|
+
### Main Hooks
|
|
189
|
+
|
|
190
|
+
| Hook | Description |
|
|
191
|
+
|------|-------------|
|
|
192
|
+
| `useAuth()` | Authentication - sign in, sign up, sign out |
|
|
193
|
+
| `useClient()` | Access to all blocks via client |
|
|
194
|
+
|
|
195
|
+
### Block-Specific Hooks
|
|
196
|
+
|
|
197
|
+
| Hook | Description |
|
|
198
|
+
|------|-------------|
|
|
199
|
+
| `useAuthenticationBlock()` | Authentication block instance |
|
|
200
|
+
| `useSearchBlock()` | Search block instance |
|
|
201
|
+
| `useProductsBlock()` | Products block instance |
|
|
202
|
+
| `useCrmBlock()` | CRM block instance |
|
|
203
|
+
| `useContentBlock()` | Content block instance |
|
|
204
|
+
| `useGeolocationBlock()` | Geolocation block instance |
|
|
205
|
+
| `useConversationsBlock()` | Conversations block instance |
|
|
206
|
+
| `useFilesBlock()` | Files block instance |
|
|
207
|
+
| `useFormsBlock()` | Forms block instance |
|
|
208
|
+
| `useAssetsBlock()` | Assets block instance |
|
|
209
|
+
| `useCampaignsBlock()` | Campaigns block instance |
|
|
210
|
+
| `useCompanyBlock()` | Company block instance |
|
|
211
|
+
| `useRewardsBlock()` | Rewards block instance |
|
|
212
|
+
| `useSalesBlock()` | Sales block instance |
|
|
213
|
+
| `useWalletBlock()` | Wallet block instance |
|
|
214
|
+
| `useJarvisBlock()` | Jarvis AI block instance |
|
|
215
|
+
| `useOnboardingBlock()` | Onboarding block instance |
|
|
216
|
+
| `useUniversityBlock()` | University block instance |
|
|
217
|
+
|
|
218
|
+
### Feature Hooks
|
|
219
|
+
|
|
220
|
+
| Hook | Description |
|
|
221
|
+
|------|-------------|
|
|
222
|
+
| `useSearch()` | Search with state management |
|
|
223
|
+
| `useFavorites()` | Favorites management |
|
|
224
|
+
| `useUsers()` | User management (admin) |
|
|
225
|
+
| `useMfa()` | Multi-factor authentication |
|
|
226
|
+
| `useOAuth()` | OAuth operations |
|
|
227
|
+
| `useAvatars()` | User avatar management |
|
|
228
|
+
| `useTenants()` | Tenant management |
|
|
229
|
+
|
|
230
|
+
## Authentication Examples
|
|
231
|
+
|
|
232
|
+
### useAuth - Sign In
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
'use client';
|
|
236
|
+
|
|
237
|
+
import { useAuth } from '@23blocks/react';
|
|
238
|
+
|
|
239
|
+
export function LoginForm() {
|
|
240
|
+
const { signIn, isAuthenticated } = useAuth();
|
|
241
|
+
const [email, setEmail] = useState('');
|
|
242
|
+
const [password, setPassword] = useState('');
|
|
243
|
+
const [error, setError] = useState('');
|
|
244
|
+
|
|
245
|
+
const handleSignIn = async (e: React.FormEvent) => {
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
try {
|
|
248
|
+
// Required: email, password
|
|
249
|
+
const { user, accessToken } = await signIn({ email, password });
|
|
250
|
+
console.log('Welcome', user.email);
|
|
251
|
+
} catch (err) {
|
|
252
|
+
setError(err.message);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// ...
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### useAuth - Sign Up (Registration)
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
'use client';
|
|
264
|
+
|
|
265
|
+
import { useAuth } from '@23blocks/react';
|
|
266
|
+
|
|
267
|
+
export function RegisterForm() {
|
|
268
|
+
const { signUp } = useAuth();
|
|
269
|
+
|
|
270
|
+
const handleSignUp = async (e: React.FormEvent) => {
|
|
271
|
+
e.preventDefault();
|
|
272
|
+
|
|
273
|
+
// Sign up with required fields only
|
|
274
|
+
const { user, accessToken, message } = await signUp({
|
|
275
|
+
email: 'new@example.com', // Required
|
|
276
|
+
password: 'password', // Required
|
|
277
|
+
passwordConfirmation: 'password', // Required - must match password
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// accessToken may be undefined if email confirmation is enabled
|
|
281
|
+
if (accessToken) {
|
|
282
|
+
console.log('Logged in as', user.email);
|
|
283
|
+
} else {
|
|
284
|
+
console.log(message); // "Confirmation email sent"
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// Sign up with optional fields
|
|
289
|
+
const handleFullSignUp = async () => {
|
|
290
|
+
const { user } = await signUp({
|
|
291
|
+
// Required
|
|
292
|
+
email: 'new@example.com',
|
|
293
|
+
password: 'securePassword123',
|
|
294
|
+
passwordConfirmation: 'securePassword123',
|
|
295
|
+
|
|
296
|
+
// Optional
|
|
297
|
+
name: 'John Doe',
|
|
298
|
+
username: 'johndoe',
|
|
299
|
+
roleId: 'role-uuid',
|
|
300
|
+
confirmSuccessUrl: 'https://yourapp.com/confirmed', // Redirect after email confirmation
|
|
301
|
+
timeZone: 'America/New_York',
|
|
302
|
+
preferredLanguage: 'en',
|
|
303
|
+
payload: { referralCode: 'ABC123' },
|
|
304
|
+
subscription: 'premium-plan',
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### useAuth - Sign Out
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
const { signOut } = useAuth();
|
|
314
|
+
|
|
315
|
+
const handleSignOut = async () => {
|
|
316
|
+
await signOut();
|
|
317
|
+
// Tokens are automatically cleared
|
|
318
|
+
};
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### useAuth - Full Example
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
'use client';
|
|
325
|
+
|
|
326
|
+
import { useAuth } from '@23blocks/react';
|
|
327
|
+
|
|
328
|
+
export function AuthComponent() {
|
|
329
|
+
const {
|
|
330
|
+
signIn,
|
|
331
|
+
signUp,
|
|
332
|
+
signOut,
|
|
333
|
+
isAuthenticated,
|
|
334
|
+
getAccessToken,
|
|
335
|
+
getCurrentUser,
|
|
336
|
+
} = useAuth();
|
|
337
|
+
|
|
338
|
+
// Check authentication
|
|
339
|
+
if (isAuthenticated()) {
|
|
340
|
+
return (
|
|
341
|
+
<div>
|
|
342
|
+
<button onClick={signOut}>Sign Out</button>
|
|
343
|
+
</div>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return <LoginForm />;
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Email Confirmation
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
import { useAuth } from '@23blocks/react';
|
|
355
|
+
|
|
356
|
+
export function EmailConfirmation() {
|
|
357
|
+
const { confirmEmail, resendConfirmation } = useAuth();
|
|
358
|
+
|
|
359
|
+
// Confirm email with token from URL
|
|
360
|
+
const handleConfirm = async (token: string) => {
|
|
361
|
+
const user = await confirmEmail(token);
|
|
362
|
+
console.log('Email confirmed for', user.email);
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// Resend confirmation email
|
|
366
|
+
const handleResend = async (email: string) => {
|
|
367
|
+
await resendConfirmation({
|
|
368
|
+
email,
|
|
369
|
+
confirmSuccessUrl: 'https://yourapp.com/confirmed', // Optional
|
|
370
|
+
});
|
|
371
|
+
console.log('Confirmation email sent');
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### useSearch
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
'use client';
|
|
380
|
+
|
|
381
|
+
import { useSearchBlock } from '@23blocks/react';
|
|
382
|
+
import { useState, useEffect } from 'react';
|
|
383
|
+
|
|
384
|
+
export function SearchComponent() {
|
|
385
|
+
const search = useSearchBlock();
|
|
386
|
+
const [query, setQuery] = useState('');
|
|
387
|
+
const [results, setResults] = useState([]);
|
|
388
|
+
|
|
389
|
+
useEffect(() => {
|
|
390
|
+
if (query.length < 2) return;
|
|
391
|
+
|
|
392
|
+
const timer = setTimeout(async () => {
|
|
393
|
+
const response = await search.search.search({ query, limit: 10 });
|
|
394
|
+
setResults(response.results);
|
|
395
|
+
}, 300);
|
|
396
|
+
|
|
397
|
+
return () => clearTimeout(timer);
|
|
398
|
+
}, [query, search]);
|
|
399
|
+
|
|
400
|
+
return (
|
|
401
|
+
<div>
|
|
402
|
+
<input
|
|
403
|
+
type="search"
|
|
404
|
+
value={query}
|
|
405
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
406
|
+
placeholder="Search..."
|
|
407
|
+
/>
|
|
408
|
+
<ul>
|
|
409
|
+
{results.map((result) => (
|
|
410
|
+
<li key={result.id}>{result.title}</li>
|
|
411
|
+
))}
|
|
412
|
+
</ul>
|
|
413
|
+
</div>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### useFavorites
|
|
419
|
+
|
|
420
|
+
```tsx
|
|
421
|
+
'use client';
|
|
422
|
+
|
|
423
|
+
import { useFavorites } from '@23blocks/react';
|
|
424
|
+
|
|
425
|
+
export function FavoriteButton({ itemId, itemType }: Props) {
|
|
426
|
+
const { favorites, addFavorite, removeFavorite, isLoading } = useFavorites();
|
|
427
|
+
|
|
428
|
+
const isFavorited = favorites.some(
|
|
429
|
+
(f) => f.entityUniqueId === itemId && f.entityType === itemType
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
const handleToggle = async () => {
|
|
433
|
+
if (isFavorited) {
|
|
434
|
+
const fav = favorites.find((f) => f.entityUniqueId === itemId);
|
|
435
|
+
if (fav) await removeFavorite(fav.id);
|
|
436
|
+
} else {
|
|
437
|
+
await addFavorite({ entityUniqueId: itemId, entityType: itemType });
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
return (
|
|
442
|
+
<button onClick={handleToggle} disabled={isLoading}>
|
|
443
|
+
{isFavorited ? 'Favorited' : 'Add to Favorites'}
|
|
444
|
+
</button>
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
## Server-Side Rendering (SSR)
|
|
450
|
+
|
|
451
|
+
### Handling Client-Only Code
|
|
452
|
+
|
|
453
|
+
```tsx
|
|
454
|
+
'use client';
|
|
455
|
+
|
|
456
|
+
import { useAuth } from '@23blocks/react';
|
|
457
|
+
import { useState, useEffect } from 'react';
|
|
458
|
+
|
|
459
|
+
// This component only renders on the client
|
|
460
|
+
export function UserProfile() {
|
|
461
|
+
const { getCurrentUser, isAuthenticated } = useAuth();
|
|
462
|
+
const [user, setUser] = useState(null);
|
|
463
|
+
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
if (isAuthenticated()) {
|
|
466
|
+
getCurrentUser().then(setUser);
|
|
467
|
+
}
|
|
468
|
+
}, []);
|
|
469
|
+
|
|
470
|
+
if (!user) return <p>Loading...</p>;
|
|
471
|
+
return <p>Hello, {user.email}</p>;
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Server Components with Client Boundaries
|
|
476
|
+
|
|
477
|
+
```tsx
|
|
478
|
+
// app/dashboard/page.tsx (Server Component)
|
|
479
|
+
import { UserProfile } from '@/components/user-profile'; // Client Component
|
|
480
|
+
|
|
481
|
+
export default function DashboardPage() {
|
|
482
|
+
return (
|
|
483
|
+
<div>
|
|
484
|
+
<h1>Dashboard</h1>
|
|
485
|
+
<UserProfile /> {/* Client boundary */}
|
|
486
|
+
</div>
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Data Fetching on Server
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
// app/products/page.tsx
|
|
495
|
+
import { createHttpTransport } from '@23blocks/transport-http';
|
|
496
|
+
import { createProductsBlock } from '@23blocks/block-products';
|
|
497
|
+
|
|
498
|
+
async function getProducts() {
|
|
499
|
+
const transport = createHttpTransport({
|
|
500
|
+
baseUrl: process.env.PRODUCTS_URL!,
|
|
501
|
+
headers: () => ({ 'x-api-key': process.env.API_KEY! }),
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const products = createProductsBlock(transport, {
|
|
505
|
+
apiKey: process.env.API_KEY!,
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
return products.products.list({ limit: 20 });
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export default async function ProductsPage() {
|
|
512
|
+
const { data: products } = await getProducts();
|
|
513
|
+
|
|
514
|
+
return (
|
|
515
|
+
<div>
|
|
516
|
+
{products.map((product) => (
|
|
517
|
+
<div key={product.id}>{product.name}</div>
|
|
518
|
+
))}
|
|
519
|
+
</div>
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## Error Handling
|
|
525
|
+
|
|
526
|
+
```tsx
|
|
527
|
+
import { isBlockErrorException, ErrorCodes } from '@23blocks/contracts';
|
|
528
|
+
|
|
529
|
+
const handleSubmit = async () => {
|
|
530
|
+
try {
|
|
531
|
+
await signIn({ email, password });
|
|
532
|
+
} catch (err) {
|
|
533
|
+
if (isBlockErrorException(err)) {
|
|
534
|
+
switch (err.code) {
|
|
535
|
+
case ErrorCodes.INVALID_CREDENTIALS:
|
|
536
|
+
setError('Invalid email or password');
|
|
537
|
+
break;
|
|
538
|
+
case ErrorCodes.UNAUTHORIZED:
|
|
539
|
+
setError('Session expired');
|
|
540
|
+
break;
|
|
541
|
+
case ErrorCodes.VALIDATION_ERROR:
|
|
542
|
+
setError(err.message);
|
|
543
|
+
break;
|
|
544
|
+
default:
|
|
545
|
+
setError(err.message);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
## Advanced Setup (Custom Transport)
|
|
553
|
+
|
|
554
|
+
```tsx
|
|
555
|
+
'use client';
|
|
556
|
+
|
|
557
|
+
import { Blocks23Provider } from '@23blocks/react';
|
|
558
|
+
import { createHttpTransport } from '@23blocks/transport-http';
|
|
559
|
+
import { useMemo } from 'react';
|
|
560
|
+
|
|
561
|
+
export function BlocksProvider({ children }: { children: React.ReactNode }) {
|
|
562
|
+
const transport = useMemo(() => createHttpTransport({
|
|
563
|
+
baseUrl: process.env.NEXT_PUBLIC_AUTH_URL!,
|
|
564
|
+
headers: () => {
|
|
565
|
+
if (typeof window === 'undefined') return {};
|
|
566
|
+
const token = localStorage.getItem('access_token');
|
|
567
|
+
return {
|
|
568
|
+
'x-api-key': process.env.NEXT_PUBLIC_API_KEY!,
|
|
569
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
570
|
+
};
|
|
571
|
+
},
|
|
572
|
+
}), []);
|
|
573
|
+
|
|
574
|
+
return (
|
|
575
|
+
<Blocks23Provider
|
|
576
|
+
transport={transport}
|
|
577
|
+
authentication={{ apiKey: process.env.NEXT_PUBLIC_API_KEY! }}
|
|
578
|
+
search={{ apiKey: process.env.NEXT_PUBLIC_API_KEY! }}
|
|
579
|
+
>
|
|
580
|
+
{children}
|
|
581
|
+
</Blocks23Provider>
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
## Environment Variables
|
|
587
|
+
|
|
588
|
+
```env
|
|
589
|
+
# .env.local
|
|
590
|
+
|
|
591
|
+
# Service URLs
|
|
592
|
+
NEXT_PUBLIC_AUTH_URL=https://auth.yourapp.com
|
|
593
|
+
NEXT_PUBLIC_PRODUCTS_URL=https://products.yourapp.com
|
|
594
|
+
NEXT_PUBLIC_CRM_URL=https://crm.yourapp.com
|
|
595
|
+
|
|
596
|
+
# Client-side API key
|
|
597
|
+
NEXT_PUBLIC_API_KEY=your-api-key
|
|
598
|
+
|
|
599
|
+
# Server-side only
|
|
600
|
+
API_KEY=your-secret-api-key
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
## TypeScript
|
|
604
|
+
|
|
605
|
+
All hooks and contexts are fully typed:
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
import type { User, SignInResponse, SignUpResponse } from '@23blocks/block-authentication';
|
|
609
|
+
|
|
610
|
+
const handleSignIn = async (): Promise<SignInResponse | null> => {
|
|
611
|
+
return await signIn({ email, password });
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
// SignUpResponse.accessToken is optional
|
|
615
|
+
const handleSignUp = async (): Promise<void> => {
|
|
616
|
+
const { user, accessToken, message } = await signUp({
|
|
617
|
+
email,
|
|
618
|
+
password,
|
|
619
|
+
passwordConfirmation: password,
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
if (accessToken) {
|
|
623
|
+
// User is logged in
|
|
624
|
+
} else {
|
|
625
|
+
// Email confirmation required
|
|
626
|
+
alert(message);
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
## Related Packages
|
|
632
|
+
|
|
633
|
+
- [`@23blocks/sdk`](https://www.npmjs.com/package/@23blocks/sdk) - Full SDK package
|
|
634
|
+
- [`@23blocks/angular`](https://www.npmjs.com/package/@23blocks/angular) - Angular integration
|
|
635
|
+
|
|
636
|
+
## License
|
|
637
|
+
|
|
638
|
+
MIT - Copyright (c) 2024 23blocks
|
package/dist/index.esm.js
CHANGED
|
@@ -309,7 +309,7 @@ const Blocks23Context = /*#__PURE__*/ createContext(null);
|
|
|
309
309
|
credentials: authMode === 'cookie' ? 'include' : undefined,
|
|
310
310
|
headers: ()=>{
|
|
311
311
|
const headers = _({}, staticHeaders, {
|
|
312
|
-
'api-key': apiKey
|
|
312
|
+
'x-api-key': apiKey
|
|
313
313
|
});
|
|
314
314
|
if (tenantId) {
|
|
315
315
|
headers['tenant-id'] = tenantId;
|