@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 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
- │ └── lib-app-config/
22
- │ ├── lib-app-config.component.ts
23
- │ ├── lib-app-config.component.html
24
- │ └── lib-app-config.component.scss
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
- │ └── error-catching.interceptor.ts
29
+ │ ├── error-catching.interceptor.ts
30
+ │ └── public-api.ts
28
31
  ├── interfaces/
29
- │ └── app-config.interface.ts
32
+ │ ├── app-config.interface.ts
33
+ │ └── public-api.ts
30
34
  ├── services/
31
35
  │ ├── api-loader.service.ts
32
- │ └── base-api.service.ts
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
- // Components
40
- export * from './components/lib-app-config/lib-app-config.component';
41
-
42
- // Interceptors
43
- export * from './interceptors/api-loader.interceptor';
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
- [key: string]: IServiceConfig; // Extensible for custom services
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: 'auth' \| 'iam' \| 'storage') → boolean` | Check if a service feature is enabled |
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
- - Extracts message from `error.error.message` (string or array), `error.message`, or fallback
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 and check-login) |
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 (`'auth' \| 'iam' \| 'storage'`) |
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-16
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.3", ngImport: i0, type: ApiLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
39
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ApiLoaderService, providedIn: 'root' });
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.3", ngImport: i0, type: ApiLoaderService, decorators: [{
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.3", ngImport: i0, type: LibAppConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
53
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", 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"] }] });
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.3", ngImport: i0, type: LibAppConfigComponent, decorators: [{
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', standalone: true, 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"] }]
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
- if (error.status === 401 || error.status === 403 || isCheckLogin) {
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 message = extractErrorMessage(error);
139
+ const { summary, detail } = extractErrorInfo(error);
119
140
  messageService.add({
120
141
  severity: 'error',
121
- summary: 'Error',
122
- detail: message,
142
+ summary,
143
+ detail,
123
144
  });
124
145
  }
125
146
  return throwError(() => error);
126
147
  }));
127
148
  }
128
149
  /**
129
- * Extract error message from HTTP error response
150
+ * Extract error summary and detail from HTTP error response.
151
+ * Uses backend response format: { message, error, statusCode }
130
152
  */
131
- function extractErrorMessage(error) {
153
+ function extractErrorInfo(error) {
154
+ // Extract message from backend response
155
+ let detail;
132
156
  if (typeof error.error?.message === 'string') {
133
- return error.error.message;
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
- if (Array.isArray(error.error?.message)) {
136
- return error.error.message[0];
175
+ else {
176
+ // Fallback to status-based title
177
+ summary = getDefaultSummary(error.status);
137
178
  }
138
- if (error.message) {
139
- return error.message;
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 feature is enabled
158
- * Features are enabled if their corresponding service exists and is enabled
218
+ * Check if a service is enabled
159
219
  */
160
- function isFeatureEnabled(config, feature) {
161
- const service = config.services[feature];
220
+ function isServiceEnabled(config, serviceName) {
221
+ const service = config.services[serviceName];
162
222
  return service?.enabled ?? false;
163
223
  }
164
224
  /**
165
- * Helper function to get service base URL
166
- * Returns service-specific URL or falls back to default apiBaseUrl
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
- const service = config.services[serviceName];
170
- if (service?.enabled) {
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
- * Helper function to check if a service is enabled
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 (backward compatible)
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.services.storage?.enabled ?? false;
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.get(`${this.baseUrl}/data`);
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-beta",
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": "^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"
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
- * enableCompanyFeature in across the app
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
- [key: string]: IServiceConfig | IStorageServiceConfig;
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 feature is enabled
151
- * Features are enabled if their corresponding service exists and is enabled
192
+ * Check if a service is enabled
152
193
  */
153
- declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'iam' | 'storage'): boolean;
194
+ declare function isServiceEnabled(config: IAppConfig, serviceName: 'auth' | 'iam' | 'storage' | (string & {})): boolean;
154
195
  /**
155
- * Helper function to get service base URL
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 getServiceUrl(config: IAppConfig, serviceName: keyof IAppConfig['services']): string;
198
+ declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email'): boolean;
159
199
  /**
160
- * Helper function to check if a service is enabled
200
+ * Get service base URL or fallback to default apiBaseUrl
161
201
  */
162
- declare function isServiceEnabled(config: IAppConfig, serviceName: keyof IAppConfig['services']): boolean;
202
+ declare function getServiceUrl(config: IAppConfig, serviceName: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | (string & {})): string;
163
203
  /**
164
- * Check if company feature is enabled (backward compatible)
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 (backward compatible)
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.get(`${this.baseUrl}/data`);
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 };