Files
cbsnexora/src/app/savings/saving-account-actions/savings-account-transactions/savings-account-transactions.component.scss
T
AdityaandClaude Opus 4.7 b149fef72e feat: KYC re-verify UX, family/nominee tab gating, savings dialogs, cfcfrms shell
- KYC tab: fix Verify (PAN) and Send OTP (Aadhaar) being disabled in edit mode;
  unmask Aadhaar input and show on-file masked value as hint; resolve kycId
  from id/kycId/resourceId fallbacks.
- Client stepper/view: add familyAllowed / nomineeAllowed predicates +
  CanActivate route guards. Family tab hidden for non-individuals, Nominee
  tab hidden for non-proprietor entities. Family-members step accepts a
  nomineeOnly input for proprietorship clients. Add/Edit/Delete buttons
  inside family-members and nominee tabs gated by canAdd.
- cfcfrms: add cfcfrms-shell wrapper, restyle complaint/fraud/mule/escalate
  pages and dashboard.
- Savings: identity-verify and transaction-confirm dialogs, manage / activate
  / add-charge flows, account preview styling.
- Tasks, core shell, account-nominees, icons: minor style and behaviour
  updates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:49:43 +05:30

878 lines
18 KiB
SCSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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/.
*/
/* Banking-grade transaction-entry screen. Two-column layout: cash
denomination breakdown on the left (the cashier's primary workspace),
transaction & payment details on the right. Hero header is green for
deposits / amber for withdrawals. */
$navy: #0d1b2a;
$slate: #475569;
$muted: #64748b;
$border: #e2e8f0;
$soft: #f8fafc;
$blue: #1e40af;
$green: #059669;
$green-d: #047857;
$amber: #d97706;
$amber-d: #b45309;
$red: #b91c1c;
.sat {
width: 100%;
padding: 10px 16px 18px;
display: flex;
flex-direction: column;
gap: 10px;
}
/* ─── Hero ─────────────────────────────────────────────────────────── */
/* Deep navy-blue banking gradient — formal, high-contrast, sits well
with KYC / Risk / NEFT-RTGS / Cheque badges that follow. */
.sat__hero {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px 16px 12px;
border-radius: 12px;
color: #fff;
background: linear-gradient(135deg, #0f172a 0%, #1e3a8a 45%, #1e40af 100%);
box-shadow: 0 6px 18px -10px rgb(15 23 42 / 50%);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at top right, rgb(255 255 255 / 12%), transparent 55%);
pointer-events: none;
}
}
.sat__hero-top {
display: flex;
align-items: center;
gap: 12px;
position: relative;
z-index: 1;
}
/* Back button — sits at the top-left of the hero on a translucent
surface so it reads clearly against the dark gradient. */
.sat__hero-back {
height: 30px !important;
border-radius: 8px !important;
padding: 0 11px !important;
font-weight: 700 !important;
font-size: 12px !important;
letter-spacing: 0.01em;
background: rgb(255 255 255 / 14%) !important;
color: #fff !important;
border: 1px solid rgb(255 255 255 / 28%) !important;
flex-shrink: 0;
transition:
background 0.15s ease,
border-color 0.15s ease;
&:hover {
background: rgb(255 255 255 / 26%) !important;
border-color: rgb(255 255 255 / 42%) !important;
}
}
.sat__hero-title-wrap {
flex: 1 1 auto;
min-width: 0;
}
/* Deep teal banking gradient for withdrawals — clearly distinct from the
navy-blue deposit hero without resorting to a loud red. */
.sat--withdrawal .sat__hero {
background: linear-gradient(135deg, #134e4a 0%, #115e59 45%, #0f766e 100%);
box-shadow: 0 6px 18px -10px rgb(15 118 110 / 50%);
}
.sat__hero-ico {
width: 36px;
height: 36px;
border-radius: 9px;
background: rgb(255 255 255 / 18%);
border: 1px solid rgb(255 255 255 / 28%);
color: #fff;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 15px;
flex-shrink: 0;
}
.sat__title {
margin: 0;
font-size: 14.5px;
font-weight: 800;
letter-spacing: 0.01em;
line-height: 1.15;
}
.sat__sub {
margin: 1px 0 0;
font-size: 11.5px;
color: rgb(255 255 255 / 80%);
}
/* Withdrawal-only "Verify Identity" pill — sits next to the Counted
Total tile and opens the identity-verify dialog.
Adds a subtle outer pulse-ring (via ::before) until the cashier has
opened the dialog once — drives attention to the compliance step
without changing the button's resting colour. */
.sat__hero-verify {
position: relative;
height: 30px !important;
border-radius: 8px !important;
padding: 0 12px !important;
font-weight: 700 !important;
font-size: 12px !important;
letter-spacing: 0.01em;
color: #fff !important;
background: rgb(255 255 255 / 16%) !important;
border: 1px solid rgb(255 255 255 / 30%) !important;
flex-shrink: 0;
transition:
background 0.15s ease,
border-color 0.15s ease;
overflow: visible !important;
&:hover {
background: rgb(255 255 255 / 28%) !important;
border-color: rgb(255 255 255 / 42%) !important;
}
/* Pulse ring lives just outside the pill so it doesn't clip; sits on
a separate paint layer so it doesn't trigger layout. */
&::before {
content: '';
position: absolute;
inset: -2px;
border-radius: 10px;
border: 2px solid rgb(255 255 255 / 70%);
opacity: 0;
pointer-events: none;
will-change: transform, opacity;
}
&--pulse::before {
animation: sat-verify-pulse 1.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Pause the pulse on hover so the hover state isn't fighting the
animation — gives the cashier a calm target to click. */
&--pulse:hover::before {
animation-play-state: paused;
opacity: 0;
}
}
@keyframes sat-verify-pulse {
0% {
transform: scale(1);
opacity: 0.85;
}
70% {
transform: scale(1.18);
opacity: 0;
}
100% {
transform: scale(1.18);
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.sat__hero-verify--pulse::before {
animation: none;
opacity: 0.5;
}
}
.sat__hero-meta {
display: inline-flex;
flex-direction: column;
align-items: flex-end;
padding: 5px 12px;
border-radius: 8px;
background: rgb(255 255 255 / 18%);
border: 1px solid rgb(255 255 255 / 30%);
backdrop-filter: blur(8px);
min-width: 130px;
flex-shrink: 0;
}
.sat__hero-meta-label {
font-size: 9.5px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: rgb(255 255 255 / 78%);
}
.sat__hero-meta-value {
font-size: 15px;
font-weight: 800;
color: #fff;
font-variant-numeric: tabular-nums;
letter-spacing: -0.01em;
}
.sat__hero-meta-sub {
font-size: 10px;
color: rgb(255 255 255 / 75%);
margin-top: 0;
}
/* ─── Context strip (client / account / KYC / risk / NEFT-RTGS) ──── */
.sat__ctx {
display: flex;
align-items: stretch;
flex-wrap: wrap;
gap: 0;
padding: 5px 8px;
border-radius: 8px;
background: rgb(255 255 255 / 12%);
border: 1px solid rgb(255 255 255 / 22%);
backdrop-filter: blur(8px);
position: relative;
z-index: 1;
}
.sat__ctx-item {
display: flex;
align-items: center;
gap: 6px;
/* Tight flex-basis (110px) so all chips pack on a single row at typical
desktop widths — items shrink down to their content when space is
constrained instead of wrapping. */
flex: 1 1 110px;
padding: 2px 8px;
min-width: 0;
}
.sat__ctx-ico {
width: 22px;
height: 22px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 6px;
background: rgb(255 255 255 / 18%);
border: 1px solid rgb(255 255 255 / 25%);
color: #fff;
font-size: 10px;
flex-shrink: 0;
}
.sat__ctx-text {
display: flex;
flex-direction: column;
min-width: 0;
gap: 0;
}
.sat__ctx-label {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: rgb(255 255 255 / 70%);
font-weight: 700;
}
.sat__ctx-value {
font-size: 11.5px;
font-weight: 700;
color: #fff;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&--mono {
font-family: 'JetBrains Mono', 'Courier New', monospace;
font-size: 10.5px;
letter-spacing: 0.02em;
}
&--amount {
font-variant-numeric: tabular-nums;
font-size: 12px;
}
&--lg {
font-size: 13px;
}
}
/* Highlight the live balance — the cashier's anchor figure. */
.sat__ctx-item--balance {
background: rgb(255 255 255 / 12%);
border: 1px solid rgb(255 255 255 / 22%);
border-radius: 7px;
padding: 3px 8px;
}
.sat__ctx-divider {
width: 1px;
background: rgb(255 255 255 / 18%);
margin: 4px 0;
flex-shrink: 0;
/* Vertical divider — hidden when items wrap to multiple rows */
@media (width <= 720px) {
display: none;
}
}
.sat__ctx-badge {
display: inline-flex;
align-items: center;
gap: 5px;
height: 22px;
padding: 0 9px;
border-radius: 999px;
font-size: 11.5px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.04em;
background: rgb(255 255 255 / 18%);
border: 1px solid rgb(255 255 255 / 30%);
color: #fff;
align-self: flex-start;
width: fit-content;
fa-icon {
font-size: 10px;
}
&--ok {
background: rgb(16 185 129 / 36%);
border-color: rgb(110 231 183 / 60%);
color: #d1fae5;
}
&--warn {
background: rgb(245 158 11 / 36%);
border-color: rgb(252 211 77 / 60%);
color: #fef3c7;
}
&--danger {
background: rgb(239 68 68 / 36%);
border-color: rgb(252 165 165 / 60%);
color: #fee2e2;
}
}
/* ─── Two-column layout ───────────────────────────────────────────── */
.sat__form {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
gap: 14px;
align-items: start;
@media (width <= 1100px) {
grid-template-columns: 1fr;
}
}
.sat__panel {
background: #fff;
border: 1px solid $border;
border-radius: 14px;
box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
overflow: hidden;
}
.sat__panel-head {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
background: $soft;
border-bottom: 1px solid $border;
}
.sat__panel-ico {
width: 24px;
height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 7px;
background: linear-gradient(135deg, $green 0%, $green-d 100%);
color: #fff;
font-size: 11px;
}
.sat--withdrawal .sat__panel-ico {
background: linear-gradient(135deg, $amber 0%, $amber-d 100%);
}
.sat__panel-title {
margin: 0;
font-size: 12.5px;
font-weight: 700;
color: $navy;
letter-spacing: 0.01em;
}
.sat__panel-spacer {
flex: 1;
}
.sat__panel-action {
height: 26px !important;
border-radius: 7px !important;
font-size: 11.5px !important;
font-weight: 600 !important;
padding: 0 10px !important;
}
.sat__panel-body {
padding: 12px 14px 14px;
display: flex;
flex-direction: column;
gap: 3px;
}
/* ─── Denomination groups ─────────────────────────────────────────── */
.sat__denom-group {
padding: 8px 14px 4px;
& + & {
border-top: 1px solid #f1f5f9;
}
}
.sat__denom-group-head {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 5px;
}
.sat__denom-group-title {
font-size: 10px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.07em;
color: $slate;
}
.sat__denom-group-total {
font-size: 12px;
font-weight: 700;
color: $navy;
font-variant-numeric: tabular-nums;
}
.sat__denom-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 6px;
}
/* ── Single denomination tile ──
Two-row layout: top has the denomination chip + running subtotal,
bottom has a full-width stepper (− / count / +). Easier to read,
gives the cashier obvious tap targets, and never clips on narrow
columns. */
.sat__denom {
display: flex;
flex-direction: column;
gap: 5px;
padding: 6px 9px;
border-radius: 8px;
background: #fff;
border: 1px solid $border;
transition:
border-color 0.15s ease,
background 0.15s ease,
box-shadow 0.15s ease;
min-width: 0;
&--active {
border-color: #86efac;
background: #f0fdf4;
box-shadow: 0 1px 3px -1px rgb(4 120 87 / 18%);
}
}
.sat__denom-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 6px;
min-width: 0;
}
.sat__denom-chip {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 48px;
height: 22px;
padding: 0 9px;
border-radius: 5px;
font-size: 11.5px;
font-weight: 800;
letter-spacing: 0.02em;
font-variant-numeric: tabular-nums;
flex-shrink: 0;
&--note {
background: linear-gradient(135deg, #ecfeff 0%, #cffafe 100%);
color: #155e75;
border: 1px solid #a5f3fc;
}
&--coin {
background: linear-gradient(135deg, #fef9c3 0%, #fde68a 100%);
color: #854d0e;
border: 1px solid #fcd34d;
}
}
.sat__denom-amount {
font-size: 11.5px;
font-weight: 700;
color: $navy;
font-variant-numeric: tabular-nums;
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
/* Stepper — full-width row beneath the chip. */
.sat__denom-stepper {
display: flex;
align-items: stretch;
height: 28px;
border-radius: 6px;
border: 1px solid $border;
background: #fff;
overflow: hidden;
}
.sat__denom-count {
flex: 1 1 auto;
width: 100%;
min-width: 0;
border: none;
outline: none;
background: transparent;
text-align: center;
font-size: 12.5px;
font-weight: 700;
color: $navy;
font-variant-numeric: tabular-nums;
padding: 0 4px;
/* Hide the native number spinner — the explicit + / − buttons replace it. */
appearance: textfield;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
appearance: none;
margin: 0;
}
}
.sat__denom-step {
flex: 0 0 auto;
width: 28px;
background: $soft;
border: none;
border-right: 1px solid $border;
font-size: 15px;
font-weight: 700;
color: $slate;
cursor: pointer;
transition:
background 0.12s ease,
color 0.12s ease;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
padding: 0;
&:last-child {
border-right: none;
border-left: 1px solid $border;
}
&:hover:not(:disabled) {
background: #e2e8f0;
color: $navy;
}
&:active:not(:disabled) {
background: #cbd5e1;
}
&:disabled {
opacity: 0.35;
cursor: not-allowed;
}
}
/* ─── Tally / apply ───────────────────────────────────────────────── */
.sat__tally {
margin: 8px 14px 14px;
padding: 10px 12px;
border-radius: 10px;
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
border: 1px solid $border;
}
.sat__tally-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 3px 0;
font-size: 12px;
color: $slate;
&--grand {
margin-top: 4px;
padding-top: 8px;
border-top: 1px dashed #cbd5e1;
.sat__tally-label {
font-weight: 800;
color: $navy;
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 11px;
}
.sat__tally-value {
font-size: 15.5px;
font-weight: 800;
color: $green-d;
}
}
}
.sat--withdrawal .sat__tally-row--grand .sat__tally-value {
color: $amber-d;
}
.sat__tally-value {
font-weight: 700;
color: $navy;
font-variant-numeric: tabular-nums;
}
.sat__apply {
width: 100%;
margin-top: 8px;
height: 34px !important;
border-radius: 8px !important;
background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
color: #fff !important;
font-weight: 700 !important;
font-size: 12.5px !important;
letter-spacing: 0.01em;
box-shadow: 0 3px 10px -2px rgb(4 120 87 / 35%) !important;
&:disabled {
opacity: 0.5;
filter: grayscale(0.4);
box-shadow: none !important;
}
}
.sat--withdrawal .sat__apply {
background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
box-shadow: 0 4px 12px -2px rgb(180 83 9 / 35%) !important;
}
/* ─── Form fields (right panel) ───────────────────────────────────── */
.sat__field {
width: 100%;
::ng-deep .mat-mdc-text-field-wrapper {
background: #fff;
border-radius: 10px;
}
::ng-deep .mat-mdc-form-field-infix {
min-height: 48px;
padding-top: 12px !important;
padding-bottom: 12px !important;
}
::ng-deep .mdc-notched-outline__leading,
::ng-deep .mdc-notched-outline__notch,
::ng-deep .mdc-notched-outline__trailing {
border-color: #cbd5e1 !important;
}
}
.sat__sub-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px 12px;
.sat__field--wide {
grid-column: 1 / -1;
}
@media (width <= 600px) {
grid-template-columns: 1fr;
}
}
.sat__toggle-row {
display: flex;
align-items: center;
padding: 6px 4px 10px;
cursor: pointer;
user-select: none;
.sat__toggle-label {
margin-left: 8px;
font-size: 13px;
font-weight: 600;
color: $slate;
}
}
/* Match / mismatch banners */
.sat__mismatch,
.sat__match {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border-radius: 10px;
font-size: 12.5px;
font-weight: 500;
margin: 2px 0 8px;
}
.sat__mismatch {
background: #fffbeb;
border: 1px solid #fde68a;
color: #92400e;
&-ico {
color: $amber;
flex-shrink: 0;
}
strong {
font-weight: 800;
}
}
.sat__match {
background: #ecfdf5;
border: 1px solid #a7f3d0;
color: #065f46;
&-ico {
color: $green;
flex-shrink: 0;
}
}
/* ─── Actions ─────────────────────────────────────────────────────── */
.sat__actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 10px;
padding-top: 12px;
border-top: 1px solid #f1f5f9;
button {
height: 42px !important;
border-radius: 10px !important;
font-weight: 700 !important;
padding: 0 22px !important;
letter-spacing: 0.01em;
}
}
.sat__cancel {
color: $slate !important;
border-color: $border !important;
}
.sat__submit {
background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
color: #fff !important;
box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
&:disabled {
opacity: 0.55;
filter: grayscale(0.3);
background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
box-shadow: none !important;
}
}
.sat--withdrawal .sat__submit {
background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
box-shadow: 0 4px 12px -2px rgb(180 83 9 / 35%) !important;
&:disabled {
background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
}
}
.m-r-6 {
margin-right: 6px;
}
/* ─── Responsive ──────────────────────────────────────────────────── */
@media (width <= 720px) {
.sat__hero-top {
flex-wrap: wrap;
}
.sat__hero-meta {
width: 100%;
}
.sat__ctx-item {
flex: 1 1 calc(50% - 14px);
}
}
@media (width <= 480px) {
.sat__ctx-item {
flex: 1 1 100%;
}
.sat__actions {
flex-direction: column-reverse;
button {
width: 100%;
}
}
}