@23blocks/angular 6.5.17 → 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 +169 -380
- package/dist/README.md +169 -380
- package/dist/fesm2022/23blocks-angular.mjs +746 -7590
- package/dist/fesm2022/23blocks-angular.mjs.map +1 -1
- package/dist/index.d.ts +527 -3306
- package/dist/index.d.ts.map +1 -1
- package/package.json +5 -3
package/dist/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# @23blocks/angular
|
|
2
2
|
|
|
3
|
-
Angular bindings for the 23blocks SDK - Injectable services with
|
|
3
|
+
Angular bindings for the 23blocks SDK - Injectable services with typed delegation to block APIs.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@23blocks/angular)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
+
> **v7.0.0** - Complete rewrite from Observable wrappers to typed getter delegation. See [CHANGELOG.md](./CHANGELOG.md) for migration guide.
|
|
9
|
+
|
|
8
10
|
## Installation
|
|
9
11
|
|
|
10
12
|
```bash
|
|
@@ -15,10 +17,12 @@ npm install @23blocks/angular
|
|
|
15
17
|
|
|
16
18
|
This package provides Angular-specific bindings for the 23blocks SDK:
|
|
17
19
|
|
|
18
|
-
- **Injectable Services** - All blocks exposed as Angular services
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
20
|
+
- **Injectable Services** - All 18 blocks exposed as Angular services
|
|
21
|
+
- **Typed Delegation** - Services expose block sub-services via typed getters
|
|
22
|
+
- **Promise-based** - All methods return Promises (use `from()` for Observables)
|
|
23
|
+
- **Token Management** - Automatic token storage and refresh for auth flows
|
|
21
24
|
- **Dependency Injection** - Full DI support with providers
|
|
25
|
+
- **Zero Maintenance** - Getters auto-sync with block API types
|
|
22
26
|
|
|
23
27
|
## Quick Start
|
|
24
28
|
|
|
@@ -82,6 +86,7 @@ export class LoginComponent {
|
|
|
82
86
|
|
|
83
87
|
login() {
|
|
84
88
|
this.loading = true;
|
|
89
|
+
// Auth-flow methods return Observables with automatic token management
|
|
85
90
|
this.auth.signIn({ email: this.email, password: this.password })
|
|
86
91
|
.subscribe({
|
|
87
92
|
next: (response) => {
|
|
@@ -98,6 +103,53 @@ export class LoginComponent {
|
|
|
98
103
|
}
|
|
99
104
|
```
|
|
100
105
|
|
|
106
|
+
## Service Architecture
|
|
107
|
+
|
|
108
|
+
Services expose block sub-services through typed getters. Each getter returns the block's native service, which provides Promise-based methods.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// All non-auth services use this pattern:
|
|
112
|
+
const products = inject(ProductsService);
|
|
113
|
+
|
|
114
|
+
// Access sub-services via getters
|
|
115
|
+
const list = await products.products.list({ page: 1, perPage: 20 });
|
|
116
|
+
const cart = await products.cart.get(cartId);
|
|
117
|
+
const categories = await products.categories.list();
|
|
118
|
+
|
|
119
|
+
// Or convert to Observables with from()
|
|
120
|
+
from(products.products.list()).subscribe(list => { ... });
|
|
121
|
+
|
|
122
|
+
// Full block access for advanced use cases
|
|
123
|
+
const block = products.productsBlock;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### AuthenticationService (Hybrid)
|
|
127
|
+
|
|
128
|
+
AuthenticationService is special: auth-flow methods that manage tokens return **Observables** with automatic `tap()` for token storage. All other sub-services are delegated via getters (Promise-based).
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const auth = inject(AuthenticationService);
|
|
132
|
+
|
|
133
|
+
// Observable methods (with token management):
|
|
134
|
+
auth.signIn({ email, password }).subscribe(...)
|
|
135
|
+
auth.signUp({ email, password, passwordConfirmation }).subscribe(...)
|
|
136
|
+
auth.signOut().subscribe(...)
|
|
137
|
+
auth.refreshToken({ refreshToken }).subscribe(...)
|
|
138
|
+
auth.facebookLogin({ accessToken }).subscribe(...)
|
|
139
|
+
auth.googleLogin({ accessToken }).subscribe(...)
|
|
140
|
+
|
|
141
|
+
// Promise-based sub-services (via getters):
|
|
142
|
+
const user = await auth.users.get(userId);
|
|
143
|
+
const roles = await auth.roles.list();
|
|
144
|
+
const keys = await auth.apiKeys.list();
|
|
145
|
+
from(auth.mfa.enable(userId)).subscribe(...)
|
|
146
|
+
|
|
147
|
+
// Token management:
|
|
148
|
+
auth.isAuthenticated() // boolean | null
|
|
149
|
+
auth.getAccessToken() // string | null
|
|
150
|
+
auth.clearTokens() // void
|
|
151
|
+
```
|
|
152
|
+
|
|
101
153
|
## Configuration Options
|
|
102
154
|
|
|
103
155
|
### provideBlocks23 Options
|
|
@@ -129,18 +181,7 @@ provideBlocks23({
|
|
|
129
181
|
})
|
|
130
182
|
```
|
|
131
183
|
|
|
132
|
-
###
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
provideBlocks23({
|
|
136
|
-
apiKey: 'your-api-key',
|
|
137
|
-
urls: { authentication: 'https://auth.yourapp.com' },
|
|
138
|
-
authMode: 'token', // default
|
|
139
|
-
storage: 'localStorage', // default
|
|
140
|
-
})
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### Cookie Mode (Recommended for Security)
|
|
184
|
+
### Cookie Mode
|
|
144
185
|
|
|
145
186
|
```typescript
|
|
146
187
|
provideBlocks23({
|
|
@@ -180,162 +221,32 @@ export class AppModule {}
|
|
|
180
221
|
|
|
181
222
|
## Available Services
|
|
182
223
|
|
|
183
|
-
| Service |
|
|
224
|
+
| Service | Sub-services |
|
|
184
225
|
|---------|-------------|
|
|
185
|
-
| `AuthenticationService` |
|
|
186
|
-
| `SearchService` |
|
|
187
|
-
| `ProductsService` |
|
|
188
|
-
| `CrmService` |
|
|
189
|
-
| `ContentService` |
|
|
190
|
-
| `GeolocationService` |
|
|
191
|
-
| `ConversationsService` |
|
|
192
|
-
| `FilesService` |
|
|
193
|
-
| `FormsService` |
|
|
194
|
-
| `AssetsService` |
|
|
195
|
-
| `CampaignsService` |
|
|
196
|
-
| `CompanyService` |
|
|
197
|
-
| `RewardsService` |
|
|
198
|
-
| `SalesService` |
|
|
199
|
-
| `WalletService` |
|
|
200
|
-
| `JarvisService` |
|
|
201
|
-
| `OnboardingService` |
|
|
202
|
-
| `UniversityService` |
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
import { Component, inject } from '@angular/core';
|
|
210
|
-
import { AuthenticationService } from '@23blocks/angular';
|
|
211
|
-
|
|
212
|
-
@Component({ ... })
|
|
213
|
-
export class LoginComponent {
|
|
214
|
-
private auth = inject(AuthenticationService);
|
|
215
|
-
|
|
216
|
-
email = '';
|
|
217
|
-
password = '';
|
|
218
|
-
|
|
219
|
-
signIn() {
|
|
220
|
-
// Required: email, password
|
|
221
|
-
this.auth.signIn({ email: this.email, password: this.password }).subscribe({
|
|
222
|
-
next: ({ user, accessToken, refreshToken, expiresIn }) => {
|
|
223
|
-
console.log('Welcome', user.email);
|
|
224
|
-
// In token mode, tokens are automatically stored
|
|
225
|
-
},
|
|
226
|
-
error: (err) => {
|
|
227
|
-
console.error('Login failed:', err.message);
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
### Sign Up (Registration)
|
|
235
|
-
|
|
236
|
-
```typescript
|
|
237
|
-
@Component({ ... })
|
|
238
|
-
export class RegisterComponent {
|
|
239
|
-
private auth = inject(AuthenticationService);
|
|
240
|
-
|
|
241
|
-
// Sign up with required fields only
|
|
242
|
-
signUp() {
|
|
243
|
-
this.auth.signUp({
|
|
244
|
-
email: 'new@example.com', // Required
|
|
245
|
-
password: 'password', // Required
|
|
246
|
-
passwordConfirmation: 'password', // Required - must match password
|
|
247
|
-
}).subscribe({
|
|
248
|
-
next: ({ user, accessToken, message }) => {
|
|
249
|
-
// accessToken may be undefined if email confirmation is enabled
|
|
250
|
-
if (accessToken) {
|
|
251
|
-
console.log('Logged in as', user.email);
|
|
252
|
-
} else {
|
|
253
|
-
console.log(message); // "Confirmation email sent"
|
|
254
|
-
}
|
|
255
|
-
},
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Sign up with all optional fields
|
|
260
|
-
signUpFull() {
|
|
261
|
-
this.auth.signUp({
|
|
262
|
-
// Required
|
|
263
|
-
email: 'new@example.com',
|
|
264
|
-
password: 'securePassword123',
|
|
265
|
-
passwordConfirmation: 'securePassword123',
|
|
266
|
-
|
|
267
|
-
// Optional
|
|
268
|
-
name: 'John Doe',
|
|
269
|
-
username: 'johndoe',
|
|
270
|
-
roleId: 'role-uuid',
|
|
271
|
-
confirmSuccessUrl: 'https://yourapp.com/confirmed', // Redirect after email confirmation
|
|
272
|
-
timeZone: 'America/New_York',
|
|
273
|
-
preferredLanguage: 'en',
|
|
274
|
-
payload: { referralCode: 'ABC123' },
|
|
275
|
-
subscription: 'premium-plan',
|
|
276
|
-
}).subscribe();
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
### Sign Out
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
signOut() {
|
|
285
|
-
this.auth.signOut().subscribe({
|
|
286
|
-
next: () => {
|
|
287
|
-
console.log('Signed out');
|
|
288
|
-
// Tokens are automatically cleared
|
|
289
|
-
},
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
### Email Confirmation
|
|
295
|
-
|
|
296
|
-
```typescript
|
|
297
|
-
// Confirm email with token from URL
|
|
298
|
-
confirmEmail(token: string) {
|
|
299
|
-
this.auth.confirmEmail(token).subscribe({
|
|
300
|
-
next: (user) => {
|
|
301
|
-
console.log('Email confirmed for', user.email);
|
|
302
|
-
},
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Resend confirmation email
|
|
307
|
-
resendConfirmation(email: string) {
|
|
308
|
-
this.auth.resendConfirmation({
|
|
309
|
-
email,
|
|
310
|
-
confirmSuccessUrl: 'https://yourapp.com/confirmed', // Optional
|
|
311
|
-
}).subscribe({
|
|
312
|
-
next: () => {
|
|
313
|
-
console.log('Confirmation email sent');
|
|
314
|
-
},
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
### Get Current User
|
|
320
|
-
|
|
321
|
-
```typescript
|
|
322
|
-
// Returns user with role, avatar, and profile included
|
|
323
|
-
getCurrentUser() {
|
|
324
|
-
return this.auth.getCurrentUser();
|
|
325
|
-
}
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
### Check Authentication
|
|
329
|
-
|
|
330
|
-
```typescript
|
|
331
|
-
// Token mode: returns true/false
|
|
332
|
-
// Cookie mode: returns null (use validateToken instead)
|
|
333
|
-
isAuthenticated(): boolean | null {
|
|
334
|
-
return this.auth.isAuthenticated();
|
|
335
|
-
}
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### Full AuthenticationService Example
|
|
226
|
+
| `AuthenticationService` | auth, users, roles, permissions, apiKeys, mfa, oauth, avatars, tenants, apps, blocks, services, subscriptionModels, userSubscriptions, companySubscriptions, countries, states, counties, cities, currencies, guests, magicLinks, refreshTokens, userDevices, tenantUsers, mailTemplates, jwks, adminRsaKeys, oidc |
|
|
227
|
+
| `SearchService` | search, history, favorites, entities, identities, jarvis |
|
|
228
|
+
| `ProductsService` | products, cart, cartDetails, categories, brands, vendors, warehouses, channels, collections, productSets, shoppingLists, promotions, prices, filters, images, variations, reviews, variationReviews, stock, suggestions, addons, myCarts, remarketing, visitors, productVendors |
|
|
229
|
+
| `CrmService` | accounts, contacts, contactEvents, leads, leadFollows, opportunities, meetings, meetingParticipants, meetingBillings, quotes, subscribers, referrals, touches, categories, calendarAccounts, busyBlocks, icsTokens, zoomMeetings, zoomHosts, mailTemplates, communications, users, billingReports, calendarSync |
|
|
230
|
+
| `ContentService` | posts, postVersions, postTemplates, comments, categories, tags, users, moderation, activity, series |
|
|
231
|
+
| `GeolocationService` | locations, addresses, areas, regions, routes, bookings, premises, premiseEvents, routeTracker, locationHours, locationImages, locationSlots, locationTaxes, locationGroups, identities, locationIdentities, geoCountries, geoStates, geoCities |
|
|
232
|
+
| `ConversationsService` | messages, draftMessages, groups, groupInvites, notifications, conversations, websocketTokens, contexts, notificationSettings, availabilities, messageFiles, sources, users, meetings, webNotifications |
|
|
233
|
+
| `FilesService` | storageFiles, entityFiles, fileSchemas, userFiles, fileCategories, fileTags, delegations, fileAccess, fileAccessRequests |
|
|
234
|
+
| `FormsService` | forms, schemas, schemaVersions, instances, sets, landings, subscriptions, appointments, surveys, referrals, mailTemplates, applicationForms, crmSync |
|
|
235
|
+
| `AssetsService` | assets, events, audits, categories, tags, vendors, warehouses, entities, operations, alerts, users, images |
|
|
236
|
+
| `CampaignsService` | campaigns, campaignMedia, landingPages, audiences, landingTemplates, targets, results, markets, locations, templates, mediaResults, media |
|
|
237
|
+
| `CompanyService` | companies, departments, teams, teamMembers, quarters, positions, employeeAssignments |
|
|
238
|
+
| `RewardsService` | rewards, coupons, loyalty, badges, couponConfigurations, offerCodes, expirationRules, customers, badgeCategories, moneyRules, productRules, eventRules |
|
|
239
|
+
| `SalesService` | orders, orderDetails, orderTaxes, payments, subscriptions, subscriptionModels, entities, users, customers, flexibleOrders, stripe, mercadopago, vendorPayments |
|
|
240
|
+
| `WalletService` | wallets, transactions, authorizationCodes, webhooks |
|
|
241
|
+
| `JarvisService` | agents, prompts, workflows, executions, conversations, aiModels, entities, clusters, users, workflowParticipants, workflowSteps, workflowInstances, agentRuntime, mailTemplates, marvinChat, promptComments, executionComments |
|
|
242
|
+
| `OnboardingService` | onboardings, flows, userJourneys, userIdentities, onboard, mailTemplates, remarketing |
|
|
243
|
+
| `UniversityService` | courses, lessons, enrollments, assignments, submissions, subjects, teachers, students, courseGroups, coachingSessions, tests, registrationTokens, placements, calendars, matches, attendance, notes |
|
|
244
|
+
|
|
245
|
+
Each service also exposes a `{serviceName}Block` getter for full block access.
|
|
246
|
+
|
|
247
|
+
## Usage Examples
|
|
248
|
+
|
|
249
|
+
### Authentication
|
|
339
250
|
|
|
340
251
|
```typescript
|
|
341
252
|
import { Component, inject } from '@angular/core';
|
|
@@ -345,14 +256,14 @@ import { AuthenticationService } from '@23blocks/angular';
|
|
|
345
256
|
export class AuthComponent {
|
|
346
257
|
private auth = inject(AuthenticationService);
|
|
347
258
|
|
|
348
|
-
// Sign in
|
|
259
|
+
// Sign in (Observable with token management)
|
|
349
260
|
signIn() {
|
|
350
|
-
this.auth.signIn({ email, password }).subscribe({
|
|
261
|
+
this.auth.signIn({ email: this.email, password: this.password }).subscribe({
|
|
351
262
|
next: ({ user, accessToken }) => console.log('Welcome', user.email),
|
|
352
263
|
});
|
|
353
264
|
}
|
|
354
265
|
|
|
355
|
-
// Sign up
|
|
266
|
+
// Sign up (Observable with token management)
|
|
356
267
|
signUp() {
|
|
357
268
|
this.auth.signUp({
|
|
358
269
|
email: 'new@example.com',
|
|
@@ -361,7 +272,7 @@ export class AuthComponent {
|
|
|
361
272
|
}).subscribe();
|
|
362
273
|
}
|
|
363
274
|
|
|
364
|
-
// Sign out
|
|
275
|
+
// Sign out (Observable with token management)
|
|
365
276
|
signOut() {
|
|
366
277
|
this.auth.signOut().subscribe();
|
|
367
278
|
}
|
|
@@ -371,19 +282,23 @@ export class AuthComponent {
|
|
|
371
282
|
return this.auth.isAuthenticated();
|
|
372
283
|
}
|
|
373
284
|
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
return this.auth.
|
|
285
|
+
// Access sub-services (Promise-based)
|
|
286
|
+
async loadUser(id: string) {
|
|
287
|
+
return await this.auth.users.get(id);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async listRoles() {
|
|
291
|
+
return await this.auth.roles.list();
|
|
377
292
|
}
|
|
378
293
|
}
|
|
379
294
|
```
|
|
380
295
|
|
|
381
|
-
|
|
296
|
+
### Search
|
|
382
297
|
|
|
383
298
|
```typescript
|
|
384
299
|
import { Component, inject } from '@angular/core';
|
|
385
300
|
import { SearchService } from '@23blocks/angular';
|
|
386
|
-
import { Subject, debounceTime, switchMap } from 'rxjs';
|
|
301
|
+
import { Subject, debounceTime, switchMap, from } from 'rxjs';
|
|
387
302
|
|
|
388
303
|
@Component({
|
|
389
304
|
selector: 'app-search',
|
|
@@ -395,14 +310,14 @@ import { Subject, debounceTime, switchMap } from 'rxjs';
|
|
|
395
310
|
`,
|
|
396
311
|
})
|
|
397
312
|
export class SearchComponent {
|
|
398
|
-
private
|
|
313
|
+
private searchSvc = inject(SearchService);
|
|
399
314
|
private searchSubject = new Subject<string>();
|
|
400
315
|
results: any[] = [];
|
|
401
316
|
|
|
402
317
|
constructor() {
|
|
403
318
|
this.searchSubject.pipe(
|
|
404
319
|
debounceTime(300),
|
|
405
|
-
switchMap((query) => this.search.search({ query, limit: 10 }))
|
|
320
|
+
switchMap((query) => from(this.searchSvc.search.search({ query, limit: 10 })))
|
|
406
321
|
).subscribe({
|
|
407
322
|
next: (response) => this.results = response.results,
|
|
408
323
|
});
|
|
@@ -415,28 +330,32 @@ export class SearchComponent {
|
|
|
415
330
|
}
|
|
416
331
|
```
|
|
417
332
|
|
|
418
|
-
|
|
333
|
+
### Products
|
|
419
334
|
|
|
420
335
|
```typescript
|
|
421
|
-
import { Component, inject
|
|
336
|
+
import { Component, inject } from '@angular/core';
|
|
422
337
|
import { ProductsService } from '@23blocks/angular';
|
|
338
|
+
import { from } from 'rxjs';
|
|
423
339
|
|
|
424
340
|
@Component({ ... })
|
|
425
|
-
export class ProductsComponent
|
|
341
|
+
export class ProductsComponent {
|
|
426
342
|
private products = inject(ProductsService);
|
|
427
|
-
productList$ = this.products.list({ limit: 20 });
|
|
428
343
|
|
|
429
|
-
|
|
430
|
-
|
|
344
|
+
// Promise-based
|
|
345
|
+
async loadProducts() {
|
|
346
|
+
return await this.products.products.list({ page: 1, perPage: 20 });
|
|
431
347
|
}
|
|
432
|
-
}
|
|
433
|
-
```
|
|
434
348
|
|
|
435
|
-
|
|
349
|
+
// Observable-based
|
|
350
|
+
products$ = from(this.products.products.list({ page: 1, perPage: 20 }));
|
|
436
351
|
|
|
437
|
-
|
|
352
|
+
async addToCart(productId: string) {
|
|
353
|
+
await this.products.cart.addItem({ productId, quantity: 1 });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
438
357
|
|
|
439
|
-
###
|
|
358
|
+
### Content
|
|
440
359
|
|
|
441
360
|
```typescript
|
|
442
361
|
import { Component, inject } from '@angular/core';
|
|
@@ -446,201 +365,85 @@ import { ContentService } from '@23blocks/angular';
|
|
|
446
365
|
export class BlogComponent {
|
|
447
366
|
private content = inject(ContentService);
|
|
448
367
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
// Get a single post
|
|
453
|
-
loadPost(uniqueId: string) {
|
|
454
|
-
this.content.getPost(uniqueId).subscribe({
|
|
455
|
-
next: (post) => console.log('Post:', post.title),
|
|
456
|
-
});
|
|
368
|
+
async loadPosts() {
|
|
369
|
+
return await this.content.posts.list({ page: 1, perPage: 10 });
|
|
457
370
|
}
|
|
458
371
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
this.content.createPost({
|
|
372
|
+
async createPost() {
|
|
373
|
+
return await this.content.posts.create({
|
|
462
374
|
title: 'My New Post',
|
|
463
375
|
body: 'Post content here...',
|
|
464
376
|
status: 'published',
|
|
465
|
-
})
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async loadSeries() {
|
|
381
|
+
return await this.content.series.list({ page: 1, perPage: 10 });
|
|
466
382
|
}
|
|
467
383
|
}
|
|
468
384
|
```
|
|
469
385
|
|
|
470
|
-
###
|
|
471
|
-
|
|
472
|
-
Series allow you to group posts into ordered collections (e.g., tutorials, courses, article series).
|
|
386
|
+
### CRM
|
|
473
387
|
|
|
474
388
|
```typescript
|
|
475
389
|
import { Component, inject } from '@angular/core';
|
|
476
|
-
import {
|
|
390
|
+
import { CrmService } from '@23blocks/angular';
|
|
477
391
|
|
|
478
392
|
@Component({ ... })
|
|
479
|
-
export class
|
|
480
|
-
private
|
|
481
|
-
|
|
482
|
-
// List all series
|
|
483
|
-
series$ = this.content.listSeries({ page: 1, perPage: 10 });
|
|
484
|
-
|
|
485
|
-
// Query series with filters
|
|
486
|
-
loadPublicSeries() {
|
|
487
|
-
this.content.querySeries({
|
|
488
|
-
visibility: 'public',
|
|
489
|
-
completionStatus: 'ongoing',
|
|
490
|
-
page: 1,
|
|
491
|
-
perPage: 20,
|
|
492
|
-
}).subscribe({
|
|
493
|
-
next: (result) => console.log('Series:', result.data),
|
|
494
|
-
});
|
|
495
|
-
}
|
|
393
|
+
export class CrmComponent {
|
|
394
|
+
private crm = inject(CrmService);
|
|
496
395
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
this.content.getSeries(uniqueId).subscribe({
|
|
500
|
-
next: (series) => console.log('Series:', series.title),
|
|
501
|
-
});
|
|
396
|
+
async loadContacts() {
|
|
397
|
+
return await this.crm.contacts.list({ page: 1, perPage: 20 });
|
|
502
398
|
}
|
|
503
399
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
visibility: 'public',
|
|
510
|
-
completionStatus: 'ongoing',
|
|
511
|
-
}).subscribe({
|
|
512
|
-
next: (series) => console.log('Created:', series.uniqueId),
|
|
400
|
+
async createLead() {
|
|
401
|
+
return await this.crm.leads.create({
|
|
402
|
+
firstName: 'John',
|
|
403
|
+
lastName: 'Doe',
|
|
404
|
+
email: 'john@example.com',
|
|
513
405
|
});
|
|
514
406
|
}
|
|
515
|
-
|
|
516
|
-
// Update a series
|
|
517
|
-
updateSeries(uniqueId: string) {
|
|
518
|
-
this.content.updateSeries(uniqueId, {
|
|
519
|
-
completionStatus: 'completed',
|
|
520
|
-
}).subscribe();
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Delete a series
|
|
524
|
-
deleteSeries(uniqueId: string) {
|
|
525
|
-
this.content.deleteSeries(uniqueId).subscribe();
|
|
526
|
-
}
|
|
527
407
|
}
|
|
528
408
|
```
|
|
529
409
|
|
|
530
|
-
### Series Social Actions
|
|
531
|
-
|
|
532
|
-
```typescript
|
|
533
|
-
// Like/dislike a series
|
|
534
|
-
likeSeries(uniqueId: string) {
|
|
535
|
-
this.content.likeSeries(uniqueId).subscribe({
|
|
536
|
-
next: (series) => console.log('Likes:', series.likes),
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
dislikeSeries(uniqueId: string) {
|
|
541
|
-
this.content.dislikeSeries(uniqueId).subscribe();
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// Follow/unfollow a series
|
|
545
|
-
followSeries(uniqueId: string) {
|
|
546
|
-
this.content.followSeries(uniqueId).subscribe({
|
|
547
|
-
next: (series) => console.log('Followers:', series.followers),
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
unfollowSeries(uniqueId: string) {
|
|
552
|
-
this.content.unfollowSeries(uniqueId).subscribe();
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
// Save/unsave a series (bookmarking)
|
|
556
|
-
saveSeries(uniqueId: string) {
|
|
557
|
-
this.content.saveSeries(uniqueId).subscribe();
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
unsaveSeries(uniqueId: string) {
|
|
561
|
-
this.content.unsaveSeries(uniqueId).subscribe();
|
|
562
|
-
}
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
### Series Post Management
|
|
566
|
-
|
|
567
|
-
```typescript
|
|
568
|
-
// Get posts in a series (ordered)
|
|
569
|
-
loadSeriesPosts(seriesUniqueId: string) {
|
|
570
|
-
this.content.getSeriesPosts(seriesUniqueId).subscribe({
|
|
571
|
-
next: (posts) => console.log('Posts in series:', posts.length),
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Add a post to a series with optional sequence
|
|
576
|
-
addPostToSeries(seriesUniqueId: string, postUniqueId: string) {
|
|
577
|
-
this.content.addSeriesPost(seriesUniqueId, postUniqueId, 1).subscribe();
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// Remove a post from a series
|
|
581
|
-
removePostFromSeries(seriesUniqueId: string, postUniqueId: string) {
|
|
582
|
-
this.content.removeSeriesPost(seriesUniqueId, postUniqueId).subscribe();
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// Reorder posts in a series
|
|
586
|
-
reorderPosts(seriesUniqueId: string) {
|
|
587
|
-
this.content.reorderSeriesPosts(seriesUniqueId, {
|
|
588
|
-
posts: [
|
|
589
|
-
{ postUniqueId: 'post-1', sequence: 1 },
|
|
590
|
-
{ postUniqueId: 'post-2', sequence: 2 },
|
|
591
|
-
{ postUniqueId: 'post-3', sequence: 3 },
|
|
592
|
-
],
|
|
593
|
-
}).subscribe();
|
|
594
|
-
}
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
### Series Types
|
|
598
|
-
|
|
599
|
-
```typescript
|
|
600
|
-
import type {
|
|
601
|
-
Series,
|
|
602
|
-
CreateSeriesRequest,
|
|
603
|
-
UpdateSeriesRequest,
|
|
604
|
-
ListSeriesParams,
|
|
605
|
-
QuerySeriesParams,
|
|
606
|
-
ReorderPostsRequest,
|
|
607
|
-
SeriesVisibility, // 'public' | 'private' | 'unlisted'
|
|
608
|
-
SeriesCompletionStatus // 'ongoing' | 'completed' | 'hiatus' | 'cancelled'
|
|
609
|
-
} from '@23blocks/block-content';
|
|
610
|
-
```
|
|
611
|
-
|
|
612
410
|
## RxJS Patterns
|
|
613
411
|
|
|
614
|
-
|
|
412
|
+
Since sub-services return Promises, use `from()` to work with Observables:
|
|
615
413
|
|
|
616
414
|
```typescript
|
|
617
|
-
import { forkJoin } from 'rxjs';
|
|
415
|
+
import { from, forkJoin } from 'rxjs';
|
|
416
|
+
|
|
417
|
+
// Convert a single Promise
|
|
418
|
+
from(this.products.products.list()).subscribe(list => { ... });
|
|
618
419
|
|
|
619
|
-
//
|
|
420
|
+
// Combine multiple calls
|
|
620
421
|
forkJoin({
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
}).subscribe({
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
422
|
+
products: from(this.products.products.list()),
|
|
423
|
+
categories: from(this.products.categories.list()),
|
|
424
|
+
}).subscribe(({ products, categories }) => { ... });
|
|
425
|
+
|
|
426
|
+
// Use with RxJS operators
|
|
427
|
+
from(this.search.search.search({ query })).pipe(
|
|
428
|
+
map(result => result.data),
|
|
429
|
+
catchError(err => of([]))
|
|
430
|
+
).subscribe(results => { ... });
|
|
628
431
|
```
|
|
629
432
|
|
|
630
433
|
### Caching with shareReplay
|
|
631
434
|
|
|
632
435
|
```typescript
|
|
633
|
-
import { shareReplay } from 'rxjs';
|
|
436
|
+
import { from, shareReplay } from 'rxjs';
|
|
634
437
|
|
|
635
|
-
// Cache
|
|
636
|
-
|
|
438
|
+
// Cache categories
|
|
439
|
+
categories$ = from(this.products.categories.list()).pipe(
|
|
637
440
|
shareReplay(1)
|
|
638
441
|
);
|
|
639
442
|
```
|
|
640
443
|
|
|
641
|
-
|
|
444
|
+
## Error Handling
|
|
642
445
|
|
|
643
|
-
Every error includes a unique request ID for
|
|
446
|
+
Every error includes a unique request ID for debugging:
|
|
644
447
|
|
|
645
448
|
```typescript
|
|
646
449
|
import { isBlockErrorException, ErrorCodes } from '@23blocks/contracts';
|
|
@@ -648,7 +451,6 @@ import { isBlockErrorException, ErrorCodes } from '@23blocks/contracts';
|
|
|
648
451
|
this.auth.signIn({ email, password }).subscribe({
|
|
649
452
|
error: (err) => {
|
|
650
453
|
if (isBlockErrorException(err)) {
|
|
651
|
-
// Request tracing for debugging
|
|
652
454
|
console.log('Request ID:', err.requestId); // "req_m5abc_xyz123"
|
|
653
455
|
console.log('Duration:', err.duration); // 145 (ms)
|
|
654
456
|
|
|
@@ -659,15 +461,9 @@ this.auth.signIn({ email, password }).subscribe({
|
|
|
659
461
|
case ErrorCodes.UNAUTHORIZED:
|
|
660
462
|
this.error = 'Session expired';
|
|
661
463
|
break;
|
|
662
|
-
case ErrorCodes.VALIDATION_ERROR:
|
|
663
|
-
this.error = err.message;
|
|
664
|
-
break;
|
|
665
464
|
default:
|
|
666
465
|
this.error = err.message;
|
|
667
466
|
}
|
|
668
|
-
|
|
669
|
-
// Send request ID to support for debugging
|
|
670
|
-
// "Please check request req_m5abc_xyz123"
|
|
671
467
|
}
|
|
672
468
|
},
|
|
673
469
|
});
|
|
@@ -716,10 +512,16 @@ import { of } from 'rxjs';
|
|
|
716
512
|
|
|
717
513
|
describe('LoginComponent', () => {
|
|
718
514
|
const mockAuth = {
|
|
515
|
+
// Auth-flow methods return Observables
|
|
719
516
|
signIn: jest.fn().mockReturnValue(of({
|
|
720
517
|
user: { email: 'test@test.com' },
|
|
721
518
|
accessToken: 'token',
|
|
722
519
|
})),
|
|
520
|
+
// Sub-service getters return objects with Promise methods
|
|
521
|
+
users: {
|
|
522
|
+
get: jest.fn().mockResolvedValue({ email: 'test@test.com' }),
|
|
523
|
+
list: jest.fn().mockResolvedValue({ data: [] }),
|
|
524
|
+
},
|
|
723
525
|
};
|
|
724
526
|
|
|
725
527
|
beforeEach(() => {
|
|
@@ -732,6 +534,17 @@ describe('LoginComponent', () => {
|
|
|
732
534
|
});
|
|
733
535
|
```
|
|
734
536
|
|
|
537
|
+
## TypeScript
|
|
538
|
+
|
|
539
|
+
All services are fully typed. Import types from block packages:
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
import type { User, SignInResponse, SignUpResponse } from '@23blocks/block-authentication';
|
|
543
|
+
import type { Product } from '@23blocks/block-products';
|
|
544
|
+
import type { Contact, Lead } from '@23blocks/block-crm';
|
|
545
|
+
import type { Post, Series } from '@23blocks/block-content';
|
|
546
|
+
```
|
|
547
|
+
|
|
735
548
|
## Environment Variables
|
|
736
549
|
|
|
737
550
|
```typescript
|
|
@@ -758,30 +571,6 @@ export const appConfig: ApplicationConfig = {
|
|
|
758
571
|
};
|
|
759
572
|
```
|
|
760
573
|
|
|
761
|
-
## TypeScript
|
|
762
|
-
|
|
763
|
-
All services are fully typed:
|
|
764
|
-
|
|
765
|
-
```typescript
|
|
766
|
-
import type { User, SignInResponse, SignUpResponse } from '@23blocks/block-authentication';
|
|
767
|
-
|
|
768
|
-
// SignInResponse
|
|
769
|
-
interface SignInResponse {
|
|
770
|
-
user: User;
|
|
771
|
-
accessToken: string;
|
|
772
|
-
refreshToken?: string;
|
|
773
|
-
tokenType: string;
|
|
774
|
-
expiresIn?: number;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// SignUpResponse - accessToken is optional (email confirmation)
|
|
778
|
-
interface SignUpResponse {
|
|
779
|
-
user: User;
|
|
780
|
-
accessToken?: string; // undefined if email confirmation enabled
|
|
781
|
-
message?: string;
|
|
782
|
-
}
|
|
783
|
-
```
|
|
784
|
-
|
|
785
574
|
## Related Packages
|
|
786
575
|
|
|
787
576
|
- [`@23blocks/sdk`](https://www.npmjs.com/package/@23blocks/sdk) - Full SDK package
|