[✨] 로그인 및 화면 구조 변경
1. 회원가입 후 자동 로그인 2. 로그인 후 페이지 처리 3. 로딩 인디케이터 동작 구조 변경
This commit is contained in:
parent
1649818434
commit
3ffec93958
|
@ -5,9 +5,10 @@
|
|||
|
||||
<!-- Top 영역 -->
|
||||
@* <TopBanner /> *@
|
||||
@if (!isHideTop)
|
||||
|
||||
@if (!isHidePrjTop)
|
||||
{
|
||||
<TopNav />
|
||||
<TopProjectNav />
|
||||
}
|
||||
|
||||
<!-- 본문 영역 -->
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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">
|
||||
<img src="//k.kakaocdn.net/14/dn/btqCn0WEmI3/nijroPfbpCa4at5EIsjyf0/o.jpg"
|
||||
alt="카카오 로그인"
|
||||
class="rounded w-full transition duration-150 hover:brightness-90" />
|
||||
</button>
|
||||
@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>
|
|
@ -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()
|
||||
{
|
||||
LoadingService.ShowLoading();
|
||||
var url = "/api/v1/out/user/kakao/auth";
|
||||
var response = await Http.GetFromJsonAsync<JsonElement>(url);
|
||||
var kakaoUrl = response.GetProperty("url").GetString();
|
||||
Console.WriteLine(kakaoUrl);
|
||||
if (!string.IsNullOrEmpty(kakaoUrl))
|
||||
// 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
|
||||
{
|
||||
NavigationManager.NavigateTo(kakaoUrl, true);
|
||||
LoadingService.ShowLoading();
|
||||
|
||||
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();
|
||||
|
||||
if (!string.IsNullOrEmpty(kakaoUrl))
|
||||
{
|
||||
// JavaScript를 통해 페이지 이동
|
||||
await JS.InvokeVoidAsync("eval", $"window.location.replace('{kakaoUrl}')");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"카카오 로그인 오류: {ex.Message}");
|
||||
LoadingService.HideLoading();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
<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
|
||||
/>
|
||||
<div class="flex w-full max-w-[480px] items-center gap-2">
|
||||
<input
|
||||
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>
|
||||
|
|
|
@ -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잠시 후 다시 시도해주세요.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,8 +254,16 @@ public partial class Register : ComponentBase
|
|||
|
||||
private async Task OpenDatePicker()
|
||||
{
|
||||
if (birth == null) birth = DateTime.Now;
|
||||
await JS.InvokeVoidAsync("openDatePicker", dateInputRef);
|
||||
try
|
||||
{
|
||||
if (birth == null) birth = DateTime.Now;
|
||||
// showPicker 대신 click() 이벤트를 발생시켜 달력을 표시
|
||||
await JS.InvokeVoidAsync("eval", $"document.querySelector('input[type=\"date\"]').click()");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"달력 표시 오류: {ex.Message}");
|
||||
}
|
||||
}
|
||||
private void OnBirthChanged(ChangeEventArgs e)
|
||||
{
|
||||
|
|
|
@ -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>
|
||||
<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>
|
||||
@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>
|
||||
<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>
|
||||
@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>
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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');
|
||||
if (overlay) overlay.remove();
|
||||
}, remaining);
|
||||
|
||||
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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user