From f98405b791f077e74fb2cbcc60ed46cc7b804346 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Tue, 30 Jun 2026 21:59:22 +0900 Subject: [PATCH] feat: revamp UI/UX of homepage & portal, clean warnings, and update ROADMAP_WBS --- ROADMAP_WBS.md | 43 +++++ .../Components/Admin/Pages/Dashboard.razor | 9 + TaxBaik.Web/Pages/Portal/Index.cshtml | 12 +- TaxBaik.Web/Pages/_Layout.cshtml | 2 +- .../CustomAuthenticationStateProvider.cs | 6 +- TaxBaik.Web/wwwroot/css/site.css | 159 ++++++++++++++++++ 6 files changed, 219 insertions(+), 12 deletions(-) diff --git a/ROADMAP_WBS.md b/ROADMAP_WBS.md index 17fd147..a4ec6fb 100644 --- a/ROADMAP_WBS.md +++ b/ROADMAP_WBS.md @@ -522,3 +522,46 @@ Todo: - WBS-UX-03/04 구현 완료 - WBS-CRM-01/02/03/04/05 구현 완료 (배포 후 검증 필요) - WBS-CRM-06/07/08 (텔레그램·포털·소셜 로그인) Phase 3 미착수 + +--- + +## ── 홈페이지 · 어드민 · 포털 프리미엄 UX/UI 개편 (2026-06-30) ────────────────── + +## WBS-UX-05 홈페이지 프리미엄 UI 및 마이크로 인터랙션 + +목표: 홈페이지 디자인을 극도로 모던하고 신뢰성 있는 프리미엄 스타일로 전면 개편한다. + +성공 기준: +- Hero 섹션에 유려한 배경 그라데이션 및 부드러운 CSS 애니메이션 효과 적용 +- 서비스 카드에 섀도우 및 보더 트랜지션, 골드/그린 그라데이션 호버 이펙트 추가 +- 신뢰도 스트립 카드에 입체감 및 돋보이는 레이아웃 설계 +- Noto Sans KR 외에 Outfit/Inter 등의 보조 영문 폰트 결합으로 타이포그래피 고급화 + +Todo: +- [x] `site.css` 내 Hero 섹션 그라데이션 및 CSS 애니메이션 보강 +- [x] 서비스 카드 및 신뢰도 스트립 컴포넌트 프리미엄 스타일로 개편 +- [x] 홈페이지 폰트 스택 확장 및 메인 레이아웃 적용 + +## WBS-PORTAL-01 고객 포털 UI/UX 고도화 및 글래스모피즘 + +목표: 고객 마이 포털 화면을 미려하고 현대적인 글래스모피즘 디자인으로 개편하여 이용 가치를 극대화한다. + +성공 기준: +- 포털 메인 대시보드 카드를 Glassmorphism 스타일(blur, semi-transparent border)로 변경 +- 세무 신고 현황 테이블 및 상담 이력 타임라인 컴포넌트의 모던 디자인화 + +Todo: +- [x] `site.css` 내 포털 전용 모던 글래스모피즘 클래스군 추가 +- [x] `Portal/Index.cshtml` 레이아웃 및 컴포넌트 UI 고도화 + +## WBS-MAINT-02 코드 품질 및 경고 결함 차단 + +목표: 빌드 컴파일 타임 경고(Warnings)를 0으로 유지하여 미래 코드 결함을 방지한다. + +성공 기준: +- `dotnet build` 수행 시 경고 0개 달성 + +Todo: +- [x] `CustomAuthenticationStateProvider.cs` Nullable 경고 수정 +- [x] `Dashboard.razor` 미사용 변수 제거 및 UI 연계 바인딩 처리 + diff --git a/TaxBaik.Web/Components/Admin/Pages/Dashboard.razor b/TaxBaik.Web/Components/Admin/Pages/Dashboard.razor index 993a391..79e2719 100644 --- a/TaxBaik.Web/Components/Admin/Pages/Dashboard.razor +++ b/TaxBaik.Web/Components/Admin/Pages/Dashboard.razor @@ -17,6 +17,15 @@ +@if (!string.IsNullOrEmpty(errorMessage)) +{ + @errorMessage +} +@if (isLoading) +{ + +} +
diff --git a/TaxBaik.Web/Pages/Portal/Index.cshtml b/TaxBaik.Web/Pages/Portal/Index.cshtml index 42a3a64..26092ac 100644 --- a/TaxBaik.Web/Pages/Portal/Index.cshtml +++ b/TaxBaik.Web/Pages/Portal/Index.cshtml @@ -54,7 +54,7 @@
-
+

@@ -124,7 +124,7 @@
-
+

최근 상담 및 지원 이력 @@ -139,14 +139,10 @@ } else { -
+
@foreach (var activity in Model.Consultations) { -
- -
- +
@activity.ActivityType @activity.ActivityDate.ToString("yyyy-MM-dd") diff --git a/TaxBaik.Web/Pages/_Layout.cshtml b/TaxBaik.Web/Pages/_Layout.cshtml index 9463b77..51b53b0 100644 --- a/TaxBaik.Web/Pages/_Layout.cshtml +++ b/TaxBaik.Web/Pages/_Layout.cshtml @@ -29,7 +29,7 @@ - + diff --git a/TaxBaik.Web/Services/CustomAuthenticationStateProvider.cs b/TaxBaik.Web/Services/CustomAuthenticationStateProvider.cs index 3325328..4ca3413 100644 --- a/TaxBaik.Web/Services/CustomAuthenticationStateProvider.cs +++ b/TaxBaik.Web/Services/CustomAuthenticationStateProvider.cs @@ -79,7 +79,7 @@ public class CustomAuthenticationStateProvider : AuthenticationStateProvider } } - var principal = _authService.ValidateToken(accessToken); + var principal = _authService.ValidateToken(accessToken ?? string.Empty); if (principal == null) { await LogoutAsync(); @@ -115,13 +115,13 @@ public class CustomAuthenticationStateProvider : AuthenticationStateProvider private bool ShouldRefreshToken() { // 토큰이 5분 이내로 만료되면 갱신 (300초 = 5분) - if (_tokenStore.TokenExpiryTicks <= 0) + if (!_tokenStore.TokenExpiryTicks.HasValue || _tokenStore.TokenExpiryTicks.Value <= 0) return false; const int refreshThresholdSeconds = 300; try { - var expiryTime = new DateTime((long)_tokenStore.TokenExpiryTicks, DateTimeKind.Utc); + var expiryTime = new DateTime(_tokenStore.TokenExpiryTicks.Value, DateTimeKind.Utc); var timeUntilExpiry = expiryTime - DateTime.UtcNow; return timeUntilExpiry.TotalSeconds <= refreshThresholdSeconds && timeUntilExpiry.TotalSeconds > 0; } diff --git a/TaxBaik.Web/wwwroot/css/site.css b/TaxBaik.Web/wwwroot/css/site.css index cd8aba8..17431c5 100644 --- a/TaxBaik.Web/wwwroot/css/site.css +++ b/TaxBaik.Web/wwwroot/css/site.css @@ -746,3 +746,162 @@ img { .faq-answer ul li { margin-bottom: 0.4rem; } + +/* ===== 프리미엄 고도화 & 마이크로 인터랙션 (2026-06-30) ===== */ + +/* 영어/숫자용 폰트 클래스 */ +.font-numeric, .font-heading-en { + font-family: 'Outfit', 'Inter', 'Noto Sans KR', sans-serif; +} + +/* 히어로 섹션 프리미엄 개편 (메쉬 그라데이션 및 CSS 애니메이션) */ +.hero-section { + background: radial-gradient(circle at 10% 20%, rgba(46, 92, 78, 1) 0%, rgba(31, 58, 48, 1) 44%, rgba(13, 30, 26, 1) 100%) !important; + position: relative; + overflow: hidden; +} + +.hero-section::before { + content: ''; + position: absolute; + top: -30%; + right: -10%; + width: 600px; + height: 600px; + background: radial-gradient(circle, rgba(200, 157, 110, 0.25) 0%, rgba(200, 157, 110, 0) 70%); + border-radius: 50%; + animation: floatAnimation 8s ease-in-out infinite; + pointer-events: none; +} + +.hero-section::after { + content: ''; + position: absolute; + bottom: -20%; + left: -10%; + width: 500px; + height: 500px; + background: radial-gradient(circle, rgba(232, 228, 216, 0.15) 0%, rgba(232, 228, 216, 0) 70%); + border-radius: 50%; + animation: floatAnimation2 12s ease-in-out infinite alternate; + pointer-events: none; +} + +@keyframes floatAnimation { + 0% { transform: translateY(0px) scale(1); } + 50% { transform: translateY(-30px) scale(1.05); } + 100% { transform: translateY(0px) scale(1); } +} + +@keyframes floatAnimation2 { + 0% { transform: translateX(0px) rotate(0deg); } + 50% { transform: translateX(20px) translateY(15px) rotate(10deg); } + 100% { transform: translateX(0px) rotate(0deg); } +} + +/* 신뢰도 스트립 카드 리뉴얼 */ +.trust-strip { + background-color: var(--color-bg-alt); + padding: 3rem 0; + margin-top: -1.5rem; + position: relative; + z-index: 10; +} + +.trust-item { + background: white; + border-radius: var(--radius-lg); + padding: 2rem 1.5rem; + box-shadow: 0 10px 30px rgba(61, 40, 23, 0.05); + border: 1px solid rgba(200, 157, 110, 0.15); + transition: all var(--transition-normal); + text-align: center; +} + +.trust-item:hover { + transform: translateY(-5px); + box-shadow: 0 15px 35px rgba(61, 40, 23, 0.1); + border-color: var(--color-primary); +} + +.trust-icon { + font-size: 2.5rem; + margin-bottom: 1rem; + display: inline-block; + filter: drop-shadow(0 4px 6px rgba(0,0,0,0.1)); + transition: transform var(--transition-fast); +} + +.trust-item:hover .trust-icon { + transform: scale(1.15) rotate(5deg); +} + +/* 서비스 카드 고도화 */ +.service-card { + border: 1px solid rgba(217, 211, 196, 0.6) !important; + box-shadow: 0 10px 25px rgba(61, 40, 23, 0.03) !important; + transition: all var(--transition-normal) !important; +} + +.service-card:hover { + transform: translateY(-8px) !important; + box-shadow: 0 20px 40px rgba(61, 40, 23, 0.1) !important; + border-color: var(--color-primary) !important; +} + +.service-card--featured { + background: linear-gradient(180deg, #FFFFFF 0%, #FAF8F5 100%) !important; + border-left: 4px solid var(--color-primary) !important; +} + +/* 글래스모피즘 포털 클래스 (Glassmorphism Portal Classes) */ +.glass-card { + background: rgba(255, 255, 255, 0.7) !important; + backdrop-filter: blur(12px) saturate(180%) !important; + -webkit-backdrop-filter: blur(12px) saturate(180%) !important; + border: 1px solid rgba(255, 255, 255, 0.4) !important; + box-shadow: 0 8px 32px 0 rgba(61, 40, 23, 0.05) !important; + border-radius: var(--radius-lg); + transition: all var(--transition-normal); +} + +.glass-card:hover { + background: rgba(255, 255, 255, 0.8) !important; + box-shadow: 0 8px 32px 0 rgba(61, 40, 23, 0.08) !important; +} + +.portal-welcome-strip { + background: linear-gradient(135deg, var(--color-secondary-dark) 0%, #152A22 100%); + border-radius: var(--radius-lg); + color: white; + padding: 2.5rem; + box-shadow: var(--shadow-lg); + border-bottom: 3px solid var(--color-primary); +} + +/* 타임라인 컴포넌트 뷰티화 */ +.timeline-item-modern { + border-left: 2px solid rgba(200, 157, 110, 0.4); + position: relative; + padding-left: 1.5rem; + padding-bottom: 2rem; +} + +.timeline-item-modern::after { + content: ''; + position: absolute; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--color-primary); + left: -7px; + top: 6px; + box-shadow: 0 0 0 4px rgba(200, 157, 110, 0.25); + transition: all var(--transition-fast); +} + +.timeline-item-modern:hover::after { + background: var(--color-secondary); + box-shadow: 0 0 0 6px rgba(46, 92, 78, 0.3); + transform: scale(1.1); +}