@flusys/ng-core 3.0.0-rc → 3.0.1

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
@@ -2,15 +2,15 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- `@flusys/ng-core` is the foundation package that provides core functionality for all FLUSYS Angular libraries and applications. It contains configuration management, HTTP interceptors, base services, and type definitions.
5
+ `@flusys/ng-core` is the foundation package providing core functionality for all FLUSYS Angular libraries. Contains configuration management, HTTP interceptors, base services, and type definitions.
6
6
 
7
- **Key Principle:** ng-core has NO internal dependencies on other @flusys packages. All other packages depend on ng-core.
7
+ **Key Principle:** ng-core has NO dependencies on other @flusys packages. All other packages depend on ng-core.
8
8
 
9
9
  ## Package Information
10
10
 
11
11
  - **Package:** `@flusys/ng-core`
12
- - **Dependencies:** None (base package)
13
- - **Dependents:** ng-shared, ng-layout, ng-auth, ng-iam, ng-storage, flusysng
12
+ - **Version:** 3.0.1
13
+ - **Peer Dependencies:** `@angular/*: >=21.0.0`, `primeng: >=21.0.0`
14
14
  - **Build Command:** `npm run build:ng-core`
15
15
 
16
16
  ## Package Structure
@@ -18,48 +18,55 @@
18
18
  ```
19
19
  ng-core/
20
20
  ├── components/
21
- │ └── lib-app-config/
22
- │ ├── lib-app-config.component.ts
23
- │ ├── lib-app-config.component.html
24
- │ └── lib-app-config.component.scss
21
+ │ └── lib-app-config/ # Global toast, confirm, loader
25
22
  ├── interceptors/
26
23
  │ ├── api-loader.interceptor.ts
27
24
  │ └── error-catching.interceptor.ts
28
25
  ├── interfaces/
29
- │ └── app-config.interface.ts
26
+ │ └── app-config.interface.ts # IAppConfig, helpers, constants
30
27
  ├── services/
31
28
  │ ├── api-loader.service.ts
32
29
  │ └── base-api.service.ts
33
30
  └── public-api.ts
34
31
  ```
35
32
 
36
- ## Public API Exports
33
+ ## Public API
37
34
 
38
35
  ```typescript
39
36
  // 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';
37
+ export { LibAppConfigComponent } from './components/public-api';
48
38
 
49
39
  // Services
50
- export * from './services/api-loader.service';
51
- export * from './services/base-api.service';
40
+ export { ApiLoaderService, BaseApiService } from './services/public-api';
41
+
42
+ // Interceptors
43
+ export { apiLoaderInterceptor, errorCatchingInterceptor, SKIP_ERROR_TOAST } from './interceptors/public-api';
44
+
45
+ // Interfaces & Types
46
+ export {
47
+ APP_CONFIG, IAppConfig, IServiceConfig, IStorageServiceConfig,
48
+ IAuthorConfig, IRegistrationDefaults, PermissionMode,
49
+ } from './interfaces/public-api';
50
+
51
+ // Helper Functions
52
+ export {
53
+ getDatabaseMode, isServiceEnabled, isFeatureEnabled, getServiceUrl,
54
+ isCompanyFeatureEnabled, isStorageFeatureEnabled, isFormBuilderFeatureEnabled,
55
+ isEmailFeatureEnabled, getPermissionMode, getDefaultStorageConfigId,
56
+ isSignUpEnabled, getDefaultCompanySlug, getDefaultBranchSlug, getRegistrationDefaults,
57
+ } from './interfaces/public-api';
58
+
59
+ // Constants
60
+ export { DEFAULT_APP_NAME, DEFAULT_AUTHOR } from './interfaces/public-api';
52
61
  ```
53
62
 
54
63
  ---
55
64
 
56
- ## Features
57
-
58
- ### 1. APP_CONFIG (Configuration Management)
65
+ ## 1. APP_CONFIG (Configuration Management)
59
66
 
60
67
  Centralized configuration system with dynamic service URLs and feature flags.
61
68
 
62
- #### IAppConfig Interface
69
+ ### IAppConfig Interface
63
70
 
64
71
  ```typescript
65
72
  export type PermissionMode = 'rbac' | 'direct' | 'full';
@@ -69,356 +76,233 @@ export interface IServiceConfig {
69
76
  enabled: boolean;
70
77
  }
71
78
 
72
- export interface IAuthorConfig {
73
- name: string;
74
- url: string;
79
+ export interface IStorageServiceConfig extends IServiceConfig {
80
+ defaultStorageConfigId?: string; // Default storage config for uploads
81
+ }
82
+
83
+ export interface IAuthorConfig { name: string; url: string; }
84
+
85
+ export interface IRegistrationDefaults {
86
+ companySlug?: string; // Pre-fill company code (readonly)
87
+ branchSlug?: string; // Pre-fill branch code (readonly)
75
88
  }
76
89
 
77
90
  export interface IAppConfig {
78
91
  appName: string;
79
- author?: IAuthorConfig; // Author/Brand info for footer attribution
92
+ author?: IAuthorConfig;
80
93
  apiBaseUrl: string; // Default fallback URL
81
- enableCompanyFeature: boolean; // Company/branch selection
94
+ enableCompanyFeature: boolean; // Company selection/switching
95
+ enableSignUp?: boolean; // Registration enabled (default: true)
96
+ registrationDefaults?: IRegistrationDefaults;
82
97
 
83
98
  services: {
84
99
  auth: IServiceConfig;
100
+ administration: IServiceConfig;
85
101
  iam: IServiceConfig;
86
- storage: IServiceConfig;
87
- [key: string]: IServiceConfig; // Extensible for custom services
102
+ storage: IStorageServiceConfig;
103
+ formBuilder: IServiceConfig;
104
+ email: IServiceConfig;
88
105
  };
89
106
 
90
107
  multiTenant: {
91
- enabled: boolean; // true = multi-tenant DB, false = single DB
92
- tenantHeader: string; // Header name (e.g., 'x-tenant-id')
93
- tenantId?: string; // Optional tenant ID value
108
+ enabled: boolean; // true = multi-tenant DB, false = single DB
109
+ tenantHeader: string; // Header name (e.g., 'x-tenant-id')
110
+ tenantId?: string;
94
111
  };
95
112
 
96
- permissionMode?: PermissionMode; // Defaults to 'full'
113
+ permissionMode?: PermissionMode; // Defaults to 'full'
97
114
  }
98
- ```
99
-
100
- #### APP_CONFIG Token
101
115
 
102
- ```typescript
103
116
  export const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');
104
117
  ```
105
118
 
106
- **Provider setup in app.config.ts:**
107
-
108
- ```typescript
109
- import { APP_CONFIG } from '@flusys/ng-core';
110
- import { DEFAULT_APP_CONFIG } from './config/app.config';
111
-
112
- export const appConfig: ApplicationConfig = {
113
- providers: [
114
- { provide: APP_CONFIG, useValue: DEFAULT_APP_CONFIG },
115
- ]
116
- };
117
- ```
119
+ ### Helper Functions
118
120
 
119
- #### Environment Structure (Shared Base Pattern)
121
+ | Function | Returns | Description |
122
+ |----------|---------|-------------|
123
+ | `getDatabaseMode(config)` | `'single' \| 'multi-tenant'` | Database mode from `multiTenant.enabled` |
124
+ | `isServiceEnabled(config, name)` | `boolean` | Check if service is enabled |
125
+ | `isFeatureEnabled(config, feature)` | `boolean` | Same as above with strict ServiceName type |
126
+ | `getServiceUrl(config, name)` | `string` | Service URL or fallback to `apiBaseUrl` |
127
+ | `isCompanyFeatureEnabled(config)` | `boolean` | Check `enableCompanyFeature` |
128
+ | `isStorageFeatureEnabled(config)` | `boolean` | Check `services.storage.enabled` |
129
+ | `isFormBuilderFeatureEnabled(config)` | `boolean` | Check `services.formBuilder.enabled` |
130
+ | `isEmailFeatureEnabled(config)` | `boolean` | Check `services.email.enabled` |
131
+ | `getPermissionMode(config)` | `PermissionMode` | Permission mode (default: `'full'`) |
132
+ | `getDefaultStorageConfigId(config)` | `string \| undefined` | Default storage config ID |
133
+ | `isSignUpEnabled(config)` | `boolean` | Sign-up enabled (default: `true`) |
134
+ | `getDefaultCompanySlug(config)` | `string \| undefined` | Registration default company |
135
+ | `getDefaultBranchSlug(config)` | `string \| undefined` | Registration default branch |
136
+ | `getRegistrationDefaults(config)` | `IRegistrationDefaults \| undefined` | Full registration defaults |
137
+
138
+ **ServiceName:** `'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email'`
120
139
 
121
- All common configuration is extracted to a shared base:
140
+ ### Constants
122
141
 
123
142
  ```typescript
124
- // projects/flusysng/src/environments/environment.base.ts
125
- export const BASE_ENVIRONMENT = {
126
- appName: 'FLUSYS',
127
- multiTenant: { enabled: false, tenantHeader: 'x-tenant-id' },
128
- storage: { apiPath: '/upload/file', maxFileSize: 10485760, allowedMimeTypes: [...] },
129
- };
130
-
131
- // projects/flusysng/src/environments/environment.ts (development)
132
- import { BASE_ENVIRONMENT } from './environment.base';
133
-
134
- export const environment = {
135
- ...BASE_ENVIRONMENT,
136
- production: false,
137
- apiBaseUrl: 'http://localhost:2002',
138
- services: {
139
- auth: { baseUrl: 'http://localhost:2002/auth', enabled: true },
140
- iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
141
- storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
142
- },
143
- };
143
+ export const DEFAULT_APP_NAME = 'FLUSYS';
144
+ export const DEFAULT_AUTHOR: IAuthorConfig = { name: 'Flusys', url: 'https://flusys.vercel.app' };
144
145
  ```
145
146
 
146
- #### Helper Functions
147
+ ### Configuration Modes
147
148
 
148
- ```typescript
149
- import {
150
- getDatabaseMode,
151
- isFeatureEnabled,
152
- getServiceUrl,
153
- isServiceEnabled,
154
- isCompanyFeatureEnabled,
155
- isStorageFeatureEnabled,
156
- getPermissionMode,
157
- } from '@flusys/ng-core';
158
- ```
149
+ | Mode | `enableCompanyFeature` | `multiTenant.enabled` | Services |
150
+ |------|------------------------|----------------------|----------|
151
+ | Simple | `false` | `false` | Disabled |
152
+ | Single DB + Multi-Company | `true` | `false` | Enabled |
153
+ | Multi-Tenant | `true` | `true` | Enabled |
159
154
 
160
- | Function | Signature | Description |
161
- |----------|-----------|-------------|
162
- | `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 |
166
- | `isCompanyFeatureEnabled` | `(config) → boolean` | Check `enableCompanyFeature` flag |
167
- | `isStorageFeatureEnabled` | `(config) → boolean` | Check `services.storage.enabled` |
168
- | `getPermissionMode` | `(config) → PermissionMode` | Get permission mode (defaults to `'full'`) |
155
+ ---
169
156
 
170
- #### Constants
157
+ ## 2. HTTP Interceptors
171
158
 
172
- ```typescript
173
- export const DEFAULT_APP_NAME = 'FLUSYS';
174
- export const DEFAULT_AUTHOR: IAuthorConfig = {
175
- name: 'Flusys',
176
- url: 'https://flusys.vercel.app',
177
- };
178
- ```
159
+ Both use modern Angular functional interceptor pattern.
179
160
 
180
- #### Configuration Modes
161
+ ### apiLoaderInterceptor
181
162
 
182
- **Mode 1: Simple (No Company, No IAM)**
183
- ```typescript
184
- {
185
- enableCompanyFeature: false,
186
- multiTenant: { enabled: false, tenantHeader: 'x-tenant-id' },
187
- services: {
188
- auth: { baseUrl: '...', enabled: false },
189
- iam: { baseUrl: '...', enabled: false },
190
- storage: { baseUrl: '...', enabled: false },
191
- }
192
- }
193
- ```
163
+ Tracks loading state with tagged loaders via `x-loader-tag` header.
194
164
 
195
- **Mode 2: Single DB + Multi-Company**
196
165
  ```typescript
197
- {
198
- enableCompanyFeature: true,
199
- multiTenant: { enabled: false, tenantHeader: 'x-tenant-id' },
200
- services: {
201
- auth: { baseUrl: 'http://localhost:2002/auth', enabled: true },
202
- iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
203
- storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
204
- }
205
- }
206
- ```
166
+ export function apiLoaderInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
167
+ const loaderService = inject(ApiLoaderService);
168
+ const loaderTag = req.headers.get('x-loader-tag') ?? loaderService.DEFAULT_TAG;
207
169
 
208
- **Mode 3: Multi-Tenant**
209
- ```typescript
210
- {
211
- enableCompanyFeature: true,
212
- multiTenant: { enabled: true, tenantHeader: 'x-tenant-id', tenantId: 'tenant-123' },
213
- services: {
214
- auth: { baseUrl: 'http://localhost:2002/auth', enabled: true },
215
- iam: { baseUrl: 'http://localhost:2002/iam', enabled: true },
216
- storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
217
- }
170
+ loaderService.increase(loaderTag);
171
+ const cleanReq = req.clone({ headers: req.headers.delete('x-loader-tag') });
172
+
173
+ return next(cleanReq).pipe(finalize(() => loaderService.decrease(loaderTag)));
218
174
  }
219
175
  ```
220
176
 
221
- ---
222
-
223
- ### 2. HTTP Interceptors
224
-
225
- ng-core provides two functional interceptors. Both use the modern Angular functional interceptor pattern.
226
-
227
- #### apiLoaderInterceptor
228
-
229
- Tracks HTTP request loading state with tagged loaders. Uses a custom `x-loader-tag` header to group related requests.
230
-
231
- **Behavior:**
232
- 1. Reads `x-loader-tag` header from request (defaults to `'default'`)
233
- 2. Increments loader count for that tag
234
- 3. Removes `x-loader-tag` header before sending to backend
235
- 4. Decrements loader count on request completion (success or error)
236
-
177
+ **Usage:**
237
178
  ```typescript
238
- import { apiLoaderInterceptor } from '@flusys/ng-core';
239
-
240
- // Tag a specific request
241
179
  this.http.post('/api/users/get-all', filter, {
242
180
  headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })
243
181
  });
244
-
245
- // Check loading state for that tag
246
- apiLoaderService.getLoader('users-list')(); // true while loading
247
182
  ```
248
183
 
249
- #### errorCatchingInterceptor
184
+ ### errorCatchingInterceptor
250
185
 
251
- Displays user-friendly error toast messages for failed HTTP requests via PrimeNG `MessageService`.
186
+ Displays error toasts via PrimeNG `MessageService`.
252
187
 
253
- **Behavior:**
254
- - Skips 401 errors (handled by `tokenRefreshInterceptor` in ng-auth)
255
- - 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
257
- - SSR-safe: only displays messages in browser (`isPlatformBrowser` check)
188
+ | Skips | Reason |
189
+ |-------|--------|
190
+ | 401 | Handled by `tokenRefreshInterceptor` |
191
+ | 403 | Handled by permission UI |
192
+ | `/check-login` | Expected to fail when not logged in |
193
+ | `SKIP_ERROR_TOAST` context | Component handles locally |
258
194
 
259
- #### Interceptor Chain
195
+ **Error Extraction:** Uses `error.error.error` as summary, `error.error.message` as detail (handles NestJS array format). SSR-safe.
260
196
 
197
+ **Skip Toast for Local Handling:**
261
198
  ```typescript
262
- import { errorCatchingInterceptor, apiLoaderInterceptor } from '@flusys/ng-core';
263
- import { authInterceptor, tokenRefreshInterceptor } from '@flusys/ng-auth';
264
-
265
- provideHttpClient(
266
- withFetch(),
267
- withInterceptors([
268
- authInterceptor, // from @flusys/ng-auth (adds auth + tenant headers)
269
- tokenRefreshInterceptor, // from @flusys/ng-auth (handles 401 + token refresh)
270
- errorCatchingInterceptor, // from @flusys/ng-core (displays error toasts)
271
- apiLoaderInterceptor, // from @flusys/ng-core (tracks loading state)
272
- ])
273
- )
199
+ this.http.post(url, data, { context: new HttpContext().set(SKIP_ERROR_TOAST, true) });
274
200
  ```
275
201
 
276
- **Order matters:** Auth interceptors first, then error handling, then loader tracking.
277
-
278
202
  ---
279
203
 
280
- ### 3. Services
281
-
282
- #### ApiLoaderService
283
-
284
- Signal-based loading state management with tagged loaders. Provided at root level.
204
+ ## 3. Services
285
205
 
286
- **Signals:**
206
+ ### ApiLoaderService
287
207
 
288
- | Signal | Type | Description |
289
- |--------|------|-------------|
290
- | `show` | `Signal<boolean>` | `true` if ANY loader tag has active requests |
208
+ Signal-based loading state with tagged loaders.
291
209
 
292
- **Methods:**
210
+ ```typescript
211
+ @Injectable({ providedIn: 'root' })
212
+ export class ApiLoaderService {
213
+ private readonly loaders = signal<Record<string, number>>({});
214
+ readonly DEFAULT_TAG = 'default';
293
215
 
294
- | Method | Parameters | Description |
295
- |--------|-----------|-------------|
296
- | `increase(name?)` | `name: string = 'default'` | Increment loader count for a tag |
297
- | `decrease(name?)` | `name: string = 'default'` | Decrement loader count for a tag (min 0) |
298
- | `getLoader(name)` | `name: string` | Get computed `Signal<boolean>` for a specific tag |
216
+ readonly show: Signal<boolean> = computed(() =>
217
+ Object.values(this.loaders()).some((count) => count > 0)
218
+ );
299
219
 
300
- **Properties:**
220
+ getLoader(name: string): Signal<boolean> {
221
+ return computed(() => (this.loaders()[name] ?? 0) > 0);
222
+ }
301
223
 
302
- | Property | Type | Description |
303
- |----------|------|-------------|
304
- | `DEFAULT_TAG` | `string` | Default tag name (`'default'`) |
224
+ increase(name: string = this.DEFAULT_TAG): void { /* increment */ }
225
+ decrease(name: string = this.DEFAULT_TAG): void { /* decrement, min 0 */ }
226
+ }
227
+ ```
305
228
 
306
229
  **Usage:**
307
-
308
230
  ```typescript
309
- // Global loading state
310
- @if (apiLoaderService.show()) {
311
- <p-progressBar mode="indeterminate" />
312
- }
313
-
314
- // Tag-specific loading state
315
- @if (apiLoaderService.getLoader('users-list')()) {
316
- <p>Loading users...</p>
317
- }
231
+ @if (apiLoaderService.show()) { <p-progressBar mode="indeterminate" /> }
232
+ @if (apiLoaderService.getLoader('users-list')()) { <p-skeleton /> }
318
233
  ```
319
234
 
320
- #### BaseApiService
235
+ ### BaseApiService
321
236
 
322
- Abstract base class for API services. Provides automatic URL resolution from APP_CONFIG.
237
+ Abstract base for API services with automatic URL resolution.
323
238
 
324
- **Protected members:**
325
-
326
- | Member | Type | Description |
327
- |--------|------|-------------|
328
- | `http` | `HttpClient` | Injected HTTP client |
329
- | `appConfig` | `IAppConfig` | Injected app configuration |
330
- | `baseUrl` | `string` | Resolved base URL for the service |
239
+ ```typescript
240
+ export abstract class BaseApiService {
241
+ protected readonly http = inject(HttpClient);
242
+ protected readonly appConfig = inject(APP_CONFIG);
243
+ protected readonly baseUrl: string;
331
244
 
332
- **Protected methods:**
245
+ constructor(serviceName: string) {
246
+ this.baseUrl = getServiceUrl(this.appConfig, serviceName);
247
+ }
333
248
 
334
- | Method | Parameters | Returns | Description |
335
- |--------|-----------|---------|-------------|
336
- | `getUrl(endpoint)` | `endpoint: string` | `string` | Build full URL, removes leading slash to avoid double slashes |
249
+ protected getUrl(endpoint: string): string {
250
+ const clean = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
251
+ return `${this.baseUrl}/${clean}`;
252
+ }
253
+ }
254
+ ```
337
255
 
338
256
  **Usage:**
339
-
340
257
  ```typescript
341
258
  @Injectable({ providedIn: 'root' })
342
259
  export class UserApiService extends BaseApiService {
343
- constructor() {
344
- super('auth'); // Resolves to services.auth.baseUrl
345
- }
346
-
347
- getAll(filter: any) {
348
- return this.http.post(this.getUrl('user/get-all'), filter);
349
- }
350
-
351
- getById(id: string) {
352
- return this.http.post(`${this.getUrl('user/get')}/${id}`, {});
353
- }
260
+ constructor() { super('auth'); }
261
+ getAll(filter: any) { return this.http.post(this.getUrl('user/get-all'), filter); }
354
262
  }
355
263
  ```
356
264
 
357
265
  ---
358
266
 
359
- ### 4. Components
267
+ ## 4. Components
360
268
 
361
- #### LibAppConfigComponent
269
+ ### LibAppConfigComponent
362
270
 
363
- Root configuration component providing global UI elements. Include once at app root.
271
+ Root configuration component. Include once at app root.
364
272
 
365
- **Selector:** `lib-app-config`
273
+ - **Selector:** `lib-app-config`
274
+ - **Change Detection:** `OnPush`
366
275
 
367
- **Change Detection:** `OnPush` (optimal performance)
368
-
369
- **Provides:**
370
- - Global loading progress bar (fixed top, 4px height, z-index 998)
371
- - Toast notifications (PrimeNG Toast)
372
- - Confirm dialogs (PrimeNG ConfirmDialog)
373
-
374
- **Template:**
375
- ```html
376
- @if (apiLoaderService.show()) {
377
- <p-progressBar mode="indeterminate" />
378
- }
379
- <p-toast />
380
- <p-confirmdialog />
381
- ```
276
+ **Provides:** Global loading progress bar (fixed top, 4px, z-index 998), toast notifications (success uses primary color), confirm dialogs.
382
277
 
383
278
  **Usage:**
384
279
  ```typescript
385
- import { LibAppConfigComponent } from '@flusys/ng-core';
386
-
387
280
  @Component({
388
281
  selector: 'app-root',
389
- standalone: true,
390
282
  imports: [LibAppConfigComponent, RouterOutlet],
391
- template: `
392
- <lib-app-config />
393
- <router-outlet />
394
- `
283
+ template: `<lib-app-config /><router-outlet />`
395
284
  })
396
285
  export class AppComponent {}
397
286
  ```
398
287
 
399
288
  ---
400
289
 
401
- ## Usage Examples
402
-
403
- ### Basic App Setup
290
+ ## Setup
404
291
 
405
292
  ```typescript
406
293
  // app.config.ts
407
- import { ApplicationConfig } from '@angular/core';
408
- import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
409
294
  import { APP_CONFIG, errorCatchingInterceptor, apiLoaderInterceptor } from '@flusys/ng-core';
410
295
  import { authInterceptor, tokenRefreshInterceptor } from '@flusys/ng-auth';
411
- import { DEFAULT_APP_CONFIG } from './config/app.config';
412
296
 
413
297
  export const appConfig: ApplicationConfig = {
414
298
  providers: [
415
299
  provideHttpClient(
416
300
  withFetch(),
417
301
  withInterceptors([
418
- authInterceptor,
419
- tokenRefreshInterceptor,
420
- errorCatchingInterceptor,
421
- apiLoaderInterceptor,
302
+ authInterceptor, // ng-auth: auth + tenant headers
303
+ tokenRefreshInterceptor, // ng-auth: 401 + refresh
304
+ errorCatchingInterceptor, // ng-core: error toasts
305
+ apiLoaderInterceptor, // ng-core: loading state
422
306
  ])
423
307
  ),
424
308
  { provide: APP_CONFIG, useValue: DEFAULT_APP_CONFIG },
@@ -426,140 +310,33 @@ export const appConfig: ApplicationConfig = {
426
310
  };
427
311
  ```
428
312
 
429
- ### Creating an API Service
430
-
431
- ```typescript
432
- import { Injectable } from '@angular/core';
433
- import { BaseApiService } from '@flusys/ng-core';
434
-
435
- @Injectable({ providedIn: 'root' })
436
- export class AuthApiService extends BaseApiService {
437
- constructor() {
438
- super('auth'); // Resolves to services.auth.baseUrl
439
- }
440
-
441
- login(credentials: any) {
442
- return this.http.post(this.getUrl('auth/login'), credentials);
443
- }
444
- }
445
- ```
446
-
447
- ### Using Tagged Loaders
448
-
449
- ```typescript
450
- import { HttpHeaders } from '@angular/common/http';
451
- import { ApiLoaderService } from '@flusys/ng-core';
452
-
453
- // In a service - tag requests
454
- this.http.post('/api/users/get-all', filter, {
455
- headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })
456
- });
457
-
458
- // In a component - check specific loader
459
- readonly usersLoading = this.apiLoaderService.getLoader('users-list');
460
-
461
- // In template
462
- @if (usersLoading()) {
463
- <p-skeleton />
464
- }
465
- ```
466
-
467
313
  ---
468
314
 
469
315
  ## Best Practices
470
316
 
471
- 1. **Always provide APP_CONFIG** at application root level
472
- 2. **Match backend configuration mode** (`multiTenant.enabled` should align with FLUSYS_NEST setup)
473
- 3. **Enable features based on backend availability** (`services.iam.enabled` only if IAM module is deployed)
474
- 4. **Never import from other @flusys packages** in ng-core
475
- 5. **Keep ng-core minimal** - only foundation functionality
476
- 6. **Use `BaseApiService`** for all API services to avoid hardcoded URLs
477
- 7. **Don't create custom error handling** - use `errorCatchingInterceptor`
478
- 8. **Don't manually track loading** - use `apiLoaderInterceptor` with `x-loader-tag` headers
317
+ 1. **Always provide APP_CONFIG** at application root
318
+ 2. **Match backend mode** - `multiTenant.enabled` should align with FLUSYS_NEST
319
+ 3. **Never import other @flusys packages** in ng-core
320
+ 4. **Use `BaseApiService`** for all API services
321
+ 5. **Use tagged loaders** for granular loading states
479
322
 
480
323
  ## Common Issues
481
324
 
482
- ### APP_CONFIG Not Found
483
-
484
- **Problem:** `NullInjectorError: No provider for InjectionToken APP_CONFIG`
485
-
486
- **Solution:** Provide APP_CONFIG in app.config.ts:
487
- ```typescript
488
- { provide: APP_CONFIG, useValue: DEFAULT_APP_CONFIG }
489
- ```
490
-
491
- ### Circular Dependency
492
-
493
- **Problem:** Build fails with circular dependency error involving ng-core.
494
-
495
- **Solution:** ng-core should NEVER import from other @flusys packages. Check imports and remove any references to ng-shared, ng-layout, or ng-auth.
325
+ | Issue | Solution |
326
+ |-------|----------|
327
+ | `NullInjectorError: APP_CONFIG` | Add `{ provide: APP_CONFIG, useValue: config }` to providers |
328
+ | Circular dependency | ng-core must NOT import other @flusys packages |
329
+ | Loader flickers | Counter-based system handles concurrent requests automatically |
330
+ | No error toast | Check: MessageService configured, not 401/403, not SKIP_ERROR_TOAST |
496
331
 
497
332
  ---
498
333
 
499
- ## API Reference
500
-
501
- ### Tokens
502
-
503
- | Token | Type | Description |
504
- |-------|------|-------------|
505
- | `APP_CONFIG` | `InjectionToken<IAppConfig>` | Application configuration |
506
-
507
- ### Interceptors
508
-
509
- | Function | Description |
510
- |----------|-------------|
511
- | `apiLoaderInterceptor` | Loading state management with tagged loaders |
512
- | `errorCatchingInterceptor` | Error toast display (skips 401 and check-login) |
513
-
514
- ### Services
515
-
516
- | Service | Description |
517
- |---------|-------------|
518
- | `ApiLoaderService` | Signal-based loading state with tagged loaders |
519
- | `BaseApiService` | Abstract base class for API services with URL resolution |
520
-
521
- ### Interfaces & Types
522
-
523
- | Export | Description |
524
- |--------|-------------|
525
- | `IAppConfig` | App configuration interface |
526
- | `IServiceConfig` | Service configuration (`baseUrl`, `enabled`) |
527
- | `IAuthorConfig` | Author/brand configuration (`name`, `url`) |
528
- | `PermissionMode` | `'rbac' \| 'direct' \| 'full'` |
529
-
530
- ### Helper Functions
531
-
532
- | Function | Description |
533
- |----------|-------------|
534
- | `getDatabaseMode(config)` | Returns `'single'` or `'multi-tenant'` |
535
- | `isFeatureEnabled(config, feature)` | Check service feature enabled (`'auth' \| 'iam' \| 'storage'`) |
536
- | `getServiceUrl(config, serviceName)` | Get service URL with fallback |
537
- | `isServiceEnabled(config, serviceName)` | Check if service is enabled |
538
- | `isCompanyFeatureEnabled(config)` | Check company feature flag |
539
- | `isStorageFeatureEnabled(config)` | Check storage service enabled |
540
- | `getPermissionMode(config)` | Get permission mode (default `'full'`) |
541
-
542
- ### Constants
543
-
544
- | Constant | Value | Description |
545
- |----------|-------|-------------|
546
- | `DEFAULT_APP_NAME` | `'FLUSYS'` | Default application name |
547
- | `DEFAULT_AUTHOR` | `{ name: 'Flusys', url: '...' }` | Default author/brand info |
548
-
549
- ### Components
550
-
551
- | Component | Selector | Description |
552
- |-----------|----------|-------------|
553
- | `LibAppConfigComponent` | `lib-app-config` | Global toast, confirm dialog, and loading bar |
554
-
555
334
  ## See Also
556
335
 
557
- - **[SHARED-GUIDE.md](./SHARED-GUIDE.md)** - API services, components, directives
558
- - **[LAYOUT-GUIDE.md](./LAYOUT-GUIDE.md)** - Layout system
559
- - **[Angular Patterns](../../.claude/docs/angular/)** - Framework patterns
560
- - **[FLUSYS_NEST/docs/CORE-GUIDE.md](../../FLUSYS_NEST/docs/CORE-GUIDE.md)** - Backend core module
336
+ - [SHARED-GUIDE.md](./SHARED-GUIDE.md) - Components, directives, pipes
337
+ - [LAYOUT-GUIDE.md](./LAYOUT-GUIDE.md) - Layout system
338
+ - [FLUSYS_NEST/docs/CORE-GUIDE.md](../../FLUSYS_NEST/docs/CORE-GUIDE.md) - Backend core
561
339
 
562
340
  ---
563
341
 
564
- **Last Updated:** 2026-02-21
565
- **Angular Version:** 21
342
+ **Last Updated:** 2026-02-25 | **Version:** 3.0.1 | **Angular:** 21
@@ -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,10 +49,10 @@ 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
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
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
  }] });
@@ -202,68 +202,66 @@ function getDefaultSummary(status) {
202
202
  }
203
203
  }
204
204
 
205
- /**
206
- * App Configuration Token
207
- * Use this to inject app config in services/components
208
- */
209
205
  const APP_CONFIG = new InjectionToken('APP_CONFIG');
210
- /**
211
- * Get database mode from config
212
- * Derived from multiTenant.enabled
213
- */
214
206
  function getDatabaseMode(config) {
215
207
  return config.multiTenant.enabled ? 'multi-tenant' : 'single';
216
208
  }
217
- /**
218
- * Check if a service is enabled
219
- */
220
209
  function isServiceEnabled(config, serviceName) {
221
210
  const service = config.services[serviceName];
222
211
  return service?.enabled ?? false;
223
212
  }
224
- /**
225
- * Check if a feature is enabled (alias for isServiceEnabled with strict types)
226
- */
227
213
  function isFeatureEnabled(config, feature) {
228
214
  return isServiceEnabled(config, feature);
229
215
  }
230
- /**
231
- * Get service base URL or fallback to default apiBaseUrl
232
- */
233
216
  function getServiceUrl(config, serviceName) {
234
217
  if (isServiceEnabled(config, serviceName)) {
235
218
  const service = config.services[serviceName];
236
- return service.baseUrl;
219
+ return service?.baseUrl ?? config.apiBaseUrl;
237
220
  }
238
221
  return config.apiBaseUrl;
239
222
  }
240
- /**
241
- * Check if company feature is enabled
242
- */
243
223
  function isCompanyFeatureEnabled(config) {
244
224
  return config.enableCompanyFeature ?? false;
245
225
  }
246
- /**
247
- * Check if storage feature is enabled
248
- */
249
226
  function isStorageFeatureEnabled(config) {
250
227
  return isServiceEnabled(config, 'storage');
251
228
  }
252
- /**
253
- * Get permission mode from config
254
- * Defaults to 'full' if not specified
255
- */
229
+ function isFormBuilderFeatureEnabled(config) {
230
+ return isServiceEnabled(config, 'formBuilder');
231
+ }
232
+ function isEmailFeatureEnabled(config) {
233
+ return isServiceEnabled(config, 'email');
234
+ }
235
+ function isEventManagerFeatureEnabled(config) {
236
+ return isServiceEnabled(config, 'eventManager');
237
+ }
238
+ function isNotificationFeatureEnabled(config) {
239
+ return isServiceEnabled(config, 'notification');
240
+ }
241
+ function getNotificationSocketUrl(config) {
242
+ const service = config.services.notification;
243
+ if (!service?.enabled)
244
+ return null;
245
+ return service.socketUrl ?? service.baseUrl;
246
+ }
256
247
  function getPermissionMode(config) {
257
248
  return config.permissionMode ?? 'full';
258
249
  }
259
- /**
260
- * Get default storage config ID from config
261
- * Used for file upload operations when no storageConfigId is provided
262
- */
263
250
  function getDefaultStorageConfigId(config) {
264
251
  return config.services.storage?.defaultStorageConfigId;
265
252
  }
266
- // Default branding constants
253
+ function isSignUpEnabled(config) {
254
+ return config.enableSignUp ?? true;
255
+ }
256
+ function getDefaultCompanySlug(config) {
257
+ return config.registrationDefaults?.companySlug;
258
+ }
259
+ function getDefaultBranchSlug(config) {
260
+ return config.registrationDefaults?.branchSlug;
261
+ }
262
+ function getRegistrationDefaults(config) {
263
+ return config.registrationDefaults;
264
+ }
267
265
  const DEFAULT_APP_NAME = 'FLUSYS';
268
266
  const DEFAULT_AUTHOR = {
269
267
  name: 'Flusys',
@@ -318,5 +316,5 @@ class BaseApiService {
318
316
  * Generated bundle index. Do not edit.
319
317
  */
320
318
 
321
- export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultStorageConfigId, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isFeatureEnabled, isServiceEnabled, isStorageFeatureEnabled };
319
+ export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getNotificationSocketUrl, getPermissionMode, getRegistrationDefaults, getServiceUrl, isCompanyFeatureEnabled, isEmailFeatureEnabled, isEventManagerFeatureEnabled, isFeatureEnabled, isFormBuilderFeatureEnabled, isNotificationFeatureEnabled, isServiceEnabled, isSignUpEnabled, isStorageFeatureEnabled };
322
320
  //# 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 { 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 * 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 * 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 };\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' | 'iam' | 'storage',\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' | 'iam' | 'storage' | (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 * 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 * 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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"}
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\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\nexport interface IServiceConfig {\n baseUrl: string;\n enabled: boolean;\n}\n\nexport interface IStorageServiceConfig extends IServiceConfig {\n /** Default storage config ID (falls back to company/branch default if not set) */\n defaultStorageConfigId?: string;\n}\n\nexport interface INotificationServiceConfig extends IServiceConfig {\n /** WebSocket URL (defaults to baseUrl if not set) */\n socketUrl?: string;\n}\n\nexport interface IAuthorConfig {\n name: string;\n url: string;\n}\n\nexport interface IRegistrationDefaults {\n companySlug?: string;\n branchSlug?: string;\n}\n\nexport interface IAppConfig {\n appName: string;\n author?: IAuthorConfig;\n apiBaseUrl: string;\n enableCompanyFeature: boolean;\n /** @default true */\n enableSignUp?: boolean;\n registrationDefaults?: IRegistrationDefaults;\n services: {\n auth: IServiceConfig;\n administration: IServiceConfig;\n iam: IServiceConfig;\n storage: IStorageServiceConfig;\n formBuilder: IServiceConfig;\n email: IServiceConfig;\n eventManager: IServiceConfig;\n notification?: INotificationServiceConfig;\n };\n multiTenant: {\n enabled: boolean;\n tenantHeader: string;\n tenantId?: string;\n };\n /** 'rbac' | 'direct' | 'full' (default: 'full') */\n permissionMode?: PermissionMode;\n}\n\nexport const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');\n\nexport function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant' {\n return config.multiTenant.enabled ? 'multi-tenant' : 'single';\n}\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\nexport function isFeatureEnabled(\n config: IAppConfig,\n feature: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | 'eventManager' | 'notification',\n): boolean {\n return isServiceEnabled(config, feature);\n}\n\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | 'eventManager' | 'notification' | (string & {}),\n): string {\n if (isServiceEnabled(config, serviceName)) {\n const service = config.services[serviceName as keyof typeof config.services];\n return service?.baseUrl ?? config.apiBaseUrl;\n }\n return config.apiBaseUrl;\n}\n\nexport function isCompanyFeatureEnabled(config: IAppConfig): boolean {\n return config.enableCompanyFeature ?? false;\n}\n\nexport function isStorageFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'storage');\n}\n\nexport function isFormBuilderFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'formBuilder');\n}\n\nexport function isEmailFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'email');\n}\n\nexport function isEventManagerFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'eventManager');\n}\n\nexport function isNotificationFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'notification');\n}\n\nexport function getNotificationSocketUrl(config: IAppConfig): string | null {\n const service = config.services.notification;\n if (!service?.enabled) return null;\n return service.socketUrl ?? service.baseUrl;\n}\n\nexport function getPermissionMode(config: IAppConfig): PermissionMode {\n return config.permissionMode ?? 'full';\n}\n\nexport function getDefaultStorageConfigId(config: IAppConfig): string | undefined {\n return config.services.storage?.defaultStorageConfigId;\n}\n\nexport function isSignUpEnabled(config: IAppConfig): boolean {\n return config.enableSignUp ?? true;\n}\n\nexport function getDefaultCompanySlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.companySlug;\n}\n\nexport function getDefaultBranchSlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.branchSlug;\n}\n\nexport function getRegistrationDefaults(config: IAppConfig): IRegistrationDefaults | undefined {\n return config.registrationDefaults;\n}\n\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": "3.0.0-rc",
3
+ "version": "3.0.1",
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",
@@ -25,4 +25,4 @@
25
25
  "dependencies": {
26
26
  "tslib": "^2.3.0"
27
27
  }
28
- }
28
+ }
@@ -80,120 +80,71 @@ declare const SKIP_ERROR_TOAST: HttpContextToken<boolean>;
80
80
  */
81
81
  declare function errorCatchingInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>>;
82
82
 
83
- /**
84
- * IAM Permission Mode - controls permission system behavior
85
- */
86
83
  type PermissionMode = 'rbac' | 'direct' | 'full';
87
- /**
88
- * Service Configuration Interface
89
- */
90
84
  interface IServiceConfig {
91
85
  baseUrl: string;
92
86
  enabled: boolean;
93
87
  }
94
- /**
95
- * Storage Service Configuration Interface
96
- * Extends base service config with storage-specific options
97
- */
98
88
  interface IStorageServiceConfig extends IServiceConfig {
99
- /**
100
- * Default storage config ID from backend storage_config table
101
- * Used when no storageConfigId is provided to upload operations
102
- * If not set, backend will use company/branch default config
103
- */
89
+ /** Default storage config ID (falls back to company/branch default if not set) */
104
90
  defaultStorageConfigId?: string;
105
91
  }
106
- /**
107
- * Author/Brand Configuration
108
- */
92
+ interface INotificationServiceConfig extends IServiceConfig {
93
+ /** WebSocket URL (defaults to baseUrl if not set) */
94
+ socketUrl?: string;
95
+ }
109
96
  interface IAuthorConfig {
110
97
  name: string;
111
98
  url: string;
112
99
  }
113
- /**
114
- * App Configuration Interface
115
- * Centralized configuration for feature toggles and behavior control
116
- */
100
+ interface IRegistrationDefaults {
101
+ companySlug?: string;
102
+ branchSlug?: string;
103
+ }
117
104
  interface IAppConfig {
118
- /** Application name */
119
105
  appName: string;
120
- /** Author/Brand info for footer attribution */
121
106
  author?: IAuthorConfig;
122
- /** Default API base URL (fallback) */
123
107
  apiBaseUrl: string;
124
- /**
125
- * Enable company feature across the app
126
- * When true, enables company selection/switching in layout and feature modules
127
- */
128
108
  enableCompanyFeature: boolean;
129
- /**
130
- * Service-specific base URLs
131
- * If a service exists in this object, its feature is enabled
132
- * Database mode is determined by multiTenant.enabled
133
- */
109
+ /** @default true */
110
+ enableSignUp?: boolean;
111
+ registrationDefaults?: IRegistrationDefaults;
134
112
  services: {
135
113
  auth: IServiceConfig;
114
+ administration: IServiceConfig;
136
115
  iam: IServiceConfig;
137
116
  storage: IStorageServiceConfig;
117
+ formBuilder: IServiceConfig;
118
+ email: IServiceConfig;
119
+ eventManager: IServiceConfig;
120
+ notification?: INotificationServiceConfig;
138
121
  };
139
- /**
140
- * Multi-tenant configuration
141
- * enabled: true → Database mode = multi-tenant
142
- * enabled: false → Database mode = single
143
- */
144
122
  multiTenant: {
145
123
  enabled: boolean;
146
124
  tenantHeader: string;
147
125
  tenantId?: string;
148
126
  };
149
- /**
150
- * IAM Permission Mode - controls permission system behavior
151
- * - 'rbac': Role-Based Access Control only (users get permissions via roles)
152
- * - 'direct': Direct action assignments only (actions assigned directly to users)
153
- * - 'full': Both RBAC and direct permissions (most flexible, default)
154
- */
127
+ /** 'rbac' | 'direct' | 'full' (default: 'full') */
155
128
  permissionMode?: PermissionMode;
156
129
  }
157
- /**
158
- * App Configuration Token
159
- * Use this to inject app config in services/components
160
- */
161
130
  declare const APP_CONFIG: InjectionToken<IAppConfig>;
162
- /**
163
- * Get database mode from config
164
- * Derived from multiTenant.enabled
165
- */
166
131
  declare function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant';
167
- /**
168
- * Check if a service is enabled
169
- */
170
132
  declare function isServiceEnabled(config: IAppConfig, serviceName: 'auth' | 'iam' | 'storage' | (string & {})): boolean;
171
- /**
172
- * Check if a feature is enabled (alias for isServiceEnabled with strict types)
173
- */
174
- declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'iam' | 'storage'): boolean;
175
- /**
176
- * Get service base URL or fallback to default apiBaseUrl
177
- */
178
- declare function getServiceUrl(config: IAppConfig, serviceName: 'auth' | 'iam' | 'storage' | (string & {})): string;
179
- /**
180
- * Check if company feature is enabled
181
- */
133
+ declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | 'eventManager' | 'notification'): boolean;
134
+ declare function getServiceUrl(config: IAppConfig, serviceName: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | 'eventManager' | 'notification' | (string & {})): string;
182
135
  declare function isCompanyFeatureEnabled(config: IAppConfig): boolean;
183
- /**
184
- * Check if storage feature is enabled
185
- */
186
136
  declare function isStorageFeatureEnabled(config: IAppConfig): boolean;
187
- /**
188
- * Get permission mode from config
189
- * Defaults to 'full' if not specified
190
- */
137
+ declare function isFormBuilderFeatureEnabled(config: IAppConfig): boolean;
138
+ declare function isEmailFeatureEnabled(config: IAppConfig): boolean;
139
+ declare function isEventManagerFeatureEnabled(config: IAppConfig): boolean;
140
+ declare function isNotificationFeatureEnabled(config: IAppConfig): boolean;
141
+ declare function getNotificationSocketUrl(config: IAppConfig): string | null;
191
142
  declare function getPermissionMode(config: IAppConfig): PermissionMode;
192
- /**
193
- * Get default storage config ID from config
194
- * Used for file upload operations when no storageConfigId is provided
195
- */
196
143
  declare function getDefaultStorageConfigId(config: IAppConfig): string | undefined;
144
+ declare function isSignUpEnabled(config: IAppConfig): boolean;
145
+ declare function getDefaultCompanySlug(config: IAppConfig): string | undefined;
146
+ declare function getDefaultBranchSlug(config: IAppConfig): string | undefined;
147
+ declare function getRegistrationDefaults(config: IAppConfig): IRegistrationDefaults | undefined;
197
148
  declare const DEFAULT_APP_NAME = "FLUSYS";
198
149
  declare const DEFAULT_AUTHOR: IAuthorConfig;
199
150
 
@@ -233,5 +184,5 @@ declare abstract class BaseApiService {
233
184
  protected getUrl(endpoint: string): string;
234
185
  }
235
186
 
236
- export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultStorageConfigId, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isFeatureEnabled, isServiceEnabled, isStorageFeatureEnabled };
237
- export type { IAppConfig, IAuthorConfig, IServiceConfig, IStorageServiceConfig, PermissionMode };
187
+ export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getNotificationSocketUrl, getPermissionMode, getRegistrationDefaults, getServiceUrl, isCompanyFeatureEnabled, isEmailFeatureEnabled, isEventManagerFeatureEnabled, isFeatureEnabled, isFormBuilderFeatureEnabled, isNotificationFeatureEnabled, isServiceEnabled, isSignUpEnabled, isStorageFeatureEnabled };
188
+ export type { IAppConfig, IAuthorConfig, INotificationServiceConfig, IRegistrationDefaults, IServiceConfig, IStorageServiceConfig, PermissionMode };