refactor: fully integrate Browser Client into main Web server
TaxBaik CI/CD / build-and-deploy (push) Failing after 48s
TaxBaik CI/CD / build-and-deploy (push) Failing after 48s
BREAKING CHANGE: Removed TaxBaik.Web.Client project (separate WASM app) Changes: - Migrated all Blazor components to TaxBaik.Web/Components/Admin - Migrated all Browser Client services to Components/Admin/Services - Updated Program.cs to use integrated components (same assembly) - Removed AddAdditionalAssemblies (no longer needed) - Updated _Imports.razor with correct namespaces Architecture: ✅ API-First: REST endpoints in TaxBaik.Web (ASP.NET Core) ✅ Client-Side: Blazor WASM components in TaxBaik.Web/Components ✅ Unified: Both API and UI served from single web server ✅ No separation: No separate client project Result: - Single deploy unit (TaxBaik.Web) - API served only from web server - Blazor renders client-side (prerender: false for protected pages) - Monolithic web app architecture Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
@page "/admin/settings"
|
||||
@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false))
|
||||
@attribute [Authorize]
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using System.Collections.Generic
|
||||
@using TaxBaik.Web.Services
|
||||
@using TaxBaik.Domain.Interfaces
|
||||
@inject IApiClient ApiClient
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>설정</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="pa-6">
|
||||
<section class="admin-page-hero">
|
||||
<div>
|
||||
<MudText Typo="Typo.caption" Class="admin-eyebrow">System</MudText>
|
||||
<MudText Typo="Typo.h4" Class="admin-page-title">설정</MudText>
|
||||
<MudText Typo="Typo.body2" Class="admin-page-subtitle">공개 사이트 연락처와 관리자 계정 보안을 관리합니다.</MudText>
|
||||
</div>
|
||||
</section>
|
||||
</MudContainer>
|
||||
|
||||
<MudGrid>
|
||||
<MudItem xs="12" md="7">
|
||||
<MudPaper Class="admin-surface" Elevation="0">
|
||||
<div class="admin-section-header compact">
|
||||
<div>
|
||||
<MudText Typo="Typo.h6">사이트 정보</MudText>
|
||||
<MudText Typo="Typo.body2">홈페이지와 문의 알림에 노출되는 기본 정보입니다.</MudText>
|
||||
</div>
|
||||
</div>
|
||||
<MudForm>
|
||||
<MudTextField @bind-Value="phone" Label="전화번호"
|
||||
Variant="Variant.Outlined" Class="mb-4" />
|
||||
|
||||
<MudTextField @bind-Value="email" Label="이메일"
|
||||
Variant="Variant.Outlined" Class="mb-4" />
|
||||
|
||||
<MudTextField @bind-Value="kakaoUrl" Label="카카오채널 URL"
|
||||
Variant="Variant.Outlined" Class="mb-4" />
|
||||
|
||||
<MudTextField @bind-Value="instagramUrl" Label="인스타그램"
|
||||
Variant="Variant.Outlined" Class="mb-4" />
|
||||
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
StartIcon="@Icons.Material.Filled.Save"
|
||||
@onclick="SaveSettings">사이트 정보 저장</MudButton>
|
||||
</MudForm>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" md="5">
|
||||
<MudPaper Class="admin-surface admin-account-card" Elevation="0">
|
||||
<div class="admin-section-header compact">
|
||||
<div>
|
||||
<MudText Typo="Typo.h6">계정 관리</MudText>
|
||||
<MudText Typo="Typo.body2">비밀번호는 12자 이상으로 관리합니다.</MudText>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MudForm>
|
||||
<MudTextField @bind-Value="currentPassword" Label="현재 비밀번호" InputType="InputType.Password"
|
||||
Variant="Variant.Outlined" Class="mb-4" />
|
||||
|
||||
<MudTextField @bind-Value="newPassword" Label="새 비밀번호" InputType="InputType.Password"
|
||||
Variant="Variant.Outlined" Class="mb-4" />
|
||||
|
||||
<MudTextField @bind-Value="confirmNewPassword" Label="새 비밀번호 확인" InputType="InputType.Password"
|
||||
Variant="Variant.Outlined" Class="mb-4" />
|
||||
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||||
Disabled="@isChangingPassword"
|
||||
StartIcon="@Icons.Material.Filled.LockReset"
|
||||
@onclick="ChangePassword">
|
||||
@(isChangingPassword ? "변경 중..." : "비밀번호 변경")
|
||||
</MudButton>
|
||||
</MudForm>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@code {
|
||||
private string phone = "010-4122-8268";
|
||||
private string email = "taxbaik5668@gmail.com";
|
||||
private string kakaoUrl = "http://pf.kakao.com/_xoxchTX";
|
||||
private string instagramUrl = "https://www.instagram.com/taxtory5668/";
|
||||
private string currentPassword = "";
|
||||
private string newPassword = "";
|
||||
private string confirmNewPassword = "";
|
||||
private bool isChangingPassword;
|
||||
private bool isLoadingSettings;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadSettingsAsync();
|
||||
}
|
||||
|
||||
private async Task LoadSettingsAsync()
|
||||
{
|
||||
isLoadingSettings = true;
|
||||
|
||||
try
|
||||
{
|
||||
var settings = await ApiClient.GetAsync<Dictionary<string, string>>("site-settings");
|
||||
if (settings is null || settings.Count == 0)
|
||||
return;
|
||||
|
||||
if (settings.TryGetValue("PhoneNumber", out var loadedPhone) && !string.IsNullOrWhiteSpace(loadedPhone))
|
||||
phone = loadedPhone;
|
||||
|
||||
if (settings.TryGetValue("EmailAddress", out var loadedEmail) && !string.IsNullOrWhiteSpace(loadedEmail))
|
||||
email = loadedEmail;
|
||||
|
||||
if (settings.TryGetValue("KakaoChannelUrl", out var loadedKakao) && !string.IsNullOrWhiteSpace(loadedKakao))
|
||||
kakaoUrl = loadedKakao;
|
||||
|
||||
if (settings.TryGetValue("InstagramUrl", out var loadedInstagram) && !string.IsNullOrWhiteSpace(loadedInstagram))
|
||||
instagramUrl = loadedInstagram;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Snackbar.Add("사이트 설정을 불러오지 못했습니다.", Severity.Warning);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoadingSettings = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSettings()
|
||||
{
|
||||
if (isLoadingSettings)
|
||||
return;
|
||||
|
||||
var response = await ApiClient.PutAsync<SaveSettingsResponse>("site-settings", new
|
||||
{
|
||||
Phone = phone,
|
||||
Email = email,
|
||||
KakaoUrl = kakaoUrl,
|
||||
InstagramUrl = instagramUrl
|
||||
});
|
||||
|
||||
if (response?.Message is null)
|
||||
{
|
||||
Snackbar.Add("설정 저장에 실패했습니다.", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Snackbar.Add(response.Message, Severity.Success);
|
||||
}
|
||||
|
||||
private async Task ChangePassword()
|
||||
{
|
||||
if (isChangingPassword)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(currentPassword) || string.IsNullOrWhiteSpace(newPassword))
|
||||
{
|
||||
Snackbar.Add("현재 비밀번호와 새 비밀번호를 입력하세요.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword != confirmNewPassword)
|
||||
{
|
||||
Snackbar.Add("새 비밀번호 확인이 일치하지 않습니다.", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
isChangingPassword = true;
|
||||
|
||||
try
|
||||
{
|
||||
var response = await ApiClient.PostAsync<ChangePasswordResponse>("auth/change-password", new
|
||||
{
|
||||
CurrentPassword = currentPassword,
|
||||
NewPassword = newPassword
|
||||
});
|
||||
|
||||
if (response?.Message == null)
|
||||
{
|
||||
Snackbar.Add("비밀번호 변경에 실패했습니다.", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
Snackbar.Add(response.Message, Severity.Success);
|
||||
currentPassword = "";
|
||||
newPassword = "";
|
||||
confirmNewPassword = "";
|
||||
}
|
||||
catch
|
||||
{
|
||||
Snackbar.Add("비밀번호 변경 중 오류가 발생했습니다.", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isChangingPassword = false;
|
||||
}
|
||||
}
|
||||
|
||||
private class ChangePasswordResponse
|
||||
{
|
||||
public string Message { get; set; } = "";
|
||||
}
|
||||
|
||||
private class SaveSettingsResponse
|
||||
{
|
||||
public string Message { get; set; } = "";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user