@23blocks/angular 5.0.0 → 6.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,603 @@
1
+ # @23blocks/angular
2
+
3
+ Angular bindings for the 23blocks SDK - Injectable services with RxJS Observables.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@23blocks/angular.svg)](https://www.npmjs.com/package/@23blocks/angular)
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/angular
12
+ ```
13
+
14
+ ## Overview
15
+
16
+ This package provides Angular-specific bindings for the 23blocks SDK:
17
+
18
+ - **Injectable Services** - All blocks exposed as Angular services
19
+ - **RxJS Observables** - All methods return Observables
20
+ - **Token Management** - Automatic token storage and refresh
21
+ - **Dependency Injection** - Full DI support with providers
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Configure providers
26
+
27
+ ```typescript
28
+ // app.config.ts
29
+ import { ApplicationConfig } from '@angular/core';
30
+ import { provideBlocks23 } from '@23blocks/angular';
31
+
32
+ export const appConfig: ApplicationConfig = {
33
+ providers: [
34
+ provideBlocks23({
35
+ apiKey: 'your-api-key',
36
+ urls: {
37
+ authentication: 'https://auth.yourapp.com',
38
+ // Add other service URLs as needed
39
+ // products: 'https://products.yourapp.com',
40
+ // crm: 'https://crm.yourapp.com',
41
+ },
42
+ }),
43
+ ],
44
+ };
45
+ ```
46
+
47
+ ### 2. Bootstrap with the config
48
+
49
+ ```typescript
50
+ // main.ts
51
+ import { bootstrapApplication } from '@angular/platform-browser';
52
+ import { appConfig } from './app/app.config';
53
+ import { AppComponent } from './app/app.component';
54
+
55
+ bootstrapApplication(AppComponent, appConfig);
56
+ ```
57
+
58
+ ### 3. Use the services
59
+
60
+ ```typescript
61
+ import { Component, inject } from '@angular/core';
62
+ import { AuthenticationService } from '@23blocks/angular';
63
+
64
+ @Component({
65
+ selector: 'app-login',
66
+ template: `
67
+ <form (ngSubmit)="login()">
68
+ <input [(ngModel)]="email" placeholder="Email" />
69
+ <input [(ngModel)]="password" type="password" placeholder="Password" />
70
+ <button type="submit" [disabled]="loading">
71
+ {{ loading ? 'Signing in...' : 'Sign In' }}
72
+ </button>
73
+ </form>
74
+ `,
75
+ })
76
+ export class LoginComponent {
77
+ private auth = inject(AuthenticationService);
78
+
79
+ email = '';
80
+ password = '';
81
+ loading = false;
82
+
83
+ login() {
84
+ this.loading = true;
85
+ this.auth.signIn({ email: this.email, password: this.password })
86
+ .subscribe({
87
+ next: (response) => {
88
+ console.log('Welcome', response.user.email);
89
+ },
90
+ error: (err) => {
91
+ console.error('Login failed:', err.message);
92
+ },
93
+ complete: () => {
94
+ this.loading = false;
95
+ },
96
+ });
97
+ }
98
+ }
99
+ ```
100
+
101
+ ## Configuration Options
102
+
103
+ ### provideBlocks23 Options
104
+
105
+ ```typescript
106
+ provideBlocks23({
107
+ // Required: Your API key
108
+ apiKey: 'your-api-key',
109
+
110
+ // Required: Service URLs (only configure what you need)
111
+ urls: {
112
+ authentication: 'https://auth.yourapp.com',
113
+ products: 'https://products.yourapp.com',
114
+ crm: 'https://crm.yourapp.com',
115
+ // ... other services
116
+ },
117
+
118
+ // Optional: Tenant ID for multi-tenant setups
119
+ tenantId: 'tenant-123',
120
+
121
+ // Optional: Authentication mode (default: 'token')
122
+ authMode: 'token', // 'token' | 'cookie'
123
+
124
+ // Optional: Token storage (default: 'localStorage')
125
+ storage: 'localStorage', // 'localStorage' | 'sessionStorage' | 'memory'
126
+ })
127
+ ```
128
+
129
+ ### Token Mode (Default)
130
+
131
+ ```typescript
132
+ provideBlocks23({
133
+ apiKey: 'your-api-key',
134
+ urls: { authentication: 'https://auth.yourapp.com' },
135
+ authMode: 'token', // default
136
+ storage: 'localStorage', // default
137
+ })
138
+ ```
139
+
140
+ ### Cookie Mode (Recommended for Security)
141
+
142
+ ```typescript
143
+ provideBlocks23({
144
+ apiKey: 'your-api-key',
145
+ urls: { authentication: 'https://auth.yourapp.com' },
146
+ authMode: 'cookie',
147
+ })
148
+ ```
149
+
150
+ ### Multi-Tenant Setup
151
+
152
+ ```typescript
153
+ provideBlocks23({
154
+ apiKey: 'your-api-key',
155
+ urls: { authentication: 'https://auth.yourapp.com' },
156
+ tenantId: 'tenant-123',
157
+ })
158
+ ```
159
+
160
+ ### NgModule-based Applications
161
+
162
+ ```typescript
163
+ // app.module.ts
164
+ import { NgModule } from '@angular/core';
165
+ import { getBlocks23Providers } from '@23blocks/angular';
166
+
167
+ @NgModule({
168
+ providers: [
169
+ ...getBlocks23Providers({
170
+ apiKey: 'your-api-key',
171
+ urls: { authentication: 'https://auth.yourapp.com' },
172
+ }),
173
+ ],
174
+ })
175
+ export class AppModule {}
176
+ ```
177
+
178
+ ## Available Services
179
+
180
+ | Service | Description |
181
+ |---------|-------------|
182
+ | `AuthenticationService` | Sign in, sign up, password reset, MFA |
183
+ | `SearchService` | Full-text search, favorites |
184
+ | `ProductsService` | Products, categories, variants, cart |
185
+ | `CrmService` | Contacts, accounts, leads, opportunities |
186
+ | `ContentService` | Posts, comments, categories, tags |
187
+ | `GeolocationService` | Addresses, locations, areas |
188
+ | `ConversationsService` | Messages, groups, notifications |
189
+ | `FilesService` | File uploads, storage |
190
+ | `FormsService` | Form builder, submissions |
191
+ | `AssetsService` | Asset management, tracking |
192
+ | `CampaignsService` | Marketing campaigns, audiences |
193
+ | `CompanyService` | Company settings, departments, teams |
194
+ | `RewardsService` | Rewards, coupons, loyalty, badges |
195
+ | `SalesService` | Orders, payments, subscriptions |
196
+ | `WalletService` | Digital wallet, transactions |
197
+ | `JarvisService` | AI assistant, prompts, workflows |
198
+ | `OnboardingService` | User onboarding flows |
199
+ | `UniversityService` | Courses, lessons, enrollments |
200
+
201
+ ## Authentication Examples
202
+
203
+ ### Sign In
204
+
205
+ ```typescript
206
+ import { Component, inject } from '@angular/core';
207
+ import { AuthenticationService } from '@23blocks/angular';
208
+
209
+ @Component({ ... })
210
+ export class LoginComponent {
211
+ private auth = inject(AuthenticationService);
212
+
213
+ email = '';
214
+ password = '';
215
+
216
+ signIn() {
217
+ // Required: email, password
218
+ this.auth.signIn({ email: this.email, password: this.password }).subscribe({
219
+ next: ({ user, accessToken, refreshToken, expiresIn }) => {
220
+ console.log('Welcome', user.email);
221
+ // In token mode, tokens are automatically stored
222
+ },
223
+ error: (err) => {
224
+ console.error('Login failed:', err.message);
225
+ },
226
+ });
227
+ }
228
+ }
229
+ ```
230
+
231
+ ### Sign Up (Registration)
232
+
233
+ ```typescript
234
+ @Component({ ... })
235
+ export class RegisterComponent {
236
+ private auth = inject(AuthenticationService);
237
+
238
+ // Sign up with required fields only
239
+ signUp() {
240
+ this.auth.signUp({
241
+ email: 'new@example.com', // Required
242
+ password: 'password', // Required
243
+ passwordConfirmation: 'password', // Required - must match password
244
+ }).subscribe({
245
+ next: ({ user, accessToken, message }) => {
246
+ // accessToken may be undefined if email confirmation is enabled
247
+ if (accessToken) {
248
+ console.log('Logged in as', user.email);
249
+ } else {
250
+ console.log(message); // "Confirmation email sent"
251
+ }
252
+ },
253
+ });
254
+ }
255
+
256
+ // Sign up with all optional fields
257
+ signUpFull() {
258
+ this.auth.signUp({
259
+ // Required
260
+ email: 'new@example.com',
261
+ password: 'securePassword123',
262
+ passwordConfirmation: 'securePassword123',
263
+
264
+ // Optional
265
+ name: 'John Doe',
266
+ username: 'johndoe',
267
+ roleId: 'role-uuid',
268
+ confirmSuccessUrl: 'https://yourapp.com/confirmed', // Redirect after email confirmation
269
+ timeZone: 'America/New_York',
270
+ preferredLanguage: 'en',
271
+ payload: { referralCode: 'ABC123' },
272
+ subscription: 'premium-plan',
273
+ }).subscribe();
274
+ }
275
+ }
276
+ ```
277
+
278
+ ### Sign Out
279
+
280
+ ```typescript
281
+ signOut() {
282
+ this.auth.signOut().subscribe({
283
+ next: () => {
284
+ console.log('Signed out');
285
+ // Tokens are automatically cleared
286
+ },
287
+ });
288
+ }
289
+ ```
290
+
291
+ ### Email Confirmation
292
+
293
+ ```typescript
294
+ // Confirm email with token from URL
295
+ confirmEmail(token: string) {
296
+ this.auth.confirmEmail(token).subscribe({
297
+ next: (user) => {
298
+ console.log('Email confirmed for', user.email);
299
+ },
300
+ });
301
+ }
302
+
303
+ // Resend confirmation email
304
+ resendConfirmation(email: string) {
305
+ this.auth.resendConfirmation({
306
+ email,
307
+ confirmSuccessUrl: 'https://yourapp.com/confirmed', // Optional
308
+ }).subscribe({
309
+ next: () => {
310
+ console.log('Confirmation email sent');
311
+ },
312
+ });
313
+ }
314
+ ```
315
+
316
+ ### Get Current User
317
+
318
+ ```typescript
319
+ // Returns user with role, avatar, and profile included
320
+ getCurrentUser() {
321
+ return this.auth.getCurrentUser();
322
+ }
323
+ ```
324
+
325
+ ### Check Authentication
326
+
327
+ ```typescript
328
+ // Token mode: returns true/false
329
+ // Cookie mode: returns null (use validateToken instead)
330
+ isAuthenticated(): boolean | null {
331
+ return this.auth.isAuthenticated();
332
+ }
333
+ ```
334
+
335
+ ### Full AuthenticationService Example
336
+
337
+ ```typescript
338
+ import { Component, inject } from '@angular/core';
339
+ import { AuthenticationService } from '@23blocks/angular';
340
+
341
+ @Component({ ... })
342
+ export class AuthComponent {
343
+ private auth = inject(AuthenticationService);
344
+
345
+ // Sign in
346
+ signIn() {
347
+ this.auth.signIn({ email, password }).subscribe({
348
+ next: ({ user, accessToken }) => console.log('Welcome', user.email),
349
+ });
350
+ }
351
+
352
+ // Sign up
353
+ signUp() {
354
+ this.auth.signUp({
355
+ email: 'new@example.com',
356
+ password: 'password',
357
+ passwordConfirmation: 'password',
358
+ }).subscribe();
359
+ }
360
+
361
+ // Sign out
362
+ signOut() {
363
+ this.auth.signOut().subscribe();
364
+ }
365
+
366
+ // Check if authenticated
367
+ isAuthenticated(): boolean | null {
368
+ return this.auth.isAuthenticated();
369
+ }
370
+
371
+ // Get current user
372
+ getCurrentUser() {
373
+ return this.auth.getCurrentUser();
374
+ }
375
+ }
376
+ ```
377
+
378
+ ## SearchService Example
379
+
380
+ ```typescript
381
+ import { Component, inject } from '@angular/core';
382
+ import { SearchService } from '@23blocks/angular';
383
+ import { Subject, debounceTime, switchMap } from 'rxjs';
384
+
385
+ @Component({
386
+ selector: 'app-search',
387
+ template: `
388
+ <input (input)="onSearch($event)" placeholder="Search..." />
389
+ <ul>
390
+ <li *ngFor="let result of results">{{ result.title }}</li>
391
+ </ul>
392
+ `,
393
+ })
394
+ export class SearchComponent {
395
+ private search = inject(SearchService);
396
+ private searchSubject = new Subject<string>();
397
+ results: any[] = [];
398
+
399
+ constructor() {
400
+ this.searchSubject.pipe(
401
+ debounceTime(300),
402
+ switchMap((query) => this.search.search({ query, limit: 10 }))
403
+ ).subscribe({
404
+ next: (response) => this.results = response.results,
405
+ });
406
+ }
407
+
408
+ onSearch(event: Event) {
409
+ const query = (event.target as HTMLInputElement).value;
410
+ this.searchSubject.next(query);
411
+ }
412
+ }
413
+ ```
414
+
415
+ ## ProductsService Example
416
+
417
+ ```typescript
418
+ import { Component, inject, OnInit } from '@angular/core';
419
+ import { ProductsService } from '@23blocks/angular';
420
+
421
+ @Component({ ... })
422
+ export class ProductsComponent implements OnInit {
423
+ private products = inject(ProductsService);
424
+ productList$ = this.products.list({ limit: 20 });
425
+
426
+ addToCart(productId: string) {
427
+ this.products.addToCart({ productId, quantity: 1 }).subscribe();
428
+ }
429
+ }
430
+ ```
431
+
432
+ ## RxJS Patterns
433
+
434
+ ### Combining Multiple Services
435
+
436
+ ```typescript
437
+ import { forkJoin } from 'rxjs';
438
+
439
+ // Fetch user and favorites in parallel
440
+ forkJoin({
441
+ user: this.auth.getCurrentUser(),
442
+ favorites: this.search.listFavorites({ limit: 10 }),
443
+ }).subscribe({
444
+ next: ({ user, favorites }) => {
445
+ console.log(user, favorites);
446
+ },
447
+ });
448
+ ```
449
+
450
+ ### Caching with shareReplay
451
+
452
+ ```typescript
453
+ import { shareReplay } from 'rxjs';
454
+
455
+ // Cache the current user
456
+ currentUser$ = this.auth.getCurrentUser().pipe(
457
+ shareReplay(1)
458
+ );
459
+ ```
460
+
461
+ ### Error Handling
462
+
463
+ ```typescript
464
+ import { isBlockErrorException, ErrorCodes } from '@23blocks/contracts';
465
+
466
+ this.auth.signIn({ email, password }).subscribe({
467
+ error: (err) => {
468
+ if (isBlockErrorException(err)) {
469
+ switch (err.code) {
470
+ case ErrorCodes.INVALID_CREDENTIALS:
471
+ this.error = 'Invalid email or password';
472
+ break;
473
+ case ErrorCodes.UNAUTHORIZED:
474
+ this.error = 'Session expired';
475
+ break;
476
+ case ErrorCodes.VALIDATION_ERROR:
477
+ this.error = err.message;
478
+ break;
479
+ default:
480
+ this.error = err.message;
481
+ }
482
+ }
483
+ },
484
+ });
485
+ ```
486
+
487
+ ## Advanced Setup (Custom Transport)
488
+
489
+ For advanced use cases requiring custom transport configuration:
490
+
491
+ ```typescript
492
+ import { ApplicationConfig } from '@angular/core';
493
+ import { provide23Blocks } from '@23blocks/angular';
494
+ import { createHttpTransport } from '@23blocks/transport-http';
495
+
496
+ const transport = createHttpTransport({
497
+ baseUrl: 'https://auth.yourapp.com',
498
+ headers: () => {
499
+ const token = localStorage.getItem('access_token');
500
+ return {
501
+ 'x-api-key': 'your-api-key',
502
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
503
+ };
504
+ },
505
+ });
506
+
507
+ export const appConfig: ApplicationConfig = {
508
+ providers: [
509
+ provide23Blocks({
510
+ transport,
511
+ authentication: { apiKey: 'your-api-key' },
512
+ search: { apiKey: 'your-api-key' },
513
+ products: { apiKey: 'your-api-key' },
514
+ }),
515
+ ],
516
+ };
517
+ ```
518
+
519
+ ## Testing
520
+
521
+ Mock services in your tests:
522
+
523
+ ```typescript
524
+ import { TestBed } from '@angular/core/testing';
525
+ import { AuthenticationService } from '@23blocks/angular';
526
+ import { of } from 'rxjs';
527
+
528
+ describe('LoginComponent', () => {
529
+ const mockAuth = {
530
+ signIn: jest.fn().mockReturnValue(of({
531
+ user: { email: 'test@test.com' },
532
+ accessToken: 'token',
533
+ })),
534
+ };
535
+
536
+ beforeEach(() => {
537
+ TestBed.configureTestingModule({
538
+ providers: [
539
+ { provide: AuthenticationService, useValue: mockAuth },
540
+ ],
541
+ });
542
+ });
543
+ });
544
+ ```
545
+
546
+ ## Environment Variables
547
+
548
+ ```typescript
549
+ // environment.ts
550
+ export const environment = {
551
+ production: false,
552
+ apiKey: 'your-api-key',
553
+ urls: {
554
+ authentication: 'https://auth.yourapp.com',
555
+ products: 'https://products.yourapp.com',
556
+ },
557
+ };
558
+
559
+ // app.config.ts
560
+ import { environment } from './environments/environment';
561
+
562
+ export const appConfig: ApplicationConfig = {
563
+ providers: [
564
+ provideBlocks23({
565
+ apiKey: environment.apiKey,
566
+ urls: environment.urls,
567
+ }),
568
+ ],
569
+ };
570
+ ```
571
+
572
+ ## TypeScript
573
+
574
+ All services are fully typed:
575
+
576
+ ```typescript
577
+ import type { User, SignInResponse, SignUpResponse } from '@23blocks/block-authentication';
578
+
579
+ // SignInResponse
580
+ interface SignInResponse {
581
+ user: User;
582
+ accessToken: string;
583
+ refreshToken?: string;
584
+ tokenType: string;
585
+ expiresIn?: number;
586
+ }
587
+
588
+ // SignUpResponse - accessToken is optional (email confirmation)
589
+ interface SignUpResponse {
590
+ user: User;
591
+ accessToken?: string; // undefined if email confirmation enabled
592
+ message?: string;
593
+ }
594
+ ```
595
+
596
+ ## Related Packages
597
+
598
+ - [`@23blocks/sdk`](https://www.npmjs.com/package/@23blocks/sdk) - Full SDK package
599
+ - [`@23blocks/react`](https://www.npmjs.com/package/@23blocks/react) - React integration
600
+
601
+ ## License
602
+
603
+ MIT - Copyright (c) 2024 23blocks
package/dist/index.esm.js CHANGED
@@ -217,7 +217,7 @@ import { createUniversityBlock } from '@23blocks/block-university';
217
217
  credentials: config.authMode === 'cookie' ? 'include' : undefined,
218
218
  headers: ()=>{
219
219
  const headers = _({}, config.headers, {
220
- 'api-key': config.apiKey
220
+ 'x-api-key': config.apiKey
221
221
  });
222
222
  if (config.tenantId) {
223
223
  headers['tenant-id'] = config.tenantId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@23blocks/angular",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "Angular bindings for 23blocks SDK - Injectable services with RxJS Observables",
5
5
  "license": "MIT",
6
6
  "author": "23blocks <hello@23blocks.com>",