Files
cbsnexora/src/app/clients/edit-client/contact-otp-block/contact-otp-block.component.scss
T
niteshandClaude Sonnet 5 733d9d22b2
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
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>
2026-07-26 14:51:04 +05:30

178 lines
3.2 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/.
*/
/* Card with 6 individually-bordered digit boxes (not the single-input/background-gradient
* trick used elsewhere in this app) — each box is its own <input>, so focus/border state is
* per-digit rather than shared across the whole code. */
:host {
display: block;
}
.cotp-block {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
padding: 28px 24px;
border: 1px solid #e7ecf3;
border-radius: 18px;
background: #fff;
box-shadow: 0 8px 24px -8px rgb(15 23 42 / 10%);
text-align: center;
&--verified {
border-color: #86efac;
background: #f7fdf9;
}
}
.cotp-block__header {
display: flex;
flex-direction: column;
gap: 6px;
}
.cotp-block__label {
font-size: 15px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.4px;
color: #1e293b;
}
.cotp-block__target {
font-size: 15px;
font-weight: 700;
color: #1074b9;
word-break: break-all;
}
.cotp-block__verified {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 7px 16px;
border-radius: 999px;
background: #dcfce7;
color: #15803d;
font-size: 13px;
font-weight: 700;
}
.cotp-block__actions {
display: flex;
justify-content: center;
gap: 12px;
width: 100%;
}
.cotp-otp {
display: flex;
justify-content: center;
gap: 10px;
}
.cotp-otp__box {
box-sizing: border-box;
width: 46px;
height: 52px;
border: 1.5px solid #dde3ec;
border-radius: 12px;
background: #fff;
color: #0f172a;
font-family: 'SF Mono', 'Cascadia Code', Consolas, monospace;
font-size: 22px;
font-weight: 700;
text-align: center;
caret-color: #1074b9;
outline: none;
transition:
border-color 0.15s,
box-shadow 0.15s;
&:hover {
border-color: #b7c2d0;
}
&:focus {
border-color: #1074b9;
box-shadow: 0 0 0 3px rgb(16 116 185 / 15%);
}
}
.cotp-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
flex: 1 1 0;
height: 48px;
padding: 0 20px;
border-radius: 999px;
font-size: 14px;
font-weight: 700;
cursor: pointer;
border: 1.5px solid transparent;
transition:
background 0.15s,
border-color 0.15s,
color 0.15s,
opacity 0.15s;
&:disabled {
cursor: not-allowed;
opacity: 0.55;
}
&--primary {
background: #1074b9;
color: #fff;
box-shadow: 0 6px 16px -4px rgb(16 116 185 / 45%);
&:hover:not(:disabled) {
background: #0d5f96;
}
}
&--ghost {
background: #fff;
color: #1074b9;
border-color: #cfe0ee;
&:hover:not(:disabled) {
border-color: #1074b9;
background: #f0f7fc;
}
}
mat-progress-spinner ::ng-deep circle {
stroke: currentcolor !important;
}
}
.cotp-error {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border-radius: 10px;
background: #fef2f2;
border: 1px solid #fecaca;
color: #b91c1c;
font-size: 12.5px;
}
@media (width <= 420px) {
.cotp-otp__box {
width: 38px;
height: 46px;
font-size: 18px;
}
}