feat: add KYC document renewal alerts, Aadhaar/PAN exceptions, and AML recurring alerts UI; remove old journal AML marking
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s

This commit is contained in:
2026-08-11 11:11:18 +05:30
parent 01e86173c1
commit 7b895a0e2b
77 changed files with 10046 additions and 1130 deletions
@@ -56,6 +56,42 @@
}
}
<!-- What PAN / Aadhaar are matched against. Shown before the officer clicks Verify, because
the provider matches on exactly these two values and returns nothing identifiable when
they disagree — a blind check that fails is much harder to act on than a visible one. -->
@if (!isNonIndividual) {
<div
class="identity-basis full-width"
[class.identity-basis--incomplete]="!verificationName || !verificationDateOfBirth"
>
<mat-icon class="identity-basis__icon">badge</mat-icon>
<div class="identity-basis__body">
<span class="identity-basis__legend">Documents are verified against the General tab</span>
<div class="identity-basis__pairs">
<span class="identity-basis__pair">
<span class="identity-basis__k">Name</span>
<span class="identity-basis__v">{{ verificationName || 'Not entered' }}</span>
</span>
<span class="identity-basis__pair">
<span class="identity-basis__k">Date of Birth</span>
<span class="identity-basis__v">
{{ verificationDateOfBirth ? (verificationDateOfBirth | date: 'dd MMM yyyy') : 'Not entered' }}
</span>
</span>
</div>
@if (!verificationName || !verificationDateOfBirth) {
<span class="identity-basis__warn">
Both are required for PAN and Aadhaar verification — complete them on the General tab first.
</span>
} @else {
<span class="identity-basis__hint">
Must match the card exactly. Salutation is excluded; a mismatch in either fails verification.
</span>
}
</div>
</div>
}
<!-- Individual PAN with inline verify -->
@if (!isMinor && kycForm.get('hasPan')?.value) {
<div class="form-field verify-field">
@@ -105,8 +141,109 @@
@if (panVerifyStatus === 'VERIFIED') {
<span class="verify-chip verified"><fa-icon icon="check-circle"></fa-icon> Verified</span>
}
@if (panVerifyStatus !== 'VERIFIED' && panDeclarationTaken) {
<span class="verify-chip declared"><fa-icon icon="file-signature"></fa-icon> Declaration</span>
}
</div>
</div>
@if (panVerifyStatus === 'VERIFIED') {
<div
class="pal-card full-width"
[class.pal-card--ok]="panAadhaarSeedingStatus === 'Y'"
[class.pal-card--fail]="panAadhaarSeedingStatus === 'N'"
[class.pal-card--info]="panAadhaarSeedingStatus === 'E'"
[class.pal-card--pending]="
panAadhaarSeedingStatus !== 'Y' && panAadhaarSeedingStatus !== 'N' && panAadhaarSeedingStatus !== 'E'
"
style="margin-bottom: 16px"
>
<div class="pal-card__left">
<div class="pal-card__icon">
<mat-icon>credit_card</mat-icon>
</div>
<div class="pal-card__txt">
<div class="pal-card__small">Verification Status</div>
<div class="pal-card__title">PAN &harr; Aadhaar</div>
<div class="pal-card__status">
@if (panAadhaarSeedingStatus === 'Y') {
<mat-icon>check_circle</mat-icon> Linked (Seeded)
} @else if (panAadhaarSeedingStatus === 'N') {
<mat-icon>cancel</mat-icon> Not Linked
} @else if (panAadhaarSeedingStatus === 'E') {
<mat-icon>info</mat-icon> Exempt
} @else {
<mat-icon>radio_button_unchecked</mat-icon> Pending
}
</div>
</div>
</div>
<div class="pal-card__badge">
@if (panAadhaarSeedingStatus === 'Y') {
<mat-icon>check</mat-icon>
} @else if (panAadhaarSeedingStatus === 'N') {
<mat-icon>close</mat-icon>
} @else if (panAadhaarSeedingStatus === 'E') {
<mat-icon>info</mat-icon>
} @else {
<mat-icon>schedule</mat-icon>
}
</div>
<div class="pal-card__hint">As per Income Tax Portal &mdash; Section 139AA</div>
</div>
}
<!--
PAN could not be verified but a declaration is on file. Shown in full because it is the
only thing standing between this client and a PAN KYC exception — an officer reviewing
the record has to be able to see what was declared, not just that something was.
-->
@if (panVerifyStatus !== 'VERIFIED' && panDeclarationTaken) {
<div class="pan-decl-card">
<div class="pan-decl-card__head">
<mat-icon class="pan-decl-card__icon">assignment_turned_in</mat-icon>
<span class="pan-decl-card__title">PAN Declaration Taken</span>
@if (panDeclarationTakenOn) {
<span class="pan-decl-card__date">{{ panDeclarationTakenOn }}</span>
}
</div>
<div class="pan-decl-card__rows">
<div class="pan-decl-card__row">
<span class="k">Does not match</span>
<span class="v strong">{{ panDeclarationMismatchLabel }}</span>
</div>
@if (panDeclaration?.nameOnPan) {
<div class="pan-decl-card__row">
<span class="k">Name on PAN</span>
<span class="v">{{ panDeclaration?.nameOnPan }}</span>
</div>
}
@if (panDeclaration?.dobOnPan) {
<div class="pan-decl-card__row">
<span class="k">DOB on PAN</span>
<span class="v">{{ panDeclaration?.dobOnPan | date: 'dd MMMM yyyy' }}</span>
</div>
}
@if (panDeclaration?.fileName) {
<div class="pan-decl-card__row">
<span class="k">Declaration file</span>
<span class="v">{{ panDeclaration?.fileName }}</span>
</div>
}
</div>
<div class="pan-decl-card__note">
PAN remains unverified. No KYC exception is raised for PAN while this declaration is on file.
</div>
@if (!readOnly) {
<div class="pan-decl-card__actions">
<button mat-stroked-button type="button" (click)="openPanDeclaration()">Edit Declaration</button>
<button mat-stroked-button type="button" class="danger" (click)="removePanDeclaration()">
Remove
</button>
</div>
}
</div>
}
}
<!-- Aadhaar with independent verify (Individual only) -->
@@ -175,9 +312,68 @@
@if (aadhaarVerifyStatus === 'VERIFIED') {
<span class="verify-chip verified"><fa-icon icon="check-circle"></fa-icon> Verified</span>
}
@if (aadhaarVerifyStatus !== 'VERIFIED' && aadhaarReceiptTaken) {
<span class="verify-chip declared"><fa-icon icon="receipt"></fa-icon> Receipt</span>
}
</div>
</div>
<!--
A customer who has applied for Aadhaar but not received it has no number to verify, so no
amount of retrying the OTP will clear the check. Offer the enrolment slip instead rather
than leaving the officer unable to finish the client.
-->
@if (aadhaarVerifyStatus !== 'VERIFIED' && !aadhaarReceiptTaken && !aadhaarOtpSent && !readOnly) {
<div class="aad-rcpt-offer full-width">
<mat-icon>receipt_long</mat-icon>
<span>Customer has applied for Aadhaar but not received it yet?</span>
<button mat-stroked-button type="button" (click)="openAadhaarReceipt()">Record Enrolment Receipt</button>
</div>
}
<!--
Aadhaar is unverified but an enrolment receipt is on file. Shown in full because it is the
only thing standing between this client and an Aadhaar KYC exception.
-->
@if (aadhaarVerifyStatus !== 'VERIFIED' && aadhaarReceiptTaken) {
<div class="aad-rcpt-card full-width">
<div class="aad-rcpt-card__head">
<mat-icon class="aad-rcpt-card__icon">receipt_long</mat-icon>
<span class="aad-rcpt-card__title">Aadhaar Enrolment Receipt Recorded</span>
@if (aadhaarReceiptTakenOn) {
<span class="aad-rcpt-card__date">{{ aadhaarReceiptTakenOn }}</span>
}
</div>
<div class="aad-rcpt-card__rows">
<div class="aad-rcpt-card__row">
<span class="k">Enrolment ID</span>
<span class="v mono">{{ aadhaarReceipt?.enrolmentId }}</span>
</div>
@if (aadhaarReceipt?.enrolmentDate) {
<div class="aad-rcpt-card__row">
<span class="k">Enrolment date</span>
<span class="v">{{ aadhaarReceipt?.enrolmentDate | date: 'dd MMMM yyyy' }}</span>
</div>
}
@if (aadhaarReceipt?.remarks) {
<div class="aad-rcpt-card__row">
<span class="k">Remarks</span>
<span class="v">{{ aadhaarReceipt?.remarks }}</span>
</div>
}
</div>
<div class="aad-rcpt-card__note">
Aadhaar remains unverified. No KYC exception is raised for Aadhaar while this receipt is on file.
</div>
@if (!readOnly) {
<div class="aad-rcpt-card__actions">
<button mat-stroked-button type="button" (click)="openAadhaarReceipt()">Edit Receipt</button>
<button mat-stroked-button type="button" class="danger" (click)="removeAadhaarReceipt()">Remove</button>
</div>
}
</div>
}
<!-- Aadhaar OTP Section -->
@if (aadhaarOtpSent && aadhaarVerifyStatus !== 'VERIFIED') {
<div class="otp-card full-width">
@@ -725,51 +921,53 @@
}
<!-- KYC Exception Flag -->
<div
class="exception-flag-box"
style="
margin-top: 20px;
padding: 16px;
border: 1px solid #f59e0b;
background: #fffbeb;
border-radius: 8px;
clear: both;
"
>
<h5
@if (!kycAllDone) {
<div
class="exception-flag-box"
style="
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
color: #b45309;
display: flex;
align-items: center;
gap: 8px;
margin-top: 20px;
padding: 16px;
border: 1px solid #f59e0b;
background: #fffbeb;
border-radius: 8px;
clear: both;
"
>
<fa-icon icon="exclamation-triangle"></fa-icon>
KYC Exception Flag
</h5>
<div style="display: flex; align-items: center; gap: 20px; flex-wrap: wrap">
<mat-checkbox formControlName="isException">Mark as KYC Exception</mat-checkbox>
<h5
style="
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
color: #b45309;
display: flex;
align-items: center;
gap: 8px;
"
>
<fa-icon icon="exclamation-triangle"></fa-icon>
KYC Exception Flag
</h5>
<div style="display: flex; align-items: center; gap: 20px; flex-wrap: wrap">
<mat-checkbox formControlName="isException">Mark as KYC Exception</mat-checkbox>
@if (kycForm.get('isException')?.value) {
<mat-form-field appearance="outline" style="flex: 1; min-width: 280px; margin-bottom: -1.25em">
<mat-label>Exception Reasons</mat-label>
<mat-select formControlName="exceptionReason" multiple>
@if (!isNonIndividual) {
<mat-option value="PAN Card Verification Failed">PAN Card Verification Failed</mat-option>
<mat-option value="Aadhaar Verification Failed">Aadhaar Verification Failed</mat-option>
} @else {
<mat-option value="Enterprise PAN Verification Failed">Enterprise PAN Verification Failed</mat-option>
<mat-option value="GSTIN Verification Failed">GSTIN Verification Failed</mat-option>
<mat-option value="CIN Verification Failed">CIN Verification Failed</mat-option>
}
</mat-select>
</mat-form-field>
}
@if (kycForm.get('isException')?.value) {
<mat-form-field appearance="outline" style="flex: 1; min-width: 280px; margin-bottom: -1.25em">
<mat-label>Exception Reasons</mat-label>
<mat-select formControlName="exceptionReason" multiple>
@if (!isNonIndividual) {
<mat-option value="PAN Card Verification Failed">PAN Card Verification Failed</mat-option>
<mat-option value="Aadhaar Verification Failed">Aadhaar Verification Failed</mat-option>
} @else {
<mat-option value="Enterprise PAN Verification Failed">Enterprise PAN Verification Failed</mat-option>
<mat-option value="GSTIN Verification Failed">GSTIN Verification Failed</mat-option>
<mat-option value="CIN Verification Failed">CIN Verification Failed</mat-option>
}
</mat-select>
</mat-form-field>
}
</div>
</div>
</div>
}
</div>
<!-- ═══ SECTION: KYC Document Management ═══ -->
@@ -847,19 +1045,23 @@
<!-- Existing saved ID-proof doc — locked by default. Replace flow
unlocks automatically when the section-level Edit button toggles
`readOnly` off. -->
@if (existingKycDocuments.length > 0 && !kycDocFiles.front && readOnly) {
@if (existingKycDocSides.front && !kycDocFiles.front && readOnly) {
<div class="kyc-doc-dropzone kyc-doc-dropzone--has kyc-doc-dropzone--locked">
<div class="kyc-doc-dropzone__existing">
@if (kycDocMergedPreview) {
<img [src]="kycDocMergedPreview" alt="Saved KYC document" class="kyc-doc-dropzone__thumb" />
@if (existingKycDocSides.front?.previewUrl) {
<img
[src]="existingKycDocSides.front?.previewUrl"
alt="Saved front side"
class="kyc-doc-dropzone__thumb"
/>
} @else {
<mat-icon class="kyc-doc-dropzone__doc-ic">description</mat-icon>
}
<div class="kyc-doc-dropzone__existing-meta">
<span class="kyc-doc-dropzone__existing-name">
{{ existingKycDocuments[0].fileName || existingKycDocuments[0].name }}
{{ existingKycDocSides.front?.fileName }}
</span>
<button type="button" class="kyc-doc-dropzone__view" (click)="viewExistingKycDoc()">
<button type="button" class="kyc-doc-dropzone__view" (click)="viewExistingKycDoc('front')">
<mat-icon>visibility</mat-icon>
Click here to view
</button>
@@ -869,8 +1071,8 @@
} @else {
<label
class="kyc-doc-dropzone"
[class.kyc-doc-dropzone--has]="kycDocFiles.front || existingKycDocuments.length > 0"
[class.kyc-doc-dropzone--req]="!kycDocFiles.front && existingKycDocuments.length === 0"
[class.kyc-doc-dropzone--has]="kycDocFiles.front || existingKycDocSides.front"
[class.kyc-doc-dropzone--req]="!kycDocFiles.front && !existingKycDocSides.front"
>
<input type="file" accept=".jpg,.jpeg,.png,.pdf" hidden (change)="onKycDocFile($event, 'front')" />
@if (kycDocFiles.front) {
@@ -886,24 +1088,28 @@
<mat-icon>close</mat-icon>
</button>
</div>
} @else if (existingKycDocuments.length > 0) {
} @else if (existingKycDocSides.front) {
<!-- Edit mode WITH existing doc — let the user view it OR
click anywhere to replace. -->
<div class="kyc-doc-dropzone__existing">
@if (kycDocMergedPreview) {
<img [src]="kycDocMergedPreview" alt="Saved KYC document" class="kyc-doc-dropzone__thumb" />
@if (existingKycDocSides.front?.previewUrl) {
<img
[src]="existingKycDocSides.front?.previewUrl"
alt="Saved front side"
class="kyc-doc-dropzone__thumb"
/>
} @else {
<mat-icon class="kyc-doc-dropzone__doc-ic">description</mat-icon>
}
<div class="kyc-doc-dropzone__existing-meta">
<span class="kyc-doc-dropzone__existing-name">
{{ existingKycDocuments[0].fileName || existingKycDocuments[0].name }}
{{ existingKycDocSides.front?.fileName }}
</span>
<div class="kyc-doc-dropzone__existing-actions">
<button
type="button"
class="kyc-doc-dropzone__view"
(click)="$event.preventDefault(); $event.stopPropagation(); viewExistingKycDoc()"
(click)="$event.preventDefault(); $event.stopPropagation(); viewExistingKycDoc('front')"
>
<mat-icon>visibility</mat-icon>
View
@@ -930,25 +1136,45 @@
<div class="kyc-doc-upload-slot">
<div class="kyc-doc-upload-head">
<span class="kyc-doc-upload-title"> <mat-icon>image</mat-icon> Back side </span>
<!-- Hide Required pill when saved combined doc covers both sides -->
@if (!(existingKycDocuments.length > 0 && !kycDocFiles.back)) {
<!-- Hide Required pill when a back scan (or a stacked scan holding it) is on file -->
@if (!((existingKycDocSides.back || kycDocIsLegacyCombined) && !kycDocFiles.back)) {
<span class="kyc-doc-upload-req">Required</span>
}
</div>
<!-- Locked view: saved combined doc covers the back side too. -->
@if (existingKycDocuments.length > 0 && !kycDocFiles.back && readOnly) {
<!--
A stacked scan that could not be cut in two has no separate back image. Say so rather
than repeating the front slot's picture here.
-->
@if (kycDocIsLegacyCombined && !kycDocFiles.back) {
<div class="kyc-doc-dropzone kyc-doc-dropzone--has kyc-doc-dropzone--locked">
<div class="kyc-doc-dropzone__existing">
@if (kycDocMergedPreview) {
<img [src]="kycDocMergedPreview" alt="Saved KYC document" class="kyc-doc-dropzone__thumb" />
<mat-icon class="kyc-doc-dropzone__doc-ic">filter_none</mat-icon>
<div class="kyc-doc-dropzone__existing-meta">
<span class="kyc-doc-dropzone__existing-name">Included in the combined scan</span>
<button type="button" class="kyc-doc-dropzone__view" (click)="viewExistingKycDoc('front')">
<mat-icon>visibility</mat-icon>
View both sides
</button>
</div>
</div>
</div>
} @else if (existingKycDocSides.back && !kycDocFiles.back && readOnly) {
<div class="kyc-doc-dropzone kyc-doc-dropzone--has kyc-doc-dropzone--locked">
<div class="kyc-doc-dropzone__existing">
@if (existingKycDocSides.back?.previewUrl) {
<img
[src]="existingKycDocSides.back?.previewUrl"
alt="Saved back side"
class="kyc-doc-dropzone__thumb"
/>
} @else {
<mat-icon class="kyc-doc-dropzone__doc-ic">description</mat-icon>
}
<div class="kyc-doc-dropzone__existing-meta">
<span class="kyc-doc-dropzone__existing-name">
{{ existingKycDocuments[0].fileName || existingKycDocuments[0].name }}
{{ existingKycDocSides.back?.fileName }}
</span>
<button type="button" class="kyc-doc-dropzone__view" (click)="viewExistingKycDoc()">
<button type="button" class="kyc-doc-dropzone__view" (click)="viewExistingKycDoc('back')">
<mat-icon>visibility</mat-icon>
Click here to view
</button>
@@ -958,8 +1184,8 @@
} @else {
<label
class="kyc-doc-dropzone"
[class.kyc-doc-dropzone--has]="kycDocFiles.back || existingKycDocuments.length > 0"
[class.kyc-doc-dropzone--req]="!kycDocFiles.back && existingKycDocuments.length === 0"
[class.kyc-doc-dropzone--has]="kycDocFiles.back || existingKycDocSides.back"
[class.kyc-doc-dropzone--req]="!kycDocFiles.back && !existingKycDocSides.back"
>
<input type="file" accept=".jpg,.jpeg,.png,.pdf" hidden (change)="onKycDocFile($event, 'back')" />
@if (kycDocFiles.back) {
@@ -975,23 +1201,27 @@
<mat-icon>close</mat-icon>
</button>
</div>
} @else if (existingKycDocuments.length > 0) {
<!-- Edit mode WITH existing combined doc — view + replace. -->
} @else if (existingKycDocSides.back) {
<!-- Edit mode WITH an existing back scan — view + replace. -->
<div class="kyc-doc-dropzone__existing">
@if (kycDocMergedPreview) {
<img [src]="kycDocMergedPreview" alt="Saved KYC document" class="kyc-doc-dropzone__thumb" />
@if (existingKycDocSides.back?.previewUrl) {
<img
[src]="existingKycDocSides.back?.previewUrl"
alt="Saved back side"
class="kyc-doc-dropzone__thumb"
/>
} @else {
<mat-icon class="kyc-doc-dropzone__doc-ic">description</mat-icon>
}
<div class="kyc-doc-dropzone__existing-meta">
<span class="kyc-doc-dropzone__existing-name">
{{ existingKycDocuments[0].fileName || existingKycDocuments[0].name }}
{{ existingKycDocSides.back?.fileName }}
</span>
<div class="kyc-doc-dropzone__existing-actions">
<button
type="button"
class="kyc-doc-dropzone__view"
(click)="$event.preventDefault(); $event.stopPropagation(); viewExistingKycDoc()"
(click)="$event.preventDefault(); $event.stopPropagation(); viewExistingKycDoc('back')"
>
<mat-icon>visibility</mat-icon>
View
@@ -1027,7 +1257,9 @@
<mat-icon>filter_none</mat-icon>
Combined preview (front + back)
</span>
<span class="kyc-merged__hint">Saved as a single PNG. Re-crop a side to update the merged file.</span>
<span class="kyc-merged__hint">
Preview only — each side is saved as its own file. Re-crop a side to update it.
</span>
</div>
<div class="kyc-merged__preview">
<img [src]="kycDocMergedPreview" alt="Merged KYC document" />
@@ -1197,27 +1429,36 @@
</div>
}
<mat-form-field appearance="outline" class="form-field full-width">
<mat-label>{{ isKycVerified ? 'Reason for Re-edit *' : 'Remarks' }}</mat-label>
<textarea
matInput
formControlName="remarks"
rows="2"
[placeholder]="isKycVerified ? 'Mandatory — explain why KYC is being re-edited' : ''"
></textarea>
@if (isKycVerified && !kycForm.get('remarks')?.value?.trim()) {
<mat-error>Remarks are mandatory when updating a verified KYC record</mat-error>
}
</mat-form-field>
@if (isKycVerified) {
<div class="kyc-reedit-alert">
<mat-icon class="kyc-reedit-alert__icon">info</mat-icon>
<span
>KYC is <strong>VERIFIED</strong>. Any update will reset the status to <strong>IN PROGRESS</strong> and
require PAN re-verification.</span
>
</div>
@if (kycForm.get('allowRiskOverride')?.value || isKycVerified) {
<mat-form-field appearance="outline" class="form-field full-width">
<mat-label>
{{
isKycVerified
? 'Reason for Re-edit'
: kycForm.get('allowRiskOverride')?.value
? 'Remarks (Required for Risk Override)'
: 'Remarks'
}}
</mat-label>
<textarea
matInput
formControlName="remarks"
rows="2"
[placeholder]="
isKycVerified
? 'Mandatory — explain why KYC is being re-edited'
: kycForm.get('allowRiskOverride')?.value
? 'Mandatory — explain why you are overriding the auto-calculated risk category'
: ''
"
></textarea>
@if ((isKycVerified || kycForm.get('allowRiskOverride')?.value) && !kycForm.get('remarks')?.value?.trim()) {
<mat-error>
Remarks are mandatory when
{{ isKycVerified ? 'updating a verified KYC record' : 'overriding the risk category' }}
</mat-error>
}
</mat-form-field>
}
</div>
@@ -1393,6 +1634,39 @@
<mat-option value="REGULATORY">Regulatory Authority</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="form-field" (click)="freezeDatePicker.open()">
<mat-label>Freeze Date</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>calendar_today</mat-icon></span>
<input matInput required [max]="today" [matDatepicker]="freezeDatePicker" formControlName="freezeDate" />
<mat-datepicker-toggle matSuffix [for]="freezeDatePicker"></mat-datepicker-toggle>
<mat-datepicker #freezeDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline" class="form-field">
<mat-label>Freeze Amount</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>currency_rupee</mat-icon></span>
<input matInput type="number" min="0" formControlName="freezeAmount" />
<mat-hint>Leave blank for a full freeze</mat-hint>
</mat-form-field>
}
@if (kycForm.get('accountStatus')?.value === 'UNFREEZE') {
<mat-form-field appearance="outline" class="form-field" (click)="unfreezeDatePicker.open()">
<mat-label>Unfreeze Date</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>lock_open</mat-icon></span>
<input matInput required [max]="today" [matDatepicker]="unfreezeDatePicker" formControlName="unfreezeDate" />
<mat-datepicker-toggle matSuffix [for]="unfreezeDatePicker"></mat-datepicker-toggle>
<mat-datepicker #unfreezeDatePicker></mat-datepicker>
</mat-form-field>
}
@if (kycForm.get('accountStatus')?.value === 'NO_DEBIT') {
<mat-form-field appearance="outline" class="form-field" (click)="noDebitDatePicker.open()">
<mat-label>No Debit From Date</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>money_off</mat-icon></span>
<input matInput required [matDatepicker]="noDebitDatePicker" formControlName="noDebitDate" />
<mat-datepicker-toggle matSuffix [for]="noDebitDatePicker"></mat-datepicker-toggle>
<mat-datepicker #noDebitDatePicker></mat-datepicker>
</mat-form-field>
}
</div>
</div>
@@ -1545,4 +1819,34 @@
</div>
</div>
</div>
<!-- Form Validation Summary -->
@if (kycForm && kycForm.invalid) {
<div
class="kyc-validation-summary"
style="
margin-top: 24px;
padding: 16px;
background: #fff5f5;
border: 1px solid #fee2e2;
border-radius: 12px;
display: flex;
align-items: flex-start;
gap: 12px;
clear: both;
"
>
<mat-icon style="color: #ef4444; flex-shrink: 0">error_outline</mat-icon>
<div style="display: flex; flex-direction: column; gap: 4px">
<span style="font-size: 14px; font-weight: 600; color: #991b1b"
>Please complete/correct the following fields to proceed:</span
>
<ul style="margin: 4px 0 0 16px; padding: 0; font-size: 13px; color: #b91c1c; line-height: 1.5">
@for (label of invalidFieldLabels; track label) {
<li>{{ label }}</li>
}
</ul>
</div>
</div>
}
</div>