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:
Aditya
2026-05-21 18:49:43 +05:30
co-authored by Claude Opus 4.7
parent bf3e26a0d0
commit b149fef72e
99 changed files with 8575 additions and 2574 deletions
@@ -16,29 +16,45 @@
<div class="step-header__left">
<fa-icon icon="users" class="step-header__icon"></fa-icon>
<div>
<h3 class="step-header__title">Family Members</h3>
<p class="step-header__sub">Add family members and dependents of the client.</p>
@if (nomineeOnly) {
<h3 class="step-header__title">Nominees</h3>
<p class="step-header__sub">
Nominees for this client. Family members are not applicable for this entity type.
</p>
} @else {
<h3 class="step-header__title">Family Members</h3>
<p class="step-header__sub">Add family members and dependents of the client.</p>
}
</div>
</div>
</div>
@if (nomineeOnly) {
<div class="fm-step__empty">
<mat-icon class="fm-step__empty-icon">shield</mat-icon>
<span>Nominees are managed per account from the <strong>Nominees</strong> tab once accounts are opened.</span>
</div>
}
<!-- ═══ FAMILY MEMBERS ═══ -->
<div class="fm-step__header">
<div class="fm-step__title">
<fa-icon icon="users" class="fm-step__icon"></fa-icon>
<span>Family Members</span>
@if (clientFamilyMembers.length > 0) {
<span class="fm-step__count">{{ clientFamilyMembers.length }}</span>
@if (!nomineeOnly) {
<div class="fm-step__header">
<div class="fm-step__title">
<fa-icon icon="users" class="fm-step__icon"></fa-icon>
<span>Family Members</span>
@if (clientFamilyMembers.length > 0) {
<span class="fm-step__count">{{ clientFamilyMembers.length }}</span>
}
</div>
@if (!readOnly) {
<button mat-flat-button color="primary" type="button" class="fm-step__add-btn" (click)="addFamilyMember()">
<mat-icon>add</mat-icon> Add Member
</button>
}
</div>
@if (!readOnly) {
<button mat-flat-button color="primary" type="button" class="fm-step__add-btn" (click)="addFamilyMember()">
<mat-icon>add</mat-icon> Add Member
</button>
}
</div>
}
@if (clientFamilyMembers.length === 0) {
@if (!nomineeOnly && clientFamilyMembers.length === 0) {
<div class="fm-step__empty fm-step__empty--required">
<mat-icon class="fm-step__empty-icon">people_outline</mat-icon>
<span>No family members added yet.</span>
@@ -47,14 +63,14 @@
}
<!-- Spouse hint for married clients -->
@if (isMarried && !hasSpouse) {
@if (!nomineeOnly && isMarried && !hasSpouse) {
<div class="fm-spouse-hint">
<mat-icon class="fm-spouse-hint__icon">favorite</mat-icon>
<span>Marital status is <strong>Married</strong>. Consider adding spouse as a family member.</span>
</div>
}
@if (clientFamilyMembers.length > 0) {
@if (!nomineeOnly && clientFamilyMembers.length > 0) {
<div class="fm-table">
<div class="fm-table__header">
<span class="fm-table__col fm-table__col--name">Name</span>
@@ -52,6 +52,12 @@ export class ClientFamilyMembersStepComponent implements OnInit {
@Input() maritalStatusOptions: any[] = [];
/** Read-only mode — hides add/edit/delete buttons */
@Input() readOnly = false;
/**
* When true, hide the family-member section entirely. Used for clients that
* are eligible for nominees but not for family members (e.g. proprietorship
* entities — see {@link nomineeAllowed} / {@link familyAllowed}).
*/
@Input() nomineeOnly = false;
/** Family Members */
clientFamilyMembers: any[] = [];
@@ -131,6 +137,7 @@ export class ClientFamilyMembersStepComponent implements OnInit {
}
addFamilyMember() {
if (this.nomineeOnly || this.readOnly) return;
const ref = this.dialog.open(ClientFamilyMemberDialogComponent, {
data: {
context: 'Add',
@@ -1024,8 +1024,8 @@
}
}
<!-- Family Members (Individual only) -->
@if (client?.legalFormId !== 2) {
<!-- Family Members — hidden when client isn't eligible (entities other than proprietorship). -->
@if (showFamilyPreview) {
<div class="info-card">
<div class="card-header">
<fa-icon icon="users" class="card-icon"></fa-icon>
@@ -24,6 +24,7 @@ import { DateFormatPipe } from '../../../pipes/date-format.pipe';
import { YesnoPipe } from '../../../pipes/yesno.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { LegalFormId } from 'app/clients/models/legal-form.enum';
import { familyAllowed, nomineeAllowed } from 'app/clients/utils/client-rules';
/**
* Client Preview Step Component
@@ -69,6 +70,11 @@ export class ClientPreviewStepComponent {
@Input() nominees: any[] = [];
/** Family members from family step */
@Input() familyMembers: any[] = [];
/**
* Constitution display name for entity clients — drives the proprietorship-only
* nominee rule so the preview matches what's eligible elsewhere.
*/
@Input() constitutionLabel: string = '';
/** Marital Status ID from general step */
@Input() maritalStatusId: number | null = null;
/** Marital Status Options */
@@ -99,6 +105,14 @@ export class ClientPreviewStepComponent {
/** Form submission event */
@Output() submitEvent = new EventEmitter();
get showFamilyPreview(): boolean {
return familyAllowed(this.client?.legalFormId, this.constitutionLabel);
}
get showNomineePreview(): boolean {
return nomineeAllowed(this.client?.legalFormId, this.constitutionLabel);
}
/** Expose enum to template */
readonly LegalFormId = LegalFormId;