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>
109 lines
3.0 KiB
SCSS
109 lines
3.0 KiB
SCSS
/**
|
|
* Copyright since 2025 Mifos Initiative
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
/* No explicit width/maxHeight is passed at every `dialog.open(CaptureImageDialogComponent)`
|
|
call site (there are several, across clients-view/create/edit flows) — capping height and
|
|
allowing internal scroll here, once, keeps the Cancel/Upload footer reachable on short
|
|
viewports instead of relying on every caller to remember a maxHeight config. */
|
|
:host {
|
|
display: block;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.capture-dialog {
|
|
padding: 4px 4px 8px;
|
|
}
|
|
|
|
.capture-title {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
margin: 0;
|
|
|
|
fa-icon {
|
|
color: #1074b9;
|
|
font-size: 18px;
|
|
}
|
|
}
|
|
|
|
.capture-instructions {
|
|
margin: 4px 0 14px;
|
|
padding: 0 16px;
|
|
font-size: 13px;
|
|
color: rgb(0 0 0 / 60%);
|
|
}
|
|
|
|
.capture-frame {
|
|
width: 100%;
|
|
max-width: 640px;
|
|
|
|
/* Capped in vh, not just px, so the frame shrinks on short viewports (laptop screens with
|
|
browser chrome eating into the visible height) instead of pushing the Capture/Cancel/
|
|
Upload buttons below the fold — `object-fit: cover` on the video means shrinking this box
|
|
never reintroduces pillarboxing, it just changes how much of the live frame is visible. */
|
|
max-height: 42vh;
|
|
margin: 0 auto;
|
|
border-radius: 14px;
|
|
overflow: hidden;
|
|
background: #111;
|
|
box-shadow: 0 8px 24px -8px rgb(15 23 42 / 25%);
|
|
border: 1px solid rgb(255 255 255 / 8%);
|
|
|
|
/* Default (profile) frame follows the requested 4:3 capture stream. */
|
|
aspect-ratio: 4 / 3;
|
|
|
|
/* Signature capture uses a shorter, wider (16:9) frame — paper signatures are landscape-ish
|
|
and this matches the wider resolution requested from getUserMedia, so the live preview
|
|
fills the frame edge-to-edge instead of pillarboxing (black bars) inside a mismatched box. */
|
|
&--signature {
|
|
aspect-ratio: 16 / 9;
|
|
max-height: 36vh;
|
|
}
|
|
}
|
|
|
|
.capture-video {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
display: block;
|
|
|
|
/* Chromium auto-injects a Picture-in-Picture toggle (and sometimes a cast button) as small
|
|
square overlay icons in a bare <video>'s corners on hover — the `disablepictureinpicture`/
|
|
`disableremoteplayback` attributes stop the *feature*, but these pseudo-elements are the
|
|
belt-and-braces way to make sure the icons themselves never render. */
|
|
&::-webkit-media-controls,
|
|
&::-webkit-media-controls-panel,
|
|
&::-internal-media-controls-overlay-cast-button {
|
|
display: none !important;
|
|
}
|
|
}
|
|
|
|
.capture-actions-row {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin: 16px 0 4px;
|
|
}
|
|
|
|
.capture-btn {
|
|
border-radius: 999px !important;
|
|
padding: 0 20px !important;
|
|
height: 42px;
|
|
font-weight: 600 !important;
|
|
}
|
|
|
|
.capture-dialog-footer {
|
|
padding: 8px 4px 0 !important;
|
|
|
|
.mat-mdc-raised-button {
|
|
border-radius: 999px !important;
|
|
font-weight: 600 !important;
|
|
}
|
|
}
|