[✨] 인트로 아카데미 리스트 로직 추가 및 API 로직 변경
|
@ -2,10 +2,8 @@
|
|||
@implements IDisposable
|
||||
|
||||
<div class="min-h-screen flex flex-col bg-gray-50 text-gray-900">
|
||||
|
||||
<!-- Top 영역 -->
|
||||
@* <TopBanner /> *@
|
||||
|
||||
@if(isAcademy)
|
||||
{
|
||||
<div class="flex flex-1 flex-col md:flex-row">
|
||||
|
@ -52,7 +50,7 @@
|
|||
|
||||
@if (LoadingService.IsLoading)
|
||||
{
|
||||
<div class="fixed inset-0 bg-black/80 flex items-center justify-center z-50">
|
||||
<div class="fixed inset-0 bg-black/30 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>
|
||||
|
|
|
@ -16,12 +16,14 @@ public partial class MainLayout : LayoutComponentBase, IDisposable
|
|||
|
||||
// 경로의 시작 부분
|
||||
// protected bool isHidePrjTop => Navigation.ToBaseRelativePath(Navigation.Uri).StartsWith("auth", StringComparison.OrdinalIgnoreCase);
|
||||
protected bool isIntro => Navigation.ToBaseRelativePath(Navigation.Uri).StartsWith("am/intro", StringComparison.OrdinalIgnoreCase);
|
||||
// 경로의 끝 부분
|
||||
protected bool isHidePrjTop => Navigation.ToBaseRelativePath(Navigation.Uri).EndsWith("auth", StringComparison.OrdinalIgnoreCase);
|
||||
protected bool isAcademy => Navigation.ToBaseRelativePath(Navigation.Uri).StartsWith("am", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
// 경로 일치
|
||||
protected bool isIntro => Navigation.ToBaseRelativePath(Navigation.Uri).Equals("am/intro", StringComparison.OrdinalIgnoreCase);
|
||||
// protected bool isIntro => Navigation.ToBaseRelativePath(Navigation.Uri).Equals("am/intro", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
|
|
|
@ -18,5 +18,5 @@ public class Academy
|
|||
public class SimpleAcademy
|
||||
{
|
||||
public required string bid { get; set; }
|
||||
public string business_name { get; set; } = string.Empty;
|
||||
public string name { get; set; }
|
||||
}
|
|
@ -1,19 +1,18 @@
|
|||
using System.Net.Http.Json;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Front.Program.Models;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Front.Program.Services;
|
||||
|
||||
public class APIService
|
||||
public class APIService(HttpClient http,
|
||||
StorageService storageService,
|
||||
SecureService secureService,
|
||||
IJSRuntime js)
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
|
||||
|
||||
public APIService(HttpClient http)
|
||||
{
|
||||
_http = http;
|
||||
}
|
||||
|
||||
|
||||
private string ChangeToString<T>(T data)
|
||||
{
|
||||
if (data == null) return string.Empty;
|
||||
|
@ -25,9 +24,60 @@ public class APIService
|
|||
public async Task<APIResponseStatus<TResponse>?> GetJsonAsync<TResponse,TRequest>(string url, TRequest value)
|
||||
{
|
||||
string parameter = ChangeToString(value);
|
||||
var response = await _http.GetFromJsonAsync<APIResponseStatus<TResponse>>($"{url}?{parameter}");
|
||||
var response = await http.GetFromJsonAsync<APIResponseStatus<TResponse>>($"{url}?{parameter}");
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<(bool success,T? data)> GetConnectServerAsnyc<T>(string url) {
|
||||
var headerValue = await storageService.GetItemAsync("Web-AM-Connect-Key");
|
||||
if (string.IsNullOrEmpty(headerValue)) return (false, default);
|
||||
|
||||
var args = new {
|
||||
url = $"{url}",
|
||||
method = "GET",
|
||||
headerKey = "Web-AM-Connect-Key",
|
||||
headerValue = headerValue,
|
||||
token = "VO00"
|
||||
};
|
||||
|
||||
var response = await js.InvokeAsync<JsonElement>(
|
||||
"fetchWithHeaderAndReturnUrl",
|
||||
args
|
||||
);
|
||||
|
||||
Console.WriteLine($"JSON 응답: {response.ToString()}");
|
||||
|
||||
if (response.TryGetProperty("data", out var dataElement))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 전체 데이터 암호화 저장
|
||||
var dataJson = dataElement.ToString();
|
||||
var serialData = JsonSerializer.Deserialize<T>(dataJson,
|
||||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
Console.WriteLine("[GetConnectServerAsnyc] 사용자 데이터 Json: " + dataJson);
|
||||
Console.WriteLine("[GetConnectServerAsnyc] 사용자 데이터 변환: " + serialData.ToString());;
|
||||
|
||||
if (serialData != null)
|
||||
{
|
||||
var encryptedData = await secureService.EncryptAsync(dataJson);
|
||||
await storageService.SetItemAsync("USER_DATA", encryptedData);
|
||||
return (true, serialData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("사용자 데이터에 필수 정보가 없습니다");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"사용자 데이터 처리 중 오류: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("데이터를 찾을 수 없습니다");
|
||||
return (false, default);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
using Front.Program.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Front.Program.Services;
|
||||
// 뷰를 참조 안하고도 로딩 상태를 알 수 있게 바꾸기
|
||||
|
||||
public class LoadingService
|
||||
{
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
public bool IsLoading { get; private set; }
|
||||
private bool isNavigationLoading { get; set; }
|
||||
|
||||
|
@ -40,4 +42,29 @@ public class LoadingService
|
|||
}
|
||||
|
||||
private void NotifyStateChanged() => OnChange?.Invoke();
|
||||
|
||||
|
||||
public LoadingService(IJSRuntime jsRuntime)
|
||||
{
|
||||
_jsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public async Task ShowLoadingAsync(bool isNavigation = false)
|
||||
{
|
||||
IsLoading = true;
|
||||
isNavigationLoading = isNavigation;
|
||||
NotifyStateChanged();
|
||||
await _jsRuntime.InvokeVoidAsync("setBodyOverflowHidden", true);
|
||||
}
|
||||
|
||||
public async Task HideLoadingAsync()
|
||||
{
|
||||
if (!isNavigationLoading)
|
||||
{
|
||||
IsLoading = false;
|
||||
NotifyStateChanged();
|
||||
await _jsRuntime.InvokeVoidAsync("setBodyOverflowHidden", false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -5,12 +5,14 @@ using Microsoft.JSInterop;
|
|||
|
||||
namespace Front.Program.ViewModels;
|
||||
|
||||
public class UserStateService(StorageService _storageService,SecureService _secureService, IJSRuntime _js)
|
||||
public class UserStateService(StorageService _storageService,SecureService _secureService, APIService _APIService,
|
||||
IJSRuntime _js)
|
||||
{
|
||||
public UserData UserData { get; set; } = new UserData();
|
||||
|
||||
public bool isFirstCheck { get; set; } = false;
|
||||
public bool isLogin { get; set; } = false;
|
||||
public Models.SimpleAcademy[] academyItems = Array.Empty<Models.SimpleAcademy>();
|
||||
|
||||
|
||||
public async Task<(bool success, UserData? userData)> GetUserDataFromStorageAsync()
|
||||
|
@ -46,59 +48,7 @@ public class UserStateService(StorageService _storageService,SecureService _secu
|
|||
|
||||
public async Task<(bool success, UserData? userData)> GetUserDataFromServerAsync()
|
||||
{
|
||||
|
||||
var headerValue = await _storageService.GetItemAsync("Web-AM-Connect-Key");
|
||||
if (string.IsNullOrEmpty(headerValue)) return (false, null);
|
||||
|
||||
var args = new {
|
||||
url = "/api/v1/in/user",
|
||||
method = "GET",
|
||||
headerKey = "Web-AM-Connect-Key",
|
||||
headerValue = headerValue,
|
||||
token = "VO00"
|
||||
};
|
||||
|
||||
var response = await _js.InvokeAsync<JsonElement>(
|
||||
"fetchWithHeaderAndReturnUrl",
|
||||
args
|
||||
);
|
||||
|
||||
// var response = await _js.InvokeAsync<JsonElement>("fetchWithHeaderAndReturnUrl",
|
||||
// "/api/v1/in/user/",
|
||||
// "GET",
|
||||
// "Web_AM_Connect_Key",
|
||||
// headerValue);
|
||||
|
||||
Console.WriteLine($"JSON 응답: {response.ToString()}");
|
||||
|
||||
if (response.TryGetProperty("data", out var dataElement))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 전체 데이터 암호화 저장
|
||||
var userDataJson = dataElement.ToString();
|
||||
var userData = JsonSerializer.Deserialize<UserData>(userDataJson,
|
||||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
|
||||
if (userData != null && !string.IsNullOrEmpty(userData.Name))
|
||||
{
|
||||
var encryptedData = await _secureService.EncryptAsync(userDataJson);
|
||||
await _storageService.SetItemAsync("USER_DATA", encryptedData);
|
||||
return (true, userData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("사용자 데이터에 필수 정보가 없습니다");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"사용자 데이터 처리 중 오류: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("사용자 데이터를 찾을 수 없습니다");
|
||||
return (false, null);
|
||||
return await _APIService.GetConnectServerAsnyc<UserData>("/api/v1/in/user");
|
||||
}
|
||||
|
||||
public async Task<bool> GetUserDataAsync()
|
||||
|
@ -163,5 +113,38 @@ public class UserStateService(StorageService _storageService,SecureService _secu
|
|||
// return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task<(bool success, List<SimpleAcademy>? simpleAcademy)> GetAcademy()
|
||||
{
|
||||
return await _APIService.GetConnectServerAsnyc<List<SimpleAcademy>>("/api/v1/in/user/academy");
|
||||
}
|
||||
//
|
||||
// var headerValue = await _storageService.GetItemAsync("Web-AM-Connect-Key");
|
||||
// if (string.IsNullOrEmpty(headerValue)) return (false, null);
|
||||
//
|
||||
// var args = new {
|
||||
// url = "/api/v1/in/user/academy",
|
||||
// method = "GET",
|
||||
// headerKey = "Web-AM-Connect-Key",
|
||||
// headerValue = headerValue,
|
||||
// token = "VO00"
|
||||
// };
|
||||
//
|
||||
// var response = await _js.InvokeAsync<JsonElement>(
|
||||
// "fetchWithHeaderAndReturnUrl",
|
||||
// args
|
||||
// );
|
||||
//
|
||||
// Console.WriteLine($"JSON 응답: {response.ToString()}");
|
||||
//
|
||||
// if (response.TryGetProperty("data", out var dataElement))
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// // 전체 데이터 암호화 저장
|
||||
// var userDataJson = dataElement.ToString();
|
||||
// var userData = JsonSerializer.Deserialize<UserData>(userDataJson,
|
||||
// new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
<div class="container pt-24">
|
||||
<div class="px-4 md:px-8 py-4">
|
||||
<div class="w-full bg-center bg-no-repeat bg-contain md:bg-contain flex flex-col justify-center items-center overflow-hidden bg-white/35 rounded-xl min-h-[218px] md:h-[300px]"
|
||||
style="background-image: url('/Resources/Images/Logo/Crystal_Icon.png');">
|
||||
style="background-image: url('Resources/Images/Logo/Crystal_Icon.png');">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -33,12 +33,12 @@
|
|||
학원을 선택해주세요.<br />
|
||||
</h3>
|
||||
<div class="max-h-[180px] overflow-y-auto rounded-xl bg-second-normal/10 border-2 border-text-detail">
|
||||
@foreach (var academy in academyItems)
|
||||
@foreach (var academy in UserStateService.academyItems)
|
||||
{
|
||||
<a href="/am/@academy.bid"
|
||||
<a href="/am/main?@academy.bid"
|
||||
class="block w-full px-4 py-3 hover:bg-second-dark border-b border-gray-200 last:border-b-0 group">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-text-black group-hover:text-text-white font-medium">@academy.business_name</span>
|
||||
<span class="text-text-black group-hover:text-text-white font-medium">@academy.name</span>
|
||||
<svg class="w-5 h-5 text-text-detail group-hover:text-text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
|
|
|
@ -12,31 +12,61 @@ public partial class AcademyIntro : ComponentBase, IDisposable
|
|||
[Inject] StorageService StorageService { get; set; } = default!;
|
||||
[Inject] QueryParamService QueryParamService { get; set; } = default!;
|
||||
[Inject] UserStateService UserStateService { get; set; } = default!;
|
||||
|
||||
[Inject] LoadingService LoadingService { get; set; } = default!;
|
||||
|
||||
private bool _isProcessing = false;
|
||||
|
||||
|
||||
protected Models.SimpleAcademy[] academyItems = Array.Empty<Models.SimpleAcademy>();
|
||||
|
||||
|
||||
protected override async void OnInitialized()
|
||||
{
|
||||
Navigation.LocationChanged += HandleLocationChanged;
|
||||
HandleLocationChanged(this, new LocationChangedEventArgs(Navigation.Uri, false));
|
||||
if (!UserStateService.isFirstCheck) await UserStateService.GetUserDataAsync();
|
||||
|
||||
academyItems = new[]
|
||||
try
|
||||
{
|
||||
new SimpleAcademy{ bid = "AA0000", business_name = "테스트 학원1"},
|
||||
new SimpleAcademy{ bid = "AA0001", business_name = "테스트 학원2"},
|
||||
new SimpleAcademy{ bid = "AA0002", business_name = "테스트 학원3"},
|
||||
new SimpleAcademy{ bid = "AA0003", business_name = "테스트 학원4"},
|
||||
new SimpleAcademy{ bid = "AA0004", business_name = "테스트 학원5"},
|
||||
new SimpleAcademy{ bid = "AA0005", business_name = "테스트 학원6"},
|
||||
new SimpleAcademy{ bid = "AA0006", business_name = "테스트 학원7"},
|
||||
|
||||
};
|
||||
Navigation.LocationChanged += HandleLocationChanged;
|
||||
HandleLocationChanged(this, new LocationChangedEventArgs(Navigation.Uri, false));
|
||||
if (!UserStateService.isFirstCheck)
|
||||
{
|
||||
LoadingService.ShowLoading();
|
||||
await UserStateService.GetUserDataAsync();
|
||||
var aca = await UserStateService.GetAcademy();
|
||||
|
||||
if (aca.success)
|
||||
{
|
||||
if (aca.simpleAcademy.Count > 0)
|
||||
{
|
||||
UserStateService.academyItems = aca.simpleAcademy.ToArray();
|
||||
Console.WriteLine($"academyItems: {UserStateService.academyItems.Length}개");
|
||||
}
|
||||
|
||||
Console.WriteLine("아카데미 정보가 없습니다. 로그인 상태를 확인해주세요.");
|
||||
}
|
||||
|
||||
Console.WriteLine($"academy: {string.Join(", ", UserStateService.academyItems.Select(a => a.name))}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
LoadingService.HideLoading();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
// 유저 값 가져오면서 같이 academy 정보도 가져와야지
|
||||
|
||||
|
||||
|
||||
// academyItems = new[]
|
||||
// {
|
||||
// new SimpleAcademy{ bid = "AA0000", business_name = "테스트 학원1"},
|
||||
// new SimpleAcademy{ bid = "AA0001", business_name = "테스트 학원2"},
|
||||
// new SimpleAcademy{ bid = "AA0002", business_name = "테스트 학원3"},
|
||||
// new SimpleAcademy{ bid = "AA0003", business_name = "테스트 학원4"},
|
||||
// new SimpleAcademy{ bid = "AA0004", business_name = "테스트 학원5"},
|
||||
// new SimpleAcademy{ bid = "AA0005", business_name = "테스트 학원6"},
|
||||
// new SimpleAcademy{ bid = "AA0006", business_name = "테스트 학원7"},
|
||||
|
||||
// };
|
||||
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
@ -58,16 +88,30 @@ public partial class AcademyIntro : ComponentBase, IDisposable
|
|||
// 쿼리 파라미터가 있는 경우에만 처리
|
||||
if (!string.IsNullOrEmpty(uri.Query))
|
||||
{
|
||||
LoadingService.ShowLoading();
|
||||
var queryParam = QueryParamService.ParseQueryParam(uri);
|
||||
await QueryParamService.AuthCheck(queryParam, StorageService);
|
||||
|
||||
// 유저 정보 확인하는거 (로그인 했으니 값 가져와야지)
|
||||
await UserStateService.GetUserDataAsync();
|
||||
var aca = await UserStateService.GetAcademy();
|
||||
|
||||
// 쿼리 파라미터를 제거한 기본 URI로 리다이렉트
|
||||
if (aca.success)
|
||||
{
|
||||
if (aca.simpleAcademy.Count > 0)
|
||||
{
|
||||
UserStateService.academyItems = aca.simpleAcademy.ToArray();
|
||||
|
||||
Console.WriteLine($"academyItems: {UserStateService.academyItems.Length}개");
|
||||
}
|
||||
Console.WriteLine("아카데미 정보가 없습니다. 로그인 상태를 확인해주세요.");
|
||||
}
|
||||
Console.WriteLine($"academy: {string.Join(", ", UserStateService.academyItems.Select(a => a.name))}");
|
||||
|
||||
// // 쿼리 파라미터를 제거한 기본 URI로 리다이렉트
|
||||
var baseUri = uri.GetLeftPart(UriPartial.Path);
|
||||
Console.WriteLine($"리다이렉트할 URI: {baseUri}");
|
||||
await InvokeAsync(StateHasChanged); // StateHasChanged를 호출하여 UI 업데이트
|
||||
// await InvokeAsync(StateHasChanged); // StateHasChanged를 호출하여 UI 업데이트
|
||||
Navigation.NavigateTo(baseUri, forceLoad: false);
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +121,13 @@ public partial class AcademyIntro : ComponentBase, IDisposable
|
|||
}
|
||||
finally
|
||||
{
|
||||
LoadingService.HideLoading();
|
||||
_isProcessing = false;
|
||||
|
||||
// var baseUri = uri.GetLeftPart(UriPartial.Path);
|
||||
// Console.WriteLine($"리다이렉트할 URI: {baseUri}");
|
||||
// await InvokeAsync(StateHasChanged); // StateHasChanged를 호출하여 UI 업데이트
|
||||
// Navigation.NavigateTo(baseUri, forceLoad: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
6
Program/Views/Academy/AcademyMain.razor
Normal file
|
@ -0,0 +1,6 @@
|
|||
@page "/am/main"
|
||||
<h3>AcademyMain</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
7
Program/Views/Academy/AcademyMain.razor.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Front.Program.Views.Academy;
|
||||
|
||||
public partial class AcademyMain : ComponentBase
|
||||
{
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
@foreach (var academy in academyItems)
|
||||
{
|
||||
<a href="/am/@academy.bid" class="block px-4 py-2 text-text-title hover:bg-gray-100">
|
||||
@academy.business_name
|
||||
@academy.name
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
|
@ -26,10 +26,18 @@
|
|||
}
|
||||
</div>
|
||||
|
||||
<div class="hidden md:flex flex-1 justify-end gap-8">
|
||||
<div class="hidden md:flex flex-1 justify-end gap-8 mr-4">
|
||||
<div class="flex flex-1 justify-end gap-8">
|
||||
<div class="flex items-center gap-9">
|
||||
<a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/about">About</a>
|
||||
<div class="flex items-center gap-6">
|
||||
<img src="Resources/Images/Icon/Notification_SET.png" alt="알림" class="w-6 h-6 object-cover" />
|
||||
<img src="Resources/Images/Icon/Setting.png" alt="설정" class="w-6 h-6 object-cover"/>
|
||||
<img src="Resources/Images/Icon/Link.png" alt="바로가기" class="w-6 h-6 object-cover"/>
|
||||
<img src="Resources/Images/Icon/Logout.png" alt="로그아웃" class="w-6 h-6 object-cover"/>
|
||||
@* <div class="w-10 h-10 rounded-full bg-gray-200 overflow-hidden"> *@
|
||||
@* <img src="Resources/Images/Icon/Logout.png" alt="로그아웃" class="w-full h-full object-cover"/> *@
|
||||
@* <a class="text-text-title font-medium leading-normal hover:text-blue-600" href="/about">About</a> *@
|
||||
@* </div> *@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
BIN
wwwroot/Resources/Images/Icon/DOT3.png
Normal file
After Width: | Height: | Size: 450 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
wwwroot/Resources/Images/Icon/Link.png
Normal file
After Width: | Height: | Size: 912 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 945 B After Width: | Height: | Size: 945 B |