Merge pull request 'main' (#20) from seonkyu.kim/AcaMate_Web:main into debug
All checks were successful
AcaMate_FO/pipeline/head This commit looks good

Reviewed-on: https://git.ipstein.myds.me/AcaMate/AcaMate_Web/pulls/20
This commit is contained in:
김선규 2025-06-05 08:08:20 +00:00
commit 6bb994d2dc
18 changed files with 670 additions and 44 deletions

View File

@ -31,6 +31,9 @@ builder.Services.AddScoped(sp => //new HttpClient
// SCOPED 으로 등록된 서비스는 DI 컨테이너에 등록된 서비스의 인스턴스를 사용합니다. // SCOPED 으로 등록된 서비스는 DI 컨테이너에 등록된 서비스의 인스턴스를 사용합니다.
builder.Services.AddScoped<APIService>(); builder.Services.AddScoped<APIService>();
builder.Services.AddScoped<CookieService>(); builder.Services.AddScoped<CookieService>();
// builder.Services.AddRazorPages();
// builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<LoadingService>();
await builder.Build().RunAsync(); await builder.Build().RunAsync();

View File

@ -1,31 +1,14 @@
@* @inherits LayoutComponentBase *@ @inherits LayoutComponentBase
@* *@ @implements IDisposable
@* *@
@* <div class="min-h-screen bg-gray-50 text-gray-900"> *@
@* <TopBanner /> *@
@* <TopNav /> *@
@* *@
@* <div class="flex flex-1"> *@
@* <SideNav /> *@
@* *@
@* <main class="flex-1 p-6"> *@
@* $1$ <!-- Body는 URL 뒤에 입력할 페이지에 따라서 그거에 맞는 @page를 찾아서 열어준다. --> #1# *@
@* @Body *@
@* </main> *@
@* </div> *@
@* *@
@* <FloatingButton /> *@
@* <BottomNav /> *@
@* <Footer/> *@
@* </div> *@
@inherits LayoutComponentBase
<div class="min-h-screen flex flex-col bg-gray-50 text-gray-900"> <div class="min-h-screen flex flex-col bg-gray-50 text-gray-900">
<!-- Top 영역 --> <!-- Top 영역 -->
@* <TopBanner /> *@ @* <TopBanner /> *@
<TopNav /> @if (!isHideTop)
{
<TopNav />
}
<!-- 본문 영역 --> <!-- 본문 영역 -->
<div class="flex flex-1 flex-col md:flex-row"> <div class="flex flex-1 flex-col md:flex-row">
@ -48,4 +31,13 @@
<!-- 하단 메뉴 --> <!-- 하단 메뉴 -->
<BottomNav /> <BottomNav />
<Footer /> <Footer />
@if (LoadingService.IsLoading)
{
<div class="fixed inset-0 bg-black/80 flex items-center justify-center z-50">
<div class="bg-gray-200/60 px-6 py-4 rounded-lg">
<div class="animate-spin h-8 w-8 border-4 border-gray-600 border-t-transparent rounded-full"></div>
</div>
</div>
}
</div> </div>

View File

@ -0,0 +1,49 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
using Front.Program.Views.Project;
using Front.Program.Services;
namespace Front.Program.Layout;
public partial class MainLayout : LayoutComponentBase, IDisposable
{
[Inject]
NavigationManager Navigation { get; set; } = default!;
[Inject]
LoadingService LoadingService { get; set; } = default!;
// protected bool isHideTop => Navigation.Uri.Contains("/auth");
protected bool isHideTop => Navigation.ToBaseRelativePath(Navigation.Uri).Equals("auth", StringComparison.OrdinalIgnoreCase);
public static bool IsLoading { get; set; }
public static IndicateType CurrentType { get; set; } = IndicateType.Page;
protected override void OnInitialized()
{
LoadingService.OnChange += StateHasChanged;
Navigation.LocationChanged += HandleLocationChanged;
}
private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
{
LoadingService.HideNavigationLoading();
}
public void Dispose()
{
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

@ -0,0 +1,42 @@
using Front.Program.Views.Project;
using Microsoft.AspNetCore.Components;
namespace Front.Program.Services;
public class LoadingService
{
public bool IsLoading { get; private set; }
public IndicateType CurrentType { get; private set; } = IndicateType.Page;
private bool isNavigationLoading { get; set; }
public event Action? OnChange;
public void ShowLoading(IndicateType type = IndicateType.Page, bool isNavigation = false)
{
IsLoading = true;
CurrentType = type;
isNavigationLoading = isNavigation;
NotifyStateChanged();
}
public void HideLoading()
{
if (!isNavigationLoading)
{
IsLoading = false;
NotifyStateChanged();
}
}
public void HideNavigationLoading()
{
if (isNavigationLoading)
{
IsLoading = false;
isNavigationLoading = false;
NotifyStateChanged();
}
}
private void NotifyStateChanged() => OnChange?.Invoke();
}

View File

@ -3,8 +3,10 @@
<div class="flex flex-col items-center justify-center min-h-screen bg-gray-100"> <div class="flex flex-col items-center justify-center min-h-screen bg-gray-100">
<h1 class="text-2xl font-bold mb-4">로그인</h1> <h1 class="text-2xl font-bold mb-4">로그인</h1>
<div class="w-full max-w-xs"> <div class="w-full max-w-xs">
<input type="text" placeholder="아이디" class="mb-4 p-2 border border-gray-300 rounded w-full" /> <button type="button" class="w-full mb-4 p-2 rounded focus:outline-none" @onclick="KakaoLogin">
<input type="password" placeholder="비밀번호" class="mb-4 p-2 border border-gray-300 rounded w-full" /> <img src="//k.kakaocdn.net/14/dn/btqCn0WEmI3/nijroPfbpCa4at5EIsjyf0/o.jpg"
<button @onclick = "KakaoLogin" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 w-full">로그인</button> alt="카카오 로그인"
class="rounded w-full transition duration-150 hover:brightness-90" />
</button>
</div> </div>
</div> </div>

View File

@ -1,3 +1,6 @@
using System.Net.Http.Json;
using System.Text.Json;
using Front.Program.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
@ -6,11 +9,21 @@ namespace Front.Program.Views.Project;
public partial class Auth : ComponentBase public partial class Auth : ComponentBase
{ {
[Inject] NavigationManager NavigationManager { get; set; } = default!; [Inject] NavigationManager NavigationManager { get; set; } = default!;
[Inject] IJSRuntime JS { get; set; } = default!; [Inject] LoadingService LoadingService { get; set; } = default!;
void KakaoLogin() // [Inject] IJSRuntime JS { get; set; } = default!;
// [Inject] CookieService Cookie { get; set; } = default!;
[Inject] HttpClient Http { get; set; } = default!;
public async Task KakaoLogin()
{ {
// await JS LoadingService.ShowLoading();
// Redirect to Kakao login page var url = "/api/v1/out/user/kakao/auth";
NavigationManager.NavigateTo("/api/v1/in/user/kakao/auth", true); var response = await Http.GetFromJsonAsync<JsonElement>(url);
var kakaoUrl = response.GetProperty("url").GetString();
Console.WriteLine(kakaoUrl);
if (!string.IsNullOrEmpty(kakaoUrl))
{
NavigationManager.NavigateTo(kakaoUrl, true);
}
} }
} }

View File

@ -0,0 +1,11 @@
<h3>PageIndicator</h3>
@if (Type == IndicateType.Page)
{
<div class="fixed top-0 left-0 w-full h-14 bg-black/70 flex items-center justify-center z-50">
<div class="bg-gray-200/80 px-4 py-2 rounded-lg">
<div class="animate-spin h-5 w-5 border-2 border-gray-600 border-t-transparent rounded-full"></div>
</div>
</div>
}

View File

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Components;
namespace Front.Program.Views.Project;
public partial class PageIndicator : ComponentBase
{
[Parameter]
public IndicateType Type { get; set; } = IndicateType.Page;
}
public enum IndicateType
{
Page,
Circle,
Progress
}

View File

@ -0,0 +1,128 @@
@page "/auth/register"
@inject IJSRuntime JSRuntime
<div class="relative flex size-full min-h-screen flex-col bg-white group/design-root overflow-x-hidden"
style='font-family: "Public Sans", "Noto Sans", sans-serif;'>
<div class="layout-container flex h-full grow flex-col">
<div class="flex flex-1 justify-center py-5 md:px-40">
<div class="layout-content-container flex flex-col w-full max-w-[480px] py-5 flex-1">
<h2 class="text-text-title tracking-light text-[28px] font-bold leading-tight px-4 text-center pb-3 pt-5">
회원가입
</h2>
<p class="text-red-600 font-normal text-xs text-right px-4">* 는 필수 사항입니다.</p>
<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">
이름
<span class="text-red-600">*</span>
</p>
<input
pattern="\d{16}"
maxlength="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"
/>
</label>
</div>
<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
/>
</label>
</div>
<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">E-Mail
<span class="text-red-600">*</span>
</p>
<input
placeholder="Enter your email"
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>
</div>
<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
maxlength="3"
pattern="\d{3}"
placeholder="010"
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="phonePart1"
inputmode="numeric"
autocomplete="off"
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
/>
<span class="self-center">-</span>
<input
maxlength="4"
pattern="\d{4}"
placeholder="1234"
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="phonePart2"
inputmode="numeric"
autocomplete="off"
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
/>
<span class="self-center">-</span>
<input
maxlength="4"
pattern="\d{4}"
placeholder="5678"
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="phonePart3"
inputmode="numeric"
autocomplete="off"
oninput="this.value = this.value.replace(/[^0-9]/g, '')"
/>
</div>
</label>
</div>
<div class="flex flex-col w-full gap-4 px-4 py-3">
<label class="flex flex-col w-full text-text-title text-base font-medium leading-normal">
주소
</label>
<input
placeholder="눌러서 주소를 선택하세요."
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-12 placeholder:text-[#6a7581] p-[12px] text-base font-normal leading-normal"
@onclick="OnClickAddress"
@bind-value="address"
readonly
/>
@if (address != "")
{
<input
placeholder="상세주소를 입력하세요."
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-12 placeholder:text-[#6a7581] p-[12px] text-base font-normal leading-normal"
@bind-value="detailAddress"
/>
}
</div>
<div class="flex px-4 py-3">
<button
@onclick="ConfirmData"
class="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-xl h-10 px-4 flex-1 bg-second-normal hover:bg-second-dark text-white text-sm font-bold leading-normal tracking-[0.015em]">
<span class="truncate">회원가입</span>
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,260 @@
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Text.Json;
using System.Net.Http.Json;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Front.Program.Layout;
namespace Front.Program.Views.Project;
public partial class Register : ComponentBase
{
[Inject] IJSRuntime JS { get; set; }
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
[Inject] private HttpClient Http { get; set; } = default!;
[Inject] private IConfiguration Configuration { get; set; } = default!;
private ElementReference dateInputRef;
private string name = "";
private DateTime? birth;
private string email = "";
private string phone = "";
private string address = "";
private string detailAddress = "";
private string snsId = "";
private string phonePart1 = "";
private string phonePart2 = "";
private string phonePart3 = "";
protected override async Task OnInitializedAsync()
{
objRef = DotNetObjectReference.Create(this);
try
{
// 쿠키에서 토큰 가져오기
var token = await JS.InvokeAsync<string>("eval", "document.cookie.split('; ').find(row => row.startsWith('Web_AM_Connect_Key='))?.split('=')[1]");
if (string.IsNullOrEmpty(token))
{
await JS.InvokeVoidAsync("alert", "인증 정보가 없습니다.");
NavigationManager.NavigateTo("/");
return;
}
// AppController를 통해 세션에서 snsId 가져오기
var request = new HttpRequestMessage(HttpMethod.Get, $"/api/v1/in/app/session/get?key=snsId");
request.Headers.Add("Web_AM_Connect_Key", token);
var response = await Http.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadFromJsonAsync<JsonElement>();
Console.WriteLine($"세션 응답: {content}");
if (content.TryGetProperty("status", out var statusElement) &&
statusElement.TryGetProperty("code", out var codeElement) &&
codeElement.GetString() == "000")
{
if (content.TryGetProperty("data", out var dataElement) &&
dataElement.TryGetProperty("data", out var snsIdElement))
{
snsId = snsIdElement.GetString() ?? "";
Console.WriteLine($"서버 세션에서 가져온 SNS ID: {snsId}");
}
}
else
{
Console.WriteLine($"세션 데이터 가져오기 실패: {content}");
}
}
else
{
Console.WriteLine($"세션 API 호출 실패: {response.StatusCode}");
}
if (string.IsNullOrEmpty(snsId))
{
Console.WriteLine("SNS ID가 없습니다.");
await JS.InvokeVoidAsync("alert", "잘못된 접근입니다.");
NavigationManager.NavigateTo("/");
}
}
catch (Exception ex)
{
Console.WriteLine($"SNS ID 가져오기 실패: {ex.Message}");
await JS.InvokeVoidAsync("alert", "세션 정보를 가져오는데 실패했습니다.");
NavigationManager.NavigateTo("/");
}
}
private async Task ConfirmData()
{
if (string.IsNullOrWhiteSpace(name))
{
await JS.InvokeVoidAsync("alert", "이름을 입력해주세요.");
return;
}
if (string.IsNullOrWhiteSpace(email))
{
await JS.InvokeVoidAsync("alert", "이메일을 입력해주세요.");
return;
}
// 전화번호 조합
if (phonePart1.Length == 3 && phonePart2.Length == 4 && phonePart3.Length == 4)
{
phone = $"{phonePart1}-{phonePart2}-{phonePart3}";
}
else
{
await JS.InvokeVoidAsync("alert", "전화번호를 올바르게 입력해주세요.");
return;
}
var registerData = new
{
name = name,
birth = birth,
email = email,
phone = phone,
address = address,
sns_id = snsId,
sns_type = "ST01",
type = "UT05",
device_id = "",
auto_login_yn = false,
login_date = DateTime.Now,
push_token = "",
location_yn = false,
camera_yn = false,
photo_yn = false,
push_yn = false,
market_app_yn = false,
market_sms_yn = false,
market_email_yn = false
};
try
{
MainLayout.ShowLoading();
// 쿠키에서 토큰 가져오기
var token = await JS.InvokeAsync<string>("eval", "document.cookie.split('; ').find(row => row.startsWith('Web_AM_Connect_Key='))?.split('=')[1] || ''");
Console.WriteLine($"쿠키에서 가져온 토큰: '{token}'");
if (string.IsNullOrEmpty(token))
{
await JS.InvokeVoidAsync("alert", "인증 정보가 없습니다.");
NavigationManager.NavigateTo("/");
MainLayout.HideLoading();
return;
}
var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/in/user/register");
request.Headers.Add("Web_AM_Connect_Key", token);
Console.WriteLine($"요청 헤더: {string.Join(", ", request.Headers.Select(h => $"{h.Key}: {string.Join(", ", h.Value)}"))}");
request.Content = JsonContent.Create(registerData);
var response = await Http.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
var status = result.GetProperty("status");
var code = status.GetProperty("code").GetString();
var message = status.GetProperty("message").GetString();
if (code == "000")
{
var data = result.GetProperty("data");
var newToken = data.GetProperty("token").GetString();
var refresh = data.GetProperty("refresh").GetString();
// 서버 세션에 토큰 저장
var sessionReq = new HttpRequestMessage(HttpMethod.Post, "/api/v1/in/app/session/set");
sessionReq.Headers.Add("Web_AM_Connect_Key", token);
sessionReq.Content = JsonContent.Create(new[] {
new { key = "token", value = newToken },
new { key = "refresh", value = refresh }
});
var sessionResponse = await Http.SendAsync(sessionReq);
// 세션 스토리지 정리
await JS.InvokeVoidAsync("sessionStorage.removeItem", "snsId");
MainLayout.HideLoading();
await JS.InvokeVoidAsync("alert", "회원가입이 완료되었습니다.");
NavigationManager.NavigateTo("/");
}
else
{
MainLayout.HideLoading();
await JS.InvokeVoidAsync("alert", $"회원가입 실패: {message}");
}
}
else
{
MainLayout.HideLoading();
Console.WriteLine($"API 호출 실패: {response.StatusCode}");
var errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"에러 내용: {errorContent}");
await JS.InvokeVoidAsync("alert", "서버 오류가 발생했습니다.");
}
}
catch (Exception ex)
{
MainLayout.HideLoading();
Console.WriteLine($"예외 발생: {ex.Message}");
await JS.InvokeVoidAsync("alert", $"오류가 발생했습니다: {ex.Message}");
}
}
private void SubmitPhone()
{
if (phonePart1.Length == 3 && phonePart2.Length == 4 && phonePart3.Length == 4)
{
var fullPhone = $"{phonePart1}-{phonePart2}-{phonePart3}";
Console.WriteLine($"입력된 전화번호: {fullPhone}");
}
else
{
Console.WriteLine("전화번호를 올바르게 입력해주세요.");
}
}
private async Task OpenDatePicker()
{
if (birth == null) birth = DateTime.Now;
await JS.InvokeVoidAsync("openDatePicker", dateInputRef);
}
private void OnBirthChanged(ChangeEventArgs e)
{
Console.WriteLine($"선택된 생일: {birth}");
}
private DotNetObjectReference<Register> objRef;
protected override void OnInitialized()
{
objRef = DotNetObjectReference.Create(this);
}
protected async Task OnClickAddress()
{
await JS.InvokeVoidAsync("openKakaoPostcodePopup", objRef);
}
[JSInvokable]
public void SetAddress(string roadAddress, string jibunAddress, string zonecode)
{
address = roadAddress;
Console.WriteLine($"SetAddress 호출됨: {roadAddress}, {jibunAddress}, {zonecode}");
StateHasChanged();
}
}

View File

@ -7,18 +7,17 @@
<div class="hidden md:flex flex-1 justify-end gap-8"> <div class="hidden md:flex flex-1 justify-end gap-8">
<div class="flex flex-1 justify-end gap-8"> <div class="flex flex-1 justify-end gap-8">
<div class="flex items-center gap-9"> <div class="flex items-center gap-9">
<a class="text-text-title font-medium leading-normal hover:text-blue-800" href="/about" @onclick="() => isOpen = !isOpen">About</a> <a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/about" @onclick="() => isOpen = !isOpen">About</a>
<a class="text-text-title font-medium leading-normal hover:text-blue-800" href="/join" @onclick="() => isOpen = !isOpen">Join</a> <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-800" href="/new" @onclick="() => isOpen = !isOpen">What's New</a> <a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/new" @onclick="() => isOpen = !isOpen">What's New</a>
</div> </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-blue-500 hover:bg-blue-800 text-white text-sm font-bold leading-normal tracking-[0.015em] mr-4" <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"> @onclick="OnClickLogin">
<span class="truncate">Get Started</span> <span class="truncate">시작하기</span>
</button> </button>
</div> </div>
</div> </div>
<button class="md:hidden mr-4" @onclick="OnClickMenuDown"> <button class="md:hidden mr-4" @onclick="OnClickMenuDown">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <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" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
@ -29,13 +28,13 @@
@if (isOpen) @if (isOpen)
{ {
<div class="md:hidden absolute top-16 left-0 w-full bg-white shadow z-50 transition-all duration-300"> <div class="md:hidden absolute top-16 left-0 w-full bg-white shadow z-50 transition-all duration-300">
<div class="flex flex-col items-start gap-4 p-4"> <div class="flex flex-col items-start gap-4 p-4 text-center">
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-800" 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="/about" @onclick="() => isOpen = !isOpen">About</a>
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-800" 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="/join" @onclick="() => isOpen = !isOpen">Join</a>
<a class="block w-full gap-y-2 text-text-title font-medium leading-normal hover:text-blue-800" href="/new" @onclick="() => isOpen = !isOpen">What's New</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-blue-500 hover:bg-blue-800 text-white text-sm font-bold leading-normal tracking-[0.015em]" <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"> @onclick="OnClickLogin">
<span class="truncate">Get Started</span> <span class="truncate">시작하기</span>
</button> </button>
</div> </div>
</div> </div>

File diff suppressed because one or more lines are too long

View File

@ -32,6 +32,12 @@
<button class="ml-4 text-red-600 font-bold dismiss"></button> <button class="ml-4 text-red-600 font-bold dismiss"></button>
</div> </div>
<script src="scripts/scroll.js"></script>
<script src="scripts/apiSender.js"></script>
<script src="scripts/jsCommonFunc.js"></script>
<script src="scripts/kakao-postcode.js"></script>
<!-- Blazor WASM 로딩 스크립트 --> <!-- Blazor WASM 로딩 스크립트 -->
<script src="_framework/blazor.webassembly.js" autostart="false"></script> <script src="_framework/blazor.webassembly.js" autostart="false"></script>
@ -69,5 +75,9 @@
document.cookie = name + '=; Max-Age=-99999999;'; document.cookie = name + '=; Max-Age=-99999999;';
}; };
</script> </script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<title>주소검색</title>
<script src="https://spi.maps.daum.net/imap/map_js_init/postcode.v2.js"></script>
<script>
window.onload = function() {
var layer = document.getElementById('layer');
new daum.Postcode({
oncomplete: function(data) {
var jibunAddress = data.jibunAddress || data.autoJibunAddress || "";
var postData = {
roadAddress: data.roadAddress,
jibunAddress: jibunAddress,
zonecode: data.zonecode,
};
window.opener.postMessage({
roadAddress: data.roadAddress,
jibunAddress: data.jibunAddress,
zonecode: data.zonecode
}, window.location.origin);
window.close();
},
width: "100%",
height: "100%"
}).embed(layer);
layer.style.display = "block";
};
</script>
<style>
html, body { margin:0; padding:0; width:100vw; height:100vh; background:#fff; }
#layer { width:100vw; height:100vh; }
</style>
</head>
<body>
<div id="layer"></div>
</body>
</html>

View File

@ -0,0 +1,23 @@
window.postWithHeader = function(url, method, headerKey, headerValue) {
fetch(url, {
method: method,
headers: {
[headerKey] : headerValue
}
}).then(res => {
if (res.redirected) {
window.location.href = res.url;
}
});
};
window.fetchWithHeaderAndReturnUrl = async function(url, method, headerKey, headerValue) {
const response = await fetch(url, {
method: method,
headers: {
[headerKey]: headerValue
}
});
const data = await response.json();
return data.url;
};

View File

@ -0,0 +1,8 @@
window.openDatePicker = function (element) {
if (element) {
const wasReadOnly = element.readOnly;
if (wasReadOnly) element.readOnly = false;
element.showPicker();
if (wasReadOnly) setTimeout(() => element.readOnly = true, 0);
}
};

View File

@ -0,0 +1,28 @@
window.addEventListener("message", function (event) {
if (event.origin !== window.location.origin) return;
if (event.data && event.data.roadAddress) {
if (window.kakaoPostcodeDotNetRef) {
window.kakaoPostcodeDotNetRef.invokeMethodAsync(
"SetAddress",
event.data.roadAddress,
event.data.jibunAddress,
event.data.zonecode
);
}
}
});
window.getKakaoPostcodeResult = function () {
return window.kakaoPostcodeResult;
};
window.openKakaoPostcodePopup = function (dotNetObjRef) {
window.kakaoPostcodeResult = null;
window.kakaoPostcodeDotNetRef = dotNetObjRef;
window.open(
"/kakao-postcode.html",
"kakaoPostcodePopup",
"width=500,height=600,scrollbars=no,resizable=no"
);
};

View File

@ -0,0 +1,3 @@
window.scrollToDown = function (y = 0, behavior = "smooth") {
window.scrollTo({ top: y, behavior: behavior });
};