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
@@ -220,6 +220,12 @@
}
</div>
}
@if (selfSelectError) {
<div class="nom-conflict-error">
<mat-icon>error_outline</mat-icon>
{{ selfSelectError }}
</div>
}
</div>
}
@@ -355,6 +361,12 @@
}
</div>
}
@if (guardianSelfSelectError) {
<div class="nom-conflict-error">
<mat-icon>error_outline</mat-icon>
{{ guardianSelfSelectError }}
</div>
}
} @else {
<span class="nom-guardian-selected" [class.nom-guardian-selected--error]="guardianSameAsNominee"
>{{ selectedGuardian.displayName }}
@@ -90,6 +90,11 @@ export class AccountNomineesTabComponent implements OnInit {
editShare: number | null = null;
editNomType: string = 'DA1';
/** Inline error shown when the user picks themselves as the nominee. */
selfSelectError: string | null = null;
/** Inline error shown when the user picks the host client as guardian. */
guardianSelfSelectError: string | null = null;
get totalShare(): number {
return this.nominees.reduce((sum, n) => sum + (n.percentageShare || 0), 0);
}
@@ -169,8 +174,15 @@ export class AccountNomineesTabComponent implements OnInit {
});
}
/** True when the given client id is the same as the savings account's
* host client — used by the search to refuse self-add as nominee. */
isSelfClient(clientId: any): boolean {
return clientId != null && this.clientId != null && String(clientId) === String(this.clientId);
}
onSearch(text: string): void {
this.searchText = text;
this.selfSelectError = null;
clearTimeout(this.searchTimer);
if (!text || text.length < 2) {
this.searchResults = [];
@@ -180,7 +192,10 @@ export class AccountNomineesTabComponent implements OnInit {
this.searchTimer = setTimeout(() => {
this.clientsService.searchByText(text, 0, 8, '', '', true).subscribe({
next: (data: any) => {
this.searchResults = data?.content || [];
const raw: any[] = data?.content || [];
// Drop the host client from the result list — a client cannot be
// their own nominee.
this.searchResults = raw.filter((c: any) => !this.isSelfClient(c.id));
this.searching = false;
this.clientsService.enrichSearchResults(this.searchResults);
},
@@ -200,6 +215,16 @@ export class AccountNomineesTabComponent implements OnInit {
}
selectClient(client: any): void {
if (this.isSelfClient(client?.id)) {
// Belt-and-braces — even if a stale result sneaks through, refuse
// the selection and surface an inline message.
this.selfSelectError =
'You cannot add the account holder as their own nominee. Please choose a different client.';
this.searchResults = [];
this.searchText = '';
return;
}
this.selfSelectError = null;
this.selectedClient = client;
this.searchResults = [];
this.searchText = '';
@@ -281,6 +306,7 @@ export class AccountNomineesTabComponent implements OnInit {
onGuardianSearch(text: string): void {
this.guardianSearchText = text;
this.guardianSelfSelectError = null;
clearTimeout(this.guardianSearchTimer);
if (!text || text.length < 2) {
this.guardianSearchResults = [];
@@ -290,7 +316,10 @@ export class AccountNomineesTabComponent implements OnInit {
this.guardianSearchTimer = setTimeout(() => {
this.clientsService.searchByText(text, 0, 8, '', '', true).subscribe({
next: (data: any) => {
this.guardianSearchResults = data?.content || [];
const raw: any[] = data?.content || [];
// Drop the host client from the guardian results too — a client
// can't be their own minor-nominee's guardian.
this.guardianSearchResults = raw.filter((c: any) => !this.isSelfClient(c.id));
this.guardianSearching = false;
this.clientsService.enrichSearchResults(this.guardianSearchResults);
},
@@ -304,6 +333,14 @@ export class AccountNomineesTabComponent implements OnInit {
selectGuardian(client: any): void {
if (this.selectedClient && client.id === this.selectedClient.id) return;
if (this.isSelfClient(client?.id)) {
this.guardianSelfSelectError =
'The account holder cannot be selected as the guardian. Please choose a different client.';
this.guardianSearchResults = [];
this.guardianSearchText = '';
return;
}
this.guardianSelfSelectError = null;
this.selectedGuardian = client;
this.guardianSearchResults = [];
this.guardianSearchText = '';
+12 -2
View File
@@ -221,7 +221,12 @@ import {
faSignature,
faPercentage,
faCompressAlt,
faExpandAlt
faExpandAlt,
faUserCheck,
faUsersCog,
faUserCog,
faServer,
faBox
} from '@fortawesome/free-solid-svg-icons';
/**
@@ -449,7 +454,12 @@ export class IconsModule {
faSignature,
faPercentage,
faCompressAlt,
faExpandAlt
faExpandAlt,
faUserCheck,
faUsersCog,
faUserCog,
faServer,
faBox
);
}
}