feat: add KYC document renewal alerts, Aadhaar/PAN exceptions, and AML recurring alerts UI; remove old journal AML marking
This commit is contained in:
@@ -1750,6 +1750,38 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
return salutation ? `${salutation} ${full}` : full;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name PAN and Aadhaar are actually matched against.
|
||||
*
|
||||
* Deliberately not {@link fullNamePreview}: that prefixes the salutation, while
|
||||
* ClientKycWritePlatformServiceJpaRepositoryImpl builds the verification name from firstname +
|
||||
* middlename + lastname only. Values are passed through untouched rather than capitalised, so what
|
||||
* the KYC step displays is exactly what goes to the provider.
|
||||
*/
|
||||
get verificationName(): string {
|
||||
return [
|
||||
this.createClientForm?.get('firstname')?.value,
|
||||
this.createClientForm?.get('middlename')?.value,
|
||||
this.createClientForm?.get('lastname')?.value
|
||||
]
|
||||
.map((part: any) => (part || '').toString().trim())
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
/** Date of birth as captured, sent to the provider formatted dd/MM/yyyy. */
|
||||
get verificationDateOfBirth(): Date | null {
|
||||
const value = this.createClientForm?.get('dateOfBirth')?.value;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return value;
|
||||
}
|
||||
const parsed = new Date(value);
|
||||
return isNaN(parsed.getTime()) ? null : parsed;
|
||||
}
|
||||
|
||||
getSelectedGender(): any {
|
||||
const val = this.createClientForm.get('genderId')?.value;
|
||||
return this.genderOptions?.find((g: any) => g.id === val) || null;
|
||||
|
||||
+52
-14
@@ -38,10 +38,17 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
}
|
||||
</div>
|
||||
<p class="sanctions-msg">
|
||||
Screening <strong>{{ clientName }}</strong> against<br />
|
||||
<strong>UN Security Council</strong> &
|
||||
<strong>Financial Intelligence Unit - India (FIU-IND under UNSC Resolution 2231)</strong>
|
||||
Screening <strong>{{ clientName }}</strong> against the<br />
|
||||
<strong>UN Security Council Consolidated List</strong><br />
|
||||
<span class="sanctions-lists"
|
||||
>ISIL (Da’esh) & Al-Qaida (1267/1989/2253) and Taliban (1988) sanctions lists</span
|
||||
>
|
||||
</p>
|
||||
@if (listDataAsOf) {
|
||||
<p class="list-as-of">
|
||||
Sanctions list data as of <strong>{{ listDataAsOf }}</strong>
|
||||
</p>
|
||||
}
|
||||
<p class="regulatory-note">{{ regulatoryBasis }}</p>
|
||||
</div>
|
||||
}
|
||||
@@ -54,8 +61,8 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
<h3 class="approve-title">No Sanctions Match Found</h3>
|
||||
<p class="approve-detail">
|
||||
<strong>{{ clientName }}</strong> has been screened against the
|
||||
<strong>UN Security Council Consolidated List</strong> and <strong>FIU-IND</strong> records. No confirmed
|
||||
match was found.
|
||||
<strong>UN Security Council Consolidated List</strong> (ISIL & Al-Qaida and Taliban sanctions lists). No
|
||||
confirmed match was found.
|
||||
</p>
|
||||
<div class="approve-meta">
|
||||
<div class="meta-row">
|
||||
@@ -222,6 +229,20 @@ import { ClientsService } from 'app/clients/clients.service';
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.list-as-of {
|
||||
font-size: 12px;
|
||||
color: #374151;
|
||||
text-align: center;
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
|
||||
.sanctions-lists {
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.regulatory-note {
|
||||
font-size: 11px;
|
||||
color: #9ca3af;
|
||||
@@ -550,16 +571,24 @@ export class SanctionsVerifyDialogComponent implements OnInit, OnDestroy {
|
||||
screeningLogId: number | null = null;
|
||||
listDataAsOf: string | null = null;
|
||||
|
||||
/**
|
||||
* Cites the direction actually in force. Chapter IX ("Requirements/obligations under International
|
||||
* Agreements") is the clause that puts the UNSC lists in scope, and the 2025 Directions superseded
|
||||
* the older Master Direction this screen used to name.
|
||||
*/
|
||||
regulatoryBasis =
|
||||
'Screened as per Section 51-A of the Unlawful Activities (Prevention) Act, 1967 and the RBI Master Direction on KYC.';
|
||||
'Screened under Section 51-A of the Unlawful Activities (Prevention) Act, 1967 and Chapter IX of the ' +
|
||||
'RBI Know Your Customer Directions, 2025 (amended as on December 29, 2025).';
|
||||
|
||||
screenedDate = new Date().toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
progressValue = 0;
|
||||
currentStep = 0;
|
||||
// Steps describe the screening that actually runs against the UNSC consolidated list; there is no
|
||||
// FIU-IND lookup behind this call, so it is no longer claimed here.
|
||||
progressSteps = [
|
||||
{ label: 'Connecting to UN Security Council database...', icon: 'globe' as any },
|
||||
{ label: 'Screening name against consolidated list...', icon: 'search' as any },
|
||||
{ label: 'Cross-checking FIU-IND records...', icon: 'database' as any },
|
||||
{ label: 'Cross-checking listed aliases and entities...', icon: 'database' as any },
|
||||
{ label: 'Analyzing results...', icon: 'chart-bar' as any }
|
||||
];
|
||||
|
||||
@@ -577,6 +606,9 @@ export class SanctionsVerifyDialogComponent implements OnInit, OnDestroy {
|
||||
next: (result: any) => {
|
||||
this.apiResult = result;
|
||||
this.apiDone = true;
|
||||
// Surfaced while the screening is still running, so the operator can see which snapshot of
|
||||
// the UNSC list the check is being made against rather than only learning it afterwards.
|
||||
this.listDataAsOf = this.formatListDate(result?.listDataAsOf);
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.apiError = err;
|
||||
@@ -652,13 +684,7 @@ export class SanctionsVerifyDialogComponent implements OnInit, OnDestroy {
|
||||
'Unable to connect to sanctions screening service.';
|
||||
} else {
|
||||
this.screeningLogId = this.apiResult?.screeningLogId || null;
|
||||
this.listDataAsOf = this.apiResult?.listDataAsOf
|
||||
? new Date(this.apiResult.listDataAsOf).toLocaleDateString('en-IN', {
|
||||
day: '2-digit',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
})
|
||||
: null;
|
||||
this.listDataAsOf = this.formatListDate(this.apiResult?.listDataAsOf);
|
||||
if (this.apiResult?.matchFound) {
|
||||
this.matchFound = true;
|
||||
this.matchCount = this.apiResult.matchCount || 0;
|
||||
@@ -668,6 +694,18 @@ export class SanctionsVerifyDialogComponent implements OnInit, OnDestroy {
|
||||
}, 400);
|
||||
}
|
||||
|
||||
/** Formats the date the UNSC snapshot was loaded; null when the backend did not report one. */
|
||||
private formatListDate(value: string | null | undefined): string | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
const parsed = new Date(value);
|
||||
if (isNaN(parsed.getTime())) {
|
||||
return null;
|
||||
}
|
||||
return parsed.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
clearInterval(this.progressTimer);
|
||||
}
|
||||
|
||||
@@ -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 ↔ 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 — 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>
|
||||
|
||||
@@ -304,6 +304,206 @@
|
||||
color: #059669;
|
||||
border: 1px solid #a7f3d0;
|
||||
}
|
||||
|
||||
// Not verified, but covered by a declaration — deliberately amber rather than green so it never
|
||||
// reads as a passing check.
|
||||
&.declared {
|
||||
background: #fffbeb;
|
||||
color: #b45309;
|
||||
border: 1px solid #fcd34d;
|
||||
}
|
||||
}
|
||||
|
||||
// ── PAN declaration summary ────────────────────────────────────────────────
|
||||
.pan-decl-card {
|
||||
margin: -4px 0 16px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #fcd34d;
|
||||
border-radius: 8px;
|
||||
background: #fffbeb;
|
||||
}
|
||||
|
||||
.pan-decl-card__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.pan-decl-card__icon {
|
||||
color: #b45309;
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.pan-decl-card__title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.pan-decl-card__date {
|
||||
margin-left: auto;
|
||||
font-size: 11px;
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.pan-decl-card__rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pan-decl-card__row {
|
||||
display: grid;
|
||||
grid-template-columns: 130px 1fr;
|
||||
gap: 10px;
|
||||
font-size: 12.5px;
|
||||
|
||||
.k {
|
||||
color: #92400e;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.v {
|
||||
color: #451a03;
|
||||
word-break: break-word;
|
||||
|
||||
&.strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pan-decl-card__note {
|
||||
margin-top: 10px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #fde68a;
|
||||
font-size: 11.5px;
|
||||
line-height: 1.5;
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.pan-decl-card__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
|
||||
.danger {
|
||||
color: #b91c1c;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Aadhaar enrolment receipt ──────────────────────────────────────────────
|
||||
// Blue rather than the declaration's amber: nothing has failed here, the customer simply does not
|
||||
// hold the document yet.
|
||||
.aad-rcpt-offer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin: -4px 0 16px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 8px;
|
||||
background: #eff6ff;
|
||||
font-size: 12.5px;
|
||||
color: #1e40af;
|
||||
|
||||
mat-icon {
|
||||
color: #2563eb;
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: auto;
|
||||
color: #1d4ed8 !important;
|
||||
border-color: #bfdbfe !important;
|
||||
}
|
||||
}
|
||||
|
||||
.aad-rcpt-card {
|
||||
margin: -4px 0 16px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 8px;
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.aad-rcpt-card__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.aad-rcpt-card__icon {
|
||||
color: #2563eb;
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.aad-rcpt-card__title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1e3a8a;
|
||||
}
|
||||
|
||||
.aad-rcpt-card__date {
|
||||
margin-left: auto;
|
||||
font-size: 11px;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.aad-rcpt-card__rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.aad-rcpt-card__row {
|
||||
display: grid;
|
||||
grid-template-columns: 130px 1fr;
|
||||
gap: 10px;
|
||||
font-size: 12.5px;
|
||||
|
||||
.k {
|
||||
color: #1e40af;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.v {
|
||||
color: #0f172a;
|
||||
word-break: break-word;
|
||||
|
||||
&.mono {
|
||||
font-family: monospace;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aad-rcpt-card__note {
|
||||
margin-top: 10px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #bfdbfe;
|
||||
font-size: 11.5px;
|
||||
line-height: 1.5;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.aad-rcpt-card__actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
|
||||
.danger {
|
||||
color: #b91c1c;
|
||||
}
|
||||
}
|
||||
|
||||
.verify-icon-ok {
|
||||
@@ -2431,3 +2631,358 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The name and DOB that PAN / Aadhaar are matched against, surfaced from the General tab so a
|
||||
mismatch is visible before the check runs rather than only after it fails. */
|
||||
.identity-basis {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 11px 14px;
|
||||
margin-bottom: 4px;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 8px;
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.identity-basis--incomplete {
|
||||
border-color: #fdba74;
|
||||
background: #fff7ed;
|
||||
}
|
||||
|
||||
.identity-basis__icon {
|
||||
flex: 0 0 auto;
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: #1d4ed8;
|
||||
|
||||
.identity-basis--incomplete & {
|
||||
color: #c2410c;
|
||||
}
|
||||
}
|
||||
|
||||
.identity-basis__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.identity-basis__legend {
|
||||
font-size: 10.5px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.identity-basis__pairs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 24px;
|
||||
}
|
||||
|
||||
.identity-basis__pair {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.identity-basis__k {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.identity-basis__v {
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.identity-basis__hint {
|
||||
font-size: 11.5px;
|
||||
color: #64748b;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.identity-basis__warn {
|
||||
font-size: 11.5px;
|
||||
font-weight: 600;
|
||||
color: #c2410c;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════
|
||||
PAN ↔ Aadhaar premium status card
|
||||
─── Replaces the legacy table-row treatment. Dark gradient with
|
||||
offset glow circles, large gradient icon, and a state-driven
|
||||
accent (green / red / blue / amber).
|
||||
═══════════════════════════════════════════════════════════════ */
|
||||
.pal-card {
|
||||
margin-top: 16px;
|
||||
width: 100%;
|
||||
min-height: 88px;
|
||||
padding: 14px 18px 30px;
|
||||
border-radius: 18px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: linear-gradient(135deg, #111827, #0f172a);
|
||||
border: 1px solid rgb(255 255 255 / 8%);
|
||||
box-shadow:
|
||||
0 12px 32px rgb(0 0 0 / 35%),
|
||||
inset 0 1px 1px rgb(255 255 255 / 5%);
|
||||
font-family: Inter, 'Segoe UI', Roboto, sans-serif;
|
||||
|
||||
/* Decorative glows — colors swap with the state variant */
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
background: #2563eb;
|
||||
border-radius: 50%;
|
||||
top: -110px;
|
||||
right: -70px;
|
||||
opacity: 0.18;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: #22c55e;
|
||||
border-radius: 50%;
|
||||
bottom: -70px;
|
||||
left: -50px;
|
||||
opacity: 0.12;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(145deg, #2563eb, #06b6d4);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
position: relative;
|
||||
box-shadow: 0 10px 20px rgb(37 99 235 / 35%);
|
||||
flex-shrink: 0;
|
||||
|
||||
mat-icon {
|
||||
font-size: 26px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgb(255 255 255 / 20%);
|
||||
}
|
||||
}
|
||||
|
||||
&__txt {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__small {
|
||||
font-size: 10.5px;
|
||||
color: #94a3b8;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: #fff;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
&__status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 4px;
|
||||
padding: 4px 11px;
|
||||
border-radius: 50px;
|
||||
background: rgb(34 197 94 / 12%);
|
||||
border: 1px solid rgb(74 222 128 / 25%);
|
||||
color: #4ade80;
|
||||
font-size: 11.5px;
|
||||
font-weight: 600;
|
||||
backdrop-filter: blur(5px);
|
||||
width: fit-content;
|
||||
|
||||
mat-icon {
|
||||
font-size: 13px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
&__badge {
|
||||
z-index: 2;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #22c55e, #16a34a);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
box-shadow: 0 0 18px rgb(34 197 94 / 45%);
|
||||
flex-shrink: 0;
|
||||
|
||||
mat-icon {
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__hint {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 16px;
|
||||
font-size: 10px;
|
||||
color: rgb(255 255 255 / 35%);
|
||||
font-style: italic;
|
||||
letter-spacing: 0.02em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* ─── State variants ─── */
|
||||
&--ok {
|
||||
&::before {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: #22c55e;
|
||||
}
|
||||
}
|
||||
|
||||
&--fail {
|
||||
border-color: rgb(239 68 68 / 22%);
|
||||
|
||||
&::before {
|
||||
background: #ef4444;
|
||||
opacity: 0.22;
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: #f87171;
|
||||
opacity: 0.16;
|
||||
}
|
||||
|
||||
.pal-card__status {
|
||||
background: rgb(239 68 68 / 14%);
|
||||
border-color: rgb(248 113 113 / 32%);
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
.pal-card__badge {
|
||||
background: linear-gradient(135deg, #ef4444, #b91c1c);
|
||||
box-shadow: 0 0 18px rgb(239 68 68 / 45%);
|
||||
}
|
||||
|
||||
.pal-card__icon {
|
||||
background: linear-gradient(145deg, #ef4444, #f97316);
|
||||
box-shadow: 0 10px 20px rgb(239 68 68 / 35%);
|
||||
}
|
||||
}
|
||||
|
||||
&--info {
|
||||
border-color: rgb(59 130 246 / 22%);
|
||||
|
||||
&::before {
|
||||
background: #3b82f6;
|
||||
opacity: 0.22;
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: #60a5fa;
|
||||
opacity: 0.14;
|
||||
}
|
||||
|
||||
.pal-card__status {
|
||||
background: rgb(59 130 246 / 14%);
|
||||
border-color: rgb(96 165 250 / 32%);
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
.pal-card__badge {
|
||||
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
||||
box-shadow: 0 0 18px rgb(59 130 246 / 45%);
|
||||
}
|
||||
}
|
||||
|
||||
&--pending {
|
||||
border-color: rgb(245 158 11 / 22%);
|
||||
|
||||
&::before {
|
||||
background: #f59e0b;
|
||||
opacity: 0.22;
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: #fbbf24;
|
||||
opacity: 0.14;
|
||||
}
|
||||
|
||||
.pal-card__status {
|
||||
background: rgb(245 158 11 / 14%);
|
||||
border-color: rgb(251 191 36 / 32%);
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.pal-card__badge {
|
||||
background: linear-gradient(135deg, #f59e0b, #b45309);
|
||||
box-shadow: 0 0 18px rgb(245 158 11 / 45%);
|
||||
}
|
||||
|
||||
.pal-card__icon {
|
||||
background: linear-gradient(145deg, #f59e0b, #d97706);
|
||||
box-shadow: 0 10px 20px rgb(245 158 11 / 35%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 540px) {
|
||||
.pal-card {
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
padding-bottom: 36px;
|
||||
|
||||
&__hint {
|
||||
position: static;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user