diff --git a/src/TaxBaik.Portal.Client/Components/Portal/App.razor b/src/TaxBaik.Portal.Client/Components/Portal/App.razor new file mode 100644 index 0000000..d1bf472 --- /dev/null +++ b/src/TaxBaik.Portal.Client/Components/Portal/App.razor @@ -0,0 +1,160 @@ +@using Microsoft.AspNetCore.Components.Web + + + + + + 백원숙 세무회계 - 관리자 + + + + + + + + + + + +
+
+ 연결 재설정 중... + 새로운 버전으로 업데이트되었습니다. + 자동으로 페이지를 새로고침합니다. 잠시만 기다려주세요. +
+
+
+
+
+

로드 중...

+
+
+ + + + + + + + + + +@code { + private bool isDarkMode = false; + private MudTheme mudTheme = new() + { + Palette = new PaletteLight() + { + Primary = "#1976D2", + PrimaryContrastText = "#FFFFFF", + Secondary = "#2D9F7E", + SecondaryContrastText = "#FFFFFF", + Tertiary = "#FF8A50", + TertiaryContrastText = "#FFFFFF", + Surface = "#F5F7FA", + Background = "#FFFFFF", + BackgroundGrey = "#F8F9FB", + DrawerBackground = "#FFFFFF", + DrawerText = "#424242", + AppbarBackground = "#FFFFFF", + AppbarText = "#424242", + TextPrimary = "#1A1A1A", + TextSecondary = "#64748B", + TextDisabled = "#94A3B8", + ActionDefault = "#1976D2", + ActionDisabled = "#BDBDBD", + Divider = "#E2E8F0", + DividerLight = "#F1F5F9", + Error = "#DC2626", + ErrorContrastText = "#FFFFFF", + Warning = "#F59E0B", + WarningContrastText = "#FFFFFF", + Info = "#06B6D4", + InfoContrastText = "#FFFFFF", + Success = "#16A34A", + SuccessContrastText = "#FFFFFF", + }, + LayoutProperties = new LayoutProperties() + { + DefaultBorderRadius = "6px" + }, + Typography = new Typography() + { + Default = new Default() + { + FontSize = ".8125rem", + FontWeight = 400, + LineHeight = 1.5 + }, + H1 = new H1() + { + FontSize = "1.75rem", + FontWeight = 600, + LineHeight = 1.2 + }, + H2 = new H2() + { + FontSize = "1.5rem", + FontWeight = 600, + LineHeight = 1.3 + }, + H3 = new H3() + { + FontSize = "1.25rem", + FontWeight = 600, + LineHeight = 1.3 + }, + H4 = new H4() + { + FontSize = "1.1rem", + FontWeight = 600, + LineHeight = 1.4 + }, + H5 = new H5() + { + FontSize = "0.95rem", + FontWeight = 500, + LineHeight = 1.4 + }, + H6 = new H6() + { + FontSize = "0.85rem", + FontWeight = 500, + LineHeight = 1.5 + } + } + }; +} diff --git a/src/TaxBaik.Portal.Client/Components/Portal/Layout/BlankLayout.razor b/src/TaxBaik.Portal.Client/Components/Portal/Layout/BlankLayout.razor new file mode 100644 index 0000000..4f3f76d --- /dev/null +++ b/src/TaxBaik.Portal.Client/Components/Portal/Layout/BlankLayout.razor @@ -0,0 +1,3 @@ +@inherits LayoutComponentBase + +@Body diff --git a/src/TaxBaik.Portal.Client/Components/Portal/Layout/MainLayout.razor b/src/TaxBaik.Portal.Client/Components/Portal/Layout/MainLayout.razor new file mode 100644 index 0000000..9ce4f48 --- /dev/null +++ b/src/TaxBaik.Portal.Client/Components/Portal/Layout/MainLayout.razor @@ -0,0 +1,6 @@ +@inherits LayoutComponentBase + + + + @Body + diff --git a/src/TaxBaik.Portal.Client/Components/Portal/Pages/Login.razor b/src/TaxBaik.Portal.Client/Components/Portal/Pages/Login.razor new file mode 100644 index 0000000..9e21ffe --- /dev/null +++ b/src/TaxBaik.Portal.Client/Components/Portal/Pages/Login.razor @@ -0,0 +1,164 @@ +@page "/login" +@page "/" +@attribute [AllowAnonymous] +@rendermode InteractiveWebAssembly + +@inject NavigationManager Navigation +@inject HttpClient Http + +고객 포탈 로그인 + + + + + +@code { + private string Email = ""; + private string Password = ""; + private string? ErrorMessage; + private bool IsLoading = false; + + private async Task HandleLogin() + { + IsLoading = true; + ErrorMessage = null; + + try + { + var loginRequest = new { email = Email, password = Password }; + var response = await Http.PostAsJsonAsync("/api/portal/login", loginRequest); + + if (response.IsSuccessStatusCode) + { + Navigation.NavigateTo("/dashboard", forceLoad: true); + } + else + { + ErrorMessage = "로그인 정보를 확인할 수 없습니다."; + } + } + catch + { + ErrorMessage = "로그인 중 오류가 발생했습니다."; + } + finally + { + IsLoading = false; + } + } + + private void LoginWithGoogle() => Navigation.NavigateTo("/api/portal/login/google"); + private void LoginWithNaver() => Navigation.NavigateTo("/api/portal/login/naver"); + private void LoginWithKakao() => Navigation.NavigateTo("/api/portal/login/kakao"); +} diff --git a/src/TaxBaik.Portal.Client/Components/Portal/Pages/Logout.razor b/src/TaxBaik.Portal.Client/Components/Portal/Pages/Logout.razor new file mode 100644 index 0000000..76b80e5 --- /dev/null +++ b/src/TaxBaik.Portal.Client/Components/Portal/Pages/Logout.razor @@ -0,0 +1,31 @@ +@page "/logout" +@attribute [Authorize] + +@inject NavigationManager Navigation +@inject HttpClient Http + +로그아웃 중... + +
+
+

로그아웃 중입니다...

+
+
+ +@code { + protected override async Task OnInitializedAsync() + { + try + { + await Http.PostAsync("/api/portal/logout", null); + } + catch + { + // 로그아웃 실패해도 무조건 로그인 페이지로 + } + finally + { + Navigation.NavigateTo("/login", forceLoad: true); + } + } +} diff --git a/src/TaxBaik.Portal.Client/Components/Portal/Routes.razor b/src/TaxBaik.Portal.Client/Components/Portal/Routes.razor new file mode 100644 index 0000000..9f7eb77 --- /dev/null +++ b/src/TaxBaik.Portal.Client/Components/Portal/Routes.razor @@ -0,0 +1,19 @@ +@namespace TaxBaik.PortalClient.Components.Portal +@using Microsoft.AspNetCore.Components.Routing + + + + + + + + + + + + 찾을 수 없음 + +

요청한 페이지를 찾을 수 없습니다.

+
+
+
diff --git a/src/TaxBaik.Portal.Client/PortalAuthenticationStateProvider.cs b/src/TaxBaik.Portal.Client/PortalAuthenticationStateProvider.cs new file mode 100644 index 0000000..feda8b3 --- /dev/null +++ b/src/TaxBaik.Portal.Client/PortalAuthenticationStateProvider.cs @@ -0,0 +1,48 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Components.Authorization; + +namespace TaxBaik.PortalClient; + +/// +/// Portal 사용자 인증 상태 (쿠키 기반) +/// 서버에서 PortalAuthDefaults.AuthenticationScheme으로 설정한 쿠키를 읽음 +/// +public class PortalAuthenticationStateProvider : AuthenticationStateProvider +{ + private readonly HttpClient _httpClient; + + public PortalAuthenticationStateProvider(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public override async Task GetAuthenticationStateAsync() + { + try + { + // 서버의 쿠키 인증 상태 확인 엔드포인트 + var response = await _httpClient.GetAsync("/api/portal/auth/me"); + + if (response.IsSuccessStatusCode) + { + var json = await response.Content.ReadAsStringAsync(); + // TODO: JSON 파싱 후 ClaimsIdentity 생성 + var claims = new List + { + new Claim(ClaimTypes.NameIdentifier, "user-id"), + new Claim(ClaimTypes.Name, "User Name") + }; + + var identity = new ClaimsIdentity(claims, "Portal"); + var user = new ClaimsPrincipal(identity); + return new AuthenticationState(user); + } + } + catch + { + // 인증 상태 확인 실패 → 미인증 상태 + } + + return new AuthenticationState(new ClaimsPrincipal()); + } +} diff --git a/src/TaxBaik.Portal.Client/Program.cs b/src/TaxBaik.Portal.Client/Program.cs new file mode 100644 index 0000000..8e36d98 --- /dev/null +++ b/src/TaxBaik.Portal.Client/Program.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using MudBlazor.Services; +using TaxBaik.PortalClient; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); + +builder.Services.AddMudServices(); + +builder.Services.AddAuthorizationCore(); + +// Portal 인증 (쿠키 기반) +builder.Services.AddScoped(); + +// HTTP 클라이언트 (쿠키 자동 포함) +builder.Services.AddScoped(sp => + new HttpClient + { + BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) + }); + +await builder.Build().RunAsync(); diff --git a/src/TaxBaik.Portal.Client/TaxBaik.Portal.Client.csproj b/src/TaxBaik.Portal.Client/TaxBaik.Portal.Client.csproj new file mode 100644 index 0000000..170d608 --- /dev/null +++ b/src/TaxBaik.Portal.Client/TaxBaik.Portal.Client.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + enable + enable + TaxBaik.PortalClient + + portal + + + + + + + + + + + + + + + diff --git a/src/TaxBaik.Portal.Client/_Imports.razor b/src/TaxBaik.Portal.Client/_Imports.razor new file mode 100644 index 0000000..27e9c7e --- /dev/null +++ b/src/TaxBaik.Portal.Client/_Imports.razor @@ -0,0 +1,12 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using MudBlazor +@using TaxBaik.PortalClient +@using static Microsoft.AspNetCore.Components.Web.RenderMode diff --git a/src/TaxBaik.Portal.Client/wwwroot/index.html b/src/TaxBaik.Portal.Client/wwwroot/index.html new file mode 100644 index 0000000..3f450f8 --- /dev/null +++ b/src/TaxBaik.Portal.Client/wwwroot/index.html @@ -0,0 +1,19 @@ + + + + + + TaxBaik - 고객 포탈 + + + + + + + +
+ + + + + diff --git a/src/TaxBaik.Web/Program.cs b/src/TaxBaik.Web/Program.cs index 3f557e1..8354afb 100644 --- a/src/TaxBaik.Web/Program.cs +++ b/src/TaxBaik.Web/Program.cs @@ -385,12 +385,15 @@ app.MapHealthChecks("/healthz"); app.MapRazorPages(); app.MapStaticAssets(); -// Admin Blazor WebAssembly SPA 호스팅 (/admin) +// Admin & Portal Blazor WebAssembly SPA 호스팅 app.UseBlazorFrameworkFiles("/admin"); app.UseStaticFiles("/admin"); +app.UseBlazorFrameworkFiles("/portal"); +app.UseStaticFiles("/portal"); -// /admin 라우팅 폴백 (SPA 라우트 처리) +// SPA 라우팅 폴백 (각 경로에서 index.html 제공) app.MapFallbackToFile("admin/{*path:nonfile}", "admin/index.html"); +app.MapFallbackToFile("portal/{*path:nonfile}", "portal/index.html"); // 애플리케이션 시작/종료 로깅 try diff --git a/src/TaxBaik.Web/TaxBaik.Web.csproj b/src/TaxBaik.Web/TaxBaik.Web.csproj index 8ea8c78..99b33fb 100644 --- a/src/TaxBaik.Web/TaxBaik.Web.csproj +++ b/src/TaxBaik.Web/TaxBaik.Web.csproj @@ -4,6 +4,7 @@ +