@page
@using Hon.IFS.Admin
@{
Layout = ThemeManager.CurrentTheme.GetAccountLayout();
}
@section scripts
{
<abp-script-bundle name="@typeof(Hon.IFS.Admin.Pages.Account.CustomLoginModel).FullName">
<abp-script src="/Pages/Account/Login.js" />
</abp-script-bundle>
}
</style>
<div class="row" style="padding-top: 10px; padding-left: 20px">
<div class="col-12 col-md-6 static-content">
<h2 style="color: red">STAY</h2>
<h2 style="color: red"><strong>CONNECTED</strong></h2>
<h2 style="color: white">STAY</h2>
<h2 style="color: white"><strong>PROTECTED</strong></h2>
</div>
<div class="col-12 col-md-5 align-content-end pe-md-5">
<div style="display: flex; justify-content: center; align-items: center;">
<h3 style="color: white">@adminBrandingProvider.AppName</h3>
</div>
<div class="card shadow-sm rounded card-padding" style="max-height: 400px; max-width: 600px;">
<div class="card-body p-2">
<h3 class="text-center mb-3">@L["Login"]</h3>
@await Component.InvokeAsync(typeof(PageAlertsViewComponent))
<form method="post" class="mt-1">
<div class="mb-1">
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control form-control-sm"
placeholder="User Name" />
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
</div>
<div class="mb-2" style="padding-top: 10px";>
<div class="input-group">
<input asp-for="LoginInput.Password" class="form-control form-control-sm"
autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength"
id="passwordInput" placeholder="Password" required-symbol="false" />
@* <span class="input-group-text" id="togglePassword" style="cursor: pointer;">
<i class="fa fa-eye" id="eyeIcon"></i>
</span> *@
<span asp-validation-for="LoginInput.Password" style="cursor: pointer; class="text-danger"></span>
</div>
</div>
<div class="row">
<div class="col">
<div class="form-check form-switch mb-3">
<input type="checkbox" class="form-check-input" id="LoginInput_RememberMe" name="LoginInput.RememberMe" value="true" />
<label class="form-check-label" for="LoginInput_RememberMe">@Html.DisplayNameFor(m => m.LoginInput.RememberMe)</label>
</div>
</div>
<div class="col text-end">
<a href="@Url.Page("./ForgotPassword", new { returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash })">@L["ForgotPassword"]</a>
</div>
</div>
<div class="d-grid gap-1">
<abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-sm mt-2 red-button">@L["Login"]</abp-button>
@if (Model.ShowCancelButton)
{
<abp-button type="submit" button-type="Secondary" formnovalidate="formnovalidate" name="Action" value="Cancel" class="btn-sm mt-2">@L["Cancel"]</abp-button>
}
</div>
</form>
@if (Model.VisibleExternalProviders.Any())
{
<div class="mt-1">
<h4>@L["UseAnotherServiceToLogIn"]</h4>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
@foreach (var provider in Model.VisibleExternalProviders)
{
<button type="submit" class="btn btn-primary btn-sm m-1" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button>
}
</form>
</div>
}
@* @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
} *@
</div>
</div>
</div>
</div>
namespace Hon.IFS.Admin.Pages.Account
{
[ExposeServices(typeof(CustomLoginModel), typeof(LoginModel))]
public class CustomLoginModel : LoginModel
{
private readonly ITenantRepository _tenantRepository;
public AdminBrandingProvider _adminBrandingProvider;
public DefaultBrandingProvider _defaultBrandingProvider;
public IIdentityUserRepository _identityUserRepository;
public IDirectoryDescriptorAppService _directoryDescriptorAppService;
public CoustomFileManager _coustomFileManager;
public CustomLoginModel(
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions,
IAccountExternalProviderAppService accountExternalProviderAppService,
ICurrentPrincipalAccessor currentPrincipalAccessor,
IAbpRecaptchaValidatorFactory recaptchaValidatorFactory,
IOptions<IdentityOptions> identityOptions,
IOptionsSnapshot<reCAPTCHAOptions> reCaptchaOptions,
ITenantRepository tenantRepository,
AdminBrandingProvider adminBrandingProvider,
DefaultBrandingProvider defaultBrandingProvider,
IIdentityUserRepository identityUserRepository,
IDirectoryDescriptorAppService directoryDescriptorAppService,
CoustomFileManager coustomFileManager)
: base(schemeProvider,
accountOptions,
recaptchaValidatorFactory,
accountExternalProviderAppService,
currentPrincipalAccessor,
identityOptions,
reCaptchaOptions)
{
_tenantRepository = tenantRepository;
_adminBrandingProvider = adminBrandingProvider;
_defaultBrandingProvider = defaultBrandingProvider;
_identityUserRepository = identityUserRepository;
_directoryDescriptorAppService = directoryDescriptorAppService;
_coustomFileManager = coustomFileManager;
}
public override async Task<IActionResult> OnGetAsync()
{
// Decode the entire query string to handle encoded parameters
var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString());
// Log the decoded query string for debugging purposes
System.Diagnostics.Debug.WriteLine(decodedQueryString);
// Parse the decoded query string manually to extract parameters
var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString);
if (queryParams.TryGetValue("tenantKey", out var resolvedTenant))
{
// Convert resolvedTenant to a string
string tenantKey = resolvedTenant.ToString(); // Safely convert to string
// Replace "undefined" with an empty string
if (tenantKey.Equals("undefined"))
{
tenantKey = ""; // Set tenantKey to empty string
}
// Check if tenantKey is null or an empty string
if (string.IsNullOrEmpty(tenantKey))
{
// Set the app name to the default value
_adminBrandingProvider.SetAppName("IFS ADMIN"); // Defaults to "IFS ADMIN"
}
else
{
// If tenantKey is valid, set the app name to the resolved tenant
_adminBrandingProvider.SetAppName(tenantKey);
}
}
return await base.OnGetAsync();
}
public override async Task<IActionResult> OnPostAsync(string action)
{
// Decode the entire query string to handle encoded parameters
var decodedQueryString = Uri.UnescapeDataString(Request.QueryString.ToString());
// Extract the username and password from the posted form data
var username = Request.Form["LoginInput.UserNameOrEmailAddress"].ToString();
var password = Request.Form["LoginInput.Password"].ToString();
// Log the decoded query string for debugging purposes
System.Diagnostics.Debug.WriteLine(decodedQueryString);
// Parse the decoded query string manually to extract parameters
var queryParams = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(decodedQueryString);
// Extract the tenantKey from the query parameters
if (!queryParams.TryGetValue("tenantKey", out var tenantKey))
{
//return BadRequest("Tenant key is not provided in the query parameters.");
}
// var resolvedUser = await _identityUserRepository.FindAsync()
var resolvedTenant = await _tenantRepository.FindByNameAsync(tenantKey);
// Step 3: Switch tenant and call base login logic with error handling
try
{
var user = await FindUserAsync(LoginInput.UserNameOrEmailAddress);
using (CurrentTenant.Change(user?.TenantId))
{
System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name);
// Check ModelState before proceeding (e.g., from data annotations)
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
System.Diagnostics.Debug.WriteLine("Pre-Login Validation Errors: " + string.Join(", ", errors));
return Page();
}
// Check if base method added errors to ModelState (e.g., invalid credentials)
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors));
// Add custom error message if no specific model errors regarding the username/password
ModelState.AddModelError("", "Invalid username or password.");
return Page();
}
var result = await base.OnPostAsync(action);
if (!ModelState.IsValid)
{
return result;
}
var tenantId = user.TenantId.GetValueOrDefault(Guid.Empty);
// **Check if directories already exist**
var existingDirectories = await _coustomFileManager.GetFilesByTenantIdAsync(tenantId);
var existingNames = existingDirectories.Select(d => d.Name).ToList();
var defaultFolders = new List<CreateDirectoryInput>
{
new CreateDirectoryInput { Name = "Documents" },
new CreateDirectoryInput { Name = "Images" },
new CreateDirectoryInput { Name = "Backups" }
};
foreach (var folder in defaultFolders)
{
if (!existingNames.Contains(folder.Name)) // **Insert only if not present**
{
await _directoryDescriptorAppService.CreateAsync(folder);
}
}
return result;
}
}
catch (Exception ex)
{
// Handle exceptions from base login or tenant context switching
System.Diagnostics.Debug.WriteLine($"Login Processing Error: {ex.Message}");
ModelState.AddModelError("", "An unexpected error occurred during login. Please try again.");
// Alerts.Warning(L["Message"]);
return Page();
}
//using (CurrentTenant.Change(resolvedTenant?.Id, resolvedTenant?.Name))
//{
// System.Diagnostics.Debug.WriteLine(CurrentTenant?.Name);
// // Call base method and handle its result
// var result = await base.OnPostAsync(action);
// // Check if base method added errors to ModelState
// if (!ModelState.IsValid)
// {
// var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
// System.Diagnostics.Debug.WriteLine("Post-Login Errors: " + string.Join(", ", errors));
// return Page();
// }
// return result;
//}
}
protected virtual async Task<IdentityUser> FindUserAsync(string uniqueUserNameOrEmailAddress)
{
IdentityUser user = null;
using (CurrentTenant.Change(null))
{
user = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ??
await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);
if (user != null)
{
return user;
}
}
foreach (var tenant in await _tenantRepository.GetListAsync())
{
using (CurrentTenant.Change(tenant.Id))
{
user = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ??
await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);
if (user != null)
{
return user;
}
}
}
return null;
}
}
}
I getting $ undefined error for login.js file Login.js?_v=638730725080000000:1 Uncaught ReferenceError: $ is not defined at Login.js?_v=638730725080000000:1:1
Message=
Source=
StackTrace:
at https://localhost:44330/Pages/Account/Login.js?_v=638730725080000000:1:1
7 Answer(s)
-
0
Hi,
It seems like the issue is caused by jQuery not being loaded before your Login.js file is executed.
Since we cannot see the bundle configuration for the CustomLoginModel, we cannot confirm exactly what is included. However, you can try updating the script bundle reference from:
<abp-script-bundle name="@typeof(CustomLoginModel).FullName">
to:
<abp-script-bundle name="@typeof(LoginModel).FullName">
This change will load the default login model’s script bundle, which includes jQuery by default. If this resolves the issue, it confirms that your custom login model bundle is missing jQuery.
If the issue still persists after this change, could you please share the contents of your host module class? Additionally, let us know if this issue occurs only on the login page or on other pages as well. This information will help us further investigate.
-
0
Yes, this worked but in the custom login page Password Eye icon disappears all the time can you help me how to resolve it
-
0
Hi,
Password Eye icon disappears all the time can you help me how to resolve it
As far as I remember this was a problem with the old version, if you upgrade to the next patch version your problem will be solved. See: https://abp.io/support/questions/6681/Show-Password-not-working-in-v803
-
0
I am currently working with version 9.1 of the Angular frontend, and I have encountered some issues that persist in both the login and registration pages.
If you have any updated code or solutions to address these issues, I would greatly appreciate your assistance in providing that information.
-
0
Hi, as far as I see in the code that you shared, the password icon part is commented out:
@* <span class="input-group-text" id="togglePassword" style="cursor: pointer;"> <i class="fa fa-eye" id="eyeIcon"></i> </span> *@
You've probably commented out because you are unable to see the related icon as you said. As you can see from the code above, we are using
fa fa-eye
classes for the icons, which correspond to the related icons in the Font Awesome library. So, you can either add the related library to your code, or change it as below to make it to use bootstrap icons:<span class="input-group-text" id="togglePassword" style="cursor: pointer;"> - <i class="fa fa-eye" id="eyeIcon"></i> + <i class="bi bi-eye-slash" id="eyeIcon"></i> </span>
Change the icon class as
bi bi-eye-slash
to use Bootstrap icons. -
0
**Sometimes in-built eye icon is coming and diapering **
Please find the complete code here below
@page @using Hon.IFS.Admin @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.Account.Localization @using Volo.Abp.Account.Settings @using Volo.Abp.Account.Web.Pages.Account @using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonX.Themes.LeptonX.Components.Common.PageAlerts @using Volo.Abp.AspNetCore.Mvc.UI.Theming @using Volo.Abp.Identity @using Volo.Abp.Settings @model Hon.IFS.Admin.Pages.Account.CustomLoginModel @inject IHtmlLocalizer<AccountResource> L @inject IThemeManager ThemeManager @inject Volo.Abp.Settings.ISettingProvider SettingProvider @inject AdminBrandingProvider adminBrandingProvider @{ Layout = ThemeManager.CurrentTheme.GetAccountLayout(); } ~~~~ @section scripts { <abp-script-bundle name="@typeof(LoginModel).FullName"> <abp-script src="/Pages/Account/Login.js" /> </abp-script-bundle> <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.5.1.min.js"></script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.unobtrusive-ajax/3.2.6/jquery.unobtrusive-ajax.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script> ~~~~ document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.alert .btn-close').forEach(button => { button.addEventListener('click', () => { const alertBox = button.closest('.alert'); if (alertBox) { alertBox.remove(); } }); }); }); </script> } <style> .card-padding { padding: 0.5rem !important; /* Adjusted back to 0.5rem for consistency */ } .full-height { min-height: 100vh; /* Kept at 100vh for full height */ display: flex; align-items: flex-start; /* Move content to the top */ padding-top: 50px; /* Adds space from the top to shift content down slightly */ } .static-content { padding-top: 40px; padding-right: 20px; } .red-button { background-color: red !important; border-color: red !important; color: white !important; } .form-check-input.small-switch { width: 2em; /* Adjust width */ height: 1em; /* Adjust height */ } <style > /* Custom switch styles */ .form-check-input:checked { background-color: red; /* Change the background color when checked */ border-color: red; /* Change the border color when checked */ } .form-check-input:focus { box-shadow: 0 0 0 0.2rem rgba(255, 0, 0, 0.25); /* Optional: Add a red focus ring */ } .form-check-input { background-color: #e9ecef; /* Change the background color when unchecked */ border-color: #ced4da; /* Change the border color when unchecked */ } #passwordInput { border-top-right-radius: 0.5rem !important; border-bottom-right-radius: 0.5rem !important; } </style> <div class="row" style="padding-top: 10px; padding-left: 20px"> <div class="col-12 col-md-6 static-content"> <h2 style="color: red">STAY</h2> <h2 style="color: red"><strong>CONNECTED</strong></h2> <h2 style="color: white">STAY</h2> <h2 style="color: white"><strong>PROTECTED</strong></h2> </div> <div class="col-12 col-md-5 align-content-end pe-md-5"> <div style="display: flex; justify-content: center; align-items: center;"> <h3 style="color: white">@adminBrandingProvider.AppName</h3> </div> <div class="card shadow-sm rounded card-padding" style="max-height: 400px; max-width: 460px;"> <div class="card-body p-2"> <h3 class="text-center mb-3">@L["Login"]</h3> <abp-alerts> @await Component.InvokeAsync(typeof(PageAlertsViewComponent)) </abp-alerts> <form method="post" class="mt-1"> <div class="mb-1"> <input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control form-control-sm" placeholder="User Name" /> <span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span> </div> <div class="mb-2" style="padding-top: 10px";> <div class="input-group"> <input asp-for="LoginInput.Password" class="form-control form-control-sm" autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength" id="passwordInput" placeholder="Password" required-symbol="false" /> </div> <div> <span asp-validation-for="LoginInput.Password" style="cursor: pointer; class="text-danger"></span> </div> </div> <div class="row"> <div class="col"> <div class="form-check form-switch mb-3"> <input type="checkbox" class="form-check-input" id="LoginInput_RememberMe" name="LoginInput.RememberMe" value="true" /> <label class="form-check-label" for="LoginInput_RememberMe">@Html.DisplayNameFor(m => m.LoginInput.RememberMe)</label> </div> </div> <div class="col text-end"> <a href="@Url.Page("./ForgotPassword", new { returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash })">@L["ForgotPassword"]</a> </div> </div> <div class="d-grid gap-1"> <abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-sm mt-2 red-button">@L["Login"]</abp-button> @if (Model.ShowCancelButton) { <abp-button type="submit" button-type="Secondary" formnovalidate="formnovalidate" name="Action" value="Cancel" class="btn-sm mt-2">@L["Cancel"]</abp-button> } </div> </form> @if (Model.VisibleExternalProviders.Any()) { <div class="mt-1"> <h4>@L["UseAnotherServiceToLogIn"]</h4> <form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post"> @foreach (var provider in Model.VisibleExternalProviders) { <button type="submit" class="btn btn-primary btn-sm m-1" name="provider" value="@provider.AuthenticationScheme" title="@L["GivenTenantIsNotAvailable", provider.DisplayName]">@provider.DisplayName</button> } </form> </div> } @* @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) { <div class="alert alert-warning"> <strong>@L["InvalidLoginRequest"]</strong> @L["ThereAreNoLoginSchemesConfiguredForThisClient"] </div> } *@ </div> </div> </div> </div>
-
0
Hi,
I see the following Password field in your code;
<div class="input-group"> <input asp-for="LoginInput.Password" class="form-control form-control-sm" autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength" id="passwordInput" placeholder="Password" required-symbol="false" /> </div> <div> <span asp-validation-for="LoginInput.Password" style="cursor: pointer;" class="text-danger"></span> </div>
If you update this part as follows, this feature will continue to work correctly.
<div class="form-floating mb-2"> <input asp-for="LoginInput.Password" id="password-input" type="password" class="form-control" placeholder="Password"> @Html.LabelFor(m => m.LoginInput.Password) <i id="PasswordVisibilityButton" class="bi bi-eye-slash show-pass-icon" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" aria-label="@L["ShowPassword"]" data-bs-original-title="@L["ShowPassword"]"></i> <i id="capslockicon" class="bi bi-capslock caps-lock-icon" style="display: none;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" aria-label="<i class='bi bi-exclamation-circle'></i> @L["CapsLockOn"]!" data-bs-original-title="<i class='bi bi-exclamation-circle'></i> @L["CapsLockOn"]!"></i> <span asp-validation-for="LoginInput.Password"/> </div>
Also, I am sharing the original version of the Login page below so you can refer to it when customizing:
@page @using Microsoft.AspNetCore.Mvc.Localization @using Microsoft.Extensions.Options @using Owl.reCAPTCHA @using Volo.Abp.Account.Localization @using Volo.Abp.Account.Public.Web.Pages.Account; @using Volo.Abp.Account.Public.Web.Security.Recaptcha @using Volo.Abp.Account.Settings @using Volo.Abp.Identity; @using Volo.Abp.Settings @model Volo.Abp.Account.Public.Web.Pages.Account.LoginModel @inject IHtmlLocalizer<AccountResource> L @inject Volo.Abp.AspNetCore.Mvc.UI.Layout.IPageLayout PageLayout @inject ISettingProvider SettingProvider @{ PageLayout.Content.Title = L["Login"].Value; var reCaptchaVersion = await SettingProvider.GetAsync<int>(AccountSettingNames.Captcha.Version); if (Model.UseCaptcha) { await Model.ReCaptchaOptions.SetAsync(reCaptchaVersion == 3 ? reCAPTCHAConsts.V3 : reCAPTCHAConsts.V2); } } @section scripts { <abp-script-bundle name="@typeof(LoginModel).FullName"> <abp-script src="/Pages/Account/Login.js" /> </abp-script-bundle> @if (Model.UseCaptcha) { if (reCaptchaVersion == 3) { <recaptcha-script-v3 /> <recaptcha-script-v3-js action="login" execute="false" /> } else { <recaptcha-script-v2 /> } } } @if (Model.IsLinkLogin) { <abp-alert alert-type="Warning"> @L["LinkAccountWarning", Url.PageLink()] </abp-alert> } @if (Model.BackToExternalLogins) { <div class="d-grid gap-2"> <a class="mb-3 btn btn-primary btn-block" href="@Url.Page("./ExternalLogins")">@L["Back"]</a> </div> } <div class="account-module-form"> @if (Model.IsSelfRegistrationEnabled) { <h5 class="mb-2">@L["NotAMemberYet"] <a class="text-decoration-none" href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">@L["Register"]</a></h5> } @if (Model.EnableLocalLogin) { <form method="post" id="loginForm"> @if (Model.UseCaptcha) { <input class="mb-3" data-captcha="true" type="hidden" name="@RecaptchaValidatorBase.RecaptchaResponseKey" id="@RecaptchaValidatorBase.RecaptchaResponseKey"/> } <div> <div class="form-floating mb-2"> <input asp-for="LoginInput.UserNameOrEmailAddress" type="text" class="form-control" placeholder="name@example.com"> @Html.LabelFor(m => m.LoginInput.UserNameOrEmailAddress) <span asp-validation-for="LoginInput.UserNameOrEmailAddress"/> </div> <div class="form-floating mb-2"> <input asp-for="LoginInput.Password" id="password-input" type="password" class="form-control" placeholder="Password"> @Html.LabelFor(m => m.LoginInput.Password) <i id="PasswordVisibilityButton" class="bi bi-eye-slash show-pass-icon" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" aria-label="@L["ShowPassword"]" data-bs-original-title="@L["ShowPassword"]"></i> <i id="capslockicon" class="bi bi-capslock caps-lock-icon" style="display: none;" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true" aria-label="<i class='bi bi-exclamation-circle'></i> @L["CapsLockOn"]!" data-bs-original-title="<i class='bi bi-exclamation-circle'></i> @L["CapsLockOn"]!"></i> <span asp-validation-for="LoginInput.Password"/> </div> </div> <abp-row> <abp-column> <div class="form-switch ps-2"> <abp-input asp-for="LoginInput.RememberMe" class="mb-4"/> </div> </abp-column> <abp-column class="text-end"> <a href="@Url.Page("./ForgotPassword", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">@L["ForgotPassword"]</a> </abp-column> </abp-row> @if (reCaptchaVersion == 2) { <script> recaptchaCallback = function (token) { $('form button[type=submit]').removeAttr("disabled"); $('#@RecaptchaValidatorBase.RecaptchaResponseKey').val(token) }; </script> <div class="mb-3"> <recaptcha-div-v2 callback="recaptchaCallback"/> </div> } <div class="d-grid gap-2"> <abp-button button-type="Primary" type="submit" class="mb-3" name="Action" value="Login" disabled="true"> <i class="bi bi-box-arrow-in-right me-1"></i> @L["Login"] </abp-button> </div> @if (Model.ShowCancelButton) { <div class="d-grid gap-2"> <abp-button button-type="Secondary" type="submit" formnovalidate="formnovalidate" class="mb-3" name="Action" value="Cancel">@L["Cancel"]</abp-button> </div> } </form> } @if (Model.VisibleExternalProviders.Any()) { if(Model.EnableLocalLogin) { <hr/> @L["OrSignInWith"] <br/> } else { @L["SignInWithOneOfTheFollowingProviders"] } <form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" asp-route-linkTenantId="@Model.LinkTenantId" asp-route-linkUserId="@Model.LinkUserId" asp-route-linkToken="@Model.LinkToken" method="post"> @foreach (var provider in Model.VisibleExternalProviders) { <button type="submit" class="mt-2 me-2 btn btn-outline-primary btn-sm" name="provider" value="@provider.AuthenticationScheme" data-busy-text="@L["ProcessingWithThreeDot"]"> @if (provider.Icon != null) { <i class="@provider.Icon"></i> } <span>@provider.DisplayName</span> </button> } </form> } </div>