Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0476bc0
feat: #212 어드민 패널 구현 (SSR)
PBEM22 Jul 20, 2026
04b74bf
fix: #212 테스트 환경 어드민/Gemini 환경변수 더미값 추가
PBEM22 Jul 21, 2026
d812034
fix: #212 CodeRabbit 리뷰 반영 - 정지 유저 로그인 차단 및 정지/탈퇴 시 RefreshToken 삭제
PBEM22 Jul 21, 2026
6c780c8
Merge branch 'origin/develop' into feat/#212/admin-panel
PBEM22 Jul 27, 2026
b6c1d3f
remove unused PROMPT_ERROR from ErrorStatus (Gemini feature removed)
PBEM22 Jul 27, 2026
8593239
fix: 주문 존재하는 분철글 어드민 강제삭제 시 FK 제약 위반 방지
PBEM22 Jul 27, 2026
33f94b9
fix: 진행 중인 분철글 삭제 차단 및 관리자 실수 방지 confirm 강화
PBEM22 Jul 27, 2026
a827493
fix: 어드민 패널 엣지 케이스 처리
PBEM22 Jul 27, 2026
a6ea908
fix: 탈퇴 유저 정지/해제 시도 시 BusinessException 처리
PBEM22 Jul 27, 2026
5c321d8
feat: #212 어드민 패널 UI 전면 개선
PBEM22 Jul 28, 2026
f554fef
fix: User 엔티티에서 IllegalStateException 제거
PBEM22 Jul 28, 2026
4af5ddf
fix: #212 CodeRabbit 리뷰 반영
PBEM22 Jul 28, 2026
85fc915
Merge remote-tracking branch 'origin/develop' into feat/#212/admin-panel
PBEM22 Jul 28, 2026
5fc998e
fix: #212 @Order를 클래스에서 @Bean 메서드로 이동
PBEM22 Jul 28, 2026
8e7218f
fix: #212 정지 유저 JWT 차단 및 분철글 삭제 race condition 처리
PBEM22 Jul 28, 2026
cb3420a
fix: #212 어드민 CSS를 정적 파일로 분리하여 UI 적용 문제 해결
PBEM22 Jul 28, 2026
83882a6
Merge remote-tracking branch 'origin/develop' into feat/#212/admin-panel
PBEM22 Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class SecurityConfig {
"/swagger-ui/**", // Swagger 문서 관련
"/v3/api-docs/**",
"/favicon.ico",
"/css/**",
"/dev/login", // 개발자용 자동 로그인 (토큰 발급)
};

Expand Down
323 changes: 323 additions & 0 deletions src/main/resources/static/css/admin.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
/* ── Variables ── */
:root {
--sidebar-bg: #0f172a;
--sidebar-hover: #1e293b;
--accent: #6366f1;
--accent-light: #818cf8;
--bg: #f1f5f9;
--surface: #ffffff;
--border: #e2e8f0;
--text: #0f172a;
--text-muted: #64748b;
}

/* ── Reset ── */
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 14px;
color: var(--text);
}

/* ── Sidebar ── */
.sidebar {
width: 240px;
min-height: 100vh;
background: var(--sidebar-bg);
display: flex;
flex-direction: column;
position: fixed;
top: 0; left: 0;
z-index: 100;
}
.sidebar-brand {
padding: 24px 20px 18px;
border-bottom: 1px solid #1e293b;
}
.sidebar-brand .brand-title {
color: #fff;
font-size: 1.05rem;
font-weight: 700;
}
.sidebar-brand .brand-sub {
color: #475569;
font-size: 0.7rem;
margin-top: 3px;
}
.sidebar-nav {
flex: 1;
padding: 10px 10px 0;
}
.nav-label {
color: #475569;
font-size: 0.62rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 14px 8px 6px;
}
.sidebar-link {
display: flex;
align-items: center;
gap: 10px;
color: #94a3b8;
text-decoration: none;
padding: 9px 12px;
border-radius: 8px;
font-size: 0.85rem;
font-weight: 500;
transition: background 0.15s, color 0.15s;
margin-bottom: 2px;
}
.sidebar-link i { font-size: 0.95rem; }
.sidebar-link:hover { background: #1e293b; color: #e2e8f0; }
.sidebar-link.active { background: var(--accent); color: #fff; }
.sidebar-footer {
padding: 10px;
border-top: 1px solid #1e293b;
}
.sidebar-footer button {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
background: none;
border: none;
color: #64748b;
font-size: 0.82rem;
font-weight: 500;
padding: 9px 12px;
border-radius: 8px;
cursor: pointer;
text-align: left;
transition: background 0.15s, color 0.15s;
}
.sidebar-footer button:hover { background: #1e293b; color: #94a3b8; }

/* ── Main Layout ── */
.main-wrapper {
margin-left: 240px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.topbar {
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: 14px 28px;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 50;
}
.topbar-title {
font-size: 0.95rem;
font-weight: 700;
color: var(--text);
}
.topbar-badge {
background: var(--bg);
color: var(--text-muted);
font-size: 0.72rem;
padding: 3px 10px;
border-radius: 20px;
font-weight: 500;
}
.main-content { padding: 24px 28px; flex: 1; }

/* ── Flash Alert ── */
.alert-flash {
border: none;
border-radius: 10px;
font-size: 0.85rem;
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
padding: 12px 16px;
}

/* ── Stat Cards (Dashboard) ── */
.stat-card {
background: var(--surface);
border-radius: 14px;
border: none;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
}
.stat-card .card-body { padding: 20px 22px; }
.stat-label {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-muted);
margin-bottom: 10px;
}
.stat-icon {
width: 38px; height: 38px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
}
.stat-value {
font-size: 1.8rem;
font-weight: 700;
color: var(--text);
line-height: 1;
margin-top: 2px;
}
.stat-desc {
font-size: 0.72rem;
color: var(--text-muted);
margin-top: 6px;
}
.stat-bar { height: 3px; }

/* ── Quick Links ── */
.quick-card {
background: var(--surface);
border-radius: 14px;
border: none;
box-shadow: 0 1px 3px rgba(0,0,0,.06);
padding: 20px 22px;
}
.quick-card .section-title {
font-size: 0.8rem;
font-weight: 600;
color: var(--text-muted);
margin-bottom: 12px;
}
.quick-link {
display: inline-flex;
align-items: center;
gap: 7px;
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
border-radius: 8px;
padding: 7px 14px;
font-size: 0.82rem;
font-weight: 500;
text-decoration: none;
transition: border-color 0.15s, background 0.15s;
}
.quick-link:hover { border-color: var(--accent); color: var(--accent); background: #eef2ff; }

/* ── Table Card ── */
.table-card {
background: var(--surface);
border-radius: 14px;
border: none;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
}
.admin-table { margin: 0; }
.admin-table thead th {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
background: #f8fafc;
border-bottom: 1px solid var(--border);
padding: 11px 16px;
white-space: nowrap;
}
.admin-table tbody td {
padding: 13px 16px;
font-size: 0.85rem;
color: var(--text);
vertical-align: middle;
border-bottom: 1px solid #f1f5f9;
}
.admin-table tbody tr:hover td { background: #f8fafc; }
.admin-table tbody tr:last-child td { border-bottom: none; }
.cell-id { font-size: 0.75rem; color: var(--text-muted); }
.cell-sub { font-size: 0.78rem; color: var(--text-muted); }

/* ── Badges ── */
.badge-pill {
display: inline-block;
font-size: 0.68rem;
font-weight: 700;
padding: 3px 9px;
border-radius: 20px;
letter-spacing: 0.01em;
}
.badge-active { background: #dcfce7; color: #15803d; }
.badge-suspended { background: #fee2e2; color: #b91c1c; }
.badge-withdrawn { background: #f1f5f9; color: #94a3b8; }
.badge-kakao { background: #fef9c3; color: #854d0e; }

.status-recruiting { background: #dbeafe; color: #1d4ed8; }
.status-closed { background: #fef9c3; color: #854d0e; }
.status-payment_done { background: #dcfce7; color: #15803d; }
.status-shipping { background: #ede9fe; color: #6d28d9; }
.status-delivered { background: #f1f5f9; color: #475569; }

/* ── Action Buttons ── */
.btn-action {
display: inline-block;
font-size: 0.75rem;
font-weight: 600;
padding: 4px 11px;
border-radius: 7px;
border: none;
cursor: pointer;
transition: filter 0.15s;
white-space: nowrap;
line-height: 1.6;
}
.btn-action:hover { filter: brightness(0.93); }
.btn-suspend { background: #fee2e2; color: #b91c1c; }
.btn-release { background: #dcfce7; color: #15803d; }
.btn-withdraw { background: #f1f5f9; color: #64748b; }
.btn-delete { background: #fee2e2; color: #b91c1c; }

/* ── Filter Tabs ── */
.filter-tabs {
display: flex;
gap: 7px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.filter-tab {
font-size: 0.78rem;
font-weight: 600;
padding: 5px 13px;
border-radius: 20px;
border: 1.5px solid var(--border);
background: var(--surface);
color: var(--text-muted);
text-decoration: none;
transition: all 0.15s;
}
.filter-tab:hover { border-color: var(--accent); color: var(--accent); }
.filter-tab.is-active { background: var(--accent); border-color: var(--accent); color: #fff; }

/* ── Empty State ── */
.empty-state {
padding: 52px 0;
text-align: center;
color: var(--text-muted);
}
.empty-state i { font-size: 2.2rem; display: block; margin-bottom: 10px; opacity: 0.4; }
.empty-state p { font-size: 0.85rem; margin: 0; }

/* ── Pagination ── */
.pagination { gap: 3px; }
.page-link {
font-size: 0.78rem;
color: var(--accent);
border-color: var(--border);
border-radius: 7px !important;
padding: 5px 10px;
}
.page-link:hover { background: #eef2ff; border-color: var(--accent); color: var(--accent); }
.page-item.active .page-link { background: var(--accent); border-color: var(--accent); }
.page-item.disabled .page-link { pointer-events: none; opacity: 0.4; }
Loading
Loading