diff --git a/src/app/adf-user-management/df-login/df-login.component.ts b/src/app/adf-user-management/df-login/df-login.component.ts
index 85f955fc..41e9e74f 100644
--- a/src/app/adf-user-management/df-login/df-login.component.ts
+++ b/src/app/adf-user-management/df-login/df-login.component.ts
@@ -32,6 +32,8 @@ import { UntilDestroy } from '@ngneat/until-destroy';
import { DfThemeService } from 'src/app/shared/services/df-theme.service';
import { CommonModule } from '@angular/common';
import { DfSnackbarService } from 'src/app/shared/services/df-snackbar.service';
+import { PopupOverlayService } from 'src/app/shared/components/df-popup/popup-overlay.service';
+
@UntilDestroy({ checkProperties: true })
@Component({
selector: 'df-user-login',
@@ -58,6 +60,7 @@ import { DfSnackbarService } from 'src/app/shared/services/df-snackbar.service';
],
})
export class DfLoginComponent implements OnInit {
+ private readonly MINIMUM_PASSWORD_LENGTH = 17;
alertMsg = '';
showAlert = false;
alertType: AlertType = 'error';
@@ -78,7 +81,8 @@ export class DfLoginComponent implements OnInit {
private authService: DfAuthService,
private router: Router,
private themeService: DfThemeService,
- private snackbarService: DfSnackbarService
+ private snackbarService: DfSnackbarService,
+ private popupOverlay: PopupOverlayService
) {
this.loginForm = this.fb.group({
services: [''],
@@ -132,6 +136,8 @@ export class DfLoginComponent implements OnInit {
if (this.loginForm.invalid) {
return;
}
+
+ const isPasswordTooShort = this.loginForm.value.password.length < this.MINIMUM_PASSWORD_LENGTH;
const credentials: LoginCredentials = {
password: this.loginForm.value.password,
};
@@ -143,17 +149,31 @@ export class DfLoginComponent implements OnInit {
} else {
credentials.email = this.loginForm.value.email;
}
+
this.authService
.login(credentials)
.pipe(
catchError(err => {
- this.alertMsg = err.error.error.message;
- this.showAlert = true;
+ if (err.status === 401 && isPasswordTooShort) {
+ this.popupOverlay.open({
+ message: `It looks like your password is too short. Our new system requires at least ${this.MINIMUM_PASSWORD_LENGTH} characters. Please reset your password to continue.`,
+ showRemindMeLater: false,
+ });
+ } else {
+ this.alertMsg = err.error?.error?.message || 'Login failed';
+ this.showAlert = true;
+ }
return throwError(() => new Error(err));
})
)
.subscribe(() => {
this.showAlert = false;
+ if (isPasswordTooShort) {
+ this.popupOverlay.open({
+ message: `Your current password is shorter than recommended (less than ${this.MINIMUM_PASSWORD_LENGTH} characters). For better security, we recommend updating your password to a longer one.`,
+ showRemindMeLater: true,
+ });
+ }
this.router.navigate([ROUTES.HOME]);
});
}
diff --git a/src/app/adf-user-management/services/df-auth.service.ts b/src/app/adf-user-management/services/df-auth.service.ts
index 6d49db53..fa0c4cfe 100644
--- a/src/app/adf-user-management/services/df-auth.service.ts
+++ b/src/app/adf-user-management/services/df-auth.service.ts
@@ -104,7 +104,7 @@ export class DfAuthService {
);
}
- logout() {
+ logout(redirectTo: any[] = [ROUTES.AUTH, ROUTES.LOGIN]) {
this.http
.delete(
this.userDataService.userData?.isSysAdmin
@@ -114,7 +114,7 @@ export class DfAuthService {
.subscribe(() => {
this.userDataService.clearToken();
this.userDataService.userData = null;
- this.router.navigate([ROUTES.AUTH, ROUTES.LOGIN]);
+ this.router.navigate(redirectTo);
});
}
}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 553f32bf..d11f663d 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -2,7 +2,6 @@ import { Component, OnInit } from '@angular/core';
import { DfLoadingSpinnerService } from './shared/services/df-loading-spinner.service';
import { NgIf, AsyncPipe } from '@angular/common';
import { RouterOutlet, Router, ActivatedRoute } from '@angular/router';
-import { DfSideNavComponent } from './shared/components/df-side-nav/df-side-nav.component';
import { DfLicenseCheckService } from './shared/services/df-license-check.service';
import { UntilDestroy } from '@ngneat/until-destroy';
import { AuthService } from './shared/services/auth.service';
@@ -15,7 +14,7 @@ import { LoginResponse } from './shared/types/auth.types';
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
standalone: true,
- imports: [DfSideNavComponent, RouterOutlet, NgIf, AsyncPipe],
+ imports: [RouterOutlet, NgIf, AsyncPipe],
})
export class AppComponent implements OnInit {
title = 'df-admin-interface';
diff --git a/src/app/shared/components/df-popup/df-popup.component.html b/src/app/shared/components/df-popup/df-popup.component.html
new file mode 100644
index 00000000..b20b0ac5
--- /dev/null
+++ b/src/app/shared/components/df-popup/df-popup.component.html
@@ -0,0 +1,24 @@
+
diff --git a/src/app/shared/components/df-popup/df-popup.component.scss b/src/app/shared/components/df-popup/df-popup.component.scss
new file mode 100644
index 00000000..7d6b8d50
--- /dev/null
+++ b/src/app/shared/components/df-popup/df-popup.component.scss
@@ -0,0 +1,110 @@
+// Popup Container
+.popup-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ height: 100vh;
+ z-index: 10000;
+}
+
+.popup {
+ position: relative;
+ width: 90%;
+ max-width: 500px;
+ background: #ffffff;
+ border-radius: 12px;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
+ padding: 24px;
+ z-index: 10001;
+ animation: popupFadeIn 0.3s ease-out;
+
+ .popup-header {
+ margin-bottom: 20px;
+ text-align: center;
+
+ h2 {
+ margin: 0;
+ color: #333333;
+ font-size: 1.5rem;
+ font-weight: 600;
+ }
+ }
+
+ .popup-content {
+ margin-bottom: 24px;
+ text-align: center;
+
+ p {
+ margin: 8px 0;
+ color: #666666;
+ line-height: 1.5;
+ }
+ }
+
+ .popup-actions {
+ display: flex;
+ justify-content: center;
+ gap: 12px;
+
+ button {
+ min-width: 120px;
+ padding: 8px 16px;
+ font-weight: 500;
+ transition: all 0.2s ease;
+
+ &:hover {
+ transform: translateY(-1px);
+ }
+ }
+ }
+}
+
+@keyframes popupFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.actions {
+ display: flex;
+ flex-direction: row;
+}
+
+// Popup Header
+.popup-header {
+ font-size: 18px;
+ font-weight: bold;
+ color: #6d4ec9; // Matches sidebar color
+ margin-bottom: 10px;
+}
+
+// Popup Content
+.popup-content {
+ font-size: 14px;
+ margin-bottom: 15px;
+}
+
+// Close Button
+.popup-close {
+ background: #6d4ec9; // Match theme
+ color: #fff;
+ border: none;
+ padding: 10px 15px;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 14px;
+ transition: background 0.3s ease;
+
+ &:hover {
+ background: #5a3bb3; // Darker on hover
+ }
+}
diff --git a/src/app/shared/components/df-popup/df-popup.component.ts b/src/app/shared/components/df-popup/df-popup.component.ts
new file mode 100644
index 00000000..cbb7c58c
--- /dev/null
+++ b/src/app/shared/components/df-popup/df-popup.component.ts
@@ -0,0 +1,42 @@
+import { Component, Inject, Optional } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { MatButtonModule } from '@angular/material/button';
+import { MatDialogModule } from '@angular/material/dialog';
+import { TranslocoPipe } from '@ngneat/transloco';
+import { Router } from '@angular/router';
+import { ROUTES } from '../../types/routes';
+import { PopupOverlayService } from './popup-overlay.service';
+import { DfAuthService } from 'src/app/adf-user-management/services/df-auth.service';
+import { POPUP_CONFIG, PopupConfig } from './popup-config';
+
+@Component({
+ selector: 'df-popup',
+ templateUrl: './df-popup.component.html',
+ standalone: true,
+ styleUrls: ['./df-popup.component.scss'],
+ imports: [CommonModule, MatButtonModule, MatDialogModule, TranslocoPipe],
+})
+export class PopupComponent {
+ constructor(
+ private router: Router,
+ private popupOverlay: PopupOverlayService,
+ private authService: DfAuthService,
+ @Optional() @Inject(POPUP_CONFIG) public config: PopupConfig
+ ) {}
+
+ get message() {
+ return this.config?.message ||
+ 'Your current password is shorter than recommended (less than 17 characters). For better security, we recommend updating your password to a longer one.';
+ }
+ get showRemindMeLater() {
+ return this.config?.showRemindMeLater !== false;
+ }
+
+ closePopup(shouldRedirect = false) {
+ this.popupOverlay.close();
+ if (shouldRedirect) {
+ this.authService.logout();
+ this.router.navigate([ROUTES.AUTH, ROUTES.RESET_PASSWORD]);
+ }
+ }
+}
diff --git a/src/app/shared/components/df-popup/popup-config.ts b/src/app/shared/components/df-popup/popup-config.ts
new file mode 100644
index 00000000..fb157bb3
--- /dev/null
+++ b/src/app/shared/components/df-popup/popup-config.ts
@@ -0,0 +1,8 @@
+import { InjectionToken } from '@angular/core';
+
+export interface PopupConfig {
+ message: string;
+ showRemindMeLater: boolean;
+}
+
+export const POPUP_CONFIG = new InjectionToken('POPUP_CONFIG');
diff --git a/src/app/shared/components/df-popup/popup-overlay.service.ts b/src/app/shared/components/df-popup/popup-overlay.service.ts
new file mode 100644
index 00000000..20bd5ed5
--- /dev/null
+++ b/src/app/shared/components/df-popup/popup-overlay.service.ts
@@ -0,0 +1,41 @@
+import { Injectable, Injector } from '@angular/core';
+import { Overlay, OverlayRef } from '@angular/cdk/overlay';
+import { ComponentPortal } from '@angular/cdk/portal';
+import { PopupComponent } from './df-popup.component';
+import { POPUP_CONFIG, PopupConfig } from './popup-config';
+
+@Injectable({ providedIn: 'root' })
+export class PopupOverlayService {
+ private overlayRef: OverlayRef | null = null;
+
+ constructor(
+ private overlay: Overlay,
+ private injector: Injector
+ ) {}
+
+ open(config?: PopupConfig) {
+ if (this.overlayRef) return;
+ const injector = Injector.create({
+ providers: [{ provide: POPUP_CONFIG, useValue: config }],
+ parent: this.injector,
+ });
+ this.overlayRef = this.overlay.create({
+ hasBackdrop: true,
+ backdropClass: 'popup-backdrop',
+ positionStrategy: this.overlay
+ .position()
+ .global()
+ .centerHorizontally()
+ .centerVertically(),
+ scrollStrategy: this.overlay.scrollStrategies.block(),
+ });
+ const portal = new ComponentPortal(PopupComponent, null, injector);
+ this.overlayRef.attach(portal);
+ this.overlayRef.backdropClick().subscribe(() => this.close());
+ }
+
+ close() {
+ this.overlayRef?.dispose();
+ this.overlayRef = null;
+ }
+}
diff --git a/src/app/shared/services/popup.service.ts b/src/app/shared/services/popup.service.ts
new file mode 100644
index 00000000..a6c64216
--- /dev/null
+++ b/src/app/shared/services/popup.service.ts
@@ -0,0 +1,26 @@
+import { Injectable } from '@angular/core';
+import { BehaviorSubject } from 'rxjs';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class PopupService {
+ private storageKey = 'showPasswordPopup';
+ private popupStateSubject = new BehaviorSubject(false);
+ popupState$ = this.popupStateSubject.asObservable();
+
+ constructor() {
+ // Initialize from localStorage
+ const savedState = this.shouldShowPopup();
+ this.popupStateSubject.next(savedState);
+ }
+
+ setShowPopup(value: boolean): void {
+ localStorage.setItem(this.storageKey, JSON.stringify(value));
+ this.popupStateSubject.next(value);
+ }
+
+ shouldShowPopup(): boolean {
+ return JSON.parse(localStorage.getItem(this.storageKey) || 'false');
+ }
+}
diff --git a/src/styles.scss b/src/styles.scss
index 123f7800..67e2c05d 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -230,3 +230,9 @@ a {
.swagger-ui .opblock .opblock-section-header {
background: unset !important;
}
+
+.popup-backdrop {
+ background: rgba(0, 0, 0, 0.6) !important;
+ backdrop-filter: blur(6px) !important;
+ -webkit-backdrop-filter: blur(6px) !important;
+}