diff --git a/src/app/shared/error-dialog/error-dialog.component.html b/src/app/shared/error-dialog/error-dialog.component.html
index e23d13a0d..892421df5 100644
--- a/src/app/shared/error-dialog/error-dialog.component.html
+++ b/src/app/shared/error-dialog/error-dialog.component.html
@@ -6,17 +6,19 @@
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
-
{{ 'Error Log' | translate }}
-
-
+
+
+
+
+ We encountered an issue. Please try again.
@if (!showAsCode) {
- {{ displayData }}
+ {{ displayData }}
}
@if (showAsCode) {
-
+
}
-
+
-
- {{ 'Cancel' | translate }}
+
+ OK
diff --git a/src/app/shared/error-dialog/error-dialog.component.scss b/src/app/shared/error-dialog/error-dialog.component.scss
index af9f4c075..6a59c437a 100644
--- a/src/app/shared/error-dialog/error-dialog.component.scss
+++ b/src/app/shared/error-dialog/error-dialog.component.scss
@@ -5,3 +5,87 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+
+:host {
+ display: block;
+ min-width: 380px;
+ max-width: 480px;
+ font-family: inherit;
+ color: #0f172a;
+}
+
+.ed-content {
+ display: flex !important;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ gap: 14px;
+ padding: 32px 28px 8px !important;
+ background: linear-gradient(180deg, #fef2f2 0%, #fff 70%);
+}
+
+.ed-icon {
+ width: 76px;
+ height: 76px;
+ border-radius: 50%;
+ background: #fee2e2;
+ color: #dc2626;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 38px;
+ box-shadow: 0 0 0 6px rgb(220 38 38 / 10%);
+ animation: ed-pop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+@keyframes ed-pop {
+ 0% {
+ transform: scale(0.6);
+ opacity: 0;
+ }
+
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+.ed-title {
+ margin: 4px 0 0;
+ font-size: 20px;
+ font-weight: 700;
+ color: #0f172a;
+}
+
+.ed-message {
+ margin: 0;
+ color: #475569;
+ font-size: 14px;
+ line-height: 1.55;
+ max-width: 380px;
+ word-break: break-word;
+}
+
+.ed-code {
+ width: 100%;
+ max-height: 320px;
+ overflow: auto;
+ text-align: left;
+ background: #0f172a;
+ color: #e2e8f0;
+ border-radius: 8px;
+ padding: 12px 14px;
+ font-family: 'SF Mono', Menlo, Consolas, monospace;
+ font-size: 12.5px;
+ line-height: 1.5;
+}
+
+.ed-actions {
+ padding: 16px 24px 22px !important;
+ background: #fff;
+ justify-content: center !important;
+
+ button {
+ min-width: 120px;
+ }
+}
diff --git a/src/app/shared/error-dialog/error-dialog.component.ts b/src/app/shared/error-dialog/error-dialog.component.ts
index e09675f03..fb0d96e49 100644
--- a/src/app/shared/error-dialog/error-dialog.component.ts
+++ b/src/app/shared/error-dialog/error-dialog.component.ts
@@ -11,13 +11,13 @@ import { Component, OnInit, SecurityContext, inject } from '@angular/core';
import {
MatDialogRef,
MAT_DIALOG_DATA,
- MatDialogTitle,
MatDialogContent,
MatDialogActions,
MatDialogClose
} from '@angular/material/dialog';
import { CdkScrollable } from '@angular/cdk/scrolling';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -26,11 +26,11 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
styleUrls: ['./error-dialog.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
- MatDialogTitle,
CdkScrollable,
MatDialogContent,
MatDialogActions,
- MatDialogClose
+ MatDialogClose,
+ FaIconComponent
]
})
export class ErrorDialogComponent {
diff --git a/src/app/shared/success-dialog/success-dialog.component.html b/src/app/shared/success-dialog/success-dialog.component.html
new file mode 100644
index 000000000..db81a3b74
--- /dev/null
+++ b/src/app/shared/success-dialog/success-dialog.component.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ {{ data.title || 'Success' }}
+ @if (data.message) {
+ {{ data.message }}
+ }
+
+
+
+
+ {{ data.buttonLabel || 'OK' }}
+
+
diff --git a/src/app/shared/success-dialog/success-dialog.component.scss b/src/app/shared/success-dialog/success-dialog.component.scss
new file mode 100644
index 000000000..2e5c3c9ab
--- /dev/null
+++ b/src/app/shared/success-dialog/success-dialog.component.scss
@@ -0,0 +1,76 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+:host {
+ display: block;
+ min-width: 380px;
+ max-width: 460px;
+ font-family: inherit;
+ color: #0f172a;
+}
+
+.sd-content {
+ display: flex !important;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ gap: 14px;
+ padding: 32px 28px 8px !important;
+ background: linear-gradient(180deg, #f0fdf4 0%, #fff 70%);
+}
+
+.sd-icon {
+ width: 76px;
+ height: 76px;
+ border-radius: 50%;
+ background: #dcfce7;
+ color: #16a34a;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 38px;
+ box-shadow: 0 0 0 6px rgb(22 163 74 / 10%);
+ animation: sd-pop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+@keyframes sd-pop {
+ 0% {
+ transform: scale(0.6);
+ opacity: 0;
+ }
+
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+.sd-title {
+ margin: 4px 0 0;
+ font-size: 20px;
+ font-weight: 700;
+ color: #0f172a;
+}
+
+.sd-message {
+ margin: 0;
+ color: #475569;
+ font-size: 14px;
+ line-height: 1.5;
+ max-width: 360px;
+}
+
+.sd-actions {
+ padding: 16px 24px 22px !important;
+ background: #fff;
+ justify-content: center !important;
+
+ button {
+ min-width: 120px;
+ }
+}
diff --git a/src/app/shared/success-dialog/success-dialog.component.ts b/src/app/shared/success-dialog/success-dialog.component.ts
new file mode 100644
index 000000000..eca68acfc
--- /dev/null
+++ b/src/app/shared/success-dialog/success-dialog.component.ts
@@ -0,0 +1,41 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+import { Component, inject } from '@angular/core';
+import {
+ MatDialogRef,
+ MAT_DIALOG_DATA,
+ MatDialogContent,
+ MatDialogActions,
+ MatDialogClose
+} from '@angular/material/dialog';
+import { MatButton } from '@angular/material/button';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+
+export interface SuccessDialogData {
+ title?: string;
+ message?: string;
+ buttonLabel?: string;
+}
+
+@Component({
+ selector: 'mifosx-success-dialog',
+ templateUrl: './success-dialog.component.html',
+ styleUrls: ['./success-dialog.component.scss'],
+ imports: [
+ MatDialogContent,
+ MatDialogActions,
+ MatDialogClose,
+ MatButton,
+ FaIconComponent
+ ]
+})
+export class SuccessDialogComponent {
+ dialogRef = inject>(MatDialogRef);
+ data = inject(MAT_DIALOG_DATA);
+}
diff --git a/src/app/users/edit-user/edit-user.component.ts b/src/app/users/edit-user/edit-user.component.ts
index f26465e06..30908290a 100644
--- a/src/app/users/edit-user/edit-user.component.ts
+++ b/src/app/users/edit-user/edit-user.component.ts
@@ -10,13 +10,15 @@
import { Component, OnInit, inject } from '@angular/core';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
-import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
+import { MatDialog } from '@angular/material/dialog';
/** Custom Services */
import { UsersService } from '../users.service';
import { MatCheckbox } from '@angular/material/checkbox';
import { MatIcon } from '@angular/material/icon';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+import { SuccessDialogComponent } from 'app/shared/success-dialog/success-dialog.component';
+import { ErrorDialogComponent } from 'app/shared/error-dialog/error-dialog.component';
/**
* Edit User Component.
@@ -28,8 +30,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
imports: [
...STANDALONE_SHARED_IMPORTS,
MatCheckbox,
- MatIcon,
- MatSnackBarModule
+ MatIcon
]
})
export class EditUserComponent implements OnInit {
@@ -37,7 +38,7 @@ export class EditUserComponent implements OnInit {
private usersService = inject(UsersService);
private route = inject(ActivatedRoute);
private router = inject(Router);
- private snackBar = inject(MatSnackBar);
+ private dialog = inject(MatDialog);
/** User Data */
userData: any;
@@ -144,23 +145,29 @@ export class EditUserComponent implements OnInit {
// Fineract sometimes omits resourceId in PUT responses — fall back to
// the user id we're editing so navigation never silently no-ops.
const targetId = response?.resourceId ?? this.userData.id;
- this.snackBar.open('User updated successfully', 'OK', {
- duration: 3000,
- panelClass: ['success-snackbar']
+ const dialogRef = this.dialog.open(SuccessDialogComponent, {
+ data: {
+ title: 'User updated',
+ message: `Changes to ${this.userData.username} have been saved successfully.`,
+ buttonLabel: 'View user'
+ },
+ panelClass: 'sd-panel',
+ disableClose: true
+ });
+ dialogRef.afterClosed().subscribe(() => {
+ this.router.navigate(
+ [
+ '../../',
+ targetId
+ ],
+ { relativeTo: this.route }
+ );
});
- this.router.navigate(
- [
- '../../',
- targetId
- ],
- { relativeTo: this.route }
- );
},
error: (err) => {
console.error('[EditUser] update failed', err);
- this.snackBar.open('Failed to update user', 'Close', {
- duration: 4000,
- panelClass: ['error-snackbar']
+ this.dialog.open(ErrorDialogComponent, {
+ data: err?.error?.defaultUserMessage || err?.error?.message || 'Failed to update user.'
});
}
});