@flusys/ng-auth 1.1.0-beta → 2.0.0-rc.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
@@ -324,7 +324,7 @@ provideHttpClient(
324
324
  Centralized endpoint definitions used by interceptors.
325
325
 
326
326
  ```typescript
327
- import { AUTH_ENDPOINTS, isAuthEndpoint, isEndpoint } from '@flusys/ng-auth';
327
+ import { AUTH_ENDPOINTS, isEndpoint } from '@flusys/ng-auth';
328
328
 
329
329
  // Endpoint constants
330
330
  AUTH_ENDPOINTS.LOGIN // '/auth/login'
@@ -334,8 +334,7 @@ AUTH_ENDPOINTS.REGISTER // '/auth/register'
334
334
  AUTH_ENDPOINTS.SELECT // '/auth/select'
335
335
  AUTH_ENDPOINTS.SWITCH_COMPANY // '/auth/switch-company'
336
336
 
337
- // Helper functions
338
- isAuthEndpoint(url); // Check if URL is any auth endpoint
337
+ // Helper function
339
338
  isEndpoint(url, endpoint); // Check if URL matches specific endpoint
340
339
  ```
341
340
 
@@ -393,27 +392,54 @@ getCompanyBranches(companyId); // User's permitted branches
393
392
 
394
393
  ## Pages
395
394
 
395
+ All page components follow Angular 21 best practices:
396
+ - `ChangeDetectionStrategy.OnPush` for optimal performance
397
+ - Signal Forms (`@angular/forms/signals`) with validation
398
+ - 100% responsive design (mobile-first with Tailwind)
399
+ - Dark mode compatible (PrimeNG + Tailwind CSS variables)
400
+ - `inject()` for dependency injection
401
+
396
402
  ### Authentication Pages
397
403
 
398
404
  **LoginPageComponent** (`/auth/login`)
399
405
  - Multi-step login: Step 1 = credentials, Step 2 = company/branch selection (if needed)
400
- - Visual step indicator
406
+ - Visual step indicator (when company feature enabled)
407
+ - Email verification step (when email not verified)
401
408
  - Auto-selects if only one company/branch available
402
409
  - Stores returnUrl for post-login redirect
410
+ - Responsive form layout with mobile padding
403
411
 
404
412
  **RegisterPageComponent** (`/auth/register`)
405
413
  - User registration with optional company creation
406
- - Email/password validation
407
- - Terms acceptance
414
+ - Two-column responsive grid layout
415
+ - Email/password validation with signal forms
416
+ - Company details section (when company feature enabled)
417
+
418
+ **ForgotPasswordPageComponent** (`/auth/forgot-password`)
419
+ - Email input with validation
420
+ - Success state showing email sent confirmation
421
+
422
+ **ResetPasswordPageComponent** (`/auth/reset-password`)
423
+ - Password reset with token validation
424
+ - Password confirmation matching
425
+ - Success/invalid token states
426
+
427
+ **VerifyEmailPageComponent** (`/auth/verify-email`)
428
+ - Token verification with SSR support (TransferState)
429
+ - Resend verification option
430
+ - Success/failure states
408
431
 
409
432
  ### Profile Page
410
433
 
411
434
  **ProfilePageComponent** (`/profile`)
435
+ - Page title with description
436
+ - Avatar section with initials fallback
412
437
  - Update name and phone
413
438
  - Upload profile picture (requires `PROFILE_UPLOAD_PROVIDER` from ng-storage)
414
- - Change password
439
+ - Change password section with strength indicator
415
440
  - View company/branch info (if company feature enabled)
416
441
  - View roles/actions (requires `PROFILE_PERMISSION_PROVIDER` from ng-iam)
442
+ - Responsive two-column layout on desktop
417
443
 
418
444
  **Optional Provider Dependencies:**
419
445
 
@@ -430,25 +456,42 @@ getCompanyBranches(companyId); // User's permitted branches
430
456
  ### Administration Pages
431
457
 
432
458
  **AdministrationPageComponent** (`/administration`)
433
- - Tabbed interface: Users, Companies, Branches
434
- - Company/Branch tabs require company feature enabled
459
+ - Page title "Administration" with dynamic subtitle
460
+ - Horizontal scrolling tabs (hidden scrollbar on mobile)
461
+ - Tabs: Users, Companies, Branches
462
+ - Company/Branch tabs only visible when company feature enabled
435
463
 
436
464
  **User Management:**
437
465
  - `UserListComponent` - LazyLoad datatable with Edit, Delete, Permission actions
438
- - `UserFormComponent` - Create/edit user form
466
+ - `UserFormComponent` - Create/edit user with auto company/branch assignment
467
+ - Permission dialogs for company/branch assignments
439
468
 
440
469
  **Company Management:**
441
- - `CompanyListComponent` - Company CRUD
442
- - `CompanyFormComponent` - Create/edit company
470
+ - `CompanyListComponent` - Company CRUD with View Branches action
471
+ - `CompanyFormComponent` - Create/edit company with auto slug generation
443
472
 
444
473
  **Branch Management:**
445
- - `BranchListComponent` - Branch CRUD filtered by company
446
- - `BranchFormComponent` - Create/edit branch
474
+ - `BranchListComponent` - Branch CRUD filtered by company dropdown
475
+ - `BranchFormComponent` - Create/edit branch with company selection
476
+
477
+ **Table Features (all list components):**
478
+ - Responsive header (stacked on mobile)
479
+ - Horizontal scroll wrapper (`overflow-x-auto`)
480
+ - Conditional pagination (`[paginator]="total() > 0"`)
481
+ - Striped rows (`p-datatable-striped`)
482
+ - Lazy loading for performance
447
483
 
448
484
  ---
449
485
 
450
486
  ## Dialog Components
451
487
 
488
+ Both dialog components use Angular 21 patterns:
489
+ - `ChangeDetectionStrategy.OnPush`
490
+ - `input.required<T>()` for required inputs
491
+ - `output<void>()` for events
492
+ - `effect()` for reactive data loading
493
+ - Responsive breakpoints (`{ '960px': '90vw', '640px': '95vw' }`)
494
+
452
495
  ### UserCompanyPermissionDialogComponent
453
496
 
454
497
  Manages user-to-company assignments with checkboxes.
@@ -468,11 +511,21 @@ import { UserCompanyPermissionDialogComponent } from '@flusys/ng-auth';
468
511
  })
469
512
  ```
470
513
 
514
+ **Inputs/Outputs:**
515
+ ```typescript
516
+ readonly visible = input.required<boolean>();
517
+ readonly user = input.required<IUser | null>();
518
+ readonly closed = output<void>();
519
+ readonly permissionsChanged = output<void>();
520
+ ```
521
+
471
522
  **Features:**
472
523
  - Shows all companies with checkboxes
473
524
  - Pre-checks assigned companies
474
525
  - Immediate save on toggle (no Save button)
475
526
  - Toast notifications for success/error
527
+ - Loading spinner during data fetch
528
+ - Disabled checkboxes while saving
476
529
 
477
530
  ### UserBranchPermissionDialogComponent
478
531
 
@@ -494,13 +547,14 @@ import { UserBranchPermissionDialogComponent } from '@flusys/ng-auth';
494
547
  ```
495
548
 
496
549
  **Features:**
497
- - Shows only companies where user has company-level permissions
498
- - User selects company → branches load
550
+ - Company dropdown shows only user's permitted companies
551
+ - Branch list loads on company selection
499
552
  - Immediate save on toggle
553
+ - Contextual empty messages (no permissions vs no branches)
500
554
  - Scoped to user's permitted companies
501
555
 
502
556
  **Workflow:**
503
- 1. Dialog opens → Loads user's permitted companies
557
+ 1. Dialog opens → Loads user's permitted companies via effect()
504
558
  2. User selects company → Loads branches for that company
505
559
  3. User toggles branch checkboxes → Immediately assigns/revokes
506
560
 
@@ -706,6 +760,67 @@ interface IUserBranchPermission { id: string; userId: string; branchId: string;
706
760
 
707
761
  ## Best Practices
708
762
 
763
+ ### Angular 21 Component Patterns
764
+
765
+ All components in ng-auth follow these patterns:
766
+
767
+ ```typescript
768
+ @Component({
769
+ selector: 'lib-example',
770
+ standalone: true,
771
+ changeDetection: ChangeDetectionStrategy.OnPush, // Always OnPush
772
+ imports: [AngularModule, PrimeModule, FormField],
773
+ template: `...`
774
+ })
775
+ export class ExampleComponent {
776
+ // Dependency injection via inject()
777
+ private readonly service = inject(MyService);
778
+
779
+ // Signal-based state
780
+ readonly isLoading = signal(false);
781
+ readonly data = signal<IData | null>(null);
782
+
783
+ // Computed signals
784
+ readonly isValid = computed(() => this.data() !== null);
785
+
786
+ // Signal Forms
787
+ readonly formModel = signal<IFormModel>({ name: '', email: '' });
788
+ readonly formTree = form(this.formModel, (f) => {
789
+ required(f.name, { message: 'Name is required' });
790
+ });
791
+
792
+ // Error handling with proper typing
793
+ async onSubmit(): Promise<void> {
794
+ try {
795
+ // ...
796
+ } catch (error: unknown) {
797
+ const err = error as { error?: { message?: string } };
798
+ // Handle error
799
+ }
800
+ }
801
+ }
802
+ ```
803
+
804
+ ### Responsive Design Patterns
805
+
806
+ ```html
807
+ <!-- Responsive container -->
808
+ <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 p-4">
809
+
810
+ <!-- Two-column form grid -->
811
+ <form class="grid grid-cols-1 md:grid-cols-2 gap-4">
812
+
813
+ <!-- Right-aligned buttons -->
814
+ <div class="flex justify-end gap-4 md:col-span-2 pt-4">
815
+
816
+ <!-- Horizontally scrollable tabs -->
817
+ <div class="scrollbar-hide flex gap-1 overflow-x-auto flex-nowrap">
818
+
819
+ <!-- Responsive table wrapper -->
820
+ <div class="overflow-x-auto">
821
+ <p-table [scrollable]="true" [paginator]="total() > 0">
822
+ ```
823
+
709
824
  ### Component Organization
710
825
 
711
826
  ```
@@ -716,7 +831,14 @@ projects/ng-auth/
716
831
  ├── pages/ ← Route-specific pages
717
832
  │ ├── login/
718
833
  │ ├── register/
719
- │ └── users/
834
+ │ ├── forgot-password/
835
+ │ ├── reset-password/
836
+ │ ├── verify-email/
837
+ │ ├── profile/
838
+ │ ├── administration/
839
+ │ ├── users/
840
+ │ ├── company/
841
+ │ └── branch/
720
842
  ├── services/
721
843
  ├── guards/
722
844
  ├── interceptors/
@@ -745,6 +867,27 @@ const allCompanies = await companyApi.fetchList(); // Security risk
745
867
  - TokenRefreshInterceptor handles automatic refresh
746
868
  - Auto-persistence for company/branch selection
747
869
 
870
+ ### Type Safety
871
+
872
+ Avoid `any` types - use explicit interfaces:
873
+
874
+ ```typescript
875
+ // ✅ CORRECT - Explicit response type
876
+ .subscribe({
877
+ next: (result: { message?: string }) => {
878
+ this.messageService.add({
879
+ severity: 'success',
880
+ detail: result.message,
881
+ });
882
+ },
883
+ });
884
+
885
+ // ❌ WRONG - Using any
886
+ .subscribe({
887
+ next: (result: any) => { ... },
888
+ });
889
+ ```
890
+
748
891
  ### Multi-Company Flow
749
892
 
750
893
  1. Login → Check if `requiresSelection: true`
@@ -801,6 +944,143 @@ providers: [
801
944
 
802
945
  ---
803
946
 
947
+ ## No-IAM Mode (Bypassing Permission Guards)
948
+
949
+ When you don't use the `@flusys/ng-iam` module but your routes use `permissionGuard`, the guard will fail because no permissions are loaded.
950
+
951
+ **Solution:** Set wildcard permissions in `PermissionValidatorService` after login.
952
+
953
+ ### How Permission Guard Works
954
+
955
+ ```
956
+ Route with permissionGuard('user.view')
957
+ ↓
958
+ permissionGuard() → inject(PermissionValidatorService)
959
+ ↓
960
+ permissionValidator.isPermissionsLoaded() → false
961
+ ↓
962
+ Redirect to '/' ❌
963
+ ```
964
+
965
+ ### Fix: Set Wildcard Permissions After Login
966
+
967
+ #### Option 1: In Login Flow (Recommended)
968
+
969
+ Update your login handling to set permissions after successful authentication:
970
+
971
+ ```typescript
972
+ // login.component.ts or auth.service.ts
973
+ import { PermissionValidatorService } from '@flusys/ng-shared';
974
+
975
+ @Component({ ... })
976
+ export class LoginComponent {
977
+ private readonly permissionValidator = inject(PermissionValidatorService);
978
+ private readonly authApi = inject(AuthApiService);
979
+ private readonly authState = inject(AuthStateService);
980
+
981
+ async onLogin(): Promise<void> {
982
+ const response = await firstValueFrom(this.authApi.login(credentials));
983
+ this.authState.setLoginState(response);
984
+
985
+ // When IAM is NOT used, grant all permissions via wildcard
986
+ this.permissionValidator.setPermissions(['*']);
987
+
988
+ // Navigate to dashboard
989
+ this.router.navigate(['/dashboard']);
990
+ }
991
+ }
992
+ ```
993
+
994
+ #### Option 2: In APP_INITIALIZER
995
+
996
+ Set permissions at app startup:
997
+
998
+ ```typescript
999
+ // app.config.ts
1000
+ import { APP_INITIALIZER } from '@angular/core';
1001
+ import { PermissionValidatorService } from '@flusys/ng-shared';
1002
+
1003
+ export const appConfig: ApplicationConfig = {
1004
+ providers: [
1005
+ {
1006
+ provide: APP_INITIALIZER,
1007
+ useFactory: (permissionValidator: PermissionValidatorService) => {
1008
+ return () => {
1009
+ // Set wildcard permissions when IAM is disabled
1010
+ permissionValidator.setPermissions(['*']);
1011
+ };
1012
+ },
1013
+ deps: [PermissionValidatorService],
1014
+ multi: true,
1015
+ },
1016
+ // ... other providers
1017
+ ],
1018
+ };
1019
+ ```
1020
+
1021
+ #### Option 3: In AuthInitService (After Session Restore)
1022
+
1023
+ If you restore sessions on page refresh, set permissions there:
1024
+
1025
+ ```typescript
1026
+ // After successful /auth/me response
1027
+ authInit.initialize().subscribe({
1028
+ next: (user) => {
1029
+ // Session restored, set wildcard permissions
1030
+ permissionValidator.setPermissions(['*']);
1031
+ },
1032
+ });
1033
+ ```
1034
+
1035
+ ### Wildcard Permission Syntax
1036
+
1037
+ The `permissionGuard` utilities support these patterns:
1038
+
1039
+ | Pattern | Meaning |
1040
+ |---------|---------|
1041
+ | `*` | Full access to everything |
1042
+ | `user.*` | All user permissions |
1043
+ | `branch.*` | All branch permissions |
1044
+
1045
+ ### For Jest Tests
1046
+
1047
+ Mock `PermissionValidatorService` in your test setup:
1048
+
1049
+ ```typescript
1050
+ import { PermissionValidatorService } from '@flusys/ng-shared';
1051
+
1052
+ describe('MyComponent', () => {
1053
+ beforeEach(async () => {
1054
+ await TestBed.configureTestingModule({
1055
+ imports: [MyComponent],
1056
+ providers: [
1057
+ {
1058
+ provide: PermissionValidatorService,
1059
+ useValue: {
1060
+ permissions: () => ['*'],
1061
+ isPermissionsLoaded: () => true,
1062
+ hasPermission: () => true,
1063
+ setPermissions: jest.fn(),
1064
+ clearPermissions: jest.fn(),
1065
+ },
1066
+ },
1067
+ ],
1068
+ }).compileComponents();
1069
+ });
1070
+ });
1071
+ ```
1072
+
1073
+ ### For Template Permission Checks
1074
+
1075
+ If using `*hasPermission` directive, it will also respect wildcard:
1076
+
1077
+ ```html
1078
+ <!-- This will show when permissions include '*' -->
1079
+ <button *hasPermission="'user.delete'">Delete User</button>
1080
+ ```
1081
+
1082
+ ---
1083
+
804
1084
  ## See Also
805
1085
 
806
1086
  - [CORE-GUIDE.md](./CORE-GUIDE.md) - APP_CONFIG configuration
@@ -811,5 +1091,25 @@ providers: [
811
1091
 
812
1092
  ---
813
1093
 
814
- **Last Updated:** 2026-02-16
1094
+ **Last Updated:** 2026-02-21
815
1095
  **Angular Version:** 21
1096
+
1097
+ ## Component Checklist
1098
+
1099
+ | Component | OnPush | Signal Forms | Responsive | Dark Mode |
1100
+ |-----------|--------|--------------|------------|-----------|
1101
+ | LoginPageComponent | ✅ | ✅ | ✅ | ✅ |
1102
+ | RegisterPageComponent | ✅ | ✅ | ✅ | ✅ |
1103
+ | ForgotPasswordPageComponent | ✅ | ✅ | ✅ | ✅ |
1104
+ | ResetPasswordPageComponent | ✅ | ✅ | ✅ | ✅ |
1105
+ | VerifyEmailPageComponent | ✅ | ✅ | ✅ | ✅ |
1106
+ | ProfilePageComponent | ✅ | ✅ | ✅ | ✅ |
1107
+ | AdministrationPageComponent | ✅ | N/A | ✅ | ✅ |
1108
+ | UserListComponent | ✅ | N/A | ✅ | ✅ |
1109
+ | UserFormComponent | ✅ | ✅ | ✅ | ✅ |
1110
+ | CompanyListComponent | ✅ | N/A | ✅ | ✅ |
1111
+ | CompanyFormComponent | ✅ | ✅ | ✅ | ✅ |
1112
+ | BranchListComponent | ✅ | N/A | ✅ | ✅ |
1113
+ | BranchFormComponent | ✅ | ✅ | ✅ | ✅ |
1114
+ | UserCompanyPermissionDialogComponent | ✅ | N/A | ✅ | ✅ |
1115
+ | UserBranchPermissionDialogComponent | ✅ | N/A | ✅ | ✅ |