[] 로그인 및 화면 구조 변경

1. 회원가입 후 자동 로그인
2. 로그인 후 페이지 처리
3. 로딩 인디케이터 동작 구조 변경
This commit is contained in:
김선규 2025-06-09 17:45:53 +09:00
parent 1649818434
commit 3ffec93958
9 changed files with 192 additions and 77 deletions

View File

@ -5,9 +5,10 @@
<!-- Top 영역 -->
@* <TopBanner /> *@
@if (!isHideTop)
@if (!isHidePrjTop)
{
<TopNav />
<TopProjectNav />
}
<!-- 본문 영역 -->

View File

@ -13,11 +13,11 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
[Inject]
LoadingService LoadingService { get; set; } = default!;
// protected bool isHideTop => Navigation.Uri.Contains("/auth");
protected bool isHideTop => Navigation.ToBaseRelativePath(Navigation.Uri).Equals("auth", StringComparison.OrdinalIgnoreCase);
// 경로의 시작 부분
// protected bool isHidePrjTop => Navigation.ToBaseRelativePath(Navigation.Uri).StartsWith("auth", StringComparison.OrdinalIgnoreCase);
public static bool IsLoading { get; set; }
public static IndicateType CurrentType { get; set; } = IndicateType.Page;
// 경로의 끝 부분
protected bool isHidePrjTop => Navigation.ToBaseRelativePath(Navigation.Uri).EndsWith("auth", StringComparison.OrdinalIgnoreCase);
protected override void OnInitialized()
{
@ -35,15 +35,4 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
LoadingService.OnChange -= StateHasChanged;
Navigation.LocationChanged -= HandleLocationChanged;
}
public static void ShowLoading(IndicateType type = IndicateType.Page)
{
IsLoading = true;
CurrentType = type;
}
public static void HideLoading()
{
IsLoading = false;
}
}

View File

@ -1,12 +1,26 @@
@page "/auth"
@page "/academy/auth"
<div class="flex flex-col items-center justify-center min-h-screen bg-gray-100">
<h1 class="text-2xl font-bold mb-4">로그인</h1>
<div class="w-full max-w-xs">
<button type="button" class="w-full mb-4 p-2 rounded focus:outline-none" @onclick="KakaoLogin">
@if (NavigationManager.Uri.Contains("/academy/auth"))
{
<button type="button" class="w-full mb-4 p-2 rounded focus:outline-none"
@onclick="@(() => KakaoLogin("/academy"))" >
<img src="//k.kakaocdn.net/14/dn/btqCn0WEmI3/nijroPfbpCa4at5EIsjyf0/o.jpg"
alt="카카오 로그인"
class="rounded w-full transition duration-150 hover:brightness-90" />
</button>
}
else
{
<button type="button" class="w-full mb-4 p-2 rounded focus:outline-none"
@onclick="@(() => KakaoLogin("/about"))" >
<img src="//k.kakaocdn.net/14/dn/btqCn0WEmI3/nijroPfbpCa4at5EIsjyf0/o.jpg"
alt="카카오 로그인"
class="rounded w-full transition duration-150 hover:brightness-90" />
</button>
}
</div>
</div>

View File

@ -6,24 +6,52 @@ using Microsoft.JSInterop;
namespace Front.Program.Views.Project;
public partial class Auth : ComponentBase
public partial class Auth : ComponentBase, IDisposable
{
[Inject] NavigationManager NavigationManager { get; set; } = default!;
[Inject] LoadingService LoadingService { get; set; } = default!;
// [Inject] IJSRuntime JS { get; set; } = default!;
// [Inject] CookieService Cookie { get; set; } = default!;
[Inject] HttpClient Http { get; set; } = default!;
[Inject] IJSRuntime JS { get; set; } = default!;
public async Task KakaoLogin()
protected override void OnInitialized()
{
// LocationChanged 이벤트 구독
NavigationManager.LocationChanged += HandleLocationChanged;
}
private void HandleLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
{
// 페이지 이동이 발생했을 때 로딩 상태 해제
Console.WriteLine($"페이지 이동 감지: {NavigationManager.Uri}");
LoadingService.HideLoading();
}
public void Dispose()
{
// 이벤트 구독 해제
NavigationManager.LocationChanged -= HandleLocationChanged;
}
public async Task KakaoLogin(string? path = null)
{
try
{
LoadingService.ShowLoading();
var url = "/api/v1/out/user/kakao/auth";
var url = $"/api/v1/out/user/kakao/auth?redirectPath={Uri.EscapeDataString(path ?? "/about")}";
var response = await Http.GetFromJsonAsync<JsonElement>(url);
var kakaoUrl = response.GetProperty("url").GetString();
Console.WriteLine(kakaoUrl);
if (!string.IsNullOrEmpty(kakaoUrl))
{
NavigationManager.NavigateTo(kakaoUrl, true);
// JavaScript를 통해 페이지 이동
await JS.InvokeVoidAsync("eval", $"window.location.replace('{kakaoUrl}')");
}
}
catch (Exception ex)
{
Console.WriteLine($"카카오 로그인 오류: {ex.Message}");
LoadingService.HideLoading();
}
}
}

View File

@ -21,7 +21,7 @@
<input
pattern="\d{16}"
maxlength="16"
placeholder="실명을 입력해주세요. 최대 16글자"
placeholder="실명을 입력해주세요. (최대 16글자)"
class="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-xl text-text-title focus:outline-0 focus:ring-0 border border-[#dde0e3] bg-white focus:border-[#dde0e3] h-14 placeholder:text-[#6a7581] p-[15px] text-base font-normal leading-normal"
@bind-value="@name"
/>
@ -31,15 +31,41 @@
<div class="flex w-full flex-wrap items-end gap-4 px-4 py-3">
<label class="flex flex-col w-full">
<p class="text-text-title text-base font-medium leading-normal pb-2">생일</p>
<div class="flex w-full max-w-[480px] items-center gap-2">
<input
type="date"
@ref="dateInputRef"
placeholder="YYYYMMDD"
class="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-xl text-text-title focus:outline-0 focus:ring-0 border border-[#dde0e3] bg-white focus:border-[#dde0e3] h-14 placeholder:text-[#6a7581] p-[15px] text-base font-normal leading-normal"
@bind-value="birth"
@onclick="OpenDatePicker"
readonly
maxlength="4"
pattern="\d{4}"
placeholder="YYYY"
class="form-input flex-1 min-w-0 h-12 text-center rounded-xl border border-[#dde0e3] bg-white focus:border-[#dde0e3] placeholder:text-[#6a7581] p-[15px] text-base font-normal leading-normal"
@bind-value="birthYear"
inputmode="numeric"
autocomplete="off"
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
/>
<span class="self-center">년</span>
<input
maxlength="2"
pattern="\d{2}"
placeholder="MM"
class="form-input flex-1 min-w-0 h-12 text-center rounded-xl border border-[#dde0e3] bg-white focus:border-[#dde0e3] placeholder:text-[#6a7581] p-[15px] text-base font-normal leading-normal"
@bind-value="birthMonth"
inputmode="numeric"
autocomplete="off"
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
/>
<span class="self-center">월</span>
<input
maxlength="2"
pattern="\d{2}"
placeholder="DD"
class="form-input flex-1 min-w-0 h-12 text-center rounded-xl border border-[#dde0e3] bg-white focus:border-[#dde0e3] placeholder:text-[#6a7581] p-[15px] text-base font-normal leading-normal"
@bind-value="birthDay"
inputmode="numeric"
autocomplete="off"
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
/>
<span class="self-center">일</span>
</div>
</label>
</div>
@ -49,7 +75,7 @@
<span class="text-red-600">*</span>
</p>
<input
placeholder="Enter your email"
placeholder="E-mail을 입력해주세요."
class="form-input flex w-full min-w-0 flex-1 resize-none overflow-hidden rounded-xl text-text-title focus:outline-0 focus:ring-0 border border-[#dde0e3] bg-white focus:border-[#dde0e3] h-14 placeholder:text-[#6a7581] p-[15px] text-base font-normal leading-normal"
@bind-value="email"/>
</label>

View File

@ -5,6 +5,7 @@ using System.Net.Http.Json;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Front.Program.Layout;
using Front.Program.Services;
namespace Front.Program.Views.Project;
@ -14,10 +15,14 @@ public partial class Register : ComponentBase
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
[Inject] private HttpClient Http { get; set; } = default!;
[Inject] private IConfiguration Configuration { get; set; } = default!;
[Inject] private LoadingService LoadingService { get; set; } = default!;
private ElementReference dateInputRef;
private string name = "";
private string birthYear = "";
private string birthMonth = "";
private string birthDay = "";
private DateTime? birth;
private string email = "";
private string phone = "";
@ -89,6 +94,27 @@ public partial class Register : ComponentBase
}
}
private void UpdateBirthDate()
{
if (int.TryParse(birthYear, out int year) &&
int.TryParse(birthMonth, out int month) &&
int.TryParse(birthDay, out int day))
{
try
{
birth = new DateTime(year, month, day);
}
catch (ArgumentOutOfRangeException)
{
birth = null;
}
}
else
{
birth = null;
}
}
private async Task ConfirmData()
{
if (string.IsNullOrWhiteSpace(name))
@ -103,6 +129,9 @@ public partial class Register : ComponentBase
return;
}
// 생일 업데이트
UpdateBirthDate();
// 전화번호 조합
if (phonePart1.Length == 3 && phonePart2.Length == 4 && phonePart3.Length == 4)
{
@ -139,7 +168,7 @@ public partial class Register : ComponentBase
try
{
MainLayout.ShowLoading();
LoadingService.ShowLoading();
// 쿠키에서 토큰 가져오기
var token = await JS.InvokeAsync<string>("eval", "document.cookie.split('; ').find(row => row.startsWith('Web_AM_Connect_Key='))?.split('=')[1] || ''");
Console.WriteLine($"쿠키에서 가져온 토큰: '{token}'");
@ -149,7 +178,7 @@ public partial class Register : ComponentBase
await JS.InvokeVoidAsync("alert", "인증 정보가 없습니다.");
NavigationManager.NavigateTo("/");
MainLayout.HideLoading();
LoadingService.HideLoading();
return;
}
@ -186,30 +215,27 @@ public partial class Register : ComponentBase
// 세션 스토리지 정리
await JS.InvokeVoidAsync("sessionStorage.removeItem", "snsId");
MainLayout.HideLoading();
LoadingService.HideLoading();
await JS.InvokeVoidAsync("alert", "회원가입이 완료되었습니다.");
NavigationManager.NavigateTo("/");
}
else
{
MainLayout.HideLoading();
await JS.InvokeVoidAsync("alert", $"회원가입 실패: {message}");
LoadingService.HideLoading();
await JS.InvokeVoidAsync("alert", $"회원가입에 실패하였습니다.\n잠시 후 다시 시도해주세요.");
}
}
else
{
MainLayout.HideLoading();
Console.WriteLine($"API 호출 실패: {response.StatusCode}");
var errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"에러 내용: {errorContent}");
await JS.InvokeVoidAsync("alert", "서버 오류가 발생했습니다.");
LoadingService.HideLoading();
await JS.InvokeVoidAsync("alert", "회원가입 중 오류가 발생했습니다.\n잠시 후 다시 시도해주세요.");
}
}
catch (Exception ex)
{
MainLayout.HideLoading();
LoadingService.HideLoading();
Console.WriteLine($"예외 발생: {ex.Message}");
await JS.InvokeVoidAsync("alert", $"오류가 발생했습니다: {ex.Message}");
await JS.InvokeVoidAsync("alert", "회원가입 중 오류가 발생했습니다.\n잠시 후 다시 시도해주세요.");
}
}
@ -227,9 +253,17 @@ public partial class Register : ComponentBase
}
private async Task OpenDatePicker()
{
try
{
if (birth == null) birth = DateTime.Now;
await JS.InvokeVoidAsync("openDatePicker", dateInputRef);
// showPicker 대신 click() 이벤트를 발생시켜 달력을 표시
await JS.InvokeVoidAsync("eval", $"document.querySelector('input[type=\"date\"]').click()");
}
catch (Exception ex)
{
Console.WriteLine($"달력 표시 오류: {ex.Message}");
}
}
private void OnBirthChanged(ChangeEventArgs e)
{

View File

@ -11,13 +11,19 @@
<a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/join" @onclick="() => isOpen = !isOpen">Join</a>
<a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/new" @onclick="() => isOpen = !isOpen">What's New</a>
</div>
@if (!isLoggedIn)
{
<button class="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-second-normal hover:bg-second-dark text-white text-sm font-bold leading-normal tracking-[0.015em] mr-4"
@onclick="OnClickLogin">
<span class="truncate">시작하기</span>
</button>
}
</div>
</div>
<button class="md:hidden mr-4" @onclick="OnClickMenuDown">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
@ -32,10 +38,13 @@
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/about" @onclick="() => isOpen = !isOpen">About</a>
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/join" @onclick="() => isOpen = !isOpen">Join</a>
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-600" href="/new" @onclick="() => isOpen = !isOpen">What's New</a>
@if (!isLoggedIn)
{
<button class="flex w-full cursor-pointer items-center justify-center rounded-lg h-10 px-4 bg-second-normal hover:bg-second-dark text-white text-sm font-bold leading-normal tracking-[0.015em]"
@onclick="OnClickLogin">
<span class="truncate">시작하기</span>
</button>
}
</div>
</div>
}

View File

@ -3,14 +3,23 @@ using Microsoft.JSInterop;
namespace Front.Program.Views.Project;
public partial class TopNav : ComponentBase
public partial class TopProjectNav : ComponentBase
{
//로그인버튼을 누르면 페이지를 이동할거야
[Inject]
NavigationManager NavigationManager { get; set; } = default!;
[Inject]
IJSRuntime JS { get; set; } = default!;
protected bool isOpen = false;
protected bool isLoggedIn = false;
protected override async Task OnInitializedAsync()
{
// 쿠키에서 로그인 상태 확인
var isLoginCookie = await JS.InvokeAsync<string>("eval", "document.cookie.split('; ').find(row => row.startsWith('IsLogin='))?.split('=')[1]");
isLoggedIn = isLoginCookie == "true";
}
public void OnClickMenuDown()
{
@ -28,7 +37,4 @@ public partial class TopNav : ComponentBase
if (isOpen) isOpen = !isOpen;
NavigationManager.NavigateTo("/auth");
}
}

View File

@ -12,7 +12,7 @@
<body class="bg-gray-50 text-gray-900 font-sans">
<!-- 로딩 오버레이 -->
<div class="fixed inset-0 z-50 flex items-center justify-center bg-gray-100" id="loading-overlay">
<div class="fixed inset-0 z-50 flex items-center justify-center bg-gray-100 hidden" id="loading-overlay" data-show-on="/about">
<!-- 로딩 오버레이 내부 -->
<div class="flex flex-col items-center">
<div class="relative w-48 h-48 overflow-hidden">
@ -48,12 +48,20 @@
Blazor.start().then(() => {
const elapsed = performance.now() - loadingStart;
const remaining = Math.max(0, MIN_LOADING_MS - elapsed);
setTimeout(() => {
const overlay = document.getElementById('loading-overlay');
const showOnPath = overlay?.getAttribute('data-show-on');
if (overlay && showOnPath && window.location.pathname === showOnPath) {
overlay.classList.remove('hidden');
setTimeout(() => {
if (overlay) overlay.remove();
}, remaining);
} else if (overlay) {
overlay.remove();
}
}).catch(err => {
console.error("❌ Blazor 로딩 실패", err);
console.error("Blazor 로딩 실패", err);
});
</script>