feat(clients): dual-OTP contact-change verification, webcam signature capture, and client history tab
Frontend counterpart to the backend dual-OTP contact-change feature:
- New ContactOtpBlockComponent (6-box segmented OTP input) used up to 4x in
Edit Client (old/new mobile, old/new email — grouped under per-field
sub-headers) and once each for mobile/email in Create Client, gating
Save/Save & Next until every required box is verified.
- edit-client.component.ts: detects mobile/email/DOB changes against the
loaded snapshot using local-date comparison (not `.toISOString()`, which
false-triggers a day-shift in positive-UTC-offset timezones) and
trimmed/case-insensitive email comparison (autofill whitespace or re-typed
casing no longer reads as a change the masked preview can't explain).
- client-kyc-step.component.ts: new `invalidFieldLabels` getter names every
currently-invalid required KYC field, surfaced via the Save & Next tooltip
in both create-client and edit-client so a greyed-out button always
explains what's still missing instead of a generic message.
- CaptureImageDialogComponent generalized for both profile photo and
signature (photograph-a-paper-signature) capture:
- Frame aspect now matches the requested getUserMedia resolution
(4:3 profile / 16:9 signature) with object-fit: cover, eliminating the
pillarboxing (black bars) a mismatched box previously caused.
- Suppresses Chromium's auto-injected PiP/cast corner overlay icons.
- Frame height capped in vh (not just px) so the dialog fits short
viewports instead of pushing Cancel/Upload below the fold.
- upload-document-dialog.component.ts: generic Documents-tab upload now also
offers "Capture from Webcam" alongside Browse, reusing the same capture
dialog.
- New client History tab (reuses the existing Audit Trail data, scoped to
this client) and dataUrlToFile/dataUrlToBlob util for routing webcam
captures through the same upload pipelines as a picked file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,79 @@
|
||||
[readOnly]="isTabReadOnly(0)"
|
||||
(legalFormChangeEvent)="legalFormChange($event)"
|
||||
></mifosx-client-general-step>
|
||||
|
||||
<!-- Dual-OTP contact-change verification panel — appears only once the mobile
|
||||
number and/or email address differ from the loaded snapshot. Save stays
|
||||
gated (see currentStepValid) until every required block below is verified. -->
|
||||
@if (!isTabReadOnly(0) && (contactChanges.mobileChanged || contactChanges.emailChanged)) {
|
||||
<div class="contact-verify-panel">
|
||||
<div class="contact-verify-panel__header">
|
||||
<mat-icon>verified_user</mat-icon>
|
||||
<div class="contact-verify-panel__header-text">
|
||||
<strong>Verify Contact Change</strong>
|
||||
<span
|
||||
>An OTP must be verified on both the old and new
|
||||
{{
|
||||
contactChanges.mobileChanged && contactChanges.emailChanged
|
||||
? 'mobile number and email address'
|
||||
: contactChanges.mobileChanged
|
||||
? 'mobile number'
|
||||
: 'email address'
|
||||
}}
|
||||
before this change can be saved.</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@if (contactChanges.mobileChanged) {
|
||||
<div class="contact-verify-panel__field-group">
|
||||
<div class="contact-verify-panel__field-title">
|
||||
<mat-icon>phone_iphone</mat-icon>
|
||||
<span>Mobile Number</span>
|
||||
</div>
|
||||
<div class="contact-verify-panel__grid">
|
||||
<mifosx-client-contact-otp-block
|
||||
[clientId]="clientId"
|
||||
channel="MOBILE"
|
||||
[destination]="contactChanges.oldMobile"
|
||||
label="Old Mobile Number"
|
||||
(stateChange)="onOldMobileOtpState($event)"
|
||||
></mifosx-client-contact-otp-block>
|
||||
<mifosx-client-contact-otp-block
|
||||
[clientId]="clientId"
|
||||
channel="MOBILE"
|
||||
[destination]="contactChanges.newMobile"
|
||||
label="New Mobile Number"
|
||||
(stateChange)="onNewMobileOtpState($event)"
|
||||
></mifosx-client-contact-otp-block>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (contactChanges.emailChanged) {
|
||||
<div class="contact-verify-panel__field-group">
|
||||
<div class="contact-verify-panel__field-title">
|
||||
<mat-icon>email</mat-icon>
|
||||
<span>Email Address</span>
|
||||
</div>
|
||||
<div class="contact-verify-panel__grid">
|
||||
<mifosx-client-contact-otp-block
|
||||
[clientId]="clientId"
|
||||
channel="EMAIL"
|
||||
[destination]="contactChanges.oldEmail"
|
||||
label="Old Email Address"
|
||||
(stateChange)="onOldEmailOtpState($event)"
|
||||
></mifosx-client-contact-otp-block>
|
||||
<mifosx-client-contact-otp-block
|
||||
[clientId]="clientId"
|
||||
channel="EMAIL"
|
||||
[destination]="contactChanges.newEmail"
|
||||
label="New Email Address"
|
||||
(stateChange)="onNewEmailOtpState($event)"
|
||||
></mifosx-client-contact-otp-block>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user