feat(users): OTP delivery method picker on user creation + post-login agreement
Create User dialog now has a mandatory Email/Mobile OTP delivery method picker (styled as a pill toggle) alongside the existing fields. Adds a new post-login agreement acceptance dialog: right after the existing 2FA flow (TwoFactorAuthenticationComponent) succeeds, or immediately after login for users without 2FA, the user must accept the bank's current agreement/disclaimer at least once before reaching the app. Also adds a "Manage User Agreement" admin screen (System section) to publish versioned agreement text without a code deploy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,7 @@ import { TenantSelectorComponent } from '../shared/tenant-selector/tenant-select
|
||||
import { LoginFormComponent } from './login-form/login-form.component';
|
||||
import { ResetPasswordComponent } from './reset-password/reset-password.component';
|
||||
import { TwoFactorAuthenticationComponent } from './two-factor-authentication/two-factor-authentication.component';
|
||||
import { AgreementAcceptComponent } from './agreement-accept/agreement-accept.component';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatMenuTrigger, MatMenu, MatMenuItem } from '@angular/material/menu';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
@@ -85,6 +86,7 @@ import { AuthenticationService } from '../core/authentication/authentication.ser
|
||||
LoginFormComponent,
|
||||
ResetPasswordComponent,
|
||||
TwoFactorAuthenticationComponent,
|
||||
AgreementAcceptComponent,
|
||||
MatMenuTrigger,
|
||||
FaIconComponent,
|
||||
MatMenu,
|
||||
@@ -220,6 +222,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
private themingService = inject(ThemingService);
|
||||
private dialog = inject(MatDialog);
|
||||
private twoFactorDialogRef: MatDialogRef<TwoFactorAuthenticationComponent> | null = null;
|
||||
private agreementAcceptDialogRef: MatDialogRef<AgreementAcceptComponent> | null = null;
|
||||
private router = inject(Router);
|
||||
private authService = inject(AuthenticationService);
|
||||
|
||||
@@ -245,6 +248,8 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
resetPassword = false;
|
||||
/** True if user requires two factor authentication. */
|
||||
twoFactorAuthenticationRequired = false;
|
||||
/** True if the signed-in user still needs to accept the current agreement/disclaimer. */
|
||||
agreementAcceptanceRequired = false;
|
||||
/** Subscription to alerts. */
|
||||
alert$: Subscription;
|
||||
logoPath = 'assets/images/cbsnexora-logo.png';
|
||||
@@ -275,16 +280,24 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
const alertType = alertEvent.type;
|
||||
if (alertType === 'Password Expired') {
|
||||
this.twoFactorAuthenticationRequired = false;
|
||||
this.agreementAcceptanceRequired = false;
|
||||
this.closeTwoFactorDialog();
|
||||
this.closeAgreementAcceptDialog();
|
||||
this.resetPassword = true;
|
||||
} else if (alertType === 'Two Factor Authentication Required') {
|
||||
this.resetPassword = false;
|
||||
this.twoFactorAuthenticationRequired = true;
|
||||
this.openTwoFactorDialog();
|
||||
} else if (alertType === 'Agreement Acceptance Required') {
|
||||
this.resetPassword = false;
|
||||
this.agreementAcceptanceRequired = true;
|
||||
this.openAgreementAcceptDialog();
|
||||
} else if (alertType === 'Authentication Success') {
|
||||
this.resetPassword = false;
|
||||
this.twoFactorAuthenticationRequired = false;
|
||||
this.agreementAcceptanceRequired = false;
|
||||
this.closeTwoFactorDialog();
|
||||
this.closeAgreementAcceptDialog();
|
||||
// Cashier users go directly to the full-screen transaction entry dashboard
|
||||
const destination = this.authService.isCashierUser() ? ['/cashier/transaction-entry'] : ['/'];
|
||||
this.router.navigate(destination, { replaceUrl: true });
|
||||
@@ -334,6 +347,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
this.theme$.unsubscribe();
|
||||
}
|
||||
this.closeTwoFactorDialog();
|
||||
this.closeAgreementAcceptDialog();
|
||||
clearInterval(this.slideTimer);
|
||||
}
|
||||
|
||||
@@ -361,6 +375,30 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
/** Open the post-login agreement acceptance dialog as a non-dismissable modal */
|
||||
private openAgreementAcceptDialog(): void {
|
||||
if (this.agreementAcceptDialogRef) return;
|
||||
this.agreementAcceptDialogRef = this.dialog.open(AgreementAcceptComponent, {
|
||||
width: '460px',
|
||||
maxWidth: '95vw',
|
||||
panelClass: 'tfa-dialog-panel',
|
||||
backdropClass: 'tfa-dialog-backdrop',
|
||||
disableClose: true,
|
||||
autoFocus: 'first-tabbable',
|
||||
restoreFocus: true
|
||||
});
|
||||
this.agreementAcceptDialogRef.afterClosed().subscribe(() => {
|
||||
this.agreementAcceptDialogRef = null;
|
||||
});
|
||||
}
|
||||
|
||||
private closeAgreementAcceptDialog(): void {
|
||||
if (this.agreementAcceptDialogRef) {
|
||||
this.agreementAcceptDialogRef.close();
|
||||
this.agreementAcceptDialogRef = null;
|
||||
}
|
||||
}
|
||||
|
||||
goToSlide(index: number): void {
|
||||
this.currentSlide = index;
|
||||
this.restartSlider();
|
||||
|
||||
Reference in New Issue
Block a user