2025-01-24 19:17:26 -03:00
|
|
|
@page "/login"
|
|
|
|
|
@using System.Text.Json;
|
2025-04-30 17:22:52 -03:00
|
|
|
@using System.Text;
|
2025-01-24 19:17:26 -03:00
|
|
|
@using System.Net;
|
|
|
|
|
@using System.ComponentModel.DataAnnotations;
|
2025-04-30 17:22:52 -03:00
|
|
|
@using phronCare.UIBlazor.Models
|
2025-01-24 19:17:26 -03:00
|
|
|
@using phronCare.UIBlazor.Services.Authorization;
|
2025-04-30 17:22:52 -03:00
|
|
|
@using phronCare.UIBlazor.Models.Login;
|
2025-01-24 19:17:26 -03:00
|
|
|
|
|
|
|
|
@inject NavigationManager _navigationManager
|
2025-04-30 17:22:52 -03:00
|
|
|
@inject HttpClient _httpClient
|
2025-01-24 19:17:26 -03:00
|
|
|
@inject ILoginService _iLoginService
|
|
|
|
|
@inject IToastService toastService
|
|
|
|
|
|
2026-07-21 09:48:54 -03:00
|
|
|
<PageTitle>Ingresar | PhronCare · SaludLAB</PageTitle>
|
|
|
|
|
|
|
|
|
|
<section class="login-shell">
|
|
|
|
|
<div class="login-showcase" aria-hidden="true">
|
|
|
|
|
<div class="showcase-brand">
|
|
|
|
|
<img src="images/SaludLAB.png" alt="" />
|
|
|
|
|
<span class="brand-copy">
|
|
|
|
|
<span class="brand-name"><strong>Phron</strong>Care</span>
|
|
|
|
|
<small>by SaludLAB</small>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="showcase-copy">
|
|
|
|
|
<span class="showcase-eyebrow">Gestión de salud</span>
|
|
|
|
|
<h1>Una operación más clara, conectada y eficiente.</h1>
|
|
|
|
|
<p>Centralizá la gestión asistencial, comercial y logística en un único espacio seguro.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="showcase-footer"><i class="fas fa-shield-halved"></i> Acceso seguro al Backoffice</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="login-panel">
|
|
|
|
|
<div class="login-card">
|
|
|
|
|
<div class="mobile-brand">
|
|
|
|
|
<img src="images/SaludLAB.png" alt="Logo SaludLAB" />
|
|
|
|
|
<span class="brand-copy">
|
|
|
|
|
<span class="brand-name"><strong>Phron</strong>Care</span>
|
|
|
|
|
<small>by SaludLAB</small>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
2025-01-24 19:17:26 -03:00
|
|
|
@if (!isSecret)
|
2026-07-21 09:48:54 -03:00
|
|
|
{
|
|
|
|
|
<div class="login-heading">
|
|
|
|
|
<span class="login-eyebrow">Bienvenido</span>
|
|
|
|
|
<h2>Iniciar sesión</h2>
|
|
|
|
|
<p>Ingresá tus credenciales para continuar.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<EditForm Model="login2FAModel" OnSubmit="LoginAsync">
|
2025-01-24 19:17:26 -03:00
|
|
|
<div class="form-group">
|
2026-07-21 09:48:54 -03:00
|
|
|
<label for="username">Usuario</label>
|
|
|
|
|
<div class="input-with-icon">
|
|
|
|
|
<i class="fas fa-user" aria-hidden="true"></i>
|
|
|
|
|
<InputText id="username" autocomplete="username" placeholder="Ingresá tu usuario" Class="form-control" @bind-Value="@login2FAModel.UserName" />
|
2025-01-24 19:17:26 -03:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
2026-07-21 09:48:54 -03:00
|
|
|
<label for="password">Contraseña</label>
|
|
|
|
|
<div class="input-with-icon">
|
|
|
|
|
<i class="fas fa-lock" aria-hidden="true"></i>
|
|
|
|
|
<InputText id="password" type="password" autocomplete="current-password" placeholder="Ingresá tu contraseña" Class="form-control" @bind-Value="login2FAModel.Password" />
|
2025-01-24 19:17:26 -03:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@if (codeTwoFactor)
|
|
|
|
|
{
|
|
|
|
|
<div class="form-group">
|
2026-07-21 09:48:54 -03:00
|
|
|
<label for="code">Código de verificación</label>
|
|
|
|
|
<div class="input-with-icon">
|
|
|
|
|
<i class="fas fa-shield-halved" aria-hidden="true"></i>
|
|
|
|
|
<InputText type="text" id="code" inputmode="numeric" autocomplete="one-time-code" placeholder="Código de seguridad" class="form-control" @bind-Value="login2FAModel.Code" />
|
|
|
|
|
</div>
|
|
|
|
|
<small class="field-hint">Ingresá el código de autenticación de dos factores.</small>
|
2025-01-24 19:17:26 -03:00
|
|
|
</div>
|
|
|
|
|
}
|
2026-07-21 09:48:54 -03:00
|
|
|
<button type="submit" class="btn btn-primary login-submit" disabled="@isSubmitting">
|
|
|
|
|
@if (isSubmitting) { <span class="spinner-border spinner-border-sm" aria-hidden="true"></span> }
|
|
|
|
|
else { <span class="fa fa-arrow-right-to-bracket" aria-hidden="true"></span> }
|
|
|
|
|
@(isSubmitting ? "Ingresando…" : "Ingresar")
|
|
|
|
|
</button>
|
|
|
|
|
<div class="login-help">
|
|
|
|
|
<span>¿Problemas para ingresar?</span>
|
|
|
|
|
<button type="button" class="reset-link" @onclick="ToggleIsSecret">Restablecer contraseña</button>
|
2025-01-24 19:17:26 -03:00
|
|
|
</div>
|
|
|
|
|
</EditForm>
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-07-21 09:48:54 -03:00
|
|
|
<div class="login-heading">
|
|
|
|
|
<span class="login-eyebrow">Recuperación de acceso</span>
|
|
|
|
|
<h2>Restablecer contraseña</h2>
|
|
|
|
|
<p>Completá los datos y el token recibido por correo.</p>
|
|
|
|
|
</div>
|
2025-01-24 19:17:26 -03:00
|
|
|
<EditForm Model="resetPassword" OnValidSubmit="ResetPasswordAsync">
|
|
|
|
|
<DataAnnotationsValidator />
|
|
|
|
|
<div class="form-group">
|
2026-07-21 09:48:54 -03:00
|
|
|
<label for="reset-email">Email</label>
|
|
|
|
|
<InputText id="reset-email" type="email" autocomplete="email" @bind-Value="resetPassword.Email" class="form-control" />
|
|
|
|
|
<ValidationMessage For="@(() => resetPassword.Email)" />
|
2025-01-24 19:17:26 -03:00
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
2026-07-21 09:48:54 -03:00
|
|
|
<label for="new-password">Nueva contraseña</label>
|
|
|
|
|
<InputText id="new-password" @bind-Value="resetPassword.Password" type="password" autocomplete="new-password" class="form-control" />
|
|
|
|
|
<ValidationMessage For="@(() => resetPassword.Password)" />
|
2025-01-24 19:17:26 -03:00
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
2026-07-21 09:48:54 -03:00
|
|
|
<label for="confirm-password">Confirmar contraseña</label>
|
|
|
|
|
<InputText id="confirm-password" @bind-Value="resetPassword.ConfirmPassword" type="password" autocomplete="new-password" class="form-control" />
|
|
|
|
|
<ValidationMessage For="@(() => resetPassword.ConfirmPassword)" />
|
2025-01-24 19:17:26 -03:00
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
2026-07-21 09:48:54 -03:00
|
|
|
<label for="reset-token">Token</label>
|
|
|
|
|
<InputTextArea id="reset-token" rows="3" @bind-Value="resetPassword.Token" class="form-control" />
|
|
|
|
|
<ValidationMessage For="@(() => resetPassword.Token)" />
|
2025-01-24 19:17:26 -03:00
|
|
|
</div>
|
2026-07-21 09:48:54 -03:00
|
|
|
<button type="submit" class="btn btn-primary login-submit" disabled="@isSubmitting">
|
|
|
|
|
@if (isSubmitting) { <span class="spinner-border spinner-border-sm" aria-hidden="true"></span> }
|
|
|
|
|
else { <span class="fa fa-key" aria-hidden="true"></span> }
|
|
|
|
|
@(isSubmitting ? "Restableciendo…" : "Restablecer contraseña")
|
|
|
|
|
</button>
|
|
|
|
|
<div class="login-help">
|
|
|
|
|
<button type="button" class="reset-link" @onclick="ToggleIsSecret"><i class="fas fa-arrow-left"></i> Volver al inicio de sesión</button>
|
2025-01-24 19:17:26 -03:00
|
|
|
</div>
|
|
|
|
|
</EditForm>
|
|
|
|
|
}
|
2026-07-21 09:48:54 -03:00
|
|
|
<div class="login-legal">PhronCare by SaludLAB · Entorno BIODEC</div>
|
|
|
|
|
</div>
|
2025-01-24 19:17:26 -03:00
|
|
|
</div>
|
2026-07-21 09:48:54 -03:00
|
|
|
</section>
|
2025-01-24 19:17:26 -03:00
|
|
|
|
|
|
|
|
@code {
|
2025-04-30 17:22:52 -03:00
|
|
|
Login loginModel = new Login();
|
|
|
|
|
Login2FA login2FAModel = new Login2FA();
|
2025-01-24 19:17:26 -03:00
|
|
|
ResetPassword resetPassword = new ResetPassword();
|
|
|
|
|
public bool isSecret = false;
|
|
|
|
|
bool codeTwoFactor = false;
|
2026-07-21 09:48:54 -03:00
|
|
|
bool isSubmitting = false;
|
2025-01-24 19:17:26 -03:00
|
|
|
string OTPCode = string.Empty;
|
|
|
|
|
|
|
|
|
|
private async Task LoginAsync()
|
|
|
|
|
{
|
2026-07-21 09:48:54 -03:00
|
|
|
if (isSubmitting) return;
|
|
|
|
|
isSubmitting = true;
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-01-24 19:17:26 -03:00
|
|
|
if (codeTwoFactor && login2FAModel.Code.Length > 4)
|
|
|
|
|
{
|
|
|
|
|
string requestUrl = $"api/Authentication/login-2FA?code={login2FAModel.Code}&username={login2FAModel.UserName}";
|
|
|
|
|
var response = await _httpClient.PostAsync(requestUrl, null);
|
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
|
|
|
{
|
|
|
|
|
var tokenResponse = await response.Content.ReadAsStringAsync();
|
|
|
|
|
|
|
|
|
|
await _iLoginService.Login(tokenResponse);
|
|
|
|
|
|
2026-07-24 16:02:48 -03:00
|
|
|
_navigationManager.NavigateTo("/");
|
2025-01-24 19:17:26 -03:00
|
|
|
}
|
|
|
|
|
else if (response.StatusCode == HttpStatusCode.BadRequest)
|
|
|
|
|
{
|
|
|
|
|
var errorResponse = await response.Content.ReadAsStringAsync();
|
|
|
|
|
var errorMessages = JsonSerializer.Deserialize<Response>(errorResponse);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
loginModel.UserName = login2FAModel.UserName;
|
|
|
|
|
loginModel.Password = login2FAModel.Password;
|
|
|
|
|
var jsonPayload = JsonSerializer.Serialize(loginModel);
|
|
|
|
|
var requestContent = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
|
|
|
|
|
var response = await _httpClient.PostAsync("api/Authentication/login", requestContent);
|
|
|
|
|
if (response.StatusCode == HttpStatusCode.BadRequest)
|
|
|
|
|
{
|
|
|
|
|
var errors = await response.Content.ReadFromJsonAsync<Dictionary<string, List<string>>>();
|
|
|
|
|
}
|
|
|
|
|
else if (response.StatusCode == HttpStatusCode.OK)
|
|
|
|
|
{
|
|
|
|
|
var tokenResponse = await response.Content.ReadAsStringAsync();
|
|
|
|
|
await _iLoginService.Login(tokenResponse);
|
|
|
|
|
}
|
|
|
|
|
else if (response.StatusCode == HttpStatusCode.Accepted)
|
|
|
|
|
{
|
|
|
|
|
codeTwoFactor = true;
|
|
|
|
|
}
|
|
|
|
|
else if (response.StatusCode == HttpStatusCode.Unauthorized)
|
|
|
|
|
{
|
|
|
|
|
var tokenResponse = await response.Content.ReadAsStringAsync();
|
|
|
|
|
toastService.ShowError(tokenResponse);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-21 09:48:54 -03:00
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
isSubmitting = false;
|
|
|
|
|
}
|
2025-01-24 19:17:26 -03:00
|
|
|
}
|
|
|
|
|
private async Task ResetPasswordAsync()
|
|
|
|
|
{
|
2026-07-21 09:48:54 -03:00
|
|
|
if (isSubmitting) return;
|
|
|
|
|
isSubmitting = true;
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-01-24 19:17:26 -03:00
|
|
|
if (resetPassword.Password != resetPassword.ConfirmPassword)
|
|
|
|
|
{
|
|
|
|
|
toastService.ShowError("Las contraseñas no coinciden. ¡Inténtalo de nuevo!");
|
|
|
|
|
}
|
|
|
|
|
else if (string.IsNullOrEmpty(resetPassword.Token))
|
|
|
|
|
{
|
|
|
|
|
toastService.ShowWarning("Debe ingresar el token enviado por correo.");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
await ResetingPassword();
|
|
|
|
|
}
|
2026-07-21 09:48:54 -03:00
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
isSubmitting = false;
|
|
|
|
|
}
|
2025-01-24 19:17:26 -03:00
|
|
|
}
|
|
|
|
|
private async Task ResetingPassword()
|
|
|
|
|
{
|
|
|
|
|
var jsonRequest = JsonSerializer.Serialize(resetPassword);
|
|
|
|
|
var requestContent = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
|
|
|
|
|
var response = await _httpClient.PostAsync("/api/Authentication/reset-password/", requestContent);
|
|
|
|
|
var messageResponse = await response.Content.ReadAsStringAsync();
|
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
|
|
|
{
|
|
|
|
|
toastService.ShowSuccess(messageResponse);
|
|
|
|
|
ToggleIsSecret();
|
|
|
|
|
StateHasChanged();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
toastService.ShowError(messageResponse);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private void ToggleIsSecret()
|
|
|
|
|
{
|
|
|
|
|
isSecret = !isSecret;
|
|
|
|
|
}
|
|
|
|
|
#region Clases
|
|
|
|
|
public class ResetPassword
|
|
|
|
|
{
|
|
|
|
|
[Required(ErrorMessage = "El campo Nueva Contraseña es obligatorio.")]
|
|
|
|
|
public string Password { get; set; } = null!;
|
|
|
|
|
[Compare("Password", ErrorMessage = "Las contraseñas no coinciden.")]
|
|
|
|
|
public string ConfirmPassword { get; set; } = null!;
|
|
|
|
|
[Required(ErrorMessage = "El campo Email es obligatorio.")]
|
|
|
|
|
[EmailAddress(ErrorMessage = "El formato ingresado es invalido")]
|
|
|
|
|
[DataType(DataType.EmailAddress)]
|
|
|
|
|
public string Email { get; set; } = null!;
|
|
|
|
|
[Required(ErrorMessage = "El campo Token es obligatorio.")]
|
|
|
|
|
public string Token { get; set; } = null!;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|