@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 ADDED
@@ -0,0 +1,638 @@
1
+ # @23blocks/react
2
+
3
+ React bindings for the 23blocks SDK - hooks and context providers.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@23blocks/react.svg)](https://www.npmjs.com/package/@23blocks/react)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@23blocks/react",
3
- "version": "6.0.0",
3
+ "version": "7.0.0",
4
4
  "description": "React bindings for 23blocks SDK - hooks and context providers",
5
5
  "license": "MIT",
6
6
  "author": "23blocks <hello@23blocks.com>",