feat: KYC re-verify UX, family/nominee tab gating, savings dialogs, cfcfrms shell
- KYC tab: fix Verify (PAN) and Send OTP (Aadhaar) being disabled in edit mode; unmask Aadhaar input and show on-file masked value as hint; resolve kycId from id/kycId/resourceId fallbacks. - Client stepper/view: add familyAllowed / nomineeAllowed predicates + CanActivate route guards. Family tab hidden for non-individuals, Nominee tab hidden for non-proprietor entities. Family-members step accepts a nomineeOnly input for proprietorship clients. Add/Edit/Delete buttons inside family-members and nominee tabs gated by canAdd. - cfcfrms: add cfcfrms-shell wrapper, restyle complaint/fraud/mule/escalate pages and dashboard. - Savings: identity-verify and transaction-confirm dialogs, manage / activate / add-charge flows, account preview styling. - Tasks, core shell, account-nominees, icons: minor style and behaviour updates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
bf3e26a0d0
commit
b149fef72e
@@ -6,100 +6,129 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<mat-stepper
|
||||
orientation="vertical"
|
||||
[linear]="false"
|
||||
class="mat-elevation-z8"
|
||||
labelPosition="bottom"
|
||||
#savingProductStepper
|
||||
<div class="csa-container">
|
||||
<!-- Hero -->
|
||||
<div class="csa-header">
|
||||
<fa-icon icon="piggy-bank" class="csa-header__icon"></fa-icon>
|
||||
<div>
|
||||
<h2 class="csa-header__title">Create Savings Account</h2>
|
||||
<div class="csa-header__sub">
|
||||
Pick a product, confirm currency, set digital channels and review before submit.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wizard tabs -->
|
||||
<mat-tab-group
|
||||
class="csa-tabs"
|
||||
animationDuration="200ms"
|
||||
[preserveContent]="true"
|
||||
[(selectedIndex)]="selectedTabIndex"
|
||||
>
|
||||
<ng-template matStepperIcon="number">
|
||||
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<ng-template matStepperIcon="edit">
|
||||
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<ng-template matStepperIcon="done">
|
||||
<fa-icon icon="check" size="sm"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<ng-template matStepperIcon="error">
|
||||
<fa-icon icon="exclamation-triangle" size="lg"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<ng-template matStepperIcon="preview">
|
||||
<fa-icon icon="eye" size="sm"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<!-- Step 1: Product Classification + Details -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Product Classification</ng-template>
|
||||
|
||||
<mifosx-savings-account-details-step
|
||||
[savingsAccountTemplate]="savingsAccountTemplate"
|
||||
(savingsAccountProductTemplate)="setTemplate($event)"
|
||||
>
|
||||
</mifosx-savings-account-details-step>
|
||||
</mat-step>
|
||||
|
||||
@if (savingsAccountProductTemplate) {
|
||||
<!-- Step 2: Currency -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Currency</ng-template>
|
||||
|
||||
<mifosx-savings-account-currency-step
|
||||
[savingsAccountProductTemplate]="savingsAccountProductTemplate"
|
||||
[primaryClientId]="savingsAccountTemplate?.clientId ?? null"
|
||||
></mifosx-savings-account-currency-step>
|
||||
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext>
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 3: Digital -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>Digital</ng-template>
|
||||
|
||||
<mifosx-savings-account-digital-step [savingsAccountProductTemplate]="savingsAccountProductTemplate">
|
||||
</mifosx-savings-account-digital-step>
|
||||
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext>
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 4: Preview -->
|
||||
<mat-step completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
|
||||
<mifosx-savings-account-preview-step
|
||||
[savingsAccountProductTemplate]="savingsAccountProductTemplate"
|
||||
<!-- Tab 1: Product Classification + Details -->
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="check-square" class="csa-tab__icon"></fa-icon>
|
||||
Product
|
||||
</ng-template>
|
||||
<div class="csa-tab-body">
|
||||
<mifosx-savings-account-details-step
|
||||
[savingsAccountTemplate]="savingsAccountTemplate"
|
||||
[savingsAccountTermsForm]="savingsAccountTermsForm"
|
||||
[savingsAccount]="savingsAccount"
|
||||
[savingsAccountDigital]="savingsAccountDigital"
|
||||
[savingsAccountNominees]="savingsAccountCurrencyStep?.nominees ?? []"
|
||||
(submitEvent)="submit()"
|
||||
(savingsAccountProductTemplate)="setTemplate($event)"
|
||||
>
|
||||
</mifosx-savings-account-preview-step>
|
||||
</mat-step>
|
||||
}
|
||||
</mat-stepper>
|
||||
</mifosx-savings-account-details-step>
|
||||
|
||||
<div class="csa-actions">
|
||||
<span class="csa-actions__spacer"></span>
|
||||
<button mat-raised-button color="primary" [disabled]="!savingsAccountProductTemplate" (click)="next()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Tab 2: Currency -->
|
||||
<mat-tab [disabled]="!savingsAccountProductTemplate">
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="dollar-sign" class="csa-tab__icon"></fa-icon>
|
||||
Currency
|
||||
</ng-template>
|
||||
<div class="csa-tab-body">
|
||||
@if (savingsAccountProductTemplate) {
|
||||
<mifosx-savings-account-currency-step
|
||||
[savingsAccountProductTemplate]="savingsAccountProductTemplate"
|
||||
[primaryClientId]="savingsAccountTemplate?.clientId ?? null"
|
||||
></mifosx-savings-account-currency-step>
|
||||
}
|
||||
|
||||
<div class="csa-actions">
|
||||
<button mat-stroked-button (click)="prev()">
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<span class="csa-actions__spacer"></span>
|
||||
<button mat-raised-button color="primary" (click)="next()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Tab 3: Digital -->
|
||||
<mat-tab [disabled]="!savingsAccountProductTemplate">
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="mobile-alt" class="csa-tab__icon"></fa-icon>
|
||||
Digital
|
||||
</ng-template>
|
||||
<div class="csa-tab-body">
|
||||
@if (savingsAccountProductTemplate) {
|
||||
<mifosx-savings-account-digital-step [savingsAccountProductTemplate]="savingsAccountProductTemplate">
|
||||
</mifosx-savings-account-digital-step>
|
||||
}
|
||||
|
||||
<div class="csa-actions">
|
||||
<button mat-stroked-button (click)="prev()">
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<span class="csa-actions__spacer"></span>
|
||||
<button mat-raised-button color="primary" (click)="next()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Tab 4: Preview / Submit -->
|
||||
<mat-tab [disabled]="!savingsAccountProductTemplate">
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="eye" class="csa-tab__icon"></fa-icon>
|
||||
{{ 'labels.inputs.PREVIEW' | translate }}
|
||||
</ng-template>
|
||||
<div class="csa-tab-body">
|
||||
@if (savingsAccountProductTemplate) {
|
||||
<mifosx-savings-account-preview-step
|
||||
[savingsAccountProductTemplate]="savingsAccountProductTemplate"
|
||||
[savingsAccountTemplate]="savingsAccountTemplate"
|
||||
[savingsAccountTermsForm]="savingsAccountTermsForm"
|
||||
[savingsAccount]="savingsAccount"
|
||||
[savingsAccountDigital]="savingsAccountDigital"
|
||||
[savingsAccountNominees]="savingsAccountCurrencyStep?.nominees ?? []"
|
||||
(submitEvent)="submit()"
|
||||
>
|
||||
</mifosx-savings-account-preview-step>
|
||||
}
|
||||
|
||||
<div class="csa-actions">
|
||||
<button mat-stroked-button (click)="prev()">
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
|
||||
@@ -6,75 +6,153 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// Container styling with proper spacing and margins
|
||||
.container {
|
||||
margin: 1.5rem auto;
|
||||
max-width: 75rem;
|
||||
width: 100%;
|
||||
padding: 0 1.5rem;
|
||||
box-sizing: border-box;
|
||||
$navy: #0d1b2a;
|
||||
$blue: #1e88e5;
|
||||
$blue-d: #1e40af;
|
||||
$indigo: #4f46e5;
|
||||
$border: #e2e8f0;
|
||||
|
||||
.csa-container {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
// Material Stepper styling with proper spacing
|
||||
:host ::ng-deep mat-stepper {
|
||||
background: transparent;
|
||||
/* ─── Hero header (matches Nexora pages) ────────────────────────────── */
|
||||
.csa-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.mat-step-header {
|
||||
padding: 1rem 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
&__icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, $blue 0%, $indigo 100%);
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
box-shadow: 0 2px 8px rgb(29 78 216 / 25%);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mat-step-content {
|
||||
padding: 1.5rem;
|
||||
margin: 1rem 0;
|
||||
overflow: visible !important;
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
color: $navy;
|
||||
line-height: 1.1;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
margin-top: 2px;
|
||||
font-size: 12.5px;
|
||||
color: #64748b;
|
||||
}
|
||||
}
|
||||
|
||||
// Form field spacing within steps
|
||||
:host ::ng-deep mat-step-content {
|
||||
overflow: visible !important;
|
||||
/* ─── Wizard tab strip ──────────────────────────────────────────────── */
|
||||
.csa-tabs {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
|
||||
overflow: hidden;
|
||||
|
||||
mat-form-field {
|
||||
margin-bottom: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.layout-row-wrap {
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive design
|
||||
@media (width <= 768px) {
|
||||
.container {
|
||||
padding: 0 1rem;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
:host ::ng-deep mat-stepper {
|
||||
.mat-step-content {
|
||||
padding: 1rem;
|
||||
margin: 0.75rem 0;
|
||||
::ng-deep {
|
||||
.mat-mdc-tab-header {
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid $border;
|
||||
}
|
||||
|
||||
.mat-step-header {
|
||||
padding: 0.75rem 1rem;
|
||||
.mat-mdc-tab {
|
||||
min-width: 140px;
|
||||
height: 50px;
|
||||
padding: 0 22px;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
letter-spacing: 0.01em;
|
||||
color: #475569 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.mat-mdc-tab:hover:not(.mdc-tab--active, .mat-mdc-tab-disabled) {
|
||||
background: rgb(30 64 175 / 5%);
|
||||
color: $navy !important;
|
||||
}
|
||||
|
||||
.mat-mdc-tab.mdc-tab--active {
|
||||
.mdc-tab__text-label {
|
||||
color: $blue-d !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Active-tab underline indicator — slightly bolder than Material's default. */
|
||||
.mdc-tab-indicator__content--underline {
|
||||
border-color: $blue-d !important;
|
||||
border-top-width: 3px !important;
|
||||
}
|
||||
|
||||
.mat-mdc-tab.mat-mdc-tab-disabled {
|
||||
opacity: 0.45 !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body-wrapper {
|
||||
min-height: 320px;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body,
|
||||
.mat-mdc-tab-body-content {
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 480px) {
|
||||
.container {
|
||||
padding: 0 0.75rem;
|
||||
margin: 0.75rem auto;
|
||||
}
|
||||
.csa-tab__icon {
|
||||
margin-right: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:host ::ng-deep mat-stepper {
|
||||
.mat-step-content {
|
||||
padding: 0.75rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
/* ─── Tab body + action bar ─────────────────────────────────────────── */
|
||||
.csa-tab-body {
|
||||
padding: 24px;
|
||||
|
||||
@media (width <= 768px) {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.csa-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
|
||||
&__spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
height: 40px !important;
|
||||
border-radius: 10px !important;
|
||||
font-weight: 600 !important;
|
||||
padding: 0 18px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Utility helpers preserved from the prior stepper template. */
|
||||
.m-l-10 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.m-r-10 {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@@ -15,27 +15,24 @@ import { FormGroup } from '@angular/forms';
|
||||
import { SavingsAccountDetailsStepComponent } from '../savings-account-stepper/savings-account-details-step/savings-account-details-step.component';
|
||||
import { SavingsAccountCurrencyStepComponent } from '../savings-account-stepper/savings-account-currency-step/savings-account-currency-step.component';
|
||||
import { SavingsAccountDigitalStepComponent } from '../savings-account-stepper/savings-account-digital-step/savings-account-digital-step.component';
|
||||
import { SavingsAccountSettingsStepComponent } from '../savings-account-stepper/savings-account-settings-step/savings-account-settings-step.component';
|
||||
|
||||
/** Custom Services */
|
||||
import { SavingsService } from '../savings.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { AlertService } from 'app/core/alert/alert.service';
|
||||
import {
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
MatStepperNext,
|
||||
MatStepperPrevious
|
||||
} from '@angular/material/stepper';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { SavingsAccountPreviewStepComponent } from '../savings-account-stepper/savings-account-preview-step/savings-account-preview-step.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Create Savings Account Component
|
||||
*
|
||||
* Wizard-tab UI: Product → Currency → Digital → Preview. Tabs 2-4 are
|
||||
* disabled until a product is picked on Tab 1 (matches the prior stepper
|
||||
* gate that only rendered later steps once `savingsAccountProductTemplate`
|
||||
* was set).
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-create-savings-account',
|
||||
@@ -43,13 +40,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
styleUrls: ['./create-savings-account.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
MatStepperNext,
|
||||
MatStepperPrevious,
|
||||
MatTabsModule,
|
||||
FaIconComponent,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
SavingsAccountDetailsStepComponent,
|
||||
SavingsAccountCurrencyStepComponent,
|
||||
SavingsAccountDigitalStepComponent,
|
||||
@@ -68,6 +60,10 @@ export class CreateSavingsAccountComponent {
|
||||
savingsAccountTemplate: any;
|
||||
/** Savings Account Product Template */
|
||||
savingsAccountProductTemplate: any;
|
||||
/** Currently visible wizard tab (0-indexed). */
|
||||
selectedTabIndex = 0;
|
||||
/** Total wizard tabs — Product, Currency, Digital, Preview. */
|
||||
private readonly totalTabs = 4;
|
||||
|
||||
@ViewChild(SavingsAccountDetailsStepComponent, { static: true })
|
||||
savingsAccountDetailsStep: SavingsAccountDetailsStepComponent;
|
||||
@@ -88,6 +84,18 @@ export class CreateSavingsAccountComponent {
|
||||
this.savingsAccountProductTemplate = $event;
|
||||
}
|
||||
|
||||
next(): void {
|
||||
if (this.selectedTabIndex < this.totalTabs - 1) {
|
||||
this.selectedTabIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
prev(): void {
|
||||
if (this.selectedTabIndex > 0) {
|
||||
this.selectedTabIndex -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
get savingsAccountDetailsForm() {
|
||||
return this.savingsAccountDetailsStep.savingsAccountDetailsForm;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user