feat: revamp UI/UX of homepage & portal, clean warnings, and update ROADMAP_WBS
TaxBaik CI/CD / build-and-deploy (push) Successful in 54s

This commit is contained in:
2026-06-30 21:59:22 +09:00
parent ee964457d9
commit f98405b791
6 changed files with 219 additions and 12 deletions
+43
View File
@@ -522,3 +522,46 @@ Todo:
- WBS-UX-03/04 구현 완료 - WBS-UX-03/04 구현 완료
- WBS-CRM-01/02/03/04/05 구현 완료 (배포 후 검증 필요) - WBS-CRM-01/02/03/04/05 구현 완료 (배포 후 검증 필요)
- WBS-CRM-06/07/08 (텔레그램·포털·소셜 로그인) Phase 3 미착수 - 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 연계 바인딩 처리
@@ -17,6 +17,15 @@
</MudButton> </MudButton>
</section> </section>
@if (!string.IsNullOrEmpty(errorMessage))
{
<MudAlert Severity="Severity.Error" Class="mb-4">@errorMessage</MudAlert>
}
@if (isLoading)
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="mb-4" />
}
<!-- Metrics Grid - Pure HTML div instead of MudGrid to ensure proper layout --> <!-- Metrics Grid - Pure HTML div instead of MudGrid to ensure proper layout -->
<div class="admin-metric-grid"> <div class="admin-metric-grid">
<div class="admin-metric-card accent-blue cursor-pointer" @onclick='(() => Nav.NavigateTo("/taxbaik/admin/inquiries"))' style="cursor: pointer;"> <div class="admin-metric-card accent-blue cursor-pointer" @onclick='(() => Nav.NavigateTo("/taxbaik/admin/inquiries"))' style="cursor: pointer;">
+4 -8
View File
@@ -54,7 +54,7 @@
<div class="row g-4"> <div class="row g-4">
<!-- 왼쪽: 세무 신고 현황 (Tax Filings) --> <!-- 왼쪽: 세무 신고 현황 (Tax Filings) -->
<div class="col-lg-8"> <div class="col-lg-8">
<div class="card border-0 shadow-sm rounded-3 mb-4"> <div class="card glass-card mb-4">
<div class="card-body p-4"> <div class="card-body p-4">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h3 class="h5 fw-bold text-dark mb-0"> <h3 class="h5 fw-bold text-dark mb-0">
@@ -124,7 +124,7 @@
<!-- 오른쪽: 상담 이력 요약 (Consulting Activities) --> <!-- 오른쪽: 상담 이력 요약 (Consulting Activities) -->
<div class="col-lg-4"> <div class="col-lg-4">
<div class="card border-0 shadow-sm rounded-3"> <div class="card glass-card">
<div class="card-body p-4"> <div class="card-body p-4">
<h3 class="h5 fw-bold text-dark mb-4"> <h3 class="h5 fw-bold text-dark mb-4">
<i class="bi bi-chat-text text-primary me-2"></i> 최근 상담 및 지원 이력 <i class="bi bi-chat-text text-primary me-2"></i> 최근 상담 및 지원 이력
@@ -139,14 +139,10 @@
} }
else else
{ {
<div class="timeline"> <div class="timeline ps-2">
@foreach (var activity in Model.Consultations) @foreach (var activity in Model.Consultations)
{ {
<div class="border-start border-2 border-primary-subtle ps-3 pb-4 position-relative"> <div class="timeline-item-modern">
<!-- 타임라인 아이콘 -->
<div class="position-absolute start-0 translate-middle-x bg-primary rounded-circle"
style="width: 10px; height: 10px; margin-left: -1px; top: 6px;"></div>
<div class="d-flex justify-content-between align-items-center mb-1"> <div class="d-flex justify-content-between align-items-center mb-1">
<span class="badge bg-primary-subtle text-primary small">@activity.ActivityType</span> <span class="badge bg-primary-subtle text-primary small">@activity.ActivityType</span>
<small class="text-muted">@activity.ActivityDate.ToString("yyyy-MM-dd")</small> <small class="text-muted">@activity.ActivityDate.ToString("yyyy-MM-dd")</small>
+1 -1
View File
@@ -29,7 +29,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net" /> <link rel="dns-prefetch" href="https://cdn.jsdelivr.net" />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Noto+Sans+KR:wght@400;500;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="canonical" href="@(ViewData["CanonicalUrl"] ?? "http://178.104.200.7/taxbaik/")" /> <link rel="canonical" href="@(ViewData["CanonicalUrl"] ?? "http://178.104.200.7/taxbaik/")" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
@@ -79,7 +79,7 @@ public class CustomAuthenticationStateProvider : AuthenticationStateProvider
} }
} }
var principal = _authService.ValidateToken(accessToken); var principal = _authService.ValidateToken(accessToken ?? string.Empty);
if (principal == null) if (principal == null)
{ {
await LogoutAsync(); await LogoutAsync();
@@ -115,13 +115,13 @@ public class CustomAuthenticationStateProvider : AuthenticationStateProvider
private bool ShouldRefreshToken() private bool ShouldRefreshToken()
{ {
// 토큰이 5분 이내로 만료되면 갱신 (300초 = 5분) // 토큰이 5분 이내로 만료되면 갱신 (300초 = 5분)
if (_tokenStore.TokenExpiryTicks <= 0) if (!_tokenStore.TokenExpiryTicks.HasValue || _tokenStore.TokenExpiryTicks.Value <= 0)
return false; return false;
const int refreshThresholdSeconds = 300; const int refreshThresholdSeconds = 300;
try try
{ {
var expiryTime = new DateTime((long)_tokenStore.TokenExpiryTicks, DateTimeKind.Utc); var expiryTime = new DateTime(_tokenStore.TokenExpiryTicks.Value, DateTimeKind.Utc);
var timeUntilExpiry = expiryTime - DateTime.UtcNow; var timeUntilExpiry = expiryTime - DateTime.UtcNow;
return timeUntilExpiry.TotalSeconds <= refreshThresholdSeconds && timeUntilExpiry.TotalSeconds > 0; return timeUntilExpiry.TotalSeconds <= refreshThresholdSeconds && timeUntilExpiry.TotalSeconds > 0;
} }
+159
View File
@@ -746,3 +746,162 @@ img {
.faq-answer ul li { .faq-answer ul li {
margin-bottom: 0.4rem; 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);
}