WEB-573: refactor password preferences UI to card-based layout

- Convert horizontal radio layout to vertical card-based blocks
- Add heading labels (Basic, Standard, Strong) for each policy option
- Position radio buttons and headings on same row with space-between
- Increase padding and spacing for improved visual clarity
- Maintain full-width blocks with consistent vertical stacking
- Preserve all existing text content and functionality
- Add trackBy function to *ngFor for performance optimization
- Use translation keys for password labels instead of hardcoded array
- Map labels based on preference ID for robustness
This commit is contained in:
Anvay
2026-01-15 01:32:18 +05:30
parent 56a965e87d
commit ed1b281127
3 changed files with 80 additions and 15 deletions
@@ -1,6 +1,5 @@
<!--
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/.
@@ -10,14 +9,25 @@
<mat-card>
<form [formGroup]="passwordPreferencesForm" (ngSubmit)="submit()">
<mat-card-content>
<mat-radio-group class="layout-column gap-10px radio-group-spacing" formControlName="validationPolicyId">
@for (passwordPreference of passwordPreferencesData; track passwordPreference) {
<mat-radio-button [value]="passwordPreference.id">
<span class="description-wrap">{{
passwordPreference.description | translateKey: 'passwordPreferences'
}}</span>
</mat-radio-button>
}
<mat-radio-group class="password-cards-container" formControlName="validationPolicyId">
<div
*ngFor="let passwordPreference of passwordPreferencesData; trackBy: trackByPasswordPreference"
class="password-card"
[class.selected]="passwordPreferencesForm.get('validationPolicyId')?.value === passwordPreference.id"
>
<div class="card-header">
<div class="card-heading">
{{ getPasswordLabel(passwordPreference) | translate }}
</div>
<mat-radio-button [value]="passwordPreference.id"></mat-radio-button>
</div>
<div class="card-description">
<span class="description-wrap">
{{ passwordPreference.description | translateKey: 'passwordPreferences' }}
</span>
</div>
</div>
</mat-radio-group>
</mat-card-content>
@@ -10,14 +10,44 @@
white-space: normal;
}
.radio-group-spacing {
.password-cards-container {
display: flex;
gap: 2rem;
flex-direction: row;
flex-direction: column;
gap: 1.5rem;
width: 100%;
}
@media (width <= 768px) {
.radio-group-spacing {
flex-direction: column;
.password-card {
width: 100%;
border: 1px solid rgb(0 0 0 / 12%);
border-radius: 4px;
padding: 1.5rem;
box-sizing: border-box;
&.selected {
border-color: currentcolor;
border-width: 2px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.card-heading {
font-weight: 500;
font-size: 1rem;
}
.card-description {
padding-left: 0;
.description-wrap {
white-space: normal;
display: block;
line-height: 1.5;
}
}
}
@@ -81,6 +81,31 @@ export class PasswordPreferencesComponent implements OnInit {
}
}
/**
* TrackBy function for ngFor optimization.
* @param index Index of the item.
* @param item Password preference item.
* @returns Unique identifier for the item.
*/
trackByPasswordPreference(index: number, item: any): any {
return item.id || index;
}
/**
* Gets the password preference label based on ID.
* @param preference Password preference object.
* @returns Translation key for the password preference label.
*/
getPasswordLabel(preference: any): string {
// Map based on ID to ensure robustness
const labelMap: { [key: number]: string } = {
1: 'labels.inputs.Basic',
2: 'labels.inputs.Standard',
3: 'labels.inputs.Strong'
};
return labelMap[preference.id] || 'labels.inputs.Unknown';
}
/**
* Submits the password preferences form and updates password preferences,
* if successful redirects to organization view.