@flusys/ng-core 2.0.0-rc.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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.0
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.0 | **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
  }] });
@@ -249,6 +249,18 @@ function isCompanyFeatureEnabled(config) {
249
249
  function isStorageFeatureEnabled(config) {
250
250
  return isServiceEnabled(config, 'storage');
251
251
  }
252
+ /**
253
+ * Check if form-builder feature is enabled
254
+ */
255
+ function isFormBuilderFeatureEnabled(config) {
256
+ return isServiceEnabled(config, 'formBuilder');
257
+ }
258
+ /**
259
+ * Check if email feature is enabled
260
+ */
261
+ function isEmailFeatureEnabled(config) {
262
+ return isServiceEnabled(config, 'email');
263
+ }
252
264
  /**
253
265
  * Get permission mode from config
254
266
  * Defaults to 'full' if not specified
@@ -263,6 +275,31 @@ function getPermissionMode(config) {
263
275
  function getDefaultStorageConfigId(config) {
264
276
  return config.services.storage?.defaultStorageConfigId;
265
277
  }
278
+ /**
279
+ * Check if sign-up/registration is enabled
280
+ * @default true
281
+ */
282
+ function isSignUpEnabled(config) {
283
+ return config.enableSignUp ?? true;
284
+ }
285
+ /**
286
+ * Get default company slug for registration
287
+ */
288
+ function getDefaultCompanySlug(config) {
289
+ return config.registrationDefaults?.companySlug;
290
+ }
291
+ /**
292
+ * Get default branch slug for registration
293
+ */
294
+ function getDefaultBranchSlug(config) {
295
+ return config.registrationDefaults?.branchSlug;
296
+ }
297
+ /**
298
+ * Get registration defaults
299
+ */
300
+ function getRegistrationDefaults(config) {
301
+ return config.registrationDefaults;
302
+ }
266
303
  // Default branding constants
267
304
  const DEFAULT_APP_NAME = 'FLUSYS';
268
305
  const DEFAULT_AUTHOR = {
@@ -318,5 +355,5 @@ class BaseApiService {
318
355
  * Generated bundle index. Do not edit.
319
356
  */
320
357
 
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 };
358
+ export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getPermissionMode, getRegistrationDefaults, getServiceUrl, isCompanyFeatureEnabled, isEmailFeatureEnabled, isFeatureEnabled, isFormBuilderFeatureEnabled, isServiceEnabled, isSignUpEnabled, isStorageFeatureEnabled };
322
359
  //# sourceMappingURL=flusys-ng-core.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"flusys-ng-core.mjs","sources":["../../../projects/ng-core/services/api-loader.service.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.ts","../../../projects/ng-core/components/lib-app-config/lib-app-config.component.html","../../../projects/ng-core/interceptors/api-loader.interceptor.ts","../../../projects/ng-core/interceptors/error-catching.interceptor.ts","../../../projects/ng-core/interfaces/app-config.interface.ts","../../../projects/ng-core/services/base-api.service.ts","../../../projects/ng-core/public-api.ts","../../../projects/ng-core/flusys-ng-core.ts"],"sourcesContent":["import { computed, Injectable, Signal, signal } from '@angular/core';\n\n@Injectable({ providedIn: 'root' })\nexport class ApiLoaderService {\n private readonly loaders = signal<Record<string, number>>({});\n readonly DEFAULT_TAG = 'default';\n\n // Global loading state\n readonly show: Signal<boolean> = computed(() => {\n const loaders = this.loaders();\n return Object.values(loaders).some((count) => count > 0);\n });\n\n // Get specific loader as a signal\n getLoader(name: string): Signal<boolean> {\n return computed(() => (this.loaders()[name] ?? 0) > 0);\n }\n\n increase(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => ({\n ...loaders,\n [name]: (loaders[name] ?? 0) + 1,\n }));\n }\n\n decrease(name: string = this.DEFAULT_TAG): void {\n this.loaders.update((loaders) => {\n const current = loaders[name] ?? 0;\n return { ...loaders, [name]: Math.max(0, current - 1) };\n });\n }\n}\n","import { 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\n/**\n * IAM Permission Mode - controls permission system behavior\n */\nexport type PermissionMode = 'rbac' | 'direct' | 'full';\n\n/**\n * Service Configuration Interface\n */\nexport interface IServiceConfig {\n baseUrl: string;\n enabled: boolean;\n}\n\n/**\n * Storage Service Configuration Interface\n * Extends base service config with storage-specific options\n */\nexport interface IStorageServiceConfig extends IServiceConfig {\n /**\n * Default storage config ID from backend storage_config table\n * Used when no storageConfigId is provided to upload operations\n * If not set, backend will use company/branch default config\n */\n defaultStorageConfigId?: string;\n}\n\n/**\n * Author/Brand Configuration\n */\nexport interface IAuthorConfig {\n name: string;\n url: string;\n}\n\n/**\n * Registration Defaults Configuration\n * Pre-fills and disables fields on registration page\n */\nexport interface IRegistrationDefaults {\n /** Pre-fill company code field and make it readonly */\n companySlug?: string;\n /** Pre-fill branch code field and make it readonly */\n branchSlug?: string;\n}\n\n/**\n * App Configuration Interface\n * Centralized configuration for feature toggles and behavior control\n */\nexport interface IAppConfig {\n /** Application name */\n appName: string;\n\n /** Author/Brand info for footer attribution */\n author?: IAuthorConfig;\n\n /** Default API base URL (fallback) */\n apiBaseUrl: string;\n\n /**\n * Enable company feature across the app\n * When true, enables company selection/switching in layout and feature modules\n */\n enableCompanyFeature: boolean;\n\n /**\n * Enable user sign-up/registration\n * When false: hides \"Sign Up\" link on login page and protects registration route\n * @default true\n */\n enableSignUp?: boolean;\n\n /**\n * Registration defaults for pre-filling company/branch fields\n * When set: pre-fills fields and makes them readonly\n */\n registrationDefaults?: IRegistrationDefaults;\n\n /**\n * Service-specific base URLs\n * If a service exists in this object, its feature is enabled\n * Database mode is determined by multiTenant.enabled\n */\n services: {\n auth: IServiceConfig;\n administration: IServiceConfig;\n iam: IServiceConfig;\n storage: IStorageServiceConfig;\n formBuilder: IServiceConfig;\n email: IServiceConfig;\n };\n\n /**\n * Multi-tenant configuration\n * enabled: true → Database mode = multi-tenant\n * enabled: false → Database mode = single\n */\n multiTenant: {\n enabled: boolean;\n tenantHeader: string;\n tenantId?: string;\n };\n\n /**\n * IAM Permission Mode - controls permission system behavior\n * - 'rbac': Role-Based Access Control only (users get permissions via roles)\n * - 'direct': Direct action assignments only (actions assigned directly to users)\n * - 'full': Both RBAC and direct permissions (most flexible, default)\n */\n permissionMode?: PermissionMode;\n}\n\n/**\n * App Configuration Token\n * Use this to inject app config in services/components\n */\nexport const APP_CONFIG = new InjectionToken<IAppConfig>('APP_CONFIG');\n\n/**\n * Get database mode from config\n * Derived from multiTenant.enabled\n */\nexport function getDatabaseMode(config: IAppConfig): 'single' | 'multi-tenant' {\n return config.multiTenant.enabled ? 'multi-tenant' : 'single';\n}\n\n/**\n * Check if a service is enabled\n */\nexport function isServiceEnabled(\n config: IAppConfig,\n serviceName: 'auth' | 'iam' | 'storage' | (string & {}),\n): boolean {\n const service = config.services[serviceName as keyof typeof config.services];\n return service?.enabled ?? false;\n}\n\n/**\n * Check if a feature is enabled (alias for isServiceEnabled with strict types)\n */\nexport function isFeatureEnabled(\n config: IAppConfig,\n feature: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email',\n): boolean {\n return isServiceEnabled(config, feature);\n}\n\n/**\n * Get service base URL or fallback to default apiBaseUrl\n */\nexport function getServiceUrl(\n config: IAppConfig,\n serviceName: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | (string & {}),\n): string {\n if (isServiceEnabled(config, serviceName)) {\n const service = config.services[serviceName as keyof typeof config.services];\n return service.baseUrl;\n }\n return config.apiBaseUrl;\n}\n\n/**\n * Check if company feature is enabled\n */\nexport function isCompanyFeatureEnabled(config: IAppConfig): boolean {\n return config.enableCompanyFeature ?? false;\n}\n\n/**\n * Check if storage feature is enabled\n */\nexport function isStorageFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'storage');\n}\n\n/**\n * Check if form-builder feature is enabled\n */\nexport function isFormBuilderFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'formBuilder');\n}\n\n/**\n * Check if email feature is enabled\n */\nexport function isEmailFeatureEnabled(config: IAppConfig): boolean {\n return isServiceEnabled(config, 'email');\n}\n\n/**\n * Get permission mode from config\n * Defaults to 'full' if not specified\n */\nexport function getPermissionMode(config: IAppConfig): PermissionMode {\n return config.permissionMode ?? 'full';\n}\n\n/**\n * Get default storage config ID from config\n * Used for file upload operations when no storageConfigId is provided\n */\nexport function getDefaultStorageConfigId(config: IAppConfig): string | undefined {\n return config.services.storage?.defaultStorageConfigId;\n}\n\n/**\n * Check if sign-up/registration is enabled\n * @default true\n */\nexport function isSignUpEnabled(config: IAppConfig): boolean {\n return config.enableSignUp ?? true;\n}\n\n/**\n * Get default company slug for registration\n */\nexport function getDefaultCompanySlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.companySlug;\n}\n\n/**\n * Get default branch slug for registration\n */\nexport function getDefaultBranchSlug(config: IAppConfig): string | undefined {\n return config.registrationDefaults?.branchSlug;\n}\n\n/**\n * Get registration defaults\n */\nexport function getRegistrationDefaults(config: IAppConfig): IRegistrationDefaults | undefined {\n return config.registrationDefaults;\n}\n\n// Default branding constants\nexport const DEFAULT_APP_NAME = 'FLUSYS';\nexport const DEFAULT_AUTHOR: IAuthorConfig = {\n name: 'Flusys',\n url: 'https://flusys.vercel.app',\n};\n","import { inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { APP_CONFIG, getServiceUrl } from '../interfaces/app-config.interface';\n\n/**\n * Base API Service\n * Provides service-specific base URL resolution\n *\n * Note: This project uses POST-only RPC - all endpoints use POST, not REST verbs.\n *\n * Usage:\n * ```typescript\n * @Injectable({ providedIn: 'root' })\n * export class MyService extends BaseApiService {\n * constructor() {\n * super('my-service'); // Service name from config\n * }\n *\n * getData() {\n * return this.http.post(`${this.baseUrl}/data/get`, {});\n * }\n * }\n * ```\n */\nexport abstract class BaseApiService {\n protected readonly http = inject(HttpClient);\n protected readonly appConfig = inject(APP_CONFIG);\n protected readonly baseUrl: string;\n\n /**\n * @param serviceName - Service name from config (e.g., 'auth', 'iam', 'storage')\n */\n constructor(serviceName: string) {\n this.baseUrl = getServiceUrl(this.appConfig, serviceName);\n }\n\n /**\n * Get full URL for an endpoint\n * @param endpoint - API endpoint (e.g., '/users/get-all')\n * @returns Full URL (e.g., 'http://localhost:2002/users/get-all')\n */\n protected getUrl(endpoint: string): string {\n // Remove leading slash if present to avoid double slashes\n const cleanEndpoint = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;\n return `${this.baseUrl}/${cleanEndpoint}`;\n }\n}\n","// Components\nexport * from './components/public-api';\n\n// Interceptors\nexport * from './interceptors/public-api';\n\n// Interfaces\nexport * from './interfaces/public-api';\n\n// Services\nexport * from './services/public-api';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@flusys/ng-core",
3
- "version": "2.0.0-rc.0",
3
+ "version": "3.0.0",
4
4
  "description": "Core utilities and services for FLUSYS Angular packages",
5
5
  "license": "MIT",
6
6
  "peerDependencies": {
7
- "@angular/common": "^21.0.0",
8
- "@angular/core": "^21.0.0",
9
- "@primeuix/themes": "^1.0.0",
10
- "primeicons": "^7.0.0",
11
- "primeng": "^21.0.0"
7
+ "@angular/common": ">=21.0.0",
8
+ "@angular/core": ">=21.0.0",
9
+ "@primeuix/themes": ">=1.0.0",
10
+ "primeicons": ">=7.0.0",
11
+ "primeng": ">=21.0.0"
12
12
  },
13
13
  "sideEffects": false,
14
14
  "module": "fesm2022/flusys-ng-core.mjs",
@@ -110,6 +110,16 @@ interface IAuthorConfig {
110
110
  name: string;
111
111
  url: string;
112
112
  }
113
+ /**
114
+ * Registration Defaults Configuration
115
+ * Pre-fills and disables fields on registration page
116
+ */
117
+ interface IRegistrationDefaults {
118
+ /** Pre-fill company code field and make it readonly */
119
+ companySlug?: string;
120
+ /** Pre-fill branch code field and make it readonly */
121
+ branchSlug?: string;
122
+ }
113
123
  /**
114
124
  * App Configuration Interface
115
125
  * Centralized configuration for feature toggles and behavior control
@@ -126,6 +136,17 @@ interface IAppConfig {
126
136
  * When true, enables company selection/switching in layout and feature modules
127
137
  */
128
138
  enableCompanyFeature: boolean;
139
+ /**
140
+ * Enable user sign-up/registration
141
+ * When false: hides "Sign Up" link on login page and protects registration route
142
+ * @default true
143
+ */
144
+ enableSignUp?: boolean;
145
+ /**
146
+ * Registration defaults for pre-filling company/branch fields
147
+ * When set: pre-fills fields and makes them readonly
148
+ */
149
+ registrationDefaults?: IRegistrationDefaults;
129
150
  /**
130
151
  * Service-specific base URLs
131
152
  * If a service exists in this object, its feature is enabled
@@ -133,8 +154,11 @@ interface IAppConfig {
133
154
  */
134
155
  services: {
135
156
  auth: IServiceConfig;
157
+ administration: IServiceConfig;
136
158
  iam: IServiceConfig;
137
159
  storage: IStorageServiceConfig;
160
+ formBuilder: IServiceConfig;
161
+ email: IServiceConfig;
138
162
  };
139
163
  /**
140
164
  * Multi-tenant configuration
@@ -171,11 +195,11 @@ declare function isServiceEnabled(config: IAppConfig, serviceName: 'auth' | 'iam
171
195
  /**
172
196
  * Check if a feature is enabled (alias for isServiceEnabled with strict types)
173
197
  */
174
- declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'iam' | 'storage'): boolean;
198
+ declare function isFeatureEnabled(config: IAppConfig, feature: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email'): boolean;
175
199
  /**
176
200
  * Get service base URL or fallback to default apiBaseUrl
177
201
  */
178
- declare function getServiceUrl(config: IAppConfig, serviceName: 'auth' | 'iam' | 'storage' | (string & {})): string;
202
+ declare function getServiceUrl(config: IAppConfig, serviceName: 'auth' | 'administration' | 'iam' | 'storage' | 'formBuilder' | 'email' | (string & {})): string;
179
203
  /**
180
204
  * Check if company feature is enabled
181
205
  */
@@ -184,6 +208,14 @@ declare function isCompanyFeatureEnabled(config: IAppConfig): boolean;
184
208
  * Check if storage feature is enabled
185
209
  */
186
210
  declare function isStorageFeatureEnabled(config: IAppConfig): boolean;
211
+ /**
212
+ * Check if form-builder feature is enabled
213
+ */
214
+ declare function isFormBuilderFeatureEnabled(config: IAppConfig): boolean;
215
+ /**
216
+ * Check if email feature is enabled
217
+ */
218
+ declare function isEmailFeatureEnabled(config: IAppConfig): boolean;
187
219
  /**
188
220
  * Get permission mode from config
189
221
  * Defaults to 'full' if not specified
@@ -194,6 +226,23 @@ declare function getPermissionMode(config: IAppConfig): PermissionMode;
194
226
  * Used for file upload operations when no storageConfigId is provided
195
227
  */
196
228
  declare function getDefaultStorageConfigId(config: IAppConfig): string | undefined;
229
+ /**
230
+ * Check if sign-up/registration is enabled
231
+ * @default true
232
+ */
233
+ declare function isSignUpEnabled(config: IAppConfig): boolean;
234
+ /**
235
+ * Get default company slug for registration
236
+ */
237
+ declare function getDefaultCompanySlug(config: IAppConfig): string | undefined;
238
+ /**
239
+ * Get default branch slug for registration
240
+ */
241
+ declare function getDefaultBranchSlug(config: IAppConfig): string | undefined;
242
+ /**
243
+ * Get registration defaults
244
+ */
245
+ declare function getRegistrationDefaults(config: IAppConfig): IRegistrationDefaults | undefined;
197
246
  declare const DEFAULT_APP_NAME = "FLUSYS";
198
247
  declare const DEFAULT_AUTHOR: IAuthorConfig;
199
248
 
@@ -233,5 +282,5 @@ declare abstract class BaseApiService {
233
282
  protected getUrl(endpoint: string): string;
234
283
  }
235
284
 
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 };
285
+ export { APP_CONFIG, ApiLoaderService, BaseApiService, DEFAULT_APP_NAME, DEFAULT_AUTHOR, LibAppConfigComponent, SKIP_ERROR_TOAST, apiLoaderInterceptor, errorCatchingInterceptor, getDatabaseMode, getDefaultBranchSlug, getDefaultCompanySlug, getDefaultStorageConfigId, getPermissionMode, getRegistrationDefaults, getServiceUrl, isCompanyFeatureEnabled, isEmailFeatureEnabled, isFeatureEnabled, isFormBuilderFeatureEnabled, isServiceEnabled, isSignUpEnabled, isStorageFeatureEnabled };
286
+ export type { IAppConfig, IAuthorConfig, IRegistrationDefaults, IServiceConfig, IStorageServiceConfig, PermissionMode };