@23blocks/angular 6.5.15 → 6.5.17
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/dist/README.md +792 -0
- package/dist/{index.esm.js → fesm2022/23blocks-angular.mjs} +1620 -1286
- package/dist/fesm2022/23blocks-angular.mjs.map +1 -0
- package/dist/index.d.ts +3874 -1
- package/dist/index.d.ts.map +1 -0
- package/package.json +18 -19
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/lib/assets/assets.service.d.ts +0 -123
- package/dist/src/lib/assets/assets.service.d.ts.map +0 -1
- package/dist/src/lib/assets/index.d.ts +0 -2
- package/dist/src/lib/assets/index.d.ts.map +0 -1
- package/dist/src/lib/authentication/authentication.service.d.ts +0 -684
- package/dist/src/lib/authentication/authentication.service.d.ts.map +0 -1
- package/dist/src/lib/authentication/index.d.ts +0 -2
- package/dist/src/lib/authentication/index.d.ts.map +0 -1
- package/dist/src/lib/campaigns/campaigns.service.d.ts +0 -111
- package/dist/src/lib/campaigns/campaigns.service.d.ts.map +0 -1
- package/dist/src/lib/campaigns/index.d.ts +0 -2
- package/dist/src/lib/campaigns/index.d.ts.map +0 -1
- package/dist/src/lib/company/company.service.d.ts +0 -117
- package/dist/src/lib/company/company.service.d.ts.map +0 -1
- package/dist/src/lib/company/index.d.ts +0 -2
- package/dist/src/lib/company/index.d.ts.map +0 -1
- package/dist/src/lib/content/content.service.d.ts +0 -177
- package/dist/src/lib/content/content.service.d.ts.map +0 -1
- package/dist/src/lib/content/index.d.ts +0 -2
- package/dist/src/lib/content/index.d.ts.map +0 -1
- package/dist/src/lib/conversations/conversations.service.d.ts +0 -130
- package/dist/src/lib/conversations/conversations.service.d.ts.map +0 -1
- package/dist/src/lib/conversations/index.d.ts +0 -2
- package/dist/src/lib/conversations/index.d.ts.map +0 -1
- package/dist/src/lib/crm/crm.service.d.ts +0 -182
- package/dist/src/lib/crm/crm.service.d.ts.map +0 -1
- package/dist/src/lib/crm/index.d.ts +0 -2
- package/dist/src/lib/crm/index.d.ts.map +0 -1
- package/dist/src/lib/files/files.service.d.ts +0 -326
- package/dist/src/lib/files/files.service.d.ts.map +0 -1
- package/dist/src/lib/files/index.d.ts +0 -2
- package/dist/src/lib/files/index.d.ts.map +0 -1
- package/dist/src/lib/forms/forms.service.d.ts +0 -132
- package/dist/src/lib/forms/forms.service.d.ts.map +0 -1
- package/dist/src/lib/forms/index.d.ts +0 -2
- package/dist/src/lib/forms/index.d.ts.map +0 -1
- package/dist/src/lib/geolocation/geolocation.service.d.ts +0 -317
- package/dist/src/lib/geolocation/geolocation.service.d.ts.map +0 -1
- package/dist/src/lib/geolocation/index.d.ts +0 -2
- package/dist/src/lib/geolocation/index.d.ts.map +0 -1
- package/dist/src/lib/index.d.ts +0 -22
- package/dist/src/lib/index.d.ts.map +0 -1
- package/dist/src/lib/jarvis/index.d.ts +0 -2
- package/dist/src/lib/jarvis/index.d.ts.map +0 -1
- package/dist/src/lib/jarvis/jarvis.service.d.ts +0 -181
- package/dist/src/lib/jarvis/jarvis.service.d.ts.map +0 -1
- package/dist/src/lib/onboarding/index.d.ts +0 -2
- package/dist/src/lib/onboarding/index.d.ts.map +0 -1
- package/dist/src/lib/onboarding/onboarding.service.d.ts +0 -93
- package/dist/src/lib/onboarding/onboarding.service.d.ts.map +0 -1
- package/dist/src/lib/products/index.d.ts +0 -2
- package/dist/src/lib/products/index.d.ts.map +0 -1
- package/dist/src/lib/products/products.service.d.ts +0 -154
- package/dist/src/lib/products/products.service.d.ts.map +0 -1
- package/dist/src/lib/providers.d.ts +0 -97
- package/dist/src/lib/providers.d.ts.map +0 -1
- package/dist/src/lib/rewards/index.d.ts +0 -2
- package/dist/src/lib/rewards/index.d.ts.map +0 -1
- package/dist/src/lib/rewards/rewards.service.d.ts +0 -172
- package/dist/src/lib/rewards/rewards.service.d.ts.map +0 -1
- package/dist/src/lib/sales/index.d.ts +0 -2
- package/dist/src/lib/sales/index.d.ts.map +0 -1
- package/dist/src/lib/sales/sales.service.d.ts +0 -147
- package/dist/src/lib/sales/sales.service.d.ts.map +0 -1
- package/dist/src/lib/search/index.d.ts +0 -2
- package/dist/src/lib/search/index.d.ts.map +0 -1
- package/dist/src/lib/search/search.service.d.ts +0 -106
- package/dist/src/lib/search/search.service.d.ts.map +0 -1
- package/dist/src/lib/simple-providers.d.ts +0 -203
- package/dist/src/lib/simple-providers.d.ts.map +0 -1
- package/dist/src/lib/tokens.d.ts +0 -134
- package/dist/src/lib/tokens.d.ts.map +0 -1
- package/dist/src/lib/university/index.d.ts +0 -2
- package/dist/src/lib/university/index.d.ts.map +0 -1
- package/dist/src/lib/university/university.service.d.ts +0 -252
- package/dist/src/lib/university/university.service.d.ts.map +0 -1
- package/dist/src/lib/wallet/index.d.ts +0 -2
- package/dist/src/lib/wallet/index.d.ts.map +0 -1
- package/dist/src/lib/wallet/wallet.service.d.ts +0 -71
- package/dist/src/lib/wallet/wallet.service.d.ts.map +0 -1
package/dist/README.md
ADDED
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
# @23blocks/angular
|
|
2
|
+
|
|
3
|
+
Angular bindings for the 23blocks SDK - Injectable services with RxJS Observables.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@23blocks/angular)
|
|
6
|
+
[](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
|
+
// Optional: Enable debug logging
|
|
128
|
+
debug: !environment.production,
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Token Mode (Default)
|
|
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)
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
provideBlocks23({
|
|
147
|
+
apiKey: 'your-api-key',
|
|
148
|
+
urls: { authentication: 'https://auth.yourapp.com' },
|
|
149
|
+
authMode: 'cookie',
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Multi-Tenant Setup
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
provideBlocks23({
|
|
157
|
+
apiKey: 'your-api-key',
|
|
158
|
+
urls: { authentication: 'https://auth.yourapp.com' },
|
|
159
|
+
tenantId: 'tenant-123',
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### NgModule-based Applications
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// app.module.ts
|
|
167
|
+
import { NgModule } from '@angular/core';
|
|
168
|
+
import { getBlocks23Providers } from '@23blocks/angular';
|
|
169
|
+
|
|
170
|
+
@NgModule({
|
|
171
|
+
providers: [
|
|
172
|
+
...getBlocks23Providers({
|
|
173
|
+
apiKey: 'your-api-key',
|
|
174
|
+
urls: { authentication: 'https://auth.yourapp.com' },
|
|
175
|
+
}),
|
|
176
|
+
],
|
|
177
|
+
})
|
|
178
|
+
export class AppModule {}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Available Services
|
|
182
|
+
|
|
183
|
+
| Service | Description |
|
|
184
|
+
|---------|-------------|
|
|
185
|
+
| `AuthenticationService` | Sign in, sign up, password reset, MFA |
|
|
186
|
+
| `SearchService` | Full-text search, favorites |
|
|
187
|
+
| `ProductsService` | Products, categories, variants, cart |
|
|
188
|
+
| `CrmService` | Contacts, accounts, leads, opportunities |
|
|
189
|
+
| `ContentService` | Posts, comments, categories, tags |
|
|
190
|
+
| `GeolocationService` | Addresses, locations, areas |
|
|
191
|
+
| `ConversationsService` | Messages, groups, notifications |
|
|
192
|
+
| `FilesService` | File uploads, storage |
|
|
193
|
+
| `FormsService` | Form builder, submissions |
|
|
194
|
+
| `AssetsService` | Asset management, tracking |
|
|
195
|
+
| `CampaignsService` | Marketing campaigns, audiences |
|
|
196
|
+
| `CompanyService` | Company settings, departments, teams |
|
|
197
|
+
| `RewardsService` | Rewards, coupons, loyalty, badges |
|
|
198
|
+
| `SalesService` | Orders, payments, subscriptions |
|
|
199
|
+
| `WalletService` | Digital wallet, transactions |
|
|
200
|
+
| `JarvisService` | AI assistant, prompts, workflows |
|
|
201
|
+
| `OnboardingService` | User onboarding flows |
|
|
202
|
+
| `UniversityService` | Courses, lessons, enrollments |
|
|
203
|
+
|
|
204
|
+
## Authentication Examples
|
|
205
|
+
|
|
206
|
+
### Sign In
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
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
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import { Component, inject } from '@angular/core';
|
|
342
|
+
import { AuthenticationService } from '@23blocks/angular';
|
|
343
|
+
|
|
344
|
+
@Component({ ... })
|
|
345
|
+
export class AuthComponent {
|
|
346
|
+
private auth = inject(AuthenticationService);
|
|
347
|
+
|
|
348
|
+
// Sign in
|
|
349
|
+
signIn() {
|
|
350
|
+
this.auth.signIn({ email, password }).subscribe({
|
|
351
|
+
next: ({ user, accessToken }) => console.log('Welcome', user.email),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Sign up
|
|
356
|
+
signUp() {
|
|
357
|
+
this.auth.signUp({
|
|
358
|
+
email: 'new@example.com',
|
|
359
|
+
password: 'password',
|
|
360
|
+
passwordConfirmation: 'password',
|
|
361
|
+
}).subscribe();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Sign out
|
|
365
|
+
signOut() {
|
|
366
|
+
this.auth.signOut().subscribe();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Check if authenticated
|
|
370
|
+
isAuthenticated(): boolean | null {
|
|
371
|
+
return this.auth.isAuthenticated();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Get current user
|
|
375
|
+
getCurrentUser() {
|
|
376
|
+
return this.auth.getCurrentUser();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## SearchService Example
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import { Component, inject } from '@angular/core';
|
|
385
|
+
import { SearchService } from '@23blocks/angular';
|
|
386
|
+
import { Subject, debounceTime, switchMap } from 'rxjs';
|
|
387
|
+
|
|
388
|
+
@Component({
|
|
389
|
+
selector: 'app-search',
|
|
390
|
+
template: `
|
|
391
|
+
<input (input)="onSearch($event)" placeholder="Search..." />
|
|
392
|
+
<ul>
|
|
393
|
+
<li *ngFor="let result of results">{{ result.title }}</li>
|
|
394
|
+
</ul>
|
|
395
|
+
`,
|
|
396
|
+
})
|
|
397
|
+
export class SearchComponent {
|
|
398
|
+
private search = inject(SearchService);
|
|
399
|
+
private searchSubject = new Subject<string>();
|
|
400
|
+
results: any[] = [];
|
|
401
|
+
|
|
402
|
+
constructor() {
|
|
403
|
+
this.searchSubject.pipe(
|
|
404
|
+
debounceTime(300),
|
|
405
|
+
switchMap((query) => this.search.search({ query, limit: 10 }))
|
|
406
|
+
).subscribe({
|
|
407
|
+
next: (response) => this.results = response.results,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
onSearch(event: Event) {
|
|
412
|
+
const query = (event.target as HTMLInputElement).value;
|
|
413
|
+
this.searchSubject.next(query);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## ProductsService Example
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { Component, inject, OnInit } from '@angular/core';
|
|
422
|
+
import { ProductsService } from '@23blocks/angular';
|
|
423
|
+
|
|
424
|
+
@Component({ ... })
|
|
425
|
+
export class ProductsComponent implements OnInit {
|
|
426
|
+
private products = inject(ProductsService);
|
|
427
|
+
productList$ = this.products.list({ limit: 20 });
|
|
428
|
+
|
|
429
|
+
addToCart(productId: string) {
|
|
430
|
+
this.products.addToCart({ productId, quantity: 1 }).subscribe();
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## ContentService Example
|
|
436
|
+
|
|
437
|
+
The ContentService provides access to posts, comments, categories, tags, and **series** (collections of posts).
|
|
438
|
+
|
|
439
|
+
### Basic Content Operations
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import { Component, inject } from '@angular/core';
|
|
443
|
+
import { ContentService } from '@23blocks/angular';
|
|
444
|
+
|
|
445
|
+
@Component({ ... })
|
|
446
|
+
export class BlogComponent {
|
|
447
|
+
private content = inject(ContentService);
|
|
448
|
+
|
|
449
|
+
// List posts with pagination
|
|
450
|
+
posts$ = this.content.listPosts({ page: 1, perPage: 10 });
|
|
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
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Create a new post
|
|
460
|
+
createPost() {
|
|
461
|
+
this.content.createPost({
|
|
462
|
+
title: 'My New Post',
|
|
463
|
+
body: 'Post content here...',
|
|
464
|
+
status: 'published',
|
|
465
|
+
}).subscribe();
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Series Operations
|
|
471
|
+
|
|
472
|
+
Series allow you to group posts into ordered collections (e.g., tutorials, courses, article series).
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
import { Component, inject } from '@angular/core';
|
|
476
|
+
import { ContentService } from '@23blocks/angular';
|
|
477
|
+
|
|
478
|
+
@Component({ ... })
|
|
479
|
+
export class SeriesComponent {
|
|
480
|
+
private content = inject(ContentService);
|
|
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
|
+
}
|
|
496
|
+
|
|
497
|
+
// Get a single series
|
|
498
|
+
loadSeries(uniqueId: string) {
|
|
499
|
+
this.content.getSeries(uniqueId).subscribe({
|
|
500
|
+
next: (series) => console.log('Series:', series.title),
|
|
501
|
+
});
|
|
502
|
+
}
|
|
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
|
+
}
|
|
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
|
+
## RxJS Patterns
|
|
613
|
+
|
|
614
|
+
### Combining Multiple Services
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
import { forkJoin } from 'rxjs';
|
|
618
|
+
|
|
619
|
+
// Fetch user and favorites in parallel
|
|
620
|
+
forkJoin({
|
|
621
|
+
user: this.auth.getCurrentUser(),
|
|
622
|
+
favorites: this.search.listFavorites({ limit: 10 }),
|
|
623
|
+
}).subscribe({
|
|
624
|
+
next: ({ user, favorites }) => {
|
|
625
|
+
console.log(user, favorites);
|
|
626
|
+
},
|
|
627
|
+
});
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Caching with shareReplay
|
|
631
|
+
|
|
632
|
+
```typescript
|
|
633
|
+
import { shareReplay } from 'rxjs';
|
|
634
|
+
|
|
635
|
+
// Cache the current user
|
|
636
|
+
currentUser$ = this.auth.getCurrentUser().pipe(
|
|
637
|
+
shareReplay(1)
|
|
638
|
+
);
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Error Handling
|
|
642
|
+
|
|
643
|
+
Every error includes a unique request ID for easy debugging and support:
|
|
644
|
+
|
|
645
|
+
```typescript
|
|
646
|
+
import { isBlockErrorException, ErrorCodes } from '@23blocks/contracts';
|
|
647
|
+
|
|
648
|
+
this.auth.signIn({ email, password }).subscribe({
|
|
649
|
+
error: (err) => {
|
|
650
|
+
if (isBlockErrorException(err)) {
|
|
651
|
+
// Request tracing for debugging
|
|
652
|
+
console.log('Request ID:', err.requestId); // "req_m5abc_xyz123"
|
|
653
|
+
console.log('Duration:', err.duration); // 145 (ms)
|
|
654
|
+
|
|
655
|
+
switch (err.code) {
|
|
656
|
+
case ErrorCodes.INVALID_CREDENTIALS:
|
|
657
|
+
this.error = 'Invalid email or password';
|
|
658
|
+
break;
|
|
659
|
+
case ErrorCodes.UNAUTHORIZED:
|
|
660
|
+
this.error = 'Session expired';
|
|
661
|
+
break;
|
|
662
|
+
case ErrorCodes.VALIDATION_ERROR:
|
|
663
|
+
this.error = err.message;
|
|
664
|
+
break;
|
|
665
|
+
default:
|
|
666
|
+
this.error = err.message;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Send request ID to support for debugging
|
|
670
|
+
// "Please check request req_m5abc_xyz123"
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
});
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
## Advanced Setup (Custom Transport)
|
|
677
|
+
|
|
678
|
+
For advanced use cases requiring custom transport configuration:
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
import { ApplicationConfig } from '@angular/core';
|
|
682
|
+
import { provide23Blocks } from '@23blocks/angular';
|
|
683
|
+
import { createHttpTransport } from '@23blocks/transport-http';
|
|
684
|
+
|
|
685
|
+
const transport = createHttpTransport({
|
|
686
|
+
baseUrl: 'https://auth.yourapp.com',
|
|
687
|
+
headers: () => {
|
|
688
|
+
const token = localStorage.getItem('access_token');
|
|
689
|
+
return {
|
|
690
|
+
'x-api-key': 'your-api-key',
|
|
691
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
692
|
+
};
|
|
693
|
+
},
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
export const appConfig: ApplicationConfig = {
|
|
697
|
+
providers: [
|
|
698
|
+
provide23Blocks({
|
|
699
|
+
transport,
|
|
700
|
+
authentication: { apiKey: 'your-api-key' },
|
|
701
|
+
search: { apiKey: 'your-api-key' },
|
|
702
|
+
products: { apiKey: 'your-api-key' },
|
|
703
|
+
}),
|
|
704
|
+
],
|
|
705
|
+
};
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
## Testing
|
|
709
|
+
|
|
710
|
+
Mock services in your tests:
|
|
711
|
+
|
|
712
|
+
```typescript
|
|
713
|
+
import { TestBed } from '@angular/core/testing';
|
|
714
|
+
import { AuthenticationService } from '@23blocks/angular';
|
|
715
|
+
import { of } from 'rxjs';
|
|
716
|
+
|
|
717
|
+
describe('LoginComponent', () => {
|
|
718
|
+
const mockAuth = {
|
|
719
|
+
signIn: jest.fn().mockReturnValue(of({
|
|
720
|
+
user: { email: 'test@test.com' },
|
|
721
|
+
accessToken: 'token',
|
|
722
|
+
})),
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
beforeEach(() => {
|
|
726
|
+
TestBed.configureTestingModule({
|
|
727
|
+
providers: [
|
|
728
|
+
{ provide: AuthenticationService, useValue: mockAuth },
|
|
729
|
+
],
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
## Environment Variables
|
|
736
|
+
|
|
737
|
+
```typescript
|
|
738
|
+
// environment.ts
|
|
739
|
+
export const environment = {
|
|
740
|
+
production: false,
|
|
741
|
+
apiKey: 'your-api-key',
|
|
742
|
+
urls: {
|
|
743
|
+
authentication: 'https://auth.yourapp.com',
|
|
744
|
+
products: 'https://products.yourapp.com',
|
|
745
|
+
},
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
// app.config.ts
|
|
749
|
+
import { environment } from './environments/environment';
|
|
750
|
+
|
|
751
|
+
export const appConfig: ApplicationConfig = {
|
|
752
|
+
providers: [
|
|
753
|
+
provideBlocks23({
|
|
754
|
+
apiKey: environment.apiKey,
|
|
755
|
+
urls: environment.urls,
|
|
756
|
+
}),
|
|
757
|
+
],
|
|
758
|
+
};
|
|
759
|
+
```
|
|
760
|
+
|
|
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
|
+
## Related Packages
|
|
786
|
+
|
|
787
|
+
- [`@23blocks/sdk`](https://www.npmjs.com/package/@23blocks/sdk) - Full SDK package
|
|
788
|
+
- [`@23blocks/react`](https://www.npmjs.com/package/@23blocks/react) - React integration
|
|
789
|
+
|
|
790
|
+
## License
|
|
791
|
+
|
|
792
|
+
MIT - Copyright (c) 2024 23blocks
|