Files
cbsnexora/src/app/login/login.component.html
T
niteshandClaude Sonnet 5 d334dd1669
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(users): OTP delivery method picker on user creation + post-login agreement
Create User dialog now has a mandatory Email/Mobile OTP delivery method
picker (styled as a pill toggle) alongside the existing fields.

Adds a new post-login agreement acceptance dialog: right after the existing
2FA flow (TwoFactorAuthenticationComponent) succeeds, or immediately after
login for users without 2FA, the user must accept the bank's current
agreement/disclaimer at least once before reaching the app.

Also adds a "Manage User Agreement" admin screen (System section) to publish
versioned agreement text without a code deploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 16:20:46 +05:30

281 lines
11 KiB
HTML

<!--
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/.
-->
<div class="login-page">
<div class="login-split">
<!-- ══ LEFT PANEL — SLIDER ══ -->
<div class="left-panel">
<div class="left-panel__dots-bg"></div>
<!-- Slides -->
<div class="slides-wrap">
@for (slide of slides; track slide.tag; let i = $index) {
<div class="slide" [class.active]="currentSlide === i" [ngClass]="'s' + (i + 1)">
<div class="slide__glow"></div>
<span class="slide__tag"><span class="slide__pulse"></span>{{ slide.tag }}</span>
<!-- LincShield SVG for FRM slide -->
@if (i === 1) {
<div class="lincshield-logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760 180" width="420" height="100">
<defs>
<linearGradient id="blueGD2" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color: #4d90e8" />
<stop offset="100%" style="stop-color: #2563c8" />
</linearGradient>
<linearGradient id="greenGD2" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color: #34d05c" />
<stop offset="100%" style="stop-color: #28b84e" />
</linearGradient>
<linearGradient id="shieldGD2" x1="0%" y1="0%" x2="60%" y2="100%">
<stop offset="0%" style="stop-color: #34d05c" />
<stop offset="100%" style="stop-color: #1a9040" />
</linearGradient>
<filter id="glow3">
<feGaussianBlur stdDeviation="3" result="b" />
<feMerge>
<feMergeNode in="b" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<path
d="M14 22 Q14 19 17 17 L44 8 Q47 7 50 8 L77 17 Q80 19 80 22 L80 72 Q80 100 47 114 Q14 100 14 72 Z"
fill="url(#shieldGD2)"
filter="url(#glow3)"
/>
<path
d="M22 28 Q22 26 24 25 L46 18 Q48 17 50 18 L72 25 Q74 26 74 28 L74 71 Q74 93 47 105 Q20 93 20 71 Z"
fill="none"
stroke="rgba(255,255,255,0.15)"
stroke-width="1.5"
/>
<path
d="M33 65 L44 76 L66 50"
stroke="white"
stroke-width="6"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
/>
<path
d="M35 42 Q47 34 59 42"
stroke="rgba(255,255,255,0.7)"
stroke-width="3"
stroke-linecap="round"
fill="none"
/>
<polygon points="59,37 59,47 65,42" fill="rgba(255,255,255,0.7)" />
<text
x="98"
y="105"
font-family="Georgia, 'Times New Roman', serif"
font-size="96"
font-weight="900"
font-style="italic"
fill="url(#blueGD2)"
letter-spacing="-2"
>
Linc
</text>
<path
d="M334 48 Q344 35 358 46"
stroke="#34D05C"
stroke-width="6.5"
stroke-linecap="round"
fill="none"
/>
<polygon points="356,38 362,50 350,49" fill="#34D05C" />
<text
x="356"
y="105"
font-family="Georgia, 'Times New Roman', serif"
font-size="96"
font-weight="900"
font-style="italic"
fill="url(#greenGD2)"
letter-spacing="-2"
>
Shield
</text>
<line x1="98" y1="122" x2="752" y2="122" stroke="#1a2d47" stroke-width="1" />
<text
x="98"
y="148"
font-family="'Trebuchet MS', 'Segoe UI', sans-serif"
font-size="18"
font-weight="700"
letter-spacing="6"
fill="#4D90E8"
>
LINK
</text>
<circle cx="178" cy="140" r="3" fill="#34D05C" />
<text
x="190"
y="148"
font-family="'Trebuchet MS', 'Segoe UI', sans-serif"
font-size="18"
font-weight="700"
letter-spacing="6"
fill="#8899bb"
>
DETECT
</text>
<circle cx="312" cy="140" r="3" fill="#34D05C" />
<text
x="324"
y="148"
font-family="'Trebuchet MS', 'Segoe UI', sans-serif"
font-size="18"
font-weight="700"
letter-spacing="6"
fill="#34D05C"
>
PROTECT.
</text>
</svg>
</div>
} @else {
<span class="slide__icon">{{ slide.icon }}</span>
}
<h2 class="slide__title" [innerHTML]="slide.title"></h2>
<p class="slide__desc">{{ slide.desc }}</p>
<div class="slide__stats">
@for (stat of slide.stats; track stat.label) {
<div class="stat-chip">
<div class="stat-chip__value">{{ stat.value }}</div>
<div class="stat-chip__label">{{ stat.label }}</div>
</div>
}
</div>
<!-- Flow diagram -->
<div class="slide__flow">
@for (node of slide.flow; track node; let last = $last) {
<div class="flow-node">{{ node }}</div>
@if (!last) {
<div class="flow-arrow"></div>
}
}
</div>
</div>
}
</div>
<!-- Nav dots -->
<div class="slide-nav">
@for (slide of slides; track slide.tag; let i = $index) {
<button class="slide-dot" [class.active]="currentSlide === i" (click)="goToSlide(i)"></button>
}
</div>
<div class="slide-progress" [style.animation-duration]="slideDuration + 'ms'"></div>
</div>
<!-- ══ RIGHT PANEL — LOGIN ══ -->
<div class="right-panel">
<!-- Settings trigger -->
<button class="settings-btn" (click)="toggleSettingsPanel()" matTooltip="Settings">
<mifosx-m3-icon [name]="settingsPanelOpen ? 'close' : 'settings'"></mifosx-m3-icon>
</button>
<!-- Settings panel -->
@if (settingsPanelOpen) {
<div class="settings-overlay" (click)="toggleSettingsPanel()"></div>
<div class="settings-panel" @settingsAnimation>
<div class="settings-panel__header">
<mifosx-m3-icon name="tune"></mifosx-m3-icon>
<h3>{{ 'labels.heading.Settings' | translate }}</h3>
</div>
<div class="settings-panel__body">
@if (showTenantSelector) {
<div class="settings-item">
<span class="settings-item__label">
<mifosx-m3-icon name="domain"></mifosx-m3-icon>
{{ 'labels.inputs.Tenant' | translate }}
</span>
<mifosx-tenant-selector></mifosx-tenant-selector>
</div>
}
<div class="settings-item">
<span class="settings-item__label">
<mifosx-m3-icon name="dark_mode"></mifosx-m3-icon>
{{ 'labels.heading.Theme' | translate }}
</span>
<mifosx-theme-toggle></mifosx-theme-toggle>
</div>
<div class="settings-item">
<span class="settings-item__label">
<mifosx-m3-icon name="language"></mifosx-m3-icon>
{{ 'labels.heading.Language' | translate }}
</span>
<mifosx-language-selector appearance="outline" [showLabel]="false"></mifosx-language-selector>
</div>
@if (allowServerSwitch()) {
<div class="settings-item">
<span class="settings-item__label">
<mifosx-m3-icon name="dns"></mifosx-m3-icon>
{{ 'labels.heading.Server' | translate }}
</span>
<mifosx-server-selector appearance="outline" [showLabel]="false"></mifosx-server-selector>
</div>
}
</div>
</div>
}
<!-- Brand -->
<div class="brand">
<div class="brand__logo">
@if (!themeDarkEnabled) {
<img [src]="logoPath" (error)="onLogoError()" alt="{{ 'APP_NAME' | translate }}" />
} @else {
<img [src]="logoPathDark" (error)="onLogoErrorDark()" alt="{{ 'APP_NAME' | translate }}" />
}
</div>
</div>
<!-- Welcome -->
<div class="welcome">
<h2>{{ 'labels.heading.Welcome Back' | translate }}</h2>
<p>{{ 'labels.text.Sign in to continue' | translate }}</p>
</div>
<!-- Login Forms -->
@if (!resetPassword && !twoFactorAuthenticationRequired && !agreementAcceptanceRequired) {
<mifosx-login-form></mifosx-login-form>
}
@if (resetPassword) {
<mifosx-reset-password></mifosx-reset-password>
}
<!-- 2FA and the post-login agreement dialog open as non-dismissable dialogs from login.component.ts -->
<div class="right-footer">RBI Licensed · PCI-DSS Compliant · ISO 27001 Certified</div>
</div>
</div>
<!-- Footer -->
<footer class="login-footer">
<span>🇮🇳</span>
<span class="login-footer__text">
Compliant with guidelines issued by &nbsp;
<strong>Institute for Development and Research in Banking Technology (IDRBT)</strong>
&nbsp;—&nbsp; Established by Reserve Bank of India
</span>
<span class="login-footer__sep">|</span>
<span class="login-footer__text">© 2025 {{ 'APP_NAME' | translate }}. All rights reserved.</span>
</footer>
</div>
<!-- Resource Menus (kept for compatibility) -->
<mat-menu #resourcesMenu="matMenu">
<a [href]="'userManual' | documentationLink" target="_blank" rel="noopener noreferrer" mat-menu-item>
{{ 'labels.links.User Manual' | translate }}
</a>
</mat-menu>