Feature-based folder structure
1. Created feature-based folder structure - Components now organized by domain feature 2. Moved all components - 20+ files moved to new locations 3. Updated _Imports.razor - Added all new namespace paths for global component access 4. Updated CustomThemes.cs namespace - Changed from WebApp.Components.Layout to WebApp.Components.Shared.Layout 5. Removed old using directives - Cleaned up Login.razor and Routes.razor 6. Removed empty directories - Cleaned up old folder structure
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
@page "/login"
|
||||
@layout EmptyLayout
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using WebApp.Authentication
|
||||
@using Microsoft.AspNetCore.Antiforgery
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using MudInputType = MudBlazor.InputType
|
||||
@using Microsoft.JSInterop
|
||||
@using Microsoft.AspNetCore.WebUtilities
|
||||
@inject NavigationManager Navigation
|
||||
@inject LoginRateLimitService RateLimitService
|
||||
@inject IHttpContextAccessor HttpContextAccessor
|
||||
@inject IAntiforgery Antiforgery
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<div class="d-flex justify-center align-center" style="min-height: 100vh;">
|
||||
<MudPaper Elevation="3" Class="pa-8" MaxWidth="400px" Style="width: 100%;">
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" GutterBottom="true">
|
||||
TSA Chapter Organizer
|
||||
</MudText>
|
||||
<MudText Typo="Typo.h6" Align="Align.Center" GutterBottom="true" Class="mb-6">
|
||||
Sign In
|
||||
</MudText>
|
||||
|
||||
@if (!string.IsNullOrEmpty(_errorMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mb-4">@_errorMessage</MudAlert>
|
||||
}
|
||||
|
||||
<form id="loginForm" method="post" action="/Auth/CookieLogin" @onkeydown="HandleKeyDown">
|
||||
<input type="hidden" name="__RequestVerificationToken" value="@_antiforgeryToken" />
|
||||
<input type="hidden" id="emailInput" name="email" value="" />
|
||||
<input type="hidden" id="passwordInput" name="password" value="" />
|
||||
<input type="hidden" id="rememberMeInput" name="rememberMe" value="" />
|
||||
|
||||
<MudTextField @bind-Value="_loginModel.Email"
|
||||
Label="Email"
|
||||
Variant="Variant.Outlined"
|
||||
InputType="MudInputType.Email"
|
||||
Required="true"
|
||||
Class="mb-4" />
|
||||
|
||||
<MudTextField @bind-Value="_loginModel.Password"
|
||||
Label="Password"
|
||||
Variant="Variant.Outlined"
|
||||
InputType="@_passwordInput"
|
||||
Required="true"
|
||||
Class="mb-4"
|
||||
Adornment="Adornment.End"
|
||||
AdornmentIcon="@_passwordInputIcon"
|
||||
OnAdornmentClick="TogglePasswordVisibility" />
|
||||
|
||||
<div class="d-flex justify-space-between align-center mb-4">
|
||||
<MudCheckBox @bind-Value="_loginModel.RememberMe"
|
||||
Label="Remember me?"
|
||||
Color="Color.Primary" />
|
||||
</div>
|
||||
|
||||
<MudButton ButtonType="ButtonType.Button"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Large"
|
||||
FullWidth="true"
|
||||
OnClick="HandleFormSubmit">
|
||||
<span>Sign In</span>
|
||||
</MudButton>
|
||||
</form>
|
||||
</MudPaper>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private LoginModel _loginModel = new();
|
||||
private string? _errorMessage;
|
||||
private bool _passwordVisibility;
|
||||
private MudInputType _passwordInput = MudInputType.Password;
|
||||
private string _passwordInputIcon = Icons.Material.Filled.VisibilityOff;
|
||||
private string _antiforgeryToken = string.Empty;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
// Generate antiforgery token
|
||||
var httpContext = HttpContextAccessor.HttpContext;
|
||||
if (httpContext != null)
|
||||
{
|
||||
var tokenSet = Antiforgery.GetAndStoreTokens(httpContext);
|
||||
_antiforgeryToken = tokenSet.RequestToken ?? string.Empty;
|
||||
}
|
||||
|
||||
// Check for error message from query parameter (set by controller on failed login)
|
||||
var uri = new Uri(Navigation.Uri);
|
||||
var queryParams = QueryHelpers.ParseQuery(uri.Query);
|
||||
if (queryParams.TryGetValue("error", out var errorValue))
|
||||
{
|
||||
_errorMessage = errorValue.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private class LoginModel
|
||||
{
|
||||
[Required(ErrorMessage = "Email is required")]
|
||||
[EmailAddress(ErrorMessage = "Invalid email format")]
|
||||
[MaxLength(100, ErrorMessage = "Email must be less than 100 characters")]
|
||||
[RegularExpression(@"^[^@\s]+@[^@\s]+\.[^@\s]+$", ErrorMessage = "Please enter a valid email address")]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "Password is required")]
|
||||
[MinLength(8, ErrorMessage = "Password must be at least 8 characters")]
|
||||
[MaxLength(100, ErrorMessage = "Password must be less than 100 characters")]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
|
||||
private void TogglePasswordVisibility()
|
||||
{
|
||||
_passwordVisibility = !_passwordVisibility;
|
||||
_passwordInputIcon = _passwordVisibility
|
||||
? Icons.Material.Filled.Visibility
|
||||
: Icons.Material.Filled.VisibilityOff;
|
||||
_passwordInput = _passwordVisibility
|
||||
? MudInputType.Text
|
||||
: MudInputType.Password;
|
||||
}
|
||||
|
||||
private async Task HandleKeyDown(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Key == "Enter")
|
||||
{
|
||||
// Blur the active element to ensure MudTextField bindings update
|
||||
await JS.InvokeVoidAsync("eval", "document.activeElement.blur()");
|
||||
|
||||
// Small delay to allow bindings to process
|
||||
await Task.Delay(50);
|
||||
|
||||
// Now submit the form
|
||||
await HandleFormSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleFormSubmit()
|
||||
{
|
||||
// Update hidden inputs with current model values, then submit the form
|
||||
await JS.InvokeVoidAsync("eval", $@"
|
||||
document.getElementById('emailInput').value = {System.Text.Json.JsonSerializer.Serialize(_loginModel.Email)};
|
||||
document.getElementById('passwordInput').value = {System.Text.Json.JsonSerializer.Serialize(_loginModel.Password)};
|
||||
document.getElementById('rememberMeInput').value = '{_loginModel.RememberMe.ToString().ToLower()}';
|
||||
document.getElementById('loginForm').submit();
|
||||
");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user