@23blocks/angular 6.5.16 → 6.5.18
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 +167 -380
- package/dist/README.md +167 -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 +20 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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)
|
|
@@ -15,10 +15,12 @@ npm install @23blocks/angular
|
|
|
15
15
|
|
|
16
16
|
This package provides Angular-specific bindings for the 23blocks SDK:
|
|
17
17
|
|
|
18
|
-
- **Injectable Services** - All blocks exposed as Angular services
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
18
|
+
- **Injectable Services** - All 18 blocks exposed as Angular services
|
|
19
|
+
- **Typed Delegation** - Services expose block sub-services via typed getters
|
|
20
|
+
- **Promise-based** - All methods return Promises (use `from()` for Observables)
|
|
21
|
+
- **Token Management** - Automatic token storage and refresh for auth flows
|
|
21
22
|
- **Dependency Injection** - Full DI support with providers
|
|
23
|
+
- **Zero Maintenance** - Getters auto-sync with block API types
|
|
22
24
|
|
|
23
25
|
## Quick Start
|
|
24
26
|
|
|
@@ -82,6 +84,7 @@ export class LoginComponent {
|
|
|
82
84
|
|
|
83
85
|
login() {
|
|
84
86
|
this.loading = true;
|
|
87
|
+
// Auth-flow methods return Observables with automatic token management
|
|
85
88
|
this.auth.signIn({ email: this.email, password: this.password })
|
|
86
89
|
.subscribe({
|
|
87
90
|
next: (response) => {
|
|
@@ -98,6 +101,53 @@ export class LoginComponent {
|
|
|
98
101
|
}
|
|
99
102
|
```
|
|
100
103
|
|
|
104
|
+
## Service Architecture
|
|
105
|
+
|
|
106
|
+
Services expose block sub-services through typed getters. Each getter returns the block's native service, which provides Promise-based methods.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// All non-auth services use this pattern:
|
|
110
|
+
const products = inject(ProductsService);
|
|
111
|
+
|
|
112
|
+
// Access sub-services via getters
|
|
113
|
+
const list = await products.products.list({ page: 1, perPage: 20 });
|
|
114
|
+
const cart = await products.cart.get(cartId);
|
|
115
|
+
const categories = await products.categories.list();
|
|
116
|
+
|
|
117
|
+
// Or convert to Observables with from()
|
|
118
|
+
from(products.products.list()).subscribe(list => { ... });
|
|
119
|
+
|
|
120
|
+
// Full block access for advanced use cases
|
|
121
|
+
const block = products.productsBlock;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### AuthenticationService (Hybrid)
|
|
125
|
+
|
|
126
|
+
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).
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const auth = inject(AuthenticationService);
|
|
130
|
+
|
|
131
|
+
// Observable methods (with token management):
|
|
132
|
+
auth.signIn({ email, password }).subscribe(...)
|
|
133
|
+
auth.signUp({ email, password, passwordConfirmation }).subscribe(...)
|
|
134
|
+
auth.signOut().subscribe(...)
|
|
135
|
+
auth.refreshToken({ refreshToken }).subscribe(...)
|
|
136
|
+
auth.facebookLogin({ accessToken }).subscribe(...)
|
|
137
|
+
auth.googleLogin({ accessToken }).subscribe(...)
|
|
138
|
+
|
|
139
|
+
// Promise-based sub-services (via getters):
|
|
140
|
+
const user = await auth.users.get(userId);
|
|
141
|
+
const roles = await auth.roles.list();
|
|
142
|
+
const keys = await auth.apiKeys.list();
|
|
143
|
+
from(auth.mfa.enable(userId)).subscribe(...)
|
|
144
|
+
|
|
145
|
+
// Token management:
|
|
146
|
+
auth.isAuthenticated() // boolean | null
|
|
147
|
+
auth.getAccessToken() // string | null
|
|
148
|
+
auth.clearTokens() // void
|
|
149
|
+
```
|
|
150
|
+
|
|
101
151
|
## Configuration Options
|
|
102
152
|
|
|
103
153
|
### provideBlocks23 Options
|
|
@@ -129,18 +179,7 @@ provideBlocks23({
|
|
|
129
179
|
})
|
|
130
180
|
```
|
|
131
181
|
|
|
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)
|
|
182
|
+
### Cookie Mode
|
|
144
183
|
|
|
145
184
|
```typescript
|
|
146
185
|
provideBlocks23({
|
|
@@ -180,162 +219,32 @@ export class AppModule {}
|
|
|
180
219
|
|
|
181
220
|
## Available Services
|
|
182
221
|
|
|
183
|
-
| Service |
|
|
222
|
+
| Service | Sub-services |
|
|
184
223
|
|---------|-------------|
|
|
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
|
|
224
|
+
| `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 |
|
|
225
|
+
| `SearchService` | search, history, favorites, entities, identities, jarvis |
|
|
226
|
+
| `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 |
|
|
227
|
+
| `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 |
|
|
228
|
+
| `ContentService` | posts, postVersions, postTemplates, comments, categories, tags, users, moderation, activity, series |
|
|
229
|
+
| `GeolocationService` | locations, addresses, areas, regions, routes, bookings, premises, premiseEvents, routeTracker, locationHours, locationImages, locationSlots, locationTaxes, locationGroups, identities, locationIdentities, geoCountries, geoStates, geoCities |
|
|
230
|
+
| `ConversationsService` | messages, draftMessages, groups, groupInvites, notifications, conversations, websocketTokens, contexts, notificationSettings, availabilities, messageFiles, sources, users, meetings, webNotifications |
|
|
231
|
+
| `FilesService` | storageFiles, entityFiles, fileSchemas, userFiles, fileCategories, fileTags, delegations, fileAccess, fileAccessRequests |
|
|
232
|
+
| `FormsService` | forms, schemas, schemaVersions, instances, sets, landings, subscriptions, appointments, surveys, referrals, mailTemplates, applicationForms, crmSync |
|
|
233
|
+
| `AssetsService` | assets, events, audits, categories, tags, vendors, warehouses, entities, operations, alerts, users, images |
|
|
234
|
+
| `CampaignsService` | campaigns, campaignMedia, landingPages, audiences, landingTemplates, targets, results, markets, locations, templates, mediaResults, media |
|
|
235
|
+
| `CompanyService` | companies, departments, teams, teamMembers, quarters, positions, employeeAssignments |
|
|
236
|
+
| `RewardsService` | rewards, coupons, loyalty, badges, couponConfigurations, offerCodes, expirationRules, customers, badgeCategories, moneyRules, productRules, eventRules |
|
|
237
|
+
| `SalesService` | orders, orderDetails, orderTaxes, payments, subscriptions, subscriptionModels, entities, users, customers, flexibleOrders, stripe, mercadopago, vendorPayments |
|
|
238
|
+
| `WalletService` | wallets, transactions, authorizationCodes, webhooks |
|
|
239
|
+
| `JarvisService` | agents, prompts, workflows, executions, conversations, aiModels, entities, clusters, users, workflowParticipants, workflowSteps, workflowInstances, agentRuntime, mailTemplates, marvinChat, promptComments, executionComments |
|
|
240
|
+
| `OnboardingService` | onboardings, flows, userJourneys, userIdentities, onboard, mailTemplates, remarketing |
|
|
241
|
+
| `UniversityService` | courses, lessons, enrollments, assignments, submissions, subjects, teachers, students, courseGroups, coachingSessions, tests, registrationTokens, placements, calendars, matches, attendance, notes |
|
|
242
|
+
|
|
243
|
+
Each service also exposes a `{serviceName}Block` getter for full block access.
|
|
244
|
+
|
|
245
|
+
## Usage Examples
|
|
246
|
+
|
|
247
|
+
### Authentication
|
|
339
248
|
|
|
340
249
|
```typescript
|
|
341
250
|
import { Component, inject } from '@angular/core';
|
|
@@ -345,14 +254,14 @@ import { AuthenticationService } from '@23blocks/angular';
|
|
|
345
254
|
export class AuthComponent {
|
|
346
255
|
private auth = inject(AuthenticationService);
|
|
347
256
|
|
|
348
|
-
// Sign in
|
|
257
|
+
// Sign in (Observable with token management)
|
|
349
258
|
signIn() {
|
|
350
|
-
this.auth.signIn({ email, password }).subscribe({
|
|
259
|
+
this.auth.signIn({ email: this.email, password: this.password }).subscribe({
|
|
351
260
|
next: ({ user, accessToken }) => console.log('Welcome', user.email),
|
|
352
261
|
});
|
|
353
262
|
}
|
|
354
263
|
|
|
355
|
-
// Sign up
|
|
264
|
+
// Sign up (Observable with token management)
|
|
356
265
|
signUp() {
|
|
357
266
|
this.auth.signUp({
|
|
358
267
|
email: 'new@example.com',
|
|
@@ -361,7 +270,7 @@ export class AuthComponent {
|
|
|
361
270
|
}).subscribe();
|
|
362
271
|
}
|
|
363
272
|
|
|
364
|
-
// Sign out
|
|
273
|
+
// Sign out (Observable with token management)
|
|
365
274
|
signOut() {
|
|
366
275
|
this.auth.signOut().subscribe();
|
|
367
276
|
}
|
|
@@ -371,19 +280,23 @@ export class AuthComponent {
|
|
|
371
280
|
return this.auth.isAuthenticated();
|
|
372
281
|
}
|
|
373
282
|
|
|
374
|
-
//
|
|
375
|
-
|
|
376
|
-
return this.auth.
|
|
283
|
+
// Access sub-services (Promise-based)
|
|
284
|
+
async loadUser(id: string) {
|
|
285
|
+
return await this.auth.users.get(id);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async listRoles() {
|
|
289
|
+
return await this.auth.roles.list();
|
|
377
290
|
}
|
|
378
291
|
}
|
|
379
292
|
```
|
|
380
293
|
|
|
381
|
-
|
|
294
|
+
### Search
|
|
382
295
|
|
|
383
296
|
```typescript
|
|
384
297
|
import { Component, inject } from '@angular/core';
|
|
385
298
|
import { SearchService } from '@23blocks/angular';
|
|
386
|
-
import { Subject, debounceTime, switchMap } from 'rxjs';
|
|
299
|
+
import { Subject, debounceTime, switchMap, from } from 'rxjs';
|
|
387
300
|
|
|
388
301
|
@Component({
|
|
389
302
|
selector: 'app-search',
|
|
@@ -395,14 +308,14 @@ import { Subject, debounceTime, switchMap } from 'rxjs';
|
|
|
395
308
|
`,
|
|
396
309
|
})
|
|
397
310
|
export class SearchComponent {
|
|
398
|
-
private
|
|
311
|
+
private searchSvc = inject(SearchService);
|
|
399
312
|
private searchSubject = new Subject<string>();
|
|
400
313
|
results: any[] = [];
|
|
401
314
|
|
|
402
315
|
constructor() {
|
|
403
316
|
this.searchSubject.pipe(
|
|
404
317
|
debounceTime(300),
|
|
405
|
-
switchMap((query) => this.search.search({ query, limit: 10 }))
|
|
318
|
+
switchMap((query) => from(this.searchSvc.search.search({ query, limit: 10 })))
|
|
406
319
|
).subscribe({
|
|
407
320
|
next: (response) => this.results = response.results,
|
|
408
321
|
});
|
|
@@ -415,28 +328,32 @@ export class SearchComponent {
|
|
|
415
328
|
}
|
|
416
329
|
```
|
|
417
330
|
|
|
418
|
-
|
|
331
|
+
### Products
|
|
419
332
|
|
|
420
333
|
```typescript
|
|
421
|
-
import { Component, inject
|
|
334
|
+
import { Component, inject } from '@angular/core';
|
|
422
335
|
import { ProductsService } from '@23blocks/angular';
|
|
336
|
+
import { from } from 'rxjs';
|
|
423
337
|
|
|
424
338
|
@Component({ ... })
|
|
425
|
-
export class ProductsComponent
|
|
339
|
+
export class ProductsComponent {
|
|
426
340
|
private products = inject(ProductsService);
|
|
427
|
-
productList$ = this.products.list({ limit: 20 });
|
|
428
341
|
|
|
429
|
-
|
|
430
|
-
|
|
342
|
+
// Promise-based
|
|
343
|
+
async loadProducts() {
|
|
344
|
+
return await this.products.products.list({ page: 1, perPage: 20 });
|
|
431
345
|
}
|
|
432
|
-
}
|
|
433
|
-
```
|
|
434
346
|
|
|
435
|
-
|
|
347
|
+
// Observable-based
|
|
348
|
+
products$ = from(this.products.products.list({ page: 1, perPage: 20 }));
|
|
436
349
|
|
|
437
|
-
|
|
350
|
+
async addToCart(productId: string) {
|
|
351
|
+
await this.products.cart.addItem({ productId, quantity: 1 });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
```
|
|
438
355
|
|
|
439
|
-
###
|
|
356
|
+
### Content
|
|
440
357
|
|
|
441
358
|
```typescript
|
|
442
359
|
import { Component, inject } from '@angular/core';
|
|
@@ -446,201 +363,85 @@ import { ContentService } from '@23blocks/angular';
|
|
|
446
363
|
export class BlogComponent {
|
|
447
364
|
private content = inject(ContentService);
|
|
448
365
|
|
|
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
|
-
});
|
|
366
|
+
async loadPosts() {
|
|
367
|
+
return await this.content.posts.list({ page: 1, perPage: 10 });
|
|
457
368
|
}
|
|
458
369
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
this.content.createPost({
|
|
370
|
+
async createPost() {
|
|
371
|
+
return await this.content.posts.create({
|
|
462
372
|
title: 'My New Post',
|
|
463
373
|
body: 'Post content here...',
|
|
464
374
|
status: 'published',
|
|
465
|
-
})
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async loadSeries() {
|
|
379
|
+
return await this.content.series.list({ page: 1, perPage: 10 });
|
|
466
380
|
}
|
|
467
381
|
}
|
|
468
382
|
```
|
|
469
383
|
|
|
470
|
-
###
|
|
471
|
-
|
|
472
|
-
Series allow you to group posts into ordered collections (e.g., tutorials, courses, article series).
|
|
384
|
+
### CRM
|
|
473
385
|
|
|
474
386
|
```typescript
|
|
475
387
|
import { Component, inject } from '@angular/core';
|
|
476
|
-
import {
|
|
388
|
+
import { CrmService } from '@23blocks/angular';
|
|
477
389
|
|
|
478
390
|
@Component({ ... })
|
|
479
|
-
export class
|
|
480
|
-
private
|
|
391
|
+
export class CrmComponent {
|
|
392
|
+
private crm = inject(CrmService);
|
|
481
393
|
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
});
|
|
394
|
+
async loadContacts() {
|
|
395
|
+
return await this.crm.contacts.list({ page: 1, perPage: 20 });
|
|
495
396
|
}
|
|
496
397
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
398
|
+
async createLead() {
|
|
399
|
+
return await this.crm.leads.create({
|
|
400
|
+
firstName: 'John',
|
|
401
|
+
lastName: 'Doe',
|
|
402
|
+
email: 'john@example.com',
|
|
501
403
|
});
|
|
502
404
|
}
|
|
503
|
-
|
|
504
|
-
// Create a new series
|
|
505
|
-
createSeries() {
|
|
506
|
-
this.content.createSeries({
|
|
507
|
-
title: 'TypeScript Fundamentals',
|
|
508
|
-
description: 'A complete guide to TypeScript',
|
|
509
|
-
visibility: 'public',
|
|
510
|
-
completionStatus: 'ongoing',
|
|
511
|
-
}).subscribe({
|
|
512
|
-
next: (series) => console.log('Created:', series.uniqueId),
|
|
513
|
-
});
|
|
514
|
-
}
|
|
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
|
-
}
|
|
528
|
-
```
|
|
529
|
-
|
|
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
405
|
}
|
|
595
406
|
```
|
|
596
407
|
|
|
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
408
|
## RxJS Patterns
|
|
613
409
|
|
|
614
|
-
|
|
410
|
+
Since sub-services return Promises, use `from()` to work with Observables:
|
|
615
411
|
|
|
616
412
|
```typescript
|
|
617
|
-
import { forkJoin } from 'rxjs';
|
|
413
|
+
import { from, forkJoin } from 'rxjs';
|
|
414
|
+
|
|
415
|
+
// Convert a single Promise
|
|
416
|
+
from(this.products.products.list()).subscribe(list => { ... });
|
|
618
417
|
|
|
619
|
-
//
|
|
418
|
+
// Combine multiple calls
|
|
620
419
|
forkJoin({
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
}).subscribe({
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
420
|
+
products: from(this.products.products.list()),
|
|
421
|
+
categories: from(this.products.categories.list()),
|
|
422
|
+
}).subscribe(({ products, categories }) => { ... });
|
|
423
|
+
|
|
424
|
+
// Use with RxJS operators
|
|
425
|
+
from(this.search.search.search({ query })).pipe(
|
|
426
|
+
map(result => result.data),
|
|
427
|
+
catchError(err => of([]))
|
|
428
|
+
).subscribe(results => { ... });
|
|
628
429
|
```
|
|
629
430
|
|
|
630
431
|
### Caching with shareReplay
|
|
631
432
|
|
|
632
433
|
```typescript
|
|
633
|
-
import { shareReplay } from 'rxjs';
|
|
434
|
+
import { from, shareReplay } from 'rxjs';
|
|
634
435
|
|
|
635
|
-
// Cache
|
|
636
|
-
|
|
436
|
+
// Cache categories
|
|
437
|
+
categories$ = from(this.products.categories.list()).pipe(
|
|
637
438
|
shareReplay(1)
|
|
638
439
|
);
|
|
639
440
|
```
|
|
640
441
|
|
|
641
|
-
|
|
442
|
+
## Error Handling
|
|
642
443
|
|
|
643
|
-
Every error includes a unique request ID for
|
|
444
|
+
Every error includes a unique request ID for debugging:
|
|
644
445
|
|
|
645
446
|
```typescript
|
|
646
447
|
import { isBlockErrorException, ErrorCodes } from '@23blocks/contracts';
|
|
@@ -648,7 +449,6 @@ import { isBlockErrorException, ErrorCodes } from '@23blocks/contracts';
|
|
|
648
449
|
this.auth.signIn({ email, password }).subscribe({
|
|
649
450
|
error: (err) => {
|
|
650
451
|
if (isBlockErrorException(err)) {
|
|
651
|
-
// Request tracing for debugging
|
|
652
452
|
console.log('Request ID:', err.requestId); // "req_m5abc_xyz123"
|
|
653
453
|
console.log('Duration:', err.duration); // 145 (ms)
|
|
654
454
|
|
|
@@ -659,15 +459,9 @@ this.auth.signIn({ email, password }).subscribe({
|
|
|
659
459
|
case ErrorCodes.UNAUTHORIZED:
|
|
660
460
|
this.error = 'Session expired';
|
|
661
461
|
break;
|
|
662
|
-
case ErrorCodes.VALIDATION_ERROR:
|
|
663
|
-
this.error = err.message;
|
|
664
|
-
break;
|
|
665
462
|
default:
|
|
666
463
|
this.error = err.message;
|
|
667
464
|
}
|
|
668
|
-
|
|
669
|
-
// Send request ID to support for debugging
|
|
670
|
-
// "Please check request req_m5abc_xyz123"
|
|
671
465
|
}
|
|
672
466
|
},
|
|
673
467
|
});
|
|
@@ -716,10 +510,16 @@ import { of } from 'rxjs';
|
|
|
716
510
|
|
|
717
511
|
describe('LoginComponent', () => {
|
|
718
512
|
const mockAuth = {
|
|
513
|
+
// Auth-flow methods return Observables
|
|
719
514
|
signIn: jest.fn().mockReturnValue(of({
|
|
720
515
|
user: { email: 'test@test.com' },
|
|
721
516
|
accessToken: 'token',
|
|
722
517
|
})),
|
|
518
|
+
// Sub-service getters return objects with Promise methods
|
|
519
|
+
users: {
|
|
520
|
+
get: jest.fn().mockResolvedValue({ email: 'test@test.com' }),
|
|
521
|
+
list: jest.fn().mockResolvedValue({ data: [] }),
|
|
522
|
+
},
|
|
723
523
|
};
|
|
724
524
|
|
|
725
525
|
beforeEach(() => {
|
|
@@ -732,6 +532,17 @@ describe('LoginComponent', () => {
|
|
|
732
532
|
});
|
|
733
533
|
```
|
|
734
534
|
|
|
535
|
+
## TypeScript
|
|
536
|
+
|
|
537
|
+
All services are fully typed. Import types from block packages:
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
import type { User, SignInResponse, SignUpResponse } from '@23blocks/block-authentication';
|
|
541
|
+
import type { Product } from '@23blocks/block-products';
|
|
542
|
+
import type { Contact, Lead } from '@23blocks/block-crm';
|
|
543
|
+
import type { Post, Series } from '@23blocks/block-content';
|
|
544
|
+
```
|
|
545
|
+
|
|
735
546
|
## Environment Variables
|
|
736
547
|
|
|
737
548
|
```typescript
|
|
@@ -758,30 +569,6 @@ export const appConfig: ApplicationConfig = {
|
|
|
758
569
|
};
|
|
759
570
|
```
|
|
760
571
|
|
|
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
572
|
## Related Packages
|
|
786
573
|
|
|
787
574
|
- [`@23blocks/sdk`](https://www.npmjs.com/package/@23blocks/sdk) - Full SDK package
|