@flusys/ng-core 1.1.0-beta → 1.1.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 +101 -32
- package/fesm2022/flusys-ng-core.mjs +139 -45
- package/fesm2022/flusys-ng-core.mjs.map +1 -1
- package/package.json +6 -6
- package/types/flusys-ng-core.d.ts +84 -19
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
## Package Information
|
|
10
10
|
|
|
11
11
|
- **Package:** `@flusys/ng-core`
|
|
12
|
+
- **Version:** 1.1.0
|
|
12
13
|
- **Dependencies:** None (base package)
|
|
13
14
|
- **Dependents:** ng-shared, ng-layout, ng-auth, ng-iam, ng-storage, flusysng
|
|
14
15
|
- **Build Command:** `npm run build:ng-core`
|
|
@@ -18,37 +19,37 @@
|
|
|
18
19
|
```
|
|
19
20
|
ng-core/
|
|
20
21
|
├── components/
|
|
21
|
-
│
|
|
22
|
-
│
|
|
23
|
-
│
|
|
24
|
-
│
|
|
22
|
+
│ ├── lib-app-config/
|
|
23
|
+
│ │ ├── lib-app-config.component.ts
|
|
24
|
+
│ │ ├── lib-app-config.component.html
|
|
25
|
+
│ │ └── lib-app-config.component.scss
|
|
26
|
+
│ └── public-api.ts
|
|
25
27
|
├── interceptors/
|
|
26
28
|
│ ├── api-loader.interceptor.ts
|
|
27
|
-
│
|
|
29
|
+
│ ├── error-catching.interceptor.ts
|
|
30
|
+
│ └── public-api.ts
|
|
28
31
|
├── interfaces/
|
|
29
|
-
│
|
|
32
|
+
│ ├── app-config.interface.ts
|
|
33
|
+
│ └── public-api.ts
|
|
30
34
|
├── services/
|
|
31
35
|
│ ├── api-loader.service.ts
|
|
32
|
-
│
|
|
36
|
+
│ ├── base-api.service.ts
|
|
37
|
+
│ └── public-api.ts
|
|
38
|
+
├── ng-package.json
|
|
39
|
+
├── package.json
|
|
40
|
+
├── tsconfig.lib.json
|
|
41
|
+
├── tsconfig.lib.prod.json
|
|
33
42
|
└── public-api.ts
|
|
34
43
|
```
|
|
35
44
|
|
|
36
45
|
## Public API Exports
|
|
37
46
|
|
|
38
47
|
```typescript
|
|
39
|
-
//
|
|
40
|
-
export * from './components/
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export * from './
|
|
44
|
-
export * from './interceptors/error-catching.interceptor';
|
|
45
|
-
|
|
46
|
-
// Interfaces
|
|
47
|
-
export * from './interfaces/app-config.interface';
|
|
48
|
-
|
|
49
|
-
// Services
|
|
50
|
-
export * from './services/api-loader.service';
|
|
51
|
-
export * from './services/base-api.service';
|
|
48
|
+
// public-api.ts - re-exports from each module's public-api
|
|
49
|
+
export * from './components/public-api';
|
|
50
|
+
export * from './interceptors/public-api';
|
|
51
|
+
export * from './interfaces/public-api';
|
|
52
|
+
export * from './services/public-api';
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
---
|
|
@@ -74,17 +75,26 @@ export interface IAuthorConfig {
|
|
|
74
75
|
url: string;
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
export interface IRegistrationDefaults {
|
|
79
|
+
companySlug?: string; // Pre-fill company code on registration
|
|
80
|
+
branchSlug?: string; // Pre-fill branch code on registration
|
|
81
|
+
}
|
|
82
|
+
|
|
77
83
|
export interface IAppConfig {
|
|
78
84
|
appName: string;
|
|
79
85
|
author?: IAuthorConfig; // Author/Brand info for footer attribution
|
|
80
86
|
apiBaseUrl: string; // Default fallback URL
|
|
81
87
|
enableCompanyFeature: boolean; // Company/branch selection
|
|
88
|
+
enableSignUp?: boolean; // Enable registration (default: true)
|
|
89
|
+
registrationDefaults?: IRegistrationDefaults; // Pre-fill registration fields
|
|
82
90
|
|
|
83
91
|
services: {
|
|
84
92
|
auth: IServiceConfig;
|
|
93
|
+
administration: IServiceConfig;
|
|
85
94
|
iam: IServiceConfig;
|
|
86
|
-
storage: IServiceConfig
|
|
87
|
-
|
|
95
|
+
storage: IStorageServiceConfig; // Extends IServiceConfig with defaultStorageConfigId
|
|
96
|
+
formBuilder: IServiceConfig;
|
|
97
|
+
email: IServiceConfig;
|
|
88
98
|
};
|
|
89
99
|
|
|
90
100
|
multiTenant: {
|
|
@@ -137,8 +147,11 @@ export const environment = {
|
|
|
137
147
|
apiBaseUrl: 'http://localhost:2002',
|
|
138
148
|
services: {
|
|
139
149
|
auth: { baseUrl: 'http://localhost:2002/auth', enabled: true },
|
|
150
|
+
administration: { baseUrl: 'http://localhost:2002/administration', enabled: true },
|
|
140
151
|
iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
|
|
141
|
-
storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
|
|
152
|
+
storage: { baseUrl: 'http://localhost:2002/storage', enabled: true, defaultStorageConfigId: '...' },
|
|
153
|
+
formBuilder: { baseUrl: 'http://localhost:2002/form-builder', enabled: true },
|
|
154
|
+
email: { baseUrl: 'http://localhost:2002/email', enabled: true },
|
|
142
155
|
},
|
|
143
156
|
};
|
|
144
157
|
```
|
|
@@ -153,19 +166,35 @@ import {
|
|
|
153
166
|
isServiceEnabled,
|
|
154
167
|
isCompanyFeatureEnabled,
|
|
155
168
|
isStorageFeatureEnabled,
|
|
169
|
+
isFormBuilderFeatureEnabled,
|
|
170
|
+
isEmailFeatureEnabled,
|
|
156
171
|
getPermissionMode,
|
|
172
|
+
getDefaultStorageConfigId,
|
|
173
|
+
isSignUpEnabled,
|
|
174
|
+
getDefaultCompanySlug,
|
|
175
|
+
getDefaultBranchSlug,
|
|
176
|
+
getRegistrationDefaults,
|
|
157
177
|
} from '@flusys/ng-core';
|
|
158
178
|
```
|
|
159
179
|
|
|
160
180
|
| Function | Signature | Description |
|
|
161
181
|
|----------|-----------|-------------|
|
|
162
182
|
| `getDatabaseMode` | `(config) → 'single' \| 'multi-tenant'` | Get database mode from `multiTenant.enabled` |
|
|
163
|
-
| `isFeatureEnabled` | `(config, feature:
|
|
164
|
-
| `getServiceUrl` | `(config, serviceName) → string` | Get service URL or fallback to `apiBaseUrl` |
|
|
165
|
-
| `isServiceEnabled` | `(config, serviceName) → boolean` | Check if a service is enabled |
|
|
183
|
+
| `isFeatureEnabled` | `(config, feature: ServiceName) → boolean` | Check if a service feature is enabled |
|
|
184
|
+
| `getServiceUrl` | `(config, serviceName: ServiceName) → string` | Get service URL or fallback to `apiBaseUrl` |
|
|
185
|
+
| `isServiceEnabled` | `(config, serviceName: ServiceName) → boolean` | Check if a service is enabled |
|
|
166
186
|
| `isCompanyFeatureEnabled` | `(config) → boolean` | Check `enableCompanyFeature` flag |
|
|
167
187
|
| `isStorageFeatureEnabled` | `(config) → boolean` | Check `services.storage.enabled` |
|
|
188
|
+
| `isFormBuilderFeatureEnabled` | `(config) → boolean` | Check `services.formBuilder.enabled` |
|
|
189
|
+
| `isEmailFeatureEnabled` | `(config) → boolean` | Check `services.email.enabled` |
|
|
168
190
|
| `getPermissionMode` | `(config) → PermissionMode` | Get permission mode (defaults to `'full'`) |
|
|
191
|
+
| `getDefaultStorageConfigId` | `(config) → string \| undefined` | Get `services.storage.defaultStorageConfigId` |
|
|
192
|
+
| `isSignUpEnabled` | `(config) → boolean` | Check `enableSignUp` flag (defaults to `true`) |
|
|
193
|
+
| `getDefaultCompanySlug` | `(config) → string \| undefined` | Get `registrationDefaults.companySlug` |
|
|
194
|
+
| `getDefaultBranchSlug` | `(config) → string \| undefined` | Get `registrationDefaults.branchSlug` |
|
|
195
|
+
| `getRegistrationDefaults` | `(config) → IRegistrationDefaults \| undefined` | Get full registration defaults object |
|
|
196
|
+
|
|
197
|
+
**ServiceName Type:** `'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email'`
|
|
169
198
|
|
|
170
199
|
#### Constants
|
|
171
200
|
|
|
@@ -186,8 +215,11 @@ export const DEFAULT_AUTHOR: IAuthorConfig = {
|
|
|
186
215
|
multiTenant: { enabled: false, tenantHeader: 'x-tenant-id' },
|
|
187
216
|
services: {
|
|
188
217
|
auth: { baseUrl: '...', enabled: false },
|
|
218
|
+
administration: { baseUrl: '...', enabled: false },
|
|
189
219
|
iam: { baseUrl: '...', enabled: false },
|
|
190
220
|
storage: { baseUrl: '...', enabled: false },
|
|
221
|
+
formBuilder: { baseUrl: '...', enabled: false },
|
|
222
|
+
email: { baseUrl: '...', enabled: false },
|
|
191
223
|
}
|
|
192
224
|
}
|
|
193
225
|
```
|
|
@@ -199,8 +231,11 @@ export const DEFAULT_AUTHOR: IAuthorConfig = {
|
|
|
199
231
|
multiTenant: { enabled: false, tenantHeader: 'x-tenant-id' },
|
|
200
232
|
services: {
|
|
201
233
|
auth: { baseUrl: 'http://localhost:2002/auth', enabled: true },
|
|
234
|
+
administration: { baseUrl: 'http://localhost:2002/administration', enabled: true },
|
|
202
235
|
iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
|
|
203
|
-
storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
|
|
236
|
+
storage: { baseUrl: 'http://localhost:2002/storage', enabled: true, defaultStorageConfigId: '...' },
|
|
237
|
+
formBuilder: { baseUrl: 'http://localhost:2002/form-builder', enabled: true },
|
|
238
|
+
email: { baseUrl: 'http://localhost:2002/email', enabled: true },
|
|
204
239
|
}
|
|
205
240
|
}
|
|
206
241
|
```
|
|
@@ -212,8 +247,11 @@ export const DEFAULT_AUTHOR: IAuthorConfig = {
|
|
|
212
247
|
multiTenant: { enabled: true, tenantHeader: 'x-tenant-id', tenantId: 'tenant-123' },
|
|
213
248
|
services: {
|
|
214
249
|
auth: { baseUrl: 'http://localhost:2002/auth', enabled: true },
|
|
250
|
+
administration: { baseUrl: 'http://localhost:2002/administration', enabled: true },
|
|
215
251
|
iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
|
|
216
|
-
storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
|
|
252
|
+
storage: { baseUrl: 'http://localhost:2002/storage', enabled: true, defaultStorageConfigId: '...' },
|
|
253
|
+
formBuilder: { baseUrl: 'http://localhost:2002/form-builder', enabled: true },
|
|
254
|
+
email: { baseUrl: 'http://localhost:2002/email', enabled: true },
|
|
217
255
|
}
|
|
218
256
|
}
|
|
219
257
|
```
|
|
@@ -252,10 +290,27 @@ Displays user-friendly error toast messages for failed HTTP requests via PrimeNG
|
|
|
252
290
|
|
|
253
291
|
**Behavior:**
|
|
254
292
|
- Skips 401 errors (handled by `tokenRefreshInterceptor` in ng-auth)
|
|
293
|
+
- Skips 403 errors (handled by permission denied UI in components)
|
|
255
294
|
- Skips `/check-login` endpoint errors (expected to fail when not logged in)
|
|
256
|
-
-
|
|
295
|
+
- Skips requests with `SKIP_ERROR_TOAST` context token set to `true`
|
|
296
|
+
- Uses backend's error title (`error.error.error`) as toast summary
|
|
297
|
+
- Uses backend's error message (`error.error.message`) as toast detail
|
|
298
|
+
- Falls back to status-based defaults for unknown error formats
|
|
257
299
|
- SSR-safe: only displays messages in browser (`isPlatformBrowser` check)
|
|
258
300
|
|
|
301
|
+
**SKIP_ERROR_TOAST Context Token:**
|
|
302
|
+
|
|
303
|
+
Use this to skip global error toast for specific requests where the component handles errors locally:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { HttpContext } from '@angular/common/http';
|
|
307
|
+
import { SKIP_ERROR_TOAST } from '@flusys/ng-core';
|
|
308
|
+
|
|
309
|
+
this.http.post(url, data, {
|
|
310
|
+
context: new HttpContext().set(SKIP_ERROR_TOAST, true)
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
259
314
|
#### Interceptor Chain
|
|
260
315
|
|
|
261
316
|
```typescript
|
|
@@ -364,6 +419,8 @@ Root configuration component providing global UI elements. Include once at app r
|
|
|
364
419
|
|
|
365
420
|
**Selector:** `lib-app-config`
|
|
366
421
|
|
|
422
|
+
**Change Detection:** `OnPush` (optimal performance)
|
|
423
|
+
|
|
367
424
|
**Provides:**
|
|
368
425
|
- Global loading progress bar (fixed top, 4px height, z-index 998)
|
|
369
426
|
- Toast notifications (PrimeNG Toast)
|
|
@@ -507,7 +564,8 @@ readonly usersLoading = this.apiLoaderService.getLoader('users-list');
|
|
|
507
564
|
| Function | Description |
|
|
508
565
|
|----------|-------------|
|
|
509
566
|
| `apiLoaderInterceptor` | Loading state management with tagged loaders |
|
|
510
|
-
| `errorCatchingInterceptor` | Error toast display (skips 401
|
|
567
|
+
| `errorCatchingInterceptor` | Error toast display (skips 401, 403, check-login, and SKIP_ERROR_TOAST) |
|
|
568
|
+
| `SKIP_ERROR_TOAST` | HttpContextToken to skip global error toast for specific requests |
|
|
511
569
|
|
|
512
570
|
### Services
|
|
513
571
|
|
|
@@ -522,7 +580,9 @@ readonly usersLoading = this.apiLoaderService.getLoader('users-list');
|
|
|
522
580
|
|--------|-------------|
|
|
523
581
|
| `IAppConfig` | App configuration interface |
|
|
524
582
|
| `IServiceConfig` | Service configuration (`baseUrl`, `enabled`) |
|
|
583
|
+
| `IStorageServiceConfig` | Storage service config (extends IServiceConfig with `defaultStorageConfigId`) |
|
|
525
584
|
| `IAuthorConfig` | Author/brand configuration (`name`, `url`) |
|
|
585
|
+
| `IRegistrationDefaults` | Registration defaults (`companySlug`, `branchSlug`) |
|
|
526
586
|
| `PermissionMode` | `'rbac' \| 'direct' \| 'full'` |
|
|
527
587
|
|
|
528
588
|
### Helper Functions
|
|
@@ -530,12 +590,20 @@ readonly usersLoading = this.apiLoaderService.getLoader('users-list');
|
|
|
530
590
|
| Function | Description |
|
|
531
591
|
|----------|-------------|
|
|
532
592
|
| `getDatabaseMode(config)` | Returns `'single'` or `'multi-tenant'` |
|
|
533
|
-
| `isFeatureEnabled(config, feature)` | Check service feature enabled (
|
|
593
|
+
| `isFeatureEnabled(config, feature)` | Check service feature enabled (ServiceName) |
|
|
534
594
|
| `getServiceUrl(config, serviceName)` | Get service URL with fallback |
|
|
535
595
|
| `isServiceEnabled(config, serviceName)` | Check if service is enabled |
|
|
536
596
|
| `isCompanyFeatureEnabled(config)` | Check company feature flag |
|
|
537
597
|
| `isStorageFeatureEnabled(config)` | Check storage service enabled |
|
|
598
|
+
| `isFormBuilderFeatureEnabled(config)` | Check formBuilder service enabled |
|
|
599
|
+
| `isEmailFeatureEnabled(config)` | Check email service enabled |
|
|
538
600
|
| `getPermissionMode(config)` | Get permission mode (default `'full'`) |
|
|
601
|
+
| `getDefaultStorageConfigId(config)` | Get default storage config ID for uploads |
|
|
602
|
+
| `isSignUpEnabled(config)` | Check sign-up enabled (default `true`) |
|
|
603
|
+
| `getDefaultCompanySlug(config)` | Get default company slug for registration |
|
|
604
|
+
| `getDefaultBranchSlug(config)` | Get default branch slug for registration |
|
|
605
|
+
|
|
606
|
+
**ServiceName:** `'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email'`
|
|
539
607
|
|
|
540
608
|
### Constants
|
|
541
609
|
|
|
@@ -559,5 +627,6 @@ readonly usersLoading = this.apiLoaderService.getLoader('users-list');
|
|
|
559
627
|
|
|
560
628
|
---
|
|
561
629
|
|
|
562
|
-
**Last Updated:** 2026-02-
|
|
630
|
+
**Last Updated:** 2026-02-23
|
|
631
|
+
**Version:** 1.1.0
|
|
563
632
|
**Angular Version:** 21
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, computed, Injectable, inject, Component, PLATFORM_ID, InjectionToken } from '@angular/core';
|
|
2
|
+
import { signal, computed, Injectable, inject, ChangeDetectionStrategy, Component, PLATFORM_ID, InjectionToken } from '@angular/core';
|
|
3
3
|
import * as i2 from 'primeng/toast';
|
|
4
4
|
import { ToastModule } from 'primeng/toast';
|
|
5
5
|
import { ConfirmDialog } from 'primeng/confirmdialog';
|
|
6
6
|
import * as i1 from 'primeng/progressbar';
|
|
7
7
|
import { ProgressBarModule } from 'primeng/progressbar';
|
|
8
8
|
import { finalize } from 'rxjs/operators';
|
|
9
|
+
import { HttpContextToken, HttpClient } from '@angular/common/http';
|
|
9
10
|
import { isPlatformBrowser } from '@angular/common';
|
|
10
11
|
import { MessageService } from 'primeng/api';
|
|
11
12
|
import { catchError, throwError } from 'rxjs';
|
|
12
|
-
import { HttpClient } from '@angular/common/http';
|
|
13
13
|
|
|
14
14
|
class ApiLoaderService {
|
|
15
15
|
loaders = signal({}, ...(ngDevMode ? [{ debugName: "loaders" }] : []));
|
|
@@ -35,10 +35,10 @@ class ApiLoaderService {
|
|
|
35
35
|
return { ...loaders, [name]: Math.max(0, current - 1) };
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
39
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.
|
|
38
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ApiLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
39
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ApiLoaderService, providedIn: 'root' });
|
|
40
40
|
}
|
|
41
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
41
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: ApiLoaderService, decorators: [{
|
|
42
42
|
type: Injectable,
|
|
43
43
|
args: [{ providedIn: 'root' }]
|
|
44
44
|
}] });
|
|
@@ -49,12 +49,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
|
|
|
49
49
|
*/
|
|
50
50
|
class LibAppConfigComponent {
|
|
51
51
|
apiLoaderService = inject(ApiLoaderService);
|
|
52
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.
|
|
53
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.
|
|
52
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LibAppConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
53
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: LibAppConfigComponent, isStandalone: true, selector: "lib-app-config", ngImport: i0, template: "@if (apiLoaderService.show()) {\n<p-progressBar mode=\"indeterminate\" />\n}\n<p-toast />\n<p-confirmdialog />\n", styles: [":host ::ng-deep .p-progressbar{position:fixed;height:4px;z-index:998;left:0;right:0}:host ::ng-deep .p-toast-message-success{background:color-mix(in srgb,var(--p-primary-color),transparent 84%)!important;border-color:var(--p-primary-color)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon svg{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon path{fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button .p-toast-close-icon{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:hover{background:color-mix(in srgb,var(--p-primary-color),transparent 70%)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:focus{box-shadow:0 0 0 .2rem color-mix(in srgb,var(--p-primary-color),transparent 50%)!important}:host ::ng-deep .p-toast-message-success .p-toast-detail{color:var(--p-text-color)!important}\n"], dependencies: [{ kind: "component", type: ConfirmDialog, selector: "p-confirmDialog, p-confirmdialog, p-confirm-dialog", inputs: ["header", "icon", "message", "style", "styleClass", "maskStyleClass", "acceptIcon", "acceptLabel", "closeAriaLabel", "acceptAriaLabel", "acceptVisible", "rejectIcon", "rejectLabel", "rejectAriaLabel", "rejectVisible", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "dismissableMask", "blockScroll", "rtl", "closable", "appendTo", "key", "autoZIndex", "baseZIndex", "transitionOptions", "focusTrap", "defaultFocus", "breakpoints", "modal", "visible", "position", "draggable"], outputs: ["onHide"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i1.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "unit", "mode", "color"] }, { kind: "ngmodule", type: ToastModule }, { kind: "component", type: i2.Toast, selector: "p-toast", inputs: ["key", "autoZIndex", "baseZIndex", "life", "styleClass", "position", "preventOpenDuplicates", "preventDuplicates", "showTransformOptions", "hideTransformOptions", "showTransitionOptions", "hideTransitionOptions", "motionOptions", "breakpoints"], outputs: ["onClose"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
54
54
|
}
|
|
55
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.
|
|
55
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: LibAppConfigComponent, decorators: [{
|
|
56
56
|
type: Component,
|
|
57
|
-
args: [{ selector: 'lib-app-config',
|
|
57
|
+
args: [{ selector: 'lib-app-config', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ConfirmDialog, ProgressBarModule, ToastModule], template: "@if (apiLoaderService.show()) {\n<p-progressBar mode=\"indeterminate\" />\n}\n<p-toast />\n<p-confirmdialog />\n", styles: [":host ::ng-deep .p-progressbar{position:fixed;height:4px;z-index:998;left:0;right:0}:host ::ng-deep .p-toast-message-success{background:color-mix(in srgb,var(--p-primary-color),transparent 84%)!important;border-color:var(--p-primary-color)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon svg{color:var(--p-primary-color)!important;fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-message-icon path{fill:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button .p-toast-close-icon{color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:hover{background:color-mix(in srgb,var(--p-primary-color),transparent 70%)!important;color:var(--p-primary-color)!important}:host ::ng-deep .p-toast-message-success .p-toast-close-button:focus{box-shadow:0 0 0 .2rem color-mix(in srgb,var(--p-primary-color),transparent 50%)!important}:host ::ng-deep .p-toast-message-success .p-toast-detail{color:var(--p-text-color)!important}\n"] }]
|
|
58
58
|
}] });
|
|
59
59
|
|
|
60
60
|
/**
|
|
@@ -79,9 +79,27 @@ function apiLoaderInterceptor(req, next) {
|
|
|
79
79
|
return next(cleanReq).pipe(finalize(() => loaderService.decrease(loaderTag)));
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* HTTP context token to skip global error toast.
|
|
84
|
+
* Use when component handles error display locally.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* this.http.post(url, data, {
|
|
89
|
+
* context: new HttpContext().set(SKIP_ERROR_TOAST, true)
|
|
90
|
+
* });
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
const SKIP_ERROR_TOAST = new HttpContextToken(() => false);
|
|
82
94
|
/**
|
|
83
95
|
* Error Catching Interceptor
|
|
84
|
-
* Displays error messages for failed HTTP requests.
|
|
96
|
+
* Displays error messages for failed HTTP requests using backend response.
|
|
97
|
+
*
|
|
98
|
+
* Features:
|
|
99
|
+
* - Uses backend's error title (error.error.error) as summary
|
|
100
|
+
* - Uses backend's error message (error.error.message) as detail
|
|
101
|
+
* - Falls back to sensible defaults based on status code
|
|
102
|
+
* - Skip with SKIP_ERROR_TOAST context token for local handling
|
|
85
103
|
*
|
|
86
104
|
* NOTE: This interceptor only handles error display.
|
|
87
105
|
* For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.
|
|
@@ -106,39 +124,82 @@ function errorCatchingInterceptor(req, next) {
|
|
|
106
124
|
const isBrowser = isPlatformBrowser(platformId);
|
|
107
125
|
// Skip error display for check-login (expected to fail when not logged in)
|
|
108
126
|
const isCheckLogin = req.url.includes('/check-login');
|
|
127
|
+
// Skip if component handles error locally
|
|
128
|
+
const skipErrorToast = req.context.get(SKIP_ERROR_TOAST);
|
|
109
129
|
return next(req).pipe(catchError((error) => {
|
|
110
130
|
// Skip 401 errors - handled by tokenRefreshInterceptor in ng-auth
|
|
111
131
|
// Skip 403 errors - handled by permission denied UI in components
|
|
112
132
|
// Skip check-login errors - expected behavior
|
|
113
|
-
|
|
133
|
+
// Skip if component handles error locally
|
|
134
|
+
if (error.status === 401 || error.status === 403 || isCheckLogin || skipErrorToast) {
|
|
114
135
|
return throwError(() => error);
|
|
115
136
|
}
|
|
116
137
|
// Display error message for other errors
|
|
117
138
|
if (isBrowser) {
|
|
118
|
-
const
|
|
139
|
+
const { summary, detail } = extractErrorInfo(error);
|
|
119
140
|
messageService.add({
|
|
120
141
|
severity: 'error',
|
|
121
|
-
summary
|
|
122
|
-
detail
|
|
142
|
+
summary,
|
|
143
|
+
detail,
|
|
123
144
|
});
|
|
124
145
|
}
|
|
125
146
|
return throwError(() => error);
|
|
126
147
|
}));
|
|
127
148
|
}
|
|
128
149
|
/**
|
|
129
|
-
* Extract error
|
|
150
|
+
* Extract error summary and detail from HTTP error response.
|
|
151
|
+
* Uses backend response format: { message, error, statusCode }
|
|
130
152
|
*/
|
|
131
|
-
function
|
|
153
|
+
function extractErrorInfo(error) {
|
|
154
|
+
// Extract message from backend response
|
|
155
|
+
let detail;
|
|
132
156
|
if (typeof error.error?.message === 'string') {
|
|
133
|
-
|
|
157
|
+
detail = error.error.message;
|
|
158
|
+
}
|
|
159
|
+
else if (Array.isArray(error.error?.message)) {
|
|
160
|
+
// NestJS validation errors come as array
|
|
161
|
+
detail = error.error.message[0];
|
|
162
|
+
}
|
|
163
|
+
else if (error.message) {
|
|
164
|
+
detail = error.message;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
detail = 'An unexpected error occurred.';
|
|
168
|
+
}
|
|
169
|
+
// Extract summary/title from backend response or use default based on status
|
|
170
|
+
let summary;
|
|
171
|
+
if (typeof error.error?.error === 'string') {
|
|
172
|
+
// Backend sends error type (e.g., "Not Found", "Bad Request", "Validation Error")
|
|
173
|
+
summary = error.error.error;
|
|
134
174
|
}
|
|
135
|
-
|
|
136
|
-
|
|
175
|
+
else {
|
|
176
|
+
// Fallback to status-based title
|
|
177
|
+
summary = getDefaultSummary(error.status);
|
|
137
178
|
}
|
|
138
|
-
|
|
139
|
-
|
|
179
|
+
return { summary, detail };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get default error summary based on HTTP status code
|
|
183
|
+
*/
|
|
184
|
+
function getDefaultSummary(status) {
|
|
185
|
+
switch (status) {
|
|
186
|
+
case 400:
|
|
187
|
+
return 'Bad Request';
|
|
188
|
+
case 404:
|
|
189
|
+
return 'Not Found';
|
|
190
|
+
case 409:
|
|
191
|
+
return 'Conflict';
|
|
192
|
+
case 422:
|
|
193
|
+
return 'Validation Error';
|
|
194
|
+
case 500:
|
|
195
|
+
return 'Server Error';
|
|
196
|
+
case 502:
|
|
197
|
+
case 503:
|
|
198
|
+
case 504:
|
|
199
|
+
return 'Service Unavailable';
|
|
200
|
+
default:
|
|
201
|
+
return 'Error';
|
|
140
202
|
}
|
|
141
|
-
return 'An unexpected error occurred.';
|
|
142
203
|
}
|
|
143
204
|
|
|
144
205
|
/**
|
|
@@ -154,45 +215,51 @@ function getDatabaseMode(config) {
|
|
|
154
215
|
return config.multiTenant.enabled ? 'multi-tenant' : 'single';
|
|
155
216
|
}
|
|
156
217
|
/**
|
|
157
|
-
* Check if a
|
|
158
|
-
* Features are enabled if their corresponding service exists and is enabled
|
|
218
|
+
* Check if a service is enabled
|
|
159
219
|
*/
|
|
160
|
-
function
|
|
161
|
-
const service = config.services[
|
|
220
|
+
function isServiceEnabled(config, serviceName) {
|
|
221
|
+
const service = config.services[serviceName];
|
|
162
222
|
return service?.enabled ?? false;
|
|
163
223
|
}
|
|
164
224
|
/**
|
|
165
|
-
*
|
|
166
|
-
|
|
225
|
+
* Check if a feature is enabled (alias for isServiceEnabled with strict types)
|
|
226
|
+
*/
|
|
227
|
+
function isFeatureEnabled(config, feature) {
|
|
228
|
+
return isServiceEnabled(config, feature);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get service base URL or fallback to default apiBaseUrl
|
|
167
232
|
*/
|
|
168
233
|
function getServiceUrl(config, serviceName) {
|
|
169
|
-
|
|
170
|
-
|
|
234
|
+
if (isServiceEnabled(config, serviceName)) {
|
|
235
|
+
const service = config.services[serviceName];
|
|
171
236
|
return service.baseUrl;
|
|
172
237
|
}
|
|
173
|
-
// Fallback to default API base URL
|
|
174
238
|
return config.apiBaseUrl;
|
|
175
239
|
}
|
|
176
240
|
/**
|
|
177
|
-
*
|
|
178
|
-
*/
|
|
179
|
-
function isServiceEnabled(config, serviceName) {
|
|
180
|
-
const service = config.services[serviceName];
|
|
181
|
-
return service?.enabled ?? false;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Check if company feature is enabled (backward compatible)
|
|
185
|
-
* Company feature is enabled if auth service is enabled
|
|
241
|
+
* Check if company feature is enabled
|
|
186
242
|
*/
|
|
187
243
|
function isCompanyFeatureEnabled(config) {
|
|
188
244
|
return config.enableCompanyFeature ?? false;
|
|
189
245
|
}
|
|
190
246
|
/**
|
|
191
|
-
* Check if storage feature is enabled
|
|
192
|
-
* Storage feature is enabled if storage service is enabled
|
|
247
|
+
* Check if storage feature is enabled
|
|
193
248
|
*/
|
|
194
249
|
function isStorageFeatureEnabled(config) {
|
|
195
|
-
return config
|
|
250
|
+
return isServiceEnabled(config, 'storage');
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Check if form-builder feature is enabled
|
|
254
|
+
*/
|
|
255
|
+
function isFormBuilderFeatureEnabled(config) {
|
|
256
|
+
return isServiceEnabled(config, 'formBuilder');
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Check if email feature is enabled
|
|
260
|
+
*/
|
|
261
|
+
function isEmailFeatureEnabled(config) {
|
|
262
|
+
return isServiceEnabled(config, 'email');
|
|
196
263
|
}
|
|
197
264
|
/**
|
|
198
265
|
* Get permission mode from config
|
|
@@ -208,6 +275,31 @@ function getPermissionMode(config) {
|
|
|
208
275
|
function getDefaultStorageConfigId(config) {
|
|
209
276
|
return config.services.storage?.defaultStorageConfigId;
|
|
210
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Check if sign-up/registration is enabled
|
|
280
|
+
* @default true
|
|
281
|
+
*/
|
|
282
|
+
function isSignUpEnabled(config) {
|
|
283
|
+
return config.enableSignUp ?? true;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get default company slug for registration
|
|
287
|
+
*/
|
|
288
|
+
function getDefaultCompanySlug(config) {
|
|
289
|
+
return config.registrationDefaults?.companySlug;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Get default branch slug for registration
|
|
293
|
+
*/
|
|
294
|
+
function getDefaultBranchSlug(config) {
|
|
295
|
+
return config.registrationDefaults?.branchSlug;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Get registration defaults
|
|
299
|
+
*/
|
|
300
|
+
function getRegistrationDefaults(config) {
|
|
301
|
+
return config.registrationDefaults;
|
|
302
|
+
}
|
|
211
303
|
// Default branding constants
|
|
212
304
|
const DEFAULT_APP_NAME = 'FLUSYS';
|
|
213
305
|
const DEFAULT_AUTHOR = {
|
|
@@ -219,6 +311,8 @@ const DEFAULT_AUTHOR = {
|
|
|
219
311
|
* Base API Service
|
|
220
312
|
* Provides service-specific base URL resolution
|
|
221
313
|
*
|
|
314
|
+
* Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.
|
|
315
|
+
*
|
|
222
316
|
* Usage:
|
|
223
317
|
* ```typescript
|
|
224
318
|
* @Injectable({ providedIn: 'root' })
|
|
@@ -228,7 +322,7 @@ const DEFAULT_AUTHOR = {
|
|
|
228
322
|
* }
|
|
229
323
|
*
|
|
230
324
|
* getData() {
|
|
231
|
-
* return this.http.
|
|
325
|
+
* return this.http.post(`${this.baseUrl}/data/get`, {});
|
|
232
326
|
* }
|
|
233
327
|
* }
|
|
234
328
|
* ```
|
|
@@ -261,5 +355,5 @@ class BaseApiService {
|
|
|
261
355
|
* Generated bundle index. Do not edit.
|
|
262
356
|
*/
|
|
263
357
|
|
|
264
|
-
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultStorageConfigId, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isFeatureEnabled, isServiceEnabled, isStorageFeatureEnabled };
|
|
358
|
+
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getPermissionMode, getRegistrationDefaults, getServiceUrl, isCompanyFeatureEnabled, isEmailFeatureEnabled, isFeatureEnabled, isFormBuilderFeatureEnabled, isServiceEnabled, isSignUpEnabled, isStorageFeatureEnabled };
|
|
265
359
|
//# sourceMappingURL=flusys-ng-core.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flusys-ng-core.mjs","sources":["../../../projects/ng-core/services/api-loader.service.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.html","../../../projects/ng-core/interceptors/api-loader.interceptor.ts","../../../projects/ng-core/interceptors/error-catching.interceptor.ts","../../../projects/ng-core/interfaces/app-config.interface.ts","../../../projects/ng-core/services/base-api.service.ts","../../../projects/ng-core/public-api.ts","../../../projects/ng-core/flusys-ng-core.ts"],"sourcesContent":["import { computed, Injectable, Signal, signal } from '@angular/core';\n\n@Injectable({ providedIn: 'root' })\nexport class ApiLoaderService {\n private readonly loaders = signal<Record<string, number>>({});\n readonly DEFAULT_TAG = 'default';\n\n // Global loading state\n readonly show: Signal<boolean> = computed(() => {\n const loaders = this.loaders();\n return Object.values(loaders).some((count) => count > 0);\n });\n\n // Get specific loader as a signal\n getLoader(name: string): Signal<boolean> {\n return computed(() => (this.loaders()[name] ?? 0) > 0);\n }\n\n increase(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => ({\n ...loaders,\n [name]: (loaders[name] ?? 0) + 1,\n }));\n }\n\n decrease(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => {\n const current = loaders[name] ?? 0;\n return { ...loaders, [name]: Math.max(0, current - 1) };\n });\n }\n}\n","import { Component, inject } from '@angular/core';\nimport { ToastModule } from 'primeng/toast';\nimport { ConfirmDialog } from 'primeng/confirmdialog';\nimport { ApiLoaderService } from '../../services/api-loader.service';\nimport { ProgressBarModule } from 'primeng/progressbar';\n\n/**\n * Root configuration component providing global UI elements.\n * Include once at app root for global toast, confirm dialog, and loader.\n */\n@Component({\n selector: 'lib-app-config',\n standalone: true,\n imports: [ConfirmDialog, ProgressBarModule, ToastModule],\n templateUrl: './lib-app-config.component.html',\n styleUrl: './lib-app-config.component.scss',\n})\nexport class LibAppConfigComponent {\n readonly apiLoaderService = inject(ApiLoaderService);\n}\n","@if (apiLoaderService.show()) {\n<p-progressBar mode=\"indeterminate\" />\n}\n<p-toast />\n<p-confirmdialog />\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { ApiLoaderService } from '../services/api-loader.service';\nimport { Observable } from 'rxjs';\nimport { finalize } from 'rxjs/operators';\n\n/**\n * Interceptor that tracks loading state for HTTP requests.\n * Uses 'x-loader-tag' header to group related requests.\n *\n * @example\n * ```typescript\n * http.get('/api/users', {\n * headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })\n * });\n * // In template: apiLoaderService.getLoader('users-list')\n * ```\n */\nexport function apiLoaderInterceptor(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn\n): Observable<HttpEvent<unknown>> {\n const loaderService = inject(ApiLoaderService);\n const loaderTag = req.headers.get('x-loader-tag') ?? loaderService.DEFAULT_TAG;\n\n loaderService.increase(loaderTag);\n\n const cleanReq = req.clone({\n headers: req.headers.delete('x-loader-tag'),\n });\n\n return next(cleanReq).pipe(\n finalize(() => loaderService.decrease(loaderTag))\n );\n}\n","import {\n HttpErrorResponse,\n HttpEvent,\n HttpHandlerFn,\n HttpRequest,\n} from '@angular/common/http';\nimport { inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MessageService } from 'primeng/api';\nimport { catchError, Observable, throwError } from 'rxjs';\n\n/**\n * Error Catching Interceptor\n * Displays error messages for failed HTTP requests.\n *\n * NOTE: This interceptor only handles error display.\n * For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.\n *\n * @example\n * ```typescript\n * // In app.config.ts\n * provideHttpClient(\n * withFetch(),\n * withInterceptors([\n * authInterceptor, // from @flusys/ng-auth\n * tokenRefreshInterceptor, // from @flusys/ng-auth\n * errorCatchingInterceptor, // from @flusys/ng-core\n * apiLoaderInterceptor, // from @flusys/ng-core\n * ])\n * )\n * ```\n */\nexport function errorCatchingInterceptor(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn\n): Observable<HttpEvent<unknown>> {\n const messageService = inject(MessageService);\n const platformId = inject(PLATFORM_ID);\n const isBrowser = isPlatformBrowser(platformId);\n\n // Skip error display for check-login (expected to fail when not logged in)\n const isCheckLogin = req.url.includes('/check-login');\n\n return next(req).pipe(\n catchError((error: HttpErrorResponse) => {\n // Skip 401 errors - handled by tokenRefreshInterceptor in ng-auth\n // Skip 403 errors - handled by permission denied UI in components\n // Skip check-login errors - expected behavior\n if (error.status === 401 || error.status === 403 || isCheckLogin) {\n return throwError(() => error);\n }\n\n // Display error message for other errors\n if (isBrowser) {\n const message = extractErrorMessage(error);\n messageService.add({\n severity: 'error',\n summary: 'Error',\n detail: message,\n });\n }\n\n return throwError(() => error);\n })\n );\n}\n\n/**\n * Extract error message from HTTP error response\n */\nfunction extractErrorMessage(error: HttpErrorResponse): string {\n if (typeof error.error?.message === 'string') {\n return error.error.message;\n }\n\n if (Array.isArray(error.error?.message)) {\n return error.error.message[0];\n }\n\n if (error.message) {\n return error.message;\n }\n\n return 'An unexpected error occurred.';\n}\n","import { InjectionToken } from '@angular/core';\n\n/**\n * IAM Permission Mode - controls permission system behavior\n */\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\n/**\n * Service Configuration Interface\n */\nexport interface IServiceConfig {\n baseUrl: string;\n enabled: boolean;\n}\n\n/**\n * Storage Service Configuration Interface\n * Extends base service config with storage-specific options\n */\nexport interface IStorageServiceConfig extends IServiceConfig {\n /**\n * Default storage config ID from backend storage_config table\n * Used when no storageConfigId is provided to upload operations\n * If not set, backend will use company/branch default config\n */\n defaultStorageConfigId?: string;\n}\n\n/**\n * Author/Brand Configuration\n */\nexport interface IAuthorConfig {\n name: string;\n url: string;\n}\n\n/**\n * App Configuration Interface\n * Centralized configuration for feature toggles and behavior control\n */\nexport interface IAppConfig {\n /** Application name */\n appName: string;\n\n /** Author/Brand info for footer attribution */\n author?: IAuthorConfig;\n\n /** Default API base URL (fallback) */\n apiBaseUrl: string; \n \n /**\n * enableCompanyFeature in across the app\n */\n enableCompanyFeature: boolean,\n\n /**\n * Service-specific base URLs\n * If a service exists in this object, its feature is enabled\n * Database mode is determined by multiTenant.enabled\n */\n services: {\n auth: IServiceConfig;\n iam: IServiceConfig;\n storage: IStorageServiceConfig;\n [key: string]: IServiceConfig | IStorageServiceConfig;\n };\n\n /**\n * Multi-tenant configuration\n * enabled: true → Database mode = multi-tenant\n * enabled: false → Database mode = single\n */\n multiTenant: {\n enabled: boolean;\n tenantHeader: string;\n tenantId?: string;\n };\n\n /**\n * IAM Permission Mode - controls permission system behavior\n * - 'rbac': Role-Based Access Control only (users get permissions via roles)\n * - 'direct': Direct action assignments only (actions assigned directly to users)\n * - 'full': Both RBAC and direct permissions (most flexible, default)\n */\n permissionMode?: PermissionMode;\n}\n\n/**\n * App Configuration Token\n * Use this to inject app config in services/components\n */\nexport const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');\n\n/**\n * Get database mode from config\n * Derived from multiTenant.enabled\n */\nexport function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant' {\n return config.multiTenant.enabled ? 'multi-tenant' : 'single';\n}\n\n/**\n * Check if a feature is enabled\n * Features are enabled if their corresponding service exists and is enabled\n */\nexport function isFeatureEnabled(\n config: IAppConfig,\n feature: 'auth' | 'iam' | 'storage',\n): boolean {\n const service = config.services[feature];\n return service?.enabled ?? false;\n}\n\n/**\n * Helper function to get service base URL\n * Returns service-specific URL or falls back to default apiBaseUrl\n */\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName: keyof IAppConfig['services'],\n): string {\n const service = config.services[serviceName];\n if (service?.enabled) {\n return service.baseUrl;\n }\n // Fallback to default API base URL\n return config.apiBaseUrl;\n}\n\n/**\n * Helper function to check if a service is enabled\n */\nexport function isServiceEnabled(\n config: IAppConfig,\n serviceName: keyof IAppConfig['services'],\n): boolean {\n const service = config.services[serviceName];\n return service?.enabled ?? false;\n}\n\n/**\n * Check if company feature is enabled (backward compatible)\n * Company feature is enabled if auth service is enabled\n */\nexport function isCompanyFeatureEnabled(config: IAppConfig): boolean {\n return config.enableCompanyFeature ?? false;\n}\n\n/**\n * Check if storage feature is enabled (backward compatible)\n * Storage feature is enabled if storage service is enabled\n */\nexport function isStorageFeatureEnabled(config: IAppConfig): boolean {\n return config.services.storage?.enabled ?? false;\n}\n\n/**\n * Get permission mode from config\n * Defaults to 'full' if not specified\n */\nexport function getPermissionMode(config: IAppConfig): PermissionMode {\n return config.permissionMode ?? 'full';\n}\n\n/**\n * Get default storage config ID from config\n * Used for file upload operations when no storageConfigId is provided\n */\nexport function getDefaultStorageConfigId(config: IAppConfig): string | undefined {\n return config.services.storage?.defaultStorageConfigId;\n}\n\n// Default branding constants\nexport const DEFAULT_APP_NAME = 'FLUSYS';\nexport const DEFAULT_AUTHOR: IAuthorConfig = {\n name: 'Flusys',\n url: 'https://flusys.vercel.app',\n};\n","import { inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { APP_CONFIG, getServiceUrl } from '../interfaces/app-config.interface';\n\n/**\n * Base API Service\n * Provides service-specific base URL resolution\n *\n * Usage:\n * ```typescript\n * @Injectable({ providedIn: 'root' })\n * export class MyService extends BaseApiService {\n * constructor() {\n * super('my-service'); // Service name from config\n * }\n *\n * getData() {\n * return this.http.get(`${this.baseUrl}/data`);\n * }\n * }\n * ```\n */\nexport abstract class BaseApiService {\n protected readonly http = inject(HttpClient);\n protected readonly appConfig = inject(APP_CONFIG);\n protected readonly baseUrl: string;\n\n /**\n * @param serviceName - Service name from config (e.g., 'auth', 'iam', 'storage')\n */\n constructor(serviceName: string) {\n this.baseUrl = getServiceUrl(this.appConfig, serviceName);\n }\n\n /**\n * Get full URL for an endpoint\n * @param endpoint - API endpoint (e.g., '/users/get-all')\n * @returns Full URL (e.g., 'http://localhost:2002/users/get-all')\n */\n protected getUrl(endpoint: string): string {\n // Remove leading slash if present to avoid double slashes\n const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;\n return `${this.baseUrl}/${cleanEndpoint}`;\n }\n}\n","// Components\nexport * from './components/lib-app-config/lib-app-config.component';\n\n// Interceptors\nexport * from './interceptors/api-loader.interceptor';\nexport * from './interceptors/error-catching.interceptor';\n\n// Interfaces\nexport * from './interfaces/app-config.interface';\n\n// Services\nexport * from './services/api-loader.service';\nexport * from './services/base-api.service';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"flusys-ng-core.mjs","sources":["../../../projects/ng-core/services/api-loader.service.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.html","../../../projects/ng-core/interceptors/api-loader.interceptor.ts","../../../projects/ng-core/interceptors/error-catching.interceptor.ts","../../../projects/ng-core/interfaces/app-config.interface.ts","../../../projects/ng-core/services/base-api.service.ts","../../../projects/ng-core/public-api.ts","../../../projects/ng-core/flusys-ng-core.ts"],"sourcesContent":["import { computed, Injectable, Signal, signal } from '@angular/core';\n\n@Injectable({ providedIn: 'root' })\nexport class ApiLoaderService {\n private readonly loaders = signal<Record<string, number>>({});\n readonly DEFAULT_TAG = 'default';\n\n // Global loading state\n readonly show: Signal<boolean> = computed(() => {\n const loaders = this.loaders();\n return Object.values(loaders).some((count) => count > 0);\n });\n\n // Get specific loader as a signal\n getLoader(name: string): Signal<boolean> {\n return computed(() => (this.loaders()[name] ?? 0) > 0);\n }\n\n increase(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => ({\n ...loaders,\n [name]: (loaders[name] ?? 0) + 1,\n }));\n }\n\n decrease(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => {\n const current = loaders[name] ?? 0;\n return { ...loaders, [name]: Math.max(0, current - 1) };\n });\n }\n}\n","import { ChangeDetectionStrategy, Component, inject } from '@angular/core';\nimport { ToastModule } from 'primeng/toast';\nimport { ConfirmDialog } from 'primeng/confirmdialog';\nimport { ApiLoaderService } from '../../services/api-loader.service';\nimport { ProgressBarModule } from 'primeng/progressbar';\n\n/**\n * Root configuration component providing global UI elements.\n * Include once at app root for global toast, confirm dialog, and loader.\n */\n@Component({\n selector: 'lib-app-config',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [ConfirmDialog, ProgressBarModule, ToastModule],\n templateUrl: './lib-app-config.component.html',\n styleUrl: './lib-app-config.component.scss',\n})\nexport class LibAppConfigComponent {\n readonly apiLoaderService = inject(ApiLoaderService);\n}\n","@if (apiLoaderService.show()) {\n<p-progressBar mode=\"indeterminate\" />\n}\n<p-toast />\n<p-confirmdialog />\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { ApiLoaderService } from '../services/api-loader.service';\nimport { Observable } from 'rxjs';\nimport { finalize } from 'rxjs/operators';\n\n/**\n * Interceptor that tracks loading state for HTTP requests.\n * Uses 'x-loader-tag' header to group related requests.\n *\n * @example\n * ```typescript\n * http.get('/api/users', {\n * headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })\n * });\n * // In template: apiLoaderService.getLoader('users-list')\n * ```\n */\nexport function apiLoaderInterceptor(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn\n): Observable<HttpEvent<unknown>> {\n const loaderService = inject(ApiLoaderService);\n const loaderTag = req.headers.get('x-loader-tag') ?? loaderService.DEFAULT_TAG;\n\n loaderService.increase(loaderTag);\n\n const cleanReq = req.clone({\n headers: req.headers.delete('x-loader-tag'),\n });\n\n return next(cleanReq).pipe(\n finalize(() => loaderService.decrease(loaderTag))\n );\n}\n","import {\n HttpErrorResponse,\n HttpEvent,\n HttpHandlerFn,\n HttpRequest,\n HttpContextToken,\n} from '@angular/common/http';\nimport { inject, PLATFORM_ID } from '@angular/core';\nimport { isPlatformBrowser } from '@angular/common';\nimport { MessageService } from 'primeng/api';\nimport { catchError, Observable, throwError } from 'rxjs';\n\n/**\n * HTTP context token to skip global error toast.\n * Use when component handles error display locally.\n *\n * @example\n * ```typescript\n * this.http.post(url, data, {\n * context: new HttpContext().set(SKIP_ERROR_TOAST, true)\n * });\n * ```\n */\nexport const SKIP_ERROR_TOAST = new HttpContextToken<boolean>(() => false);\n\n/**\n * Error Catching Interceptor\n * Displays error messages for failed HTTP requests using backend response.\n *\n * Features:\n * - Uses backend's error title (error.error.error) as summary\n * - Uses backend's error message (error.error.message) as detail\n * - Falls back to sensible defaults based on status code\n * - Skip with SKIP_ERROR_TOAST context token for local handling\n *\n * NOTE: This interceptor only handles error display.\n * For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.\n *\n * @example\n * ```typescript\n * // In app.config.ts\n * provideHttpClient(\n * withFetch(),\n * withInterceptors([\n * authInterceptor, // from @flusys/ng-auth\n * tokenRefreshInterceptor, // from @flusys/ng-auth\n * errorCatchingInterceptor, // from @flusys/ng-core\n * apiLoaderInterceptor, // from @flusys/ng-core\n * ])\n * )\n * ```\n */\nexport function errorCatchingInterceptor(\n req: HttpRequest<unknown>,\n next: HttpHandlerFn\n): Observable<HttpEvent<unknown>> {\n const messageService = inject(MessageService);\n const platformId = inject(PLATFORM_ID);\n const isBrowser = isPlatformBrowser(platformId);\n\n // Skip error display for check-login (expected to fail when not logged in)\n const isCheckLogin = req.url.includes('/check-login');\n // Skip if component handles error locally\n const skipErrorToast = req.context.get(SKIP_ERROR_TOAST);\n\n return next(req).pipe(\n catchError((error: HttpErrorResponse) => {\n // Skip 401 errors - handled by tokenRefreshInterceptor in ng-auth\n // Skip 403 errors - handled by permission denied UI in components\n // Skip check-login errors - expected behavior\n // Skip if component handles error locally\n if (error.status === 401 || error.status === 403 || isCheckLogin || skipErrorToast) {\n return throwError(() => error);\n }\n\n // Display error message for other errors\n if (isBrowser) {\n const { summary, detail } = extractErrorInfo(error);\n messageService.add({\n severity: 'error',\n summary,\n detail,\n });\n }\n\n return throwError(() => error);\n })\n );\n}\n\n/**\n * Extract error summary and detail from HTTP error response.\n * Uses backend response format: { message, error, statusCode }\n */\nfunction extractErrorInfo(error: HttpErrorResponse): { summary: string; detail: string } {\n // Extract message from backend response\n let detail: string;\n if (typeof error.error?.message === 'string') {\n detail = error.error.message;\n } else if (Array.isArray(error.error?.message)) {\n // NestJS validation errors come as array\n detail = error.error.message[0];\n } else if (error.message) {\n detail = error.message;\n } else {\n detail = 'An unexpected error occurred.';\n }\n\n // Extract summary/title from backend response or use default based on status\n let summary: string;\n if (typeof error.error?.error === 'string') {\n // Backend sends error type (e.g., \"Not Found\", \"Bad Request\", \"Validation Error\")\n summary = error.error.error;\n } else {\n // Fallback to status-based title\n summary = getDefaultSummary(error.status);\n }\n\n return { summary, detail };\n}\n\n/**\n * Get default error summary based on HTTP status code\n */\nfunction getDefaultSummary(status: number): string {\n switch (status) {\n case 400:\n return 'Bad Request';\n case 404:\n return 'Not Found';\n case 409:\n return 'Conflict';\n case 422:\n return 'Validation Error';\n case 500:\n return 'Server Error';\n case 502:\n case 503:\n case 504:\n return 'Service Unavailable';\n default:\n return 'Error';\n }\n}\n","import { InjectionToken } from '@angular/core';\n\n/**\n * IAM Permission Mode - controls permission system behavior\n */\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\n/**\n * Service Configuration Interface\n */\nexport interface IServiceConfig {\n baseUrl: string;\n enabled: boolean;\n}\n\n/**\n * Storage Service Configuration Interface\n * Extends base service config with storage-specific options\n */\nexport interface IStorageServiceConfig extends IServiceConfig {\n /**\n * Default storage config ID from backend storage_config table\n * Used when no storageConfigId is provided to upload operations\n * If not set, backend will use company/branch default config\n */\n defaultStorageConfigId?: string;\n}\n\n/**\n * Author/Brand Configuration\n */\nexport interface IAuthorConfig {\n name: string;\n url: string;\n}\n\n/**\n * Registration Defaults Configuration\n * Pre-fills and disables fields on registration page\n */\nexport interface IRegistrationDefaults {\n /** Pre-fill company code field and make it readonly */\n companySlug?: string;\n /** Pre-fill branch code field and make it readonly */\n branchSlug?: string;\n}\n\n/**\n * App Configuration Interface\n * Centralized configuration for feature toggles and behavior control\n */\nexport interface IAppConfig {\n /** Application name */\n appName: string;\n\n /** Author/Brand info for footer attribution */\n author?: IAuthorConfig;\n\n /** Default API base URL (fallback) */\n apiBaseUrl: string;\n\n /**\n * Enable company feature across the app\n * When true, enables company selection/switching in layout and feature modules\n */\n enableCompanyFeature: boolean;\n\n /**\n * Enable user sign-up/registration\n * When false: hides \"Sign Up\" link on login page and protects registration route\n * @default true\n */\n enableSignUp?: boolean;\n\n /**\n * Registration defaults for pre-filling company/branch fields\n * When set: pre-fills fields and makes them readonly\n */\n registrationDefaults?: IRegistrationDefaults;\n\n /**\n * Service-specific base URLs\n * If a service exists in this object, its feature is enabled\n * Database mode is determined by multiTenant.enabled\n */\n services: {\n auth: IServiceConfig;\n administration: IServiceConfig;\n iam: IServiceConfig;\n storage: IStorageServiceConfig;\n formBuilder: IServiceConfig;\n email: IServiceConfig;\n };\n\n /**\n * Multi-tenant configuration\n * enabled: true → Database mode = multi-tenant\n * enabled: false → Database mode = single\n */\n multiTenant: {\n enabled: boolean;\n tenantHeader: string;\n tenantId?: string;\n };\n\n /**\n * IAM Permission Mode - controls permission system behavior\n * - 'rbac': Role-Based Access Control only (users get permissions via roles)\n * - 'direct': Direct action assignments only (actions assigned directly to users)\n * - 'full': Both RBAC and direct permissions (most flexible, default)\n */\n permissionMode?: PermissionMode;\n}\n\n/**\n * App Configuration Token\n * Use this to inject app config in services/components\n */\nexport const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');\n\n/**\n * Get database mode from config\n * Derived from multiTenant.enabled\n */\nexport function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant' {\n return config.multiTenant.enabled ? 'multi-tenant' : 'single';\n}\n\n/**\n * Check if a service is enabled\n */\nexport function isServiceEnabled(\n config: IAppConfig,\n serviceName: 'auth' | 'iam' | 'storage' | (string & {}),\n): boolean {\n const service = config.services[serviceName as keyof typeof config.services];\n return service?.enabled ?? false;\n}\n\n/**\n * Check if a feature is enabled (alias for isServiceEnabled with strict types)\n */\nexport function isFeatureEnabled(\n config: IAppConfig,\n feature: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email',\n): boolean {\n return isServiceEnabled(config, feature);\n}\n\n/**\n * Get service base URL or fallback to default apiBaseUrl\n */\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | (string & {}),\n): string {\n if (isServiceEnabled(config, serviceName)) {\n const service = config.services[serviceName as keyof typeof config.services];\n return service.baseUrl;\n }\n return config.apiBaseUrl;\n}\n\n/**\n * Check if company feature is enabled\n */\nexport function isCompanyFeatureEnabled(config: IAppConfig): boolean {\n return config.enableCompanyFeature ?? false;\n}\n\n/**\n * Check if storage feature is enabled\n */\nexport function isStorageFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'storage');\n}\n\n/**\n * Check if form-builder feature is enabled\n */\nexport function isFormBuilderFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'formBuilder');\n}\n\n/**\n * Check if email feature is enabled\n */\nexport function isEmailFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'email');\n}\n\n/**\n * Get permission mode from config\n * Defaults to 'full' if not specified\n */\nexport function getPermissionMode(config: IAppConfig): PermissionMode {\n return config.permissionMode ?? 'full';\n}\n\n/**\n * Get default storage config ID from config\n * Used for file upload operations when no storageConfigId is provided\n */\nexport function getDefaultStorageConfigId(config: IAppConfig): string | undefined {\n return config.services.storage?.defaultStorageConfigId;\n}\n\n/**\n * Check if sign-up/registration is enabled\n * @default true\n */\nexport function isSignUpEnabled(config: IAppConfig): boolean {\n return config.enableSignUp ?? true;\n}\n\n/**\n * Get default company slug for registration\n */\nexport function getDefaultCompanySlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.companySlug;\n}\n\n/**\n * Get default branch slug for registration\n */\nexport function getDefaultBranchSlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.branchSlug;\n}\n\n/**\n * Get registration defaults\n */\nexport function getRegistrationDefaults(config: IAppConfig): IRegistrationDefaults | undefined {\n return config.registrationDefaults;\n}\n\n// Default branding constants\nexport const DEFAULT_APP_NAME = 'FLUSYS';\nexport const DEFAULT_AUTHOR: IAuthorConfig = {\n name: 'Flusys',\n url: 'https://flusys.vercel.app',\n};\n","import { inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { APP_CONFIG, getServiceUrl } from '../interfaces/app-config.interface';\n\n/**\n * Base API Service\n * Provides service-specific base URL resolution\n *\n * Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.\n *\n * Usage:\n * ```typescript\n * @Injectable({ providedIn: 'root' })\n * export class MyService extends BaseApiService {\n * constructor() {\n * super('my-service'); // Service name from config\n * }\n *\n * getData() {\n * return this.http.post(`${this.baseUrl}/data/get`, {});\n * }\n * }\n * ```\n */\nexport abstract class BaseApiService {\n protected readonly http = inject(HttpClient);\n protected readonly appConfig = inject(APP_CONFIG);\n protected readonly baseUrl: string;\n\n /**\n * @param serviceName - Service name from config (e.g., 'auth', 'iam', 'storage')\n */\n constructor(serviceName: string) {\n this.baseUrl = getServiceUrl(this.appConfig, serviceName);\n }\n\n /**\n * Get full URL for an endpoint\n * @param endpoint - API endpoint (e.g., '/users/get-all')\n * @returns Full URL (e.g., 'http://localhost:2002/users/get-all')\n */\n protected getUrl(endpoint: string): string {\n // Remove leading slash if present to avoid double slashes\n const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;\n return `${this.baseUrl}/${cleanEndpoint}`;\n }\n}\n","// Components\nexport * from './components/public-api';\n\n// Interceptors\nexport * from './interceptors/public-api';\n\n// Interfaces\nexport * from './interfaces/public-api';\n\n// Services\nexport * from './services/public-api';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/ng-core",
|
|
3
|
-
"version": "1.1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Core utilities and services for FLUSYS Angular packages",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"peerDependencies": {
|
|
7
|
-
"@angular/common": "
|
|
8
|
-
"@angular/core": "
|
|
9
|
-
"@primeuix/themes": "
|
|
10
|
-
"primeicons": "
|
|
11
|
-
"primeng": "
|
|
7
|
+
"@angular/common": ">=21.0.0",
|
|
8
|
+
"@angular/core": ">=21.0.0",
|
|
9
|
+
"@primeuix/themes": ">=1.0.0",
|
|
10
|
+
"primeicons": ">=7.0.0",
|
|
11
|
+
"primeng": ">=21.0.0"
|
|
12
12
|
},
|
|
13
13
|
"sideEffects": false,
|
|
14
14
|
"module": "fesm2022/flusys-ng-core.mjs",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Signal, InjectionToken } from '@angular/core';
|
|
3
|
-
import { HttpRequest, HttpHandlerFn, HttpEvent, HttpClient } from '@angular/common/http';
|
|
3
|
+
import { HttpRequest, HttpHandlerFn, HttpEvent, HttpContextToken, HttpClient } from '@angular/common/http';
|
|
4
4
|
import { Observable } from 'rxjs';
|
|
5
5
|
import * as _flusys_ng_core from '@flusys/ng-core';
|
|
6
6
|
|
|
@@ -39,9 +39,27 @@ declare class LibAppConfigComponent {
|
|
|
39
39
|
*/
|
|
40
40
|
declare function apiLoaderInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>>;
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* HTTP context token to skip global error toast.
|
|
44
|
+
* Use when component handles error display locally.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* this.http.post(url, data, {
|
|
49
|
+
* context: new HttpContext().set(SKIP_ERROR_TOAST, true)
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare const SKIP_ERROR_TOAST: HttpContextToken<boolean>;
|
|
42
54
|
/**
|
|
43
55
|
* Error Catching Interceptor
|
|
44
|
-
* Displays error messages for failed HTTP requests.
|
|
56
|
+
* Displays error messages for failed HTTP requests using backend response.
|
|
57
|
+
*
|
|
58
|
+
* Features:
|
|
59
|
+
* - Uses backend's error title (error.error.error) as summary
|
|
60
|
+
* - Uses backend's error message (error.error.message) as detail
|
|
61
|
+
* - Falls back to sensible defaults based on status code
|
|
62
|
+
* - Skip with SKIP_ERROR_TOAST context token for local handling
|
|
45
63
|
*
|
|
46
64
|
* NOTE: This interceptor only handles error display.
|
|
47
65
|
* For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.
|
|
@@ -92,6 +110,16 @@ interface IAuthorConfig {
|
|
|
92
110
|
name: string;
|
|
93
111
|
url: string;
|
|
94
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Registration Defaults Configuration
|
|
115
|
+
* Pre-fills and disables fields on registration page
|
|
116
|
+
*/
|
|
117
|
+
interface IRegistrationDefaults {
|
|
118
|
+
/** Pre-fill company code field and make it readonly */
|
|
119
|
+
companySlug?: string;
|
|
120
|
+
/** Pre-fill branch code field and make it readonly */
|
|
121
|
+
branchSlug?: string;
|
|
122
|
+
}
|
|
95
123
|
/**
|
|
96
124
|
* App Configuration Interface
|
|
97
125
|
* Centralized configuration for feature toggles and behavior control
|
|
@@ -104,9 +132,21 @@ interface IAppConfig {
|
|
|
104
132
|
/** Default API base URL (fallback) */
|
|
105
133
|
apiBaseUrl: string;
|
|
106
134
|
/**
|
|
107
|
-
*
|
|
135
|
+
* Enable company feature across the app
|
|
136
|
+
* When true, enables company selection/switching in layout and feature modules
|
|
108
137
|
*/
|
|
109
138
|
enableCompanyFeature: boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Enable user sign-up/registration
|
|
141
|
+
* When false: hides "Sign Up" link on login page and protects registration route
|
|
142
|
+
* @default true
|
|
143
|
+
*/
|
|
144
|
+
enableSignUp?: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Registration defaults for pre-filling company/branch fields
|
|
147
|
+
* When set: pre-fills fields and makes them readonly
|
|
148
|
+
*/
|
|
149
|
+
registrationDefaults?: IRegistrationDefaults;
|
|
110
150
|
/**
|
|
111
151
|
* Service-specific base URLs
|
|
112
152
|
* If a service exists in this object, its feature is enabled
|
|
@@ -114,9 +154,11 @@ interface IAppConfig {
|
|
|
114
154
|
*/
|
|
115
155
|
services: {
|
|
116
156
|
auth: IServiceConfig;
|
|
157
|
+
administration: IServiceConfig;
|
|
117
158
|
iam: IServiceConfig;
|
|
118
159
|
storage: IStorageServiceConfig;
|
|
119
|
-
|
|
160
|
+
formBuilder: IServiceConfig;
|
|
161
|
+
email: IServiceConfig;
|
|
120
162
|
};
|
|
121
163
|
/**
|
|
122
164
|
* Multi-tenant configuration
|
|
@@ -147,29 +189,33 @@ declare const APP_CONFIG: InjectionToken<IAppConfig>;
|
|
|
147
189
|
*/
|
|
148
190
|
declare function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant';
|
|
149
191
|
/**
|
|
150
|
-
* Check if a
|
|
151
|
-
* Features are enabled if their corresponding service exists and is enabled
|
|
192
|
+
* Check if a service is enabled
|
|
152
193
|
*/
|
|
153
|
-
declare function
|
|
194
|
+
declare function isServiceEnabled(config: IAppConfig, serviceName: 'auth' | 'iam' | 'storage' | (string & {})): boolean;
|
|
154
195
|
/**
|
|
155
|
-
*
|
|
156
|
-
* Returns service-specific URL or falls back to default apiBaseUrl
|
|
196
|
+
* Check if a feature is enabled (alias for isServiceEnabled with strict types)
|
|
157
197
|
*/
|
|
158
|
-
declare function
|
|
198
|
+
declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email'): boolean;
|
|
159
199
|
/**
|
|
160
|
-
*
|
|
200
|
+
* Get service base URL or fallback to default apiBaseUrl
|
|
161
201
|
*/
|
|
162
|
-
declare function
|
|
202
|
+
declare function getServiceUrl(config: IAppConfig, serviceName: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | (string & {})): string;
|
|
163
203
|
/**
|
|
164
|
-
* Check if company feature is enabled
|
|
165
|
-
* Company feature is enabled if auth service is enabled
|
|
204
|
+
* Check if company feature is enabled
|
|
166
205
|
*/
|
|
167
206
|
declare function isCompanyFeatureEnabled(config: IAppConfig): boolean;
|
|
168
207
|
/**
|
|
169
|
-
* Check if storage feature is enabled
|
|
170
|
-
* Storage feature is enabled if storage service is enabled
|
|
208
|
+
* Check if storage feature is enabled
|
|
171
209
|
*/
|
|
172
210
|
declare function isStorageFeatureEnabled(config: IAppConfig): boolean;
|
|
211
|
+
/**
|
|
212
|
+
* Check if form-builder feature is enabled
|
|
213
|
+
*/
|
|
214
|
+
declare function isFormBuilderFeatureEnabled(config: IAppConfig): boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Check if email feature is enabled
|
|
217
|
+
*/
|
|
218
|
+
declare function isEmailFeatureEnabled(config: IAppConfig): boolean;
|
|
173
219
|
/**
|
|
174
220
|
* Get permission mode from config
|
|
175
221
|
* Defaults to 'full' if not specified
|
|
@@ -180,6 +226,23 @@ declare function getPermissionMode(config: IAppConfig): PermissionMode;
|
|
|
180
226
|
* Used for file upload operations when no storageConfigId is provided
|
|
181
227
|
*/
|
|
182
228
|
declare function getDefaultStorageConfigId(config: IAppConfig): string | undefined;
|
|
229
|
+
/**
|
|
230
|
+
* Check if sign-up/registration is enabled
|
|
231
|
+
* @default true
|
|
232
|
+
*/
|
|
233
|
+
declare function isSignUpEnabled(config: IAppConfig): boolean;
|
|
234
|
+
/**
|
|
235
|
+
* Get default company slug for registration
|
|
236
|
+
*/
|
|
237
|
+
declare function getDefaultCompanySlug(config: IAppConfig): string | undefined;
|
|
238
|
+
/**
|
|
239
|
+
* Get default branch slug for registration
|
|
240
|
+
*/
|
|
241
|
+
declare function getDefaultBranchSlug(config: IAppConfig): string | undefined;
|
|
242
|
+
/**
|
|
243
|
+
* Get registration defaults
|
|
244
|
+
*/
|
|
245
|
+
declare function getRegistrationDefaults(config: IAppConfig): IRegistrationDefaults | undefined;
|
|
183
246
|
declare const DEFAULT_APP_NAME = "FLUSYS";
|
|
184
247
|
declare const DEFAULT_AUTHOR: IAuthorConfig;
|
|
185
248
|
|
|
@@ -187,6 +250,8 @@ declare const DEFAULT_AUTHOR: IAuthorConfig;
|
|
|
187
250
|
* Base API Service
|
|
188
251
|
* Provides service-specific base URL resolution
|
|
189
252
|
*
|
|
253
|
+
* Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.
|
|
254
|
+
*
|
|
190
255
|
* Usage:
|
|
191
256
|
* ```typescript
|
|
192
257
|
* @Injectable({ providedIn: 'root' })
|
|
@@ -196,7 +261,7 @@ declare const DEFAULT_AUTHOR: IAuthorConfig;
|
|
|
196
261
|
* }
|
|
197
262
|
*
|
|
198
263
|
* getData() {
|
|
199
|
-
* return this.http.
|
|
264
|
+
* return this.http.post(`${this.baseUrl}/data/get`, {});
|
|
200
265
|
* }
|
|
201
266
|
* }
|
|
202
267
|
* ```
|
|
@@ -217,5 +282,5 @@ declare abstract class BaseApiService {
|
|
|
217
282
|
protected getUrl(endpoint: string): string;
|
|
218
283
|
}
|
|
219
284
|
|
|
220
|
-
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultStorageConfigId, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isFeatureEnabled, isServiceEnabled, isStorageFeatureEnabled };
|
|
221
|
-
export type { IAppConfig, IAuthorConfig, IServiceConfig, IStorageServiceConfig, PermissionMode };
|
|
285
|
+
export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getPermissionMode, getRegistrationDefaults, getServiceUrl, isCompanyFeatureEnabled, isEmailFeatureEnabled, isFeatureEnabled, isFormBuilderFeatureEnabled, isServiceEnabled, isSignUpEnabled, isStorageFeatureEnabled };
|
|
286
|
+
export type { IAppConfig, IAuthorConfig, IRegistrationDefaults, IServiceConfig, IStorageServiceConfig, PermissionMode };
|