fix: render admin login as static form
TaxBaik CI/CD / build-and-deploy (push) Successful in 1m50s

This commit is contained in:
2026-07-01 13:43:57 +09:00
parent b12d2ae0c6
commit c9bf4f4f6f
+22 -107
View File
@@ -1,13 +1,8 @@
@page "/admin/login" @page "/admin/login"
@using System.ComponentModel.DataAnnotations
@layout TaxBaik.Web.Components.Admin.Layout.BlankLayout @layout TaxBaik.Web.Components.Admin.Layout.BlankLayout
@rendermode @(new InteractiveServerRenderMode(prerender: false))
@attribute [AllowAnonymous] @attribute [AllowAnonymous]
@inject IApiClient ApiClient
@inject NavigationManager NavigationManager
@inject CustomAuthenticationStateProvider AuthStateProvider
@inject IJSRuntime Js
@inject ILocalStorageService LocalStorageService @inject ILocalStorageService LocalStorageService
@inject IJSRuntime Js
<PageTitle>로그인</PageTitle> <PageTitle>로그인</PageTitle>
@@ -15,54 +10,39 @@
<MudPaper Class="pa-8" Elevation="3" Style="width: 100%; max-width: 400px;"> <MudPaper Class="pa-8" Elevation="3" Style="width: 100%; max-width: 400px;">
<MudText Typo="Typo.h4" Class="mb-6 text-center">관리자 로그인</MudText> <MudText Typo="Typo.h4" Class="mb-6 text-center">관리자 로그인</MudText>
<form id="admin-login-form"> <form id="admin-login-form" method="post" action="/taxbaik/admin/login">
<InputText class="mud-input mud-input-outlined mud-input-root mud-input-root-adorned-start mb-4" <input class="mud-input mud-input-outlined mud-input-root mud-input-root-adorned-start mb-4"
style="width: 100%; min-height: 56px; padding: 16px 14px;" style="width: 100%; min-height: 56px; padding: 16px 14px;"
placeholder="사용자명" placeholder="사용자명"
autocomplete="username" autocomplete="username"
@bind-Value="model.Username" name="username"
name="username" /> value="@model.Username" />
<InputText type="password" <input type="password"
class="mud-input mud-input-outlined mud-input-root mud-input-root-adorned-start mb-4" class="mud-input mud-input-outlined mud-input-root mud-input-root-adorned-start mb-4"
style="width: 100%; min-height: 56px; padding: 16px 14px;" style="width: 100%; min-height: 56px; padding: 16px 14px;"
placeholder="비밀번호" placeholder="비밀번호"
autocomplete="current-password" autocomplete="current-password"
@bind-Value="model.Password" name="password" />
name="password" />
<div class="mb-4"> <div class="mb-4">
<InputCheckbox class="mud-checkbox" @bind-Value="model.RememberMe" name="rememberMe" /> <input class="mud-checkbox" type="checkbox" name="rememberMe" />
<label style="margin-left: 8px; cursor: pointer;">아이디 저장</label> <label style="margin-left: 8px; cursor: pointer;">아이디 저장</label>
</div> </div>
@if (!string.IsNullOrEmpty(errorMessage)) <div class="mud-alert mud-alert-filled-error mb-4 login-error-message" style="display:none;">로그인 중 오류가 발생했습니다.</div>
{
<MudAlert Severity="Severity.Error" Class="mb-4">@errorMessage</MudAlert>
}
<button type="submit" <button type="submit"
class="mud-button-root mud-button mud-button-filled mud-button-filled-primary mud-elevation-0" class="mud-button-root mud-button mud-button-filled mud-button-filled-primary mud-elevation-0"
style="width: 100%; min-height: 52px; border: 0; border-radius: 4px; color: white;" style="width: 100%; min-height: 52px; border: 0; border-radius: 4px; color: white;">
disabled="@isLoading"> <span>로그인</span>
@if (isLoading)
{
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
<span>로그인 중...</span>
}
else
{
<span>로그인</span>
}
</button> </button>
</form> </form>
</MudPaper> </MudPaper>
</MudContainer> </MudContainer>
@code { @code {
private bool isLoading = false; private readonly LoginModel model = new();
private string errorMessage = "";
private LoginModel model = new();
private const string RememberedUsernameKey = "admin-remembered-username"; private const string RememberedUsernameKey = "admin-remembered-username";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
@@ -73,90 +53,25 @@
if (!string.IsNullOrEmpty(remembered)) if (!string.IsNullOrEmpty(remembered))
{ {
model.Username = remembered; model.Username = remembered;
model.RememberMe = true;
} }
} }
catch catch
{ {
// LocalStorage not available in pre-render // LocalStorage may be unavailable during prerender.
} }
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (firstRender)
{
await Js.InvokeVoidAsync("taxbaikAdminSession.syncRouteClass"); await Js.InvokeVoidAsync("taxbaikAdminSession.syncRouteClass");
} await Js.InvokeVoidAsync("taxbaikAdminSession.bindLoginForm");
private async Task HandleLogin()
{
if (isLoading)
return;
isLoading = true;
errorMessage = "";
try
{
var request = new { model.Username, model.Password };
var response = await ApiClient.PostAsync<LoginResponse>("auth/login", request);
if (response?.AccessToken == null || response?.RefreshToken == null)
{
errorMessage = "사용자명 또는 비밀번호가 올바르지 않습니다.";
isLoading = false;
return;
}
if (model.RememberMe)
{
await LocalStorageService.SetItemAsStringAsync(RememberedUsernameKey, model.Username);
}
else
{
await LocalStorageService.RemoveItemAsync(RememberedUsernameKey);
}
await ApiClient.SetAuthToken(response.AccessToken);
await AuthStateProvider.LoginAsync(response.AccessToken, response.RefreshToken, response.ExpiresIn);
NavigationManager.NavigateTo(GetReturnUrl(), forceLoad: false);
} }
catch
{
errorMessage = "로그인 중 오류가 발생했습니다.";
isLoading = false;
}
}
private class LoginResponse
{
public string AccessToken { get; set; } = "";
public string RefreshToken { get; set; } = "";
public int ExpiresIn { get; set; }
} }
private class LoginModel private class LoginModel
{ {
public string Username { get; set; } = ""; public string Username { get; set; } = "";
public string Password { get; set; } = "";
public bool RememberMe { get; set; }
}
private string GetReturnUrl()
{
var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
if (!Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("returnUrl", out var returnUrl)
|| string.IsNullOrWhiteSpace(returnUrl))
{
return "/taxbaik/admin/dashboard";
}
var value = returnUrl.ToString();
if (!value.StartsWith("admin", StringComparison.OrdinalIgnoreCase))
{
return "/taxbaik/admin/dashboard";
}
return $"/taxbaik/{value.TrimStart('/')}";
} }
} }