@flusys/ng-core 1.1.1-beta → 1.2.0-rc.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
@@ -1,563 +1,153 @@
1
- # @flusys/ng-core Package Guide
1
+ # @flusys/ng-core
2
2
 
3
- ## Overview
3
+ Foundation library for the FLUSYS Angular platform — app configuration token, base HTTP service, loader service, and HTTP interceptors.
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.
6
-
7
- **Key Principle:** ng-core has NO internal dependencies on other @flusys packages. All other packages depend on ng-core.
8
-
9
- ## Package Information
10
-
11
- - **Package:** `@flusys/ng-core`
12
- - **Dependencies:** None (base package)
13
- - **Dependents:** ng-shared, ng-layout, ng-auth, ng-iam, ng-storage, flusysng
14
- - **Build Command:** `npm run build:ng-core`
15
-
16
- ## Package Structure
17
-
18
- ```
19
- ng-core/
20
- ├── components/
21
- │ └── lib-app-config/
22
- │ ├── lib-app-config.component.ts
23
- │ ├── lib-app-config.component.html
24
- │ └── lib-app-config.component.scss
25
- ├── interceptors/
26
- │ ├── api-loader.interceptor.ts
27
- │ └── error-catching.interceptor.ts
28
- ├── interfaces/
29
- │ └── app-config.interface.ts
30
- ├── services/
31
- │ ├── api-loader.service.ts
32
- │ └── base-api.service.ts
33
- └── public-api.ts
34
- ```
35
-
36
- ## Public API Exports
37
-
38
- ```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';
52
- ```
5
+ [![npm version](https://img.shields.io/npm/v/@flusys/ng-core.svg)](https://www.npmjs.com/package/@flusys/ng-core)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
53
7
 
54
8
  ---
55
9
 
56
- ## Features
57
-
58
- ### 1. APP_CONFIG (Configuration Management)
59
-
60
- Centralized configuration system with dynamic service URLs and feature flags.
61
-
62
- #### IAppConfig Interface
63
-
64
- ```typescript
65
- export type PermissionMode = 'rbac' | 'direct' | 'full';
66
-
67
- export interface IServiceConfig {
68
- baseUrl: string;
69
- enabled: boolean;
70
- }
71
-
72
- export interface IAuthorConfig {
73
- name: string;
74
- url: string;
75
- }
10
+ ## Installation
76
11
 
77
- export interface IAppConfig {
78
- appName: string;
79
- author?: IAuthorConfig; // Author/Brand info for footer attribution
80
- apiBaseUrl: string; // Default fallback URL
81
- enableCompanyFeature: boolean; // Company/branch selection
82
-
83
- services: {
84
- auth: IServiceConfig;
85
- iam: IServiceConfig;
86
- storage: IServiceConfig;
87
- [key: string]: IServiceConfig; // Extensible for custom services
88
- };
89
-
90
- 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
94
- };
95
-
96
- permissionMode?: PermissionMode; // Defaults to 'full'
97
- }
12
+ ```bash
13
+ npm install @flusys/ng-core
98
14
  ```
99
15
 
100
- #### APP_CONFIG Token
16
+ ---
101
17
 
102
- ```typescript
103
- export const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');
104
- ```
18
+ ## 1. Register APP_CONFIG and Interceptors
105
19
 
106
- **Provider setup in app.config.ts:**
20
+ Provide the config token and HTTP interceptors once in `app.config.ts`. Every FLUSYS package reads from `APP_CONFIG`.
107
21
 
108
22
  ```typescript
109
- import { APP_CONFIG } from '@flusys/ng-core';
110
- import { DEFAULT_APP_CONFIG } from './config/app.config';
23
+ import { ApplicationConfig } from "@angular/core";
24
+ import { provideHttpClient, withFetch, withInterceptors } from "@angular/common/http";
25
+ import { APP_CONFIG, errorCatchingInterceptor, apiLoaderInterceptor } from "@flusys/ng-core";
111
26
 
112
27
  export const appConfig: ApplicationConfig = {
113
28
  providers: [
114
- { provide: APP_CONFIG, useValue: DEFAULT_APP_CONFIG },
115
- ]
116
- };
117
- ```
118
-
119
- #### Environment Structure (Shared Base Pattern)
120
-
121
- All common configuration is extracted to a shared base:
122
-
123
- ```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
- },
29
+ {
30
+ provide: APP_CONFIG,
31
+ useValue: {
32
+ appName: "My App",
33
+ appLogo: "/assets/logo.png",
34
+ apiBaseUrl: "http://localhost:2002",
35
+ production: false,
36
+ permissionMode: "FULL", // 'RBAC' | 'DIRECT' | 'FULL'
37
+ multiTenant: false,
38
+ services: {
39
+ auth: {
40
+ enabled: true,
41
+ features: {
42
+ signUp: true,
43
+ emailVerification: true,
44
+ companySelection: true,
45
+ },
46
+ },
47
+ iam: { enabled: true },
48
+ storage: { enabled: true },
49
+ formBuilder: { enabled: false },
50
+ email: { enabled: false },
51
+ eventManager: { enabled: false },
52
+ notification: { enabled: false, socketUrl: "ws://localhost:2002" }, // optional
53
+ localization: { enabled: false },
54
+ },
55
+ },
56
+ },
57
+ provideHttpClient(
58
+ withFetch(),
59
+ withInterceptors([
60
+ // authInterceptor, // from @flusys/ng-auth — add first
61
+ // tokenRefreshInterceptor, // from @flusys/ng-auth — add second
62
+ errorCatchingInterceptor, // shows toast on non-401/403 errors
63
+ apiLoaderInterceptor, // drives global progress bar
64
+ ]),
65
+ ),
66
+ ],
143
67
  };
144
68
  ```
145
69
 
146
- #### Helper Functions
147
-
148
- ```typescript
149
- import {
150
- getDatabaseMode,
151
- isFeatureEnabled,
152
- getServiceUrl,
153
- isServiceEnabled,
154
- isCompanyFeatureEnabled,
155
- isStorageFeatureEnabled,
156
- getPermissionMode,
157
- } from '@flusys/ng-core';
158
- ```
159
-
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'`) |
70
+ Add `<lib-app-config />` once in your app root template. It renders the global toast, confirm dialog, and progress bar.
169
71
 
170
- #### Constants
72
+ ---
171
73
 
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
- ```
74
+ ## 2. BaseApiService
179
75
 
180
- #### Configuration Modes
76
+ Extend to create a typed service for any POST-only RPC endpoint. The base URL is derived from `APP_CONFIG.apiBaseUrl`.
181
77
 
182
- **Mode 1: Simple (No Company, No IAM)**
183
78
  ```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
- ```
79
+ import { Injectable } from "@angular/core";
80
+ import { BaseApiService } from "@flusys/ng-core";
194
81
 
195
- **Mode 2: Single DB + Multi-Company**
196
- ```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 },
82
+ @Injectable({ providedIn: "root" })
83
+ export class ProductService extends BaseApiService {
84
+ getAll() {
85
+ return this.http.post(`${this.baseUrl}/products/get-all`, {});
204
86
  }
205
- }
206
- ```
207
87
 
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 },
88
+ findById(id: string) {
89
+ return this.http.post(this.getUrl(`products/get/${id}`), {});
217
90
  }
218
91
  }
219
92
  ```
220
93
 
221
94
  ---
222
95
 
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
-
237
- ```typescript
238
- import { apiLoaderInterceptor } from '@flusys/ng-core';
239
-
240
- // Tag a specific request
241
- this.http.post('/api/users/get-all', filter, {
242
- headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })
243
- });
244
-
245
- // Check loading state for that tag
246
- apiLoaderService.getLoader('users-list')(); // true while loading
247
- ```
248
-
249
- #### errorCatchingInterceptor
250
-
251
- Displays user-friendly error toast messages for failed HTTP requests via PrimeNG `MessageService`.
252
-
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)
258
-
259
- #### Interceptor Chain
260
-
261
- ```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
- )
274
- ```
275
-
276
- **Order matters:** Auth interceptors first, then error handling, then loader tracking.
277
-
278
- ---
279
-
280
- ### 3. Services
281
-
282
- #### ApiLoaderService
283
-
284
- Signal-based loading state management with tagged loaders. Provided at root level.
285
-
286
- **Signals:**
287
-
288
- | Signal | Type | Description |
289
- |--------|------|-------------|
290
- | `show` | `Signal<boolean>` | `true` if ANY loader tag has active requests |
291
-
292
- **Methods:**
96
+ ## 3. ApiLoaderService — Global Progress Bar
293
97
 
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 |
299
-
300
- **Properties:**
301
-
302
- | Property | Type | Description |
303
- |----------|------|-------------|
304
- | `DEFAULT_TAG` | `string` | Default tag name (`'default'`) |
305
-
306
- **Usage:**
98
+ `apiLoaderInterceptor` automatically increments/decrements a reference count per request. `show` is `true` while any request is in flight.
307
99
 
308
100
  ```typescript
309
- // Global loading state
310
- @if (apiLoaderService.show()) {
311
- <p-progressBar mode="indeterminate" />
312
- }
101
+ import { ApiLoaderService } from '@flusys/ng-core';
313
102
 
314
- // Tag-specific loading state
315
- @if (apiLoaderService.getLoader('users-list')()) {
316
- <p>Loading users...</p>
317
- }
103
+ readonly loader = inject(ApiLoaderService);
104
+ // loader.show — Signal<boolean>
318
105
  ```
319
106
 
320
- #### BaseApiService
321
-
322
- Abstract base class for API services. Provides automatic URL resolution from APP_CONFIG.
323
-
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 |
331
-
332
- **Protected methods:**
333
-
334
- | Method | Parameters | Returns | Description |
335
- |--------|-----------|---------|-------------|
336
- | `getUrl(endpoint)` | `endpoint: string` | `string` | Build full URL, removes leading slash to avoid double slashes |
337
-
338
- **Usage:**
107
+ Tag requests to get per-section loading signals:
339
108
 
340
109
  ```typescript
341
- @Injectable({ providedIn: 'root' })
342
- 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
- }
354
- }
355
- ```
356
-
357
- ---
358
-
359
- ### 4. Components
360
-
361
- #### LibAppConfigComponent
362
-
363
- Root configuration component providing global UI elements. Include once at app root.
364
-
365
- **Selector:** `lib-app-config`
110
+ import { HttpHeaders } from "@angular/common/http";
366
111
 
367
- **Provides:**
368
- - Global loading progress bar (fixed top, 4px height, z-index 998)
369
- - Toast notifications (PrimeNG Toast)
370
- - Confirm dialogs (PrimeNG ConfirmDialog)
371
-
372
- **Template:**
373
- ```html
374
- @if (apiLoaderService.show()) {
375
- <p-progressBar mode="indeterminate" />
376
- }
377
- <p-toast />
378
- <p-confirmdialog />
379
- ```
112
+ this.http.post(url, body, {
113
+ headers: new HttpHeaders({ "x-loader-tag": "user-list" }),
114
+ });
380
115
 
381
- **Usage:**
382
- ```typescript
383
- import { LibAppConfigComponent } from '@flusys/ng-core';
384
-
385
- @Component({
386
- selector: 'app-root',
387
- standalone: true,
388
- imports: [LibAppConfigComponent, RouterOutlet],
389
- template: `
390
- <lib-app-config />
391
- <router-outlet />
392
- `
393
- })
394
- export class AppComponent {}
116
+ const isUserListLoading = this.loader.getLoader("user-list"); // Signal<boolean>
395
117
  ```
396
118
 
397
119
  ---
398
120
 
399
- ## Usage Examples
400
-
401
- ### Basic App Setup
402
-
403
- ```typescript
404
- // app.config.ts
405
- import { ApplicationConfig } from '@angular/core';
406
- import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
407
- import { APP_CONFIG, errorCatchingInterceptor, apiLoaderInterceptor } from '@flusys/ng-core';
408
- import { authInterceptor, tokenRefreshInterceptor } from '@flusys/ng-auth';
409
- import { DEFAULT_APP_CONFIG } from './config/app.config';
410
-
411
- export const appConfig: ApplicationConfig = {
412
- providers: [
413
- provideHttpClient(
414
- withFetch(),
415
- withInterceptors([
416
- authInterceptor,
417
- tokenRefreshInterceptor,
418
- errorCatchingInterceptor,
419
- apiLoaderInterceptor,
420
- ])
421
- ),
422
- { provide: APP_CONFIG, useValue: DEFAULT_APP_CONFIG },
423
- ]
424
- };
425
- ```
426
-
427
- ### Creating an API Service
428
-
429
- ```typescript
430
- import { Injectable } from '@angular/core';
431
- import { BaseApiService } from '@flusys/ng-core';
432
-
433
- @Injectable({ providedIn: 'root' })
434
- export class AuthApiService extends BaseApiService {
435
- constructor() {
436
- super('auth'); // Resolves to services.auth.baseUrl
437
- }
438
-
439
- login(credentials: any) {
440
- return this.http.post(this.getUrl('auth/login'), credentials);
441
- }
442
- }
443
- ```
121
+ ## 4. Skip Error Toast Per Request
444
122
 
445
- ### Using Tagged Loaders
123
+ Opt out of the global error toast for requests you handle locally:
446
124
 
447
125
  ```typescript
448
- import { HttpHeaders } from '@angular/common/http';
449
- import { ApiLoaderService } from '@flusys/ng-core';
126
+ import { SKIP_ERROR_TOAST } from "@flusys/ng-core";
127
+ import { HttpContext } from "@angular/common/http";
450
128
 
451
- // In a service - tag requests
452
- this.http.post('/api/users/get-all', filter, {
453
- headers: new HttpHeaders({ 'x-loader-tag': 'users-list' })
129
+ this.http.post(url, body, {
130
+ context: new HttpContext().set(SKIP_ERROR_TOAST, true),
454
131
  });
455
-
456
- // In a component - check specific loader
457
- readonly usersLoading = this.apiLoaderService.getLoader('users-list');
458
-
459
- // In template
460
- @if (usersLoading()) {
461
- <p-skeleton />
462
- }
463
132
  ```
464
133
 
465
134
  ---
466
135
 
467
- ## Best Practices
136
+ ## 6. Feature Helper Functions
468
137
 
469
- 1. **Always provide APP_CONFIG** at application root level
470
- 2. **Match backend configuration mode** (`multiTenant.enabled` should align with FLUSYS_NEST setup)
471
- 3. **Enable features based on backend availability** (`services.iam.enabled` only if IAM module is deployed)
472
- 4. **Never import from other @flusys packages** in ng-core
473
- 5. **Keep ng-core minimal** - only foundation functionality
474
- 6. **Use `BaseApiService`** for all API services to avoid hardcoded URLs
475
- 7. **Don't create custom error handling** - use `errorCatchingInterceptor`
476
- 8. **Don't manually track loading** - use `apiLoaderInterceptor` with `x-loader-tag` headers
138
+ Read flags without injecting `APP_CONFIG` directly:
477
139
 
478
- ## Common Issues
479
-
480
- ### APP_CONFIG Not Found
481
-
482
- **Problem:** `NullInjectorError: No provider for InjectionToken APP_CONFIG`
483
-
484
- **Solution:** Provide APP_CONFIG in app.config.ts:
485
140
  ```typescript
486
- { provide: APP_CONFIG, useValue: DEFAULT_APP_CONFIG }
487
- ```
488
-
489
- ### Circular Dependency
141
+ import { isSignUpEnabled, getPermissionMode, getNotificationSocketUrl } from "@flusys/ng-core";
490
142
 
491
- **Problem:** Build fails with circular dependency error involving ng-core.
492
-
493
- **Solution:** ng-core should NEVER import from other @flusys packages. Check imports and remove any references to ng-shared, ng-layout, or ng-auth.
143
+ const config = inject(APP_CONFIG);
144
+ isSignUpEnabled(config); // boolean — reads services.auth.features.signUp
145
+ getPermissionMode(config); // 'RBAC' | 'DIRECT' | 'FULL'
146
+ getNotificationSocketUrl(config); // string | undefined
147
+ ```
494
148
 
495
149
  ---
496
150
 
497
- ## API Reference
498
-
499
- ### Tokens
500
-
501
- | Token | Type | Description |
502
- |-------|------|-------------|
503
- | `APP_CONFIG` | `InjectionToken<IAppConfig>` | Application configuration |
504
-
505
- ### Interceptors
506
-
507
- | Function | Description |
508
- |----------|-------------|
509
- | `apiLoaderInterceptor` | Loading state management with tagged loaders |
510
- | `errorCatchingInterceptor` | Error toast display (skips 401 and check-login) |
511
-
512
- ### Services
513
-
514
- | Service | Description |
515
- |---------|-------------|
516
- | `ApiLoaderService` | Signal-based loading state with tagged loaders |
517
- | `BaseApiService` | Abstract base class for API services with URL resolution |
518
-
519
- ### Interfaces & Types
520
-
521
- | Export | Description |
522
- |--------|-------------|
523
- | `IAppConfig` | App configuration interface |
524
- | `IServiceConfig` | Service configuration (`baseUrl`, `enabled`) |
525
- | `IAuthorConfig` | Author/brand configuration (`name`, `url`) |
526
- | `PermissionMode` | `'rbac' \| 'direct' \| 'full'` |
527
-
528
- ### Helper Functions
529
-
530
- | Function | Description |
531
- |----------|-------------|
532
- | `getDatabaseMode(config)` | Returns `'single'` or `'multi-tenant'` |
533
- | `isFeatureEnabled(config, feature)` | Check service feature enabled (`'auth' \| 'iam' \| 'storage'`) |
534
- | `getServiceUrl(config, serviceName)` | Get service URL with fallback |
535
- | `isServiceEnabled(config, serviceName)` | Check if service is enabled |
536
- | `isCompanyFeatureEnabled(config)` | Check company feature flag |
537
- | `isStorageFeatureEnabled(config)` | Check storage service enabled |
538
- | `getPermissionMode(config)` | Get permission mode (default `'full'`) |
539
-
540
- ### Constants
541
-
542
- | Constant | Value | Description |
543
- |----------|-------|-------------|
544
- | `DEFAULT_APP_NAME` | `'FLUSYS'` | Default application name |
545
- | `DEFAULT_AUTHOR` | `{ name: 'Flusys', url: '...' }` | Default author/brand info |
546
-
547
- ### Components
548
-
549
- | Component | Selector | Description |
550
- |-----------|----------|-------------|
551
- | `LibAppConfigComponent` | `lib-app-config` | Global toast, confirm dialog, and loading bar |
552
-
553
- ## See Also
554
-
555
- - **[SHARED-GUIDE.md](./SHARED-GUIDE.md)** - API services, components, directives
556
- - **[LAYOUT-GUIDE.md](./LAYOUT-GUIDE.md)** - Layout system
557
- - **[Angular Patterns](../../.claude/docs/angular/)** - Framework patterns
558
- - **[FLUSYS_NEST/docs/CORE-GUIDE.md](../../FLUSYS_NEST/docs/CORE-GUIDE.md)** - Backend core module
559
-
560
- ---
151
+ ## License
561
152
 
562
- **Last Updated:** 2026-02-16
563
- **Angular Version:** 21
153
+ MIT © FLUSYS
@@ -1,24 +1,20 @@
1
1
  import * as i0 from '@angular/core';
2
- import { signal, computed, Injectable, inject, Component, PLATFORM_ID, InjectionToken } from '@angular/core';
3
- import * as i2 from 'primeng/toast';
4
- import { ToastModule } from 'primeng/toast';
5
- import { ConfirmDialog } from 'primeng/confirmdialog';
6
- import * as i1 from 'primeng/progressbar';
7
- import { ProgressBarModule } from 'primeng/progressbar';
2
+ import { signal, computed, Injectable, inject, Component, InjectionToken } from '@angular/core';
3
+ import * as i1 from '@flusys/ng-ui';
4
+ import { ConfirmDialog, UiModule } from '@flusys/ng-ui';
8
5
  import { finalize } from 'rxjs/operators';
9
- import { isPlatformBrowser } from '@angular/common';
10
- import { MessageService } from 'primeng/api';
11
- import { catchError, throwError } from 'rxjs';
12
6
  import { HttpClient } from '@angular/common/http';
13
7
 
14
8
  class ApiLoaderService {
15
- loaders = signal({}, ...(ngDevMode ? [{ debugName: "loaders" }] : []));
9
+ loaders = signal({}, /* @ts-ignore */
10
+ ...(ngDevMode ? [{ debugName: "loaders" }] : /* istanbul ignore next */ []));
16
11
  DEFAULT_TAG = 'default';
17
12
  // Global loading state
18
13
  show = computed(() => {
19
14
  const loaders = this.loaders();
20
15
  return Object.values(loaders).some((count) => count > 0);
21
- }, ...(ngDevMode ? [{ debugName: "show" }] : []));
16
+ }, /* @ts-ignore */
17
+ ...(ngDevMode ? [{ debugName: "show" }] : /* istanbul ignore next */ []));
22
18
  // Get specific loader as a signal
23
19
  getLoader(name) {
24
20
  return computed(() => (this.loaders()[name] ?? 0) > 0);
@@ -35,26 +31,38 @@ class ApiLoaderService {
35
31
  return { ...loaders, [name]: Math.max(0, current - 1) };
36
32
  });
37
33
  }
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' });
34
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ApiLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
35
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ApiLoaderService, providedIn: 'root' });
40
36
  }
41
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ApiLoaderService, decorators: [{
37
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: ApiLoaderService, decorators: [{
42
38
  type: Injectable,
43
39
  args: [{ providedIn: 'root' }]
44
40
  }] });
45
41
 
46
- /**
47
- * Root configuration component providing global UI elements.
48
- * Include once at app root for global toast, confirm dialog, and loader.
49
- */
50
42
  class LibAppConfigComponent {
51
43
  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"] }] });
44
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LibAppConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
45
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: LibAppConfigComponent, isStandalone: true, selector: "lib-app-config", ngImport: i0, template: `
46
+ @if (apiLoaderService.show()) {
47
+ <f-progressbar mode="indeterminate" />
48
+ }
49
+ <f-toast />
50
+ <f-confirmdialog />
51
+ `, isInline: true, dependencies: [{ kind: "component", type: ConfirmDialog, selector: "f-confirmdialog", inputs: ["key", "icon", "acceptLabel", "rejectLabel", "acceptIcon", "rejectIcon", "acceptButtonStyleClass", "rejectButtonStyleClass", "closeOnEscape", "blockScroll", "defaultFocus", "styleClass", "style"] }, { kind: "ngmodule", type: UiModule }, { kind: "component", type: i1.ToastComponent, selector: "f-toast", inputs: ["key", "position"], outputs: ["onClose"] }, { kind: "component", type: i1.ProgressbarComponent, selector: "f-progressbar", inputs: ["value", "mode", "showValue", "unit", "severity", "color", "styleClass"] }] });
54
52
  }
55
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LibAppConfigComponent, decorators: [{
53
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LibAppConfigComponent, decorators: [{
56
54
  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"] }]
55
+ args: [{
56
+ selector: 'lib-app-config',
57
+ imports: [ConfirmDialog, UiModule],
58
+ template: `
59
+ @if (apiLoaderService.show()) {
60
+ <f-progressbar mode="indeterminate" />
61
+ }
62
+ <f-toast />
63
+ <f-confirmdialog />
64
+ `,
65
+ }]
58
66
  }] });
59
67
 
60
68
  /**
@@ -79,146 +87,64 @@ function apiLoaderInterceptor(req, next) {
79
87
  return next(cleanReq).pipe(finalize(() => loaderService.decrease(loaderTag)));
80
88
  }
81
89
 
82
- /**
83
- * Error Catching Interceptor
84
- * Displays error messages for failed HTTP requests.
85
- *
86
- * NOTE: This interceptor only handles error display.
87
- * For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.
88
- *
89
- * @example
90
- * ```typescript
91
- * // In app.config.ts
92
- * provideHttpClient(
93
- * withFetch(),
94
- * withInterceptors([
95
- * authInterceptor, // from @flusys/ng-auth
96
- * tokenRefreshInterceptor, // from @flusys/ng-auth
97
- * errorCatchingInterceptor, // from @flusys/ng-core
98
- * apiLoaderInterceptor, // from @flusys/ng-core
99
- * ])
100
- * )
101
- * ```
102
- */
103
- function errorCatchingInterceptor(req, next) {
104
- const messageService = inject(MessageService);
105
- const platformId = inject(PLATFORM_ID);
106
- const isBrowser = isPlatformBrowser(platformId);
107
- // Skip error display for check-login (expected to fail when not logged in)
108
- const isCheckLogin = req.url.includes('/check-login');
109
- return next(req).pipe(catchError((error) => {
110
- // Skip 401 errors - handled by tokenRefreshInterceptor in ng-auth
111
- // Skip 403 errors - handled by permission denied UI in components
112
- // Skip check-login errors - expected behavior
113
- if (error.status === 401 || error.status === 403 || isCheckLogin) {
114
- return throwError(() => error);
115
- }
116
- // Display error message for other errors
117
- if (isBrowser) {
118
- const message = extractErrorMessage(error);
119
- messageService.add({
120
- severity: 'error',
121
- summary: 'Error',
122
- detail: message,
123
- });
124
- }
125
- return throwError(() => error);
126
- }));
127
- }
128
- /**
129
- * Extract error message from HTTP error response
130
- */
131
- function extractErrorMessage(error) {
132
- if (typeof error.error?.message === 'string') {
133
- return error.error.message;
134
- }
135
- if (Array.isArray(error.error?.message)) {
136
- return error.error.message[0];
137
- }
138
- if (error.message) {
139
- return error.message;
140
- }
141
- return 'An unexpected error occurred.';
142
- }
143
-
144
- /**
145
- * App Configuration Token
146
- * Use this to inject app config in services/components
147
- */
148
90
  const APP_CONFIG = new InjectionToken('APP_CONFIG');
149
- /**
150
- * Get database mode from config
151
- * Derived from multiTenant.enabled
152
- */
153
- function getDatabaseMode(config) {
154
- return config.multiTenant.enabled ? 'multi-tenant' : 'single';
155
- }
156
- /**
157
- * Check if a feature is enabled
158
- * Features are enabled if their corresponding service exists and is enabled
159
- */
160
- function isFeatureEnabled(config, feature) {
161
- const service = config.services[feature];
91
+ function isServiceEnabled(config, serviceName) {
92
+ const service = config.services[serviceName];
162
93
  return service?.enabled ?? false;
163
94
  }
164
- /**
165
- * Helper function to get service base URL
166
- * Returns service-specific URL or falls back to default apiBaseUrl
167
- */
168
95
  function getServiceUrl(config, serviceName) {
169
- const service = config.services[serviceName];
170
- if (service?.enabled) {
171
- return service.baseUrl;
96
+ if (isServiceEnabled(config, serviceName)) {
97
+ const service = config.services[serviceName];
98
+ return service?.baseUrl ?? config.apiBaseUrl;
172
99
  }
173
- // Fallback to default API base URL
174
100
  return config.apiBaseUrl;
175
101
  }
176
- /**
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
186
- */
187
102
  function isCompanyFeatureEnabled(config) {
188
103
  return config.enableCompanyFeature ?? false;
189
104
  }
190
- /**
191
- * Check if storage feature is enabled (backward compatible)
192
- * Storage feature is enabled if storage service is enabled
193
- */
194
- function isStorageFeatureEnabled(config) {
195
- return config.services.storage?.enabled ?? false;
105
+ function getNotificationSocketUrl(config) {
106
+ const service = config.services.notification;
107
+ if (!service?.enabled)
108
+ return null;
109
+ return service.socketUrl ?? service.baseUrl;
196
110
  }
197
- /**
198
- * Get permission mode from config
199
- * Defaults to 'full' if not specified
200
- */
201
111
  function getPermissionMode(config) {
202
112
  return config.permissionMode ?? 'full';
203
113
  }
204
- /**
205
- * Get default storage config ID from config
206
- * Used for file upload operations when no storageConfigId is provided
207
- */
208
114
  function getDefaultStorageConfigId(config) {
209
115
  return config.services.storage?.defaultStorageConfigId;
210
116
  }
211
- // Default branding constants
117
+ function isSignUpEnabled(config) {
118
+ return config.enableSignUp ?? true;
119
+ }
120
+ function getDefaultCompanySlug(config) {
121
+ return config.registrationDefaults?.companySlug;
122
+ }
123
+ function getDefaultBranchSlug(config) {
124
+ return config.registrationDefaults?.branchSlug;
125
+ }
212
126
  const DEFAULT_APP_NAME = 'FLUSYS';
213
127
  const DEFAULT_AUTHOR = {
214
128
  name: 'Flusys',
215
129
  url: 'https://flusys.vercel.app',
216
130
  };
217
131
 
132
+ /** Registry for fallback messages when localization is not active */
133
+ const FALLBACK_MESSAGES_REGISTRY = new InjectionToken('FALLBACK_MESSAGES_REGISTRY');
134
+ // ✅ Provide it once at root as a single shared object
135
+ const provideFallbackMessagesRegistry = () => ({
136
+ provide: FALLBACK_MESSAGES_REGISTRY,
137
+ useValue: {}, // Single object instance shared across all injections
138
+ });
139
+ const LOCALIZATION_STATE_SERVICE = new InjectionToken('LOCALIZATION_STATE_SERVICE');
140
+ const LOCALIZATION_API_SERVICE = new InjectionToken('LOCALIZATION_API_SERVICE');
141
+
218
142
  /**
219
143
  * Base API Service
220
144
  * Provides service-specific base URL resolution
221
145
  *
146
+ * Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.
147
+ *
222
148
  * Usage:
223
149
  * ```typescript
224
150
  * @Injectable({ providedIn: 'root' })
@@ -228,7 +154,7 @@ const DEFAULT_AUTHOR = {
228
154
  * }
229
155
  *
230
156
  * getData() {
231
- * return this.http.get(`${this.baseUrl}/data`);
157
+ * return this.http.post(`${this.baseUrl}/data/get`, {});
232
158
  * }
233
159
  * }
234
160
  * ```
@@ -250,7 +176,9 @@ class BaseApiService {
250
176
  */
251
177
  getUrl(endpoint) {
252
178
  // Remove leading slash if present to avoid double slashes
253
- const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
179
+ const cleanEndpoint = endpoint.startsWith('/')
180
+ ? endpoint.substring(1)
181
+ : endpoint;
254
182
  return `${this.baseUrl}/${cleanEndpoint}`;
255
183
  }
256
184
  }
@@ -261,5 +189,5 @@ class BaseApiService {
261
189
  * Generated bundle index. Do not edit.
262
190
  */
263
191
 
264
- export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultStorageConfigId, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isFeatureEnabled, isServiceEnabled, isStorageFeatureEnabled };
192
+ export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, FALLBACK_MESSAGES_REGISTRY, LOCALIZATION_API_SERVICE, LOCALIZATION_STATE_SERVICE, LibAppConfigComponent, apiLoaderInterceptor, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getNotificationSocketUrl, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isServiceEnabled, isSignUpEnabled, provideFallbackMessagesRegistry };
265
193
  //# 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/interceptors/api-loader.interceptor.ts","../../../projects/ng-core/interfaces/app-config.interface.ts","../../../projects/ng-core/interfaces/translate-adapter.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 { ConfirmDialog, UiModule } from '@flusys/ng-ui';\nimport { ApiLoaderService } from '../../services/api-loader.service';\n\n@Component({\n selector: 'lib-app-config',\n imports: [ConfirmDialog, UiModule],\n template: `\n @if (apiLoaderService.show()) {\n <f-progressbar mode=\"indeterminate\" />\n }\n <f-toast />\n <f-confirmdialog />\n `,\n})\nexport class LibAppConfigComponent {\n readonly apiLoaderService = inject(ApiLoaderService);\n}\n","import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { finalize } from 'rxjs/operators';\nimport { ApiLoaderService } from '../services/api-loader.service';\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 =\n 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(finalize(() => loaderService.decrease(loaderTag)));\n}\n","import { InjectionToken } from '@angular/core';\n\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\nexport type ServiceName =\n | 'auth'\n | 'administration'\n | 'iam'\n | 'storage'\n | 'formBuilder'\n | 'email'\n | 'eventManager'\n | 'notification'\n | 'localization'\n | 'taskManager';\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 LayoutConfig {\n preset?: string;\n primary?: string;\n surface?: string | null;\n darkTheme?: boolean;\n menuMode?: 'static' | 'overlay' | 'topbar';\n isMemorable?: boolean;\n}\n\nexport interface IAppConfig {\n appName: string;\n appLogo: string;\n author?: IAuthorConfig;\n apiBaseUrl: string;\n enableCompanyFeature: boolean;\n /** @default true */\n enableSignUp?: boolean;\n registrationDefaults?: IRegistrationDefaults;\n layoutConfig?: LayoutConfig;\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 localization?: IServiceConfig;\n taskManager?: IServiceConfig;\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 isServiceEnabled(\n config: IAppConfig,\n serviceName: ServiceName,\n): boolean {\n const service = config.services[serviceName as keyof typeof config.services];\n return service?.enabled ?? false;\n}\n\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName: ServiceName,\n): string {\n if (isServiceEnabled(config, serviceName)) {\n const service =\n 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 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(\n config: IAppConfig,\n): 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 const DEFAULT_APP_NAME = 'FLUSYS';\nexport const DEFAULT_AUTHOR: IAuthorConfig = {\n name: 'Flusys',\n url: 'https://flusys.vercel.app',\n};\n","import { InjectionToken, Signal } from '@angular/core';\nimport { Observable } from 'rxjs';\n\nexport interface ITranslateAdapter {\n /** Current language code signal - read this to create reactive dependency */\n translate(key: string, params?: Record<string, unknown>): string;\n}\n\n/** Registry for fallback messages when localization is not active */\nexport const FALLBACK_MESSAGES_REGISTRY = new InjectionToken<\n Record<string, string>\n>('FALLBACK_MESSAGES_REGISTRY');\n// ✅ Provide it once at root as a single shared object\nexport const provideFallbackMessagesRegistry = () => ({\n provide: FALLBACK_MESSAGES_REGISTRY,\n useValue: {}, // Single object instance shared across all injections\n});\n\n// Injection Token for localization services used in the resolver\nexport interface ILocalizationStateServicesAdapter {\n updateFallbackMessages(messages: Record<string, string>): void;\n currentLanguageCode: Signal<string>;\n isModuleLoaded(module: string): boolean;\n mergeTranslations(translations: Record<string, string>): void;\n markModuleLoaded(module: string): void;\n currentLanguage: Signal<string>;\n translations: Signal<Record<string, string>>;\n fallbackMessages: Signal<Record<string, string>>;\n}\nexport interface ILocalizationApiServicesAdapter {\n getTranslationsByLanguage(languageCode: string, modules: string[]): Observable<unknown>;\n}\nexport const LOCALIZATION_STATE_SERVICE =\n new InjectionToken<ILocalizationStateServicesAdapter>(\n 'LOCALIZATION_STATE_SERVICE',\n );\nexport const LOCALIZATION_API_SERVICE =\n new InjectionToken<ILocalizationApiServicesAdapter>(\n 'LOCALIZATION_API_SERVICE',\n );\n","import { HttpClient } from '@angular/common/http';\nimport { inject } from '@angular/core';\nimport {\n APP_CONFIG,\n getServiceUrl,\n ServiceName,\n} 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: ServiceName) {\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('/')\n ? endpoint.substring(1)\n : 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,13 @@
1
1
  {
2
2
  "name": "@flusys/ng-core",
3
- "version": "1.1.1-beta",
3
+ "version": "1.2.0-rc.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": ">=22.0.0",
8
+ "@angular/core": ">=22.0.0",
9
+ "@flusys/ng-ui": ">=1.2.0-rc.1",
10
+ "rxjs": "^7.8.0"
12
11
  },
13
12
  "sideEffects": false,
14
13
  "module": "fesm2022/flusys-ng-core.mjs",
@@ -22,6 +21,7 @@
22
21
  "default": "./fesm2022/flusys-ng-core.mjs"
23
22
  }
24
23
  },
24
+ "type": "module",
25
25
  "dependencies": {
26
26
  "tslib": "^2.3.0"
27
27
  }
@@ -15,10 +15,6 @@ declare class ApiLoaderService {
15
15
  static ɵprov: i0.ɵɵInjectableDeclaration<ApiLoaderService>;
16
16
  }
17
17
 
18
- /**
19
- * Root configuration component providing global UI elements.
20
- * Include once at app root for global toast, confirm dialog, and loader.
21
- */
22
18
  declare class LibAppConfigComponent {
23
19
  readonly apiLoaderService: ApiLoaderService;
24
20
  static ɵfac: i0.ɵɵFactoryDeclaration<LibAppConfigComponent, never>;
@@ -39,154 +35,111 @@ declare class LibAppConfigComponent {
39
35
  */
40
36
  declare function apiLoaderInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>>;
41
37
 
42
- /**
43
- * Error Catching Interceptor
44
- * Displays error messages for failed HTTP requests.
45
- *
46
- * NOTE: This interceptor only handles error display.
47
- * For 401 handling and token refresh, use tokenRefreshInterceptor from @flusys/ng-auth.
48
- *
49
- * @example
50
- * ```typescript
51
- * // In app.config.ts
52
- * provideHttpClient(
53
- * withFetch(),
54
- * withInterceptors([
55
- * authInterceptor, // from @flusys/ng-auth
56
- * tokenRefreshInterceptor, // from @flusys/ng-auth
57
- * errorCatchingInterceptor, // from @flusys/ng-core
58
- * apiLoaderInterceptor, // from @flusys/ng-core
59
- * ])
60
- * )
61
- * ```
62
- */
63
- declare function errorCatchingInterceptor(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>>;
64
-
65
- /**
66
- * IAM Permission Mode - controls permission system behavior
67
- */
68
38
  type PermissionMode = 'rbac' | 'direct' | 'full';
69
- /**
70
- * Service Configuration Interface
71
- */
39
+ type ServiceName = 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | 'eventManager' | 'notification' | 'localization' | 'taskManager';
72
40
  interface IServiceConfig {
73
41
  baseUrl: string;
74
42
  enabled: boolean;
75
43
  }
76
- /**
77
- * Storage Service Configuration Interface
78
- * Extends base service config with storage-specific options
79
- */
80
44
  interface IStorageServiceConfig extends IServiceConfig {
81
- /**
82
- * Default storage config ID from backend storage_config table
83
- * Used when no storageConfigId is provided to upload operations
84
- * If not set, backend will use company/branch default config
85
- */
45
+ /** Default storage config ID (falls back to company/branch default if not set) */
86
46
  defaultStorageConfigId?: string;
87
47
  }
88
- /**
89
- * Author/Brand Configuration
90
- */
48
+ interface INotificationServiceConfig extends IServiceConfig {
49
+ /** WebSocket URL (defaults to baseUrl if not set) */
50
+ socketUrl?: string;
51
+ }
91
52
  interface IAuthorConfig {
92
53
  name: string;
93
54
  url: string;
94
55
  }
95
- /**
96
- * App Configuration Interface
97
- * Centralized configuration for feature toggles and behavior control
98
- */
56
+ interface IRegistrationDefaults {
57
+ companySlug?: string;
58
+ branchSlug?: string;
59
+ }
60
+ interface LayoutConfig {
61
+ preset?: string;
62
+ primary?: string;
63
+ surface?: string | null;
64
+ darkTheme?: boolean;
65
+ menuMode?: 'static' | 'overlay' | 'topbar';
66
+ isMemorable?: boolean;
67
+ }
99
68
  interface IAppConfig {
100
- /** Application name */
101
69
  appName: string;
102
- /** Author/Brand info for footer attribution */
70
+ appLogo: string;
103
71
  author?: IAuthorConfig;
104
- /** Default API base URL (fallback) */
105
72
  apiBaseUrl: string;
106
- /**
107
- * enableCompanyFeature in across the app
108
- */
109
73
  enableCompanyFeature: boolean;
110
- /**
111
- * Service-specific base URLs
112
- * If a service exists in this object, its feature is enabled
113
- * Database mode is determined by multiTenant.enabled
114
- */
74
+ /** @default true */
75
+ enableSignUp?: boolean;
76
+ registrationDefaults?: IRegistrationDefaults;
77
+ layoutConfig?: LayoutConfig;
115
78
  services: {
116
79
  auth: IServiceConfig;
80
+ administration: IServiceConfig;
117
81
  iam: IServiceConfig;
118
82
  storage: IStorageServiceConfig;
119
- [key: string]: IServiceConfig | IStorageServiceConfig;
83
+ formBuilder: IServiceConfig;
84
+ email: IServiceConfig;
85
+ eventManager: IServiceConfig;
86
+ notification?: INotificationServiceConfig;
87
+ localization?: IServiceConfig;
88
+ taskManager?: IServiceConfig;
120
89
  };
121
- /**
122
- * Multi-tenant configuration
123
- * enabled: true → Database mode = multi-tenant
124
- * enabled: false → Database mode = single
125
- */
126
90
  multiTenant: {
127
91
  enabled: boolean;
128
92
  tenantHeader: string;
129
93
  tenantId?: string;
130
94
  };
131
- /**
132
- * IAM Permission Mode - controls permission system behavior
133
- * - 'rbac': Role-Based Access Control only (users get permissions via roles)
134
- * - 'direct': Direct action assignments only (actions assigned directly to users)
135
- * - 'full': Both RBAC and direct permissions (most flexible, default)
136
- */
95
+ /** 'rbac' | 'direct' | 'full' (default: 'full') */
137
96
  permissionMode?: PermissionMode;
138
97
  }
139
- /**
140
- * App Configuration Token
141
- * Use this to inject app config in services/components
142
- */
143
98
  declare const APP_CONFIG: InjectionToken<IAppConfig>;
144
- /**
145
- * Get database mode from config
146
- * Derived from multiTenant.enabled
147
- */
148
- declare function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant';
149
- /**
150
- * Check if a feature is enabled
151
- * Features are enabled if their corresponding service exists and is enabled
152
- */
153
- declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'iam' | 'storage'): boolean;
154
- /**
155
- * Helper function to get service base URL
156
- * Returns service-specific URL or falls back to default apiBaseUrl
157
- */
158
- declare function getServiceUrl(config: IAppConfig, serviceName: keyof IAppConfig['services']): string;
159
- /**
160
- * Helper function to check if a service is enabled
161
- */
162
- declare function isServiceEnabled(config: IAppConfig, serviceName: keyof IAppConfig['services']): boolean;
163
- /**
164
- * Check if company feature is enabled (backward compatible)
165
- * Company feature is enabled if auth service is enabled
166
- */
99
+ declare function isServiceEnabled(config: IAppConfig, serviceName: ServiceName): boolean;
100
+ declare function getServiceUrl(config: IAppConfig, serviceName: ServiceName): string;
167
101
  declare function isCompanyFeatureEnabled(config: IAppConfig): boolean;
168
- /**
169
- * Check if storage feature is enabled (backward compatible)
170
- * Storage feature is enabled if storage service is enabled
171
- */
172
- declare function isStorageFeatureEnabled(config: IAppConfig): boolean;
173
- /**
174
- * Get permission mode from config
175
- * Defaults to 'full' if not specified
176
- */
102
+ declare function getNotificationSocketUrl(config: IAppConfig): string | null;
177
103
  declare function getPermissionMode(config: IAppConfig): PermissionMode;
178
- /**
179
- * Get default storage config ID from config
180
- * Used for file upload operations when no storageConfigId is provided
181
- */
182
104
  declare function getDefaultStorageConfigId(config: IAppConfig): string | undefined;
105
+ declare function isSignUpEnabled(config: IAppConfig): boolean;
106
+ declare function getDefaultCompanySlug(config: IAppConfig): string | undefined;
107
+ declare function getDefaultBranchSlug(config: IAppConfig): string | undefined;
183
108
  declare const DEFAULT_APP_NAME = "FLUSYS";
184
109
  declare const DEFAULT_AUTHOR: IAuthorConfig;
185
110
 
111
+ interface ITranslateAdapter {
112
+ /** Current language code signal - read this to create reactive dependency */
113
+ translate(key: string, params?: Record<string, unknown>): string;
114
+ }
115
+ /** Registry for fallback messages when localization is not active */
116
+ declare const FALLBACK_MESSAGES_REGISTRY: InjectionToken<Record<string, string>>;
117
+ declare const provideFallbackMessagesRegistry: () => {
118
+ provide: InjectionToken<Record<string, string>>;
119
+ useValue: {};
120
+ };
121
+ interface ILocalizationStateServicesAdapter {
122
+ updateFallbackMessages(messages: Record<string, string>): void;
123
+ currentLanguageCode: Signal<string>;
124
+ isModuleLoaded(module: string): boolean;
125
+ mergeTranslations(translations: Record<string, string>): void;
126
+ markModuleLoaded(module: string): void;
127
+ currentLanguage: Signal<string>;
128
+ translations: Signal<Record<string, string>>;
129
+ fallbackMessages: Signal<Record<string, string>>;
130
+ }
131
+ interface ILocalizationApiServicesAdapter {
132
+ getTranslationsByLanguage(languageCode: string, modules: string[]): Observable<unknown>;
133
+ }
134
+ declare const LOCALIZATION_STATE_SERVICE: InjectionToken<ILocalizationStateServicesAdapter>;
135
+ declare const LOCALIZATION_API_SERVICE: InjectionToken<ILocalizationApiServicesAdapter>;
136
+
186
137
  /**
187
138
  * Base API Service
188
139
  * Provides service-specific base URL resolution
189
140
  *
141
+ * Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.
142
+ *
190
143
  * Usage:
191
144
  * ```typescript
192
145
  * @Injectable({ providedIn: 'root' })
@@ -196,7 +149,7 @@ declare const DEFAULT_AUTHOR: IAuthorConfig;
196
149
  * }
197
150
  *
198
151
  * getData() {
199
- * return this.http.get(`${this.baseUrl}/data`);
152
+ * return this.http.post(`${this.baseUrl}/data/get`, {});
200
153
  * }
201
154
  * }
202
155
  * ```
@@ -208,7 +161,7 @@ declare abstract class BaseApiService {
208
161
  /**
209
162
  * @param serviceName - Service name from config (e.g., 'auth', 'iam', 'storage')
210
163
  */
211
- constructor(serviceName: string);
164
+ constructor(serviceName: ServiceName);
212
165
  /**
213
166
  * Get full URL for an endpoint
214
167
  * @param endpoint - API endpoint (e.g., '/users/get-all')
@@ -217,5 +170,5 @@ declare abstract class BaseApiService {
217
170
  protected getUrl(endpoint: string): string;
218
171
  }
219
172
 
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 };
173
+ export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, FALLBACK_MESSAGES_REGISTRY, LOCALIZATION_API_SERVICE, LOCALIZATION_STATE_SERVICE, LibAppConfigComponent, apiLoaderInterceptor, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getNotificationSocketUrl, getPermissionMode, getServiceUrl, isCompanyFeatureEnabled, isServiceEnabled, isSignUpEnabled, provideFallbackMessagesRegistry };
174
+ export type { IAppConfig, IAuthorConfig, ILocalizationApiServicesAdapter, ILocalizationStateServicesAdapter, INotificationServiceConfig, IRegistrationDefaults, IServiceConfig, IStorageServiceConfig, ITranslateAdapter, LayoutConfig, PermissionMode, ServiceName };