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
+98
-42
@@ -6,36 +6,92 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container mat-elevation-z8">
|
||||
<mat-card>
|
||||
@if (transactionType.holdamount) {
|
||||
<mat-card-title>{{ 'labels.heading.Hold Amount' | translate }}</mat-card-title>
|
||||
}
|
||||
@if (transactionType.blockaccount) {
|
||||
<mat-card-title>{{ 'labels.heading.Block Savings Account' | translate }}</mat-card-title>
|
||||
}
|
||||
<div
|
||||
class="msa"
|
||||
[class.msa--hold]="transactionType.holdamount"
|
||||
[class.msa--block-account]="transactionType.blockaccount"
|
||||
[class.msa--block-deposit]="transactionType.blockdeposit"
|
||||
[class.msa--block-withdrawal]="transactionType.blockwithdrawal"
|
||||
>
|
||||
<!-- Hero -->
|
||||
<div class="msa__hero">
|
||||
<button type="button" mat-stroked-button class="msa__back" [routerLink]="['../../transactions']">
|
||||
<fa-icon icon="arrow-left"></fa-icon>
|
||||
</button>
|
||||
|
||||
<form [formGroup]="manageSavingsAccountForm" (ngSubmit)="submit()">
|
||||
<mat-card-content class="layout-row">
|
||||
<mat-form-field class="flex-100">
|
||||
<mat-label>{{ 'labels.inputs.Reason' | translate }}</mat-label>
|
||||
<mat-select required formControlName="reasonForBlock">
|
||||
@for (codeValue of reasonOptions; track codeValue) {
|
||||
<mat-option [value]="codeValue.id">
|
||||
{{ codeValue.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
@if (manageSavingsAccountForm.controls.reasonForBlock.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Reason' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
<div class="msa__hero-ico">
|
||||
<fa-icon
|
||||
[icon]="
|
||||
transactionType.holdamount
|
||||
? 'pause'
|
||||
: transactionType.blockaccount
|
||||
? 'lock'
|
||||
: transactionType.blockdeposit
|
||||
? 'lock'
|
||||
: transactionType.blockwithdrawal
|
||||
? 'lock'
|
||||
: 'lock'
|
||||
"
|
||||
></fa-icon>
|
||||
</div>
|
||||
|
||||
<div class="msa__hero-text">
|
||||
<h2 class="msa__title">
|
||||
@if (transactionType.holdamount) {
|
||||
<mat-form-field class="flex-100" (click)="transactionDatePicker.open()">
|
||||
{{ 'labels.heading.Hold Amount' | translate }}
|
||||
} @else if (transactionType.blockaccount) {
|
||||
Block Savings Account
|
||||
} @else if (transactionType.blockdeposit) {
|
||||
Block Deposits
|
||||
} @else if (transactionType.blockwithdrawal) {
|
||||
Block Withdrawals
|
||||
}
|
||||
</h2>
|
||||
<p class="msa__sub">
|
||||
@if (transactionType.holdamount) {
|
||||
Place a hold on a portion of the available balance with a reason and amount.
|
||||
} @else if (transactionType.blockaccount) {
|
||||
Freezes all account activity. Select a reason to record the action in the audit log.
|
||||
} @else if (transactionType.blockdeposit) {
|
||||
Stops incoming credits to this account. Existing withdrawals continue to work.
|
||||
} @else if (transactionType.blockwithdrawal) {
|
||||
Stops outgoing debits from this account. Deposits continue to be accepted.
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<span class="msa__hero-pill">
|
||||
<fa-icon icon="exclamation-triangle"></fa-icon>
|
||||
Action recorded
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Form card -->
|
||||
<form class="msa__card" [formGroup]="manageSavingsAccountForm" (ngSubmit)="submit()">
|
||||
<div class="msa__card-head">
|
||||
<fa-icon icon="info-circle" class="msa__card-ico"></fa-icon>
|
||||
<h3 class="msa__card-title">Action Details</h3>
|
||||
</div>
|
||||
|
||||
<div class="msa__card-body">
|
||||
<mat-form-field appearance="outline" class="msa__field">
|
||||
<mat-label>{{ 'labels.inputs.Reason' | translate }}</mat-label>
|
||||
<mat-select required formControlName="reasonForBlock">
|
||||
@for (codeValue of reasonOptions; track codeValue.id) {
|
||||
<mat-option [value]="codeValue.id">{{ codeValue.name }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
@if (manageSavingsAccountForm.controls.reasonForBlock.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Reason' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
@if (transactionType.holdamount) {
|
||||
<div class="msa__field-row">
|
||||
<mat-form-field appearance="outline" class="msa__field" (click)="transactionDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
@@ -54,28 +110,28 @@
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
@if (transactionType.holdamount) {
|
||||
<mifosx-input-amount
|
||||
class="flex-100"
|
||||
class="msa__field"
|
||||
[currency]="currency"
|
||||
[isRequired]="true"
|
||||
[inputFormControl]="manageSavingsAccountForm.controls.transactionAmount"
|
||||
[inputLabel]="'Transaction Amount'"
|
||||
>
|
||||
</mifosx-input-amount>
|
||||
}
|
||||
</mat-card-content>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
||||
<button type="button" mat-raised-button [routerLink]="['../../transactions']">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" [disabled]="!manageSavingsAccountForm.valid">
|
||||
{{ 'labels.buttons.Confirm' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</form>
|
||||
</mat-card>
|
||||
<div class="msa__actions">
|
||||
<button type="button" mat-stroked-button class="msa__cancel" [routerLink]="['../../transactions']">
|
||||
<fa-icon icon="times" class="m-r-6"></fa-icon>
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button mat-flat-button type="submit" class="msa__confirm" [disabled]="!manageSavingsAccountForm.valid">
|
||||
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
||||
{{ 'labels.buttons.Confirm' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
+301
-2
@@ -6,6 +6,305 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.container {
|
||||
width: 37rem;
|
||||
/* Manage-Savings-Account screen — drives four flows: Hold Amount (blue),
|
||||
Block Deposit (orange), Block Withdrawal (orange), Block Account (red).
|
||||
Hero gradient + Confirm button tint shift per modifier class. */
|
||||
|
||||
$navy: #0d1b2a;
|
||||
$slate: #475569;
|
||||
$muted: #64748b;
|
||||
$border: #e2e8f0;
|
||||
$soft: #f8fafc;
|
||||
$blue: #1e88e5;
|
||||
$blue-d: #1e40af;
|
||||
$amber: #d97706;
|
||||
$amber-d: #b45309;
|
||||
$red: #b91c1c;
|
||||
$red-d: #7f1d1d;
|
||||
|
||||
.msa {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
padding: 14px 18px 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* ─── Hero ─────────────────────────────────────────────────────────── */
|
||||
.msa__hero {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 18px;
|
||||
border-radius: 12px;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
/* Default (Hold Amount) — calm blue. Modifier classes override below. */
|
||||
background: linear-gradient(135deg, #0f172a 0%, $blue-d 45%, $blue 100%);
|
||||
box-shadow: 0 6px 18px -10px rgb(30 64 175 / 50%);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at top right, rgb(255 255 255 / 12%), transparent 55%);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hero gradient variants */
|
||||
.msa--block-deposit .msa__hero,
|
||||
.msa--block-withdrawal .msa__hero {
|
||||
background: linear-gradient(135deg, #7c2d12 0%, $amber-d 45%, $amber 100%);
|
||||
box-shadow: 0 6px 18px -10px rgb(180 83 9 / 50%);
|
||||
}
|
||||
|
||||
.msa--block-account .msa__hero {
|
||||
background: linear-gradient(135deg, $red-d 0%, $red 45%, #dc2626 100%);
|
||||
box-shadow: 0 6px 18px -10px rgb(153 27 27 / 55%);
|
||||
}
|
||||
|
||||
.msa__back {
|
||||
height: 30px !important;
|
||||
min-width: 30px !important;
|
||||
width: 30px !important;
|
||||
padding: 0 !important;
|
||||
border-radius: 50% !important;
|
||||
background: rgb(255 255 255 / 14%) !important;
|
||||
color: #fff !important;
|
||||
border: 1px solid rgb(255 255 255 / 28%) !important;
|
||||
font-size: 12px !important;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
&:hover {
|
||||
background: rgb(255 255 255 / 26%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.msa__hero-ico {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 9px;
|
||||
background: rgb(255 255 255 / 18%);
|
||||
border: 1px solid rgb(255 255 255 / 28%);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.msa__hero-text {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.msa__title {
|
||||
margin: 0;
|
||||
font-size: 14.5px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.msa__sub {
|
||||
margin: 2px 0 0;
|
||||
font-size: 11.5px;
|
||||
color: rgb(255 255 255 / 82%);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.msa__hero-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
height: 22px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
background: rgb(255 255 255 / 16%);
|
||||
border: 1px solid rgb(255 255 255 / 30%);
|
||||
font-size: 10.5px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
fa-icon {
|
||||
font-size: 10px;
|
||||
opacity: 0.95;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Form card ────────────────────────────────────────────────────── */
|
||||
.msa__card {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.msa__card-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 9px 14px;
|
||||
background: $soft;
|
||||
border-bottom: 1px solid $border;
|
||||
}
|
||||
|
||||
.msa__card-ico {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 7px;
|
||||
background: linear-gradient(135deg, $blue 0%, $blue-d 100%);
|
||||
color: #fff;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.msa--block-deposit .msa__card-ico,
|
||||
.msa--block-withdrawal .msa__card-ico {
|
||||
background: linear-gradient(135deg, $amber 0%, $amber-d 100%);
|
||||
}
|
||||
|
||||
.msa--block-account .msa__card-ico {
|
||||
background: linear-gradient(135deg, $red 0%, $red-d 100%);
|
||||
}
|
||||
|
||||
.msa__card-title {
|
||||
margin: 0;
|
||||
font-size: 12.5px;
|
||||
font-weight: 700;
|
||||
color: $navy;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.msa__card-body {
|
||||
padding: 16px 18px 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.msa__field {
|
||||
width: 100%;
|
||||
|
||||
::ng-deep .mat-mdc-text-field-wrapper {
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-form-field-infix {
|
||||
min-height: 46px;
|
||||
padding-top: 11px !important;
|
||||
padding-bottom: 11px !important;
|
||||
}
|
||||
|
||||
::ng-deep .mdc-notched-outline__leading,
|
||||
::ng-deep .mdc-notched-outline__notch,
|
||||
::ng-deep .mdc-notched-outline__trailing {
|
||||
border-color: #cbd5e1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.msa__field-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ─── Actions ─────────────────────────────────────────────────────── */
|
||||
.msa__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 12px 18px 14px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
|
||||
button {
|
||||
height: 38px !important;
|
||||
border-radius: 9px !important;
|
||||
font-weight: 700 !important;
|
||||
padding: 0 18px !important;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
}
|
||||
|
||||
.msa__cancel {
|
||||
color: $slate !important;
|
||||
border-color: $border !important;
|
||||
}
|
||||
|
||||
.msa__confirm {
|
||||
/* Default — blue (Hold Amount) */
|
||||
background: linear-gradient(135deg, $blue 0%, $blue-d 100%) !important;
|
||||
color: #fff !important;
|
||||
box-shadow: 0 3px 10px -2px rgb(30 64 175 / 35%) !important;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.55;
|
||||
filter: grayscale(0.3);
|
||||
background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.msa--block-deposit .msa__confirm,
|
||||
.msa--block-withdrawal .msa__confirm {
|
||||
background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
|
||||
box-shadow: 0 3px 10px -2px rgb(180 83 9 / 35%) !important;
|
||||
|
||||
&:disabled {
|
||||
background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.msa--block-account .msa__confirm {
|
||||
background: linear-gradient(135deg, $red 0%, $red-d 100%) !important;
|
||||
box-shadow: 0 3px 10px -2px rgb(153 27 27 / 40%) !important;
|
||||
|
||||
&:disabled {
|
||||
background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.m-r-6 {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
.msa {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.msa__hero {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.msa__actions {
|
||||
flex-direction: column-reverse;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+39
-8
@@ -14,8 +14,10 @@ import { SavingsService } from 'app/savings/savings.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Currency } from 'app/shared/models/general.model';
|
||||
import { SystemService } from 'app/system/system.service';
|
||||
import { MatCard, MatCardTitle, MatCardContent, MatCardActions } from '@angular/material/card';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { InputAmountComponent } from '../../../shared/input-amount/input-amount.component';
|
||||
import { SuccessDialogComponent } from 'app/shared/success-dialog/success-dialog.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
type TransactionCommandType = 'holdamount' | 'blockaccount' | 'blockdeposit' | 'blockwithdrawal';
|
||||
@@ -33,7 +35,7 @@ interface TransactionType {
|
||||
styleUrls: ['./manage-savings-account.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatCardTitle,
|
||||
FaIconComponent,
|
||||
InputAmountComponent
|
||||
]
|
||||
})
|
||||
@@ -45,6 +47,7 @@ export class ManageSavingsAccountComponent implements OnInit {
|
||||
private router = inject(Router);
|
||||
private systemService = inject(SystemService);
|
||||
private settingsService = inject(SettingsService);
|
||||
private dialog = inject(MatDialog);
|
||||
|
||||
@Input() currency: Currency;
|
||||
/** Minimum date allowed. */
|
||||
@@ -170,9 +173,7 @@ export class ManageSavingsAccountComponent implements OnInit {
|
||||
|
||||
this.savingsService
|
||||
.executeSavingsAccountTransactionsCommand(this.savingAccountId, command, payload)
|
||||
.subscribe((response: any) => {
|
||||
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||
});
|
||||
.subscribe(() => this.showSuccessAndExit());
|
||||
} else {
|
||||
payload = {
|
||||
...this.manageSavingsAccountForm.value
|
||||
@@ -186,9 +187,39 @@ export class ManageSavingsAccountComponent implements OnInit {
|
||||
|
||||
this.savingsService
|
||||
.executeSavingsAccountCommand(this.savingAccountId, command, payload)
|
||||
.subscribe((response: any) => {
|
||||
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||
});
|
||||
.subscribe(() => this.showSuccessAndExit());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the shared success pop-up with copy tuned to the current
|
||||
* transactionType, then navigates back to the transactions tab when
|
||||
* the cashier dismisses it.
|
||||
*/
|
||||
private showSuccessAndExit(): void {
|
||||
let title = 'Action Completed';
|
||||
let message = 'The action has been recorded on the account.';
|
||||
if (this.transactionType.holdamount) {
|
||||
title = 'Amount Held Successfully';
|
||||
message = 'The selected amount has been placed on hold on this savings account.';
|
||||
} else if (this.transactionType.blockaccount) {
|
||||
title = 'Account Blocked Successfully';
|
||||
message = 'All activity on this savings account has been frozen.';
|
||||
} else if (this.transactionType.blockdeposit) {
|
||||
title = 'Deposits Blocked Successfully';
|
||||
message = 'Incoming credits to this savings account have been blocked.';
|
||||
} else if (this.transactionType.blockwithdrawal) {
|
||||
title = 'Withdrawals Blocked Successfully';
|
||||
message = 'Outgoing debits from this savings account have been blocked.';
|
||||
}
|
||||
const ref = this.dialog.open(SuccessDialogComponent, {
|
||||
width: '420px',
|
||||
maxWidth: '92vw',
|
||||
autoFocus: false,
|
||||
data: { title, message, buttonLabel: 'Done' }
|
||||
});
|
||||
ref.afterClosed().subscribe(() => {
|
||||
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user