@flusys/ng-auth 0.1.0-beta.3 → 1.0.0-rc

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