Compare commits
1 Commits
8f5330a008
...
521446d56b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
521446d56b |
@@ -71,6 +71,17 @@ body {
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
/* 접근 거부 페이지 */
|
||||
.access-denied-container {
|
||||
display: flex;
|
||||
|
||||
@@ -27,6 +27,19 @@ function App() {
|
||||
const [newProjectCode, setNewProjectCode] = useState('');
|
||||
const [newProjectName, setNewProjectName] = useState('');
|
||||
const [newClientName, setNewClientName] = useState('');
|
||||
const [pendingSignupCount, setPendingSignupCount] = useState(0);
|
||||
|
||||
// 승인 대기 중인 회원가입 수 조회
|
||||
const loadPendingSignups = async () => {
|
||||
try {
|
||||
const response = await api.get('/auth/signup-requests');
|
||||
if (response.data && response.data.requests) {
|
||||
setPendingSignupCount(response.data.count || 0);
|
||||
}
|
||||
} catch (error) {
|
||||
// 에러 무시 (관리자가 아니면 403)
|
||||
}
|
||||
};
|
||||
|
||||
// 프로젝트 목록 로드
|
||||
const loadProjects = async () => {
|
||||
@@ -104,8 +117,18 @@ function App() {
|
||||
|
||||
if (token && userData) {
|
||||
setIsAuthenticated(true);
|
||||
setUser(JSON.parse(userData));
|
||||
const userObj = JSON.parse(userData);
|
||||
setUser(userObj);
|
||||
loadProjects(); // 프로젝트 목록 로드
|
||||
|
||||
// 관리자인 경우 승인 대기 수 조회
|
||||
if (userObj.role === 'admin' || userObj.role === 'system') {
|
||||
loadPendingSignups();
|
||||
|
||||
// 30초마다 갱신
|
||||
const interval = setInterval(loadPendingSignups, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
@@ -247,11 +270,65 @@ function App() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 사용자 메뉴 */}
|
||||
<div className="user-menu-container" style={{ position: 'relative' }}>
|
||||
<button
|
||||
onClick={() => setShowUserMenu(!showUserMenu)}
|
||||
style={{
|
||||
{/* 알람 및 사용자 메뉴 */}
|
||||
<div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
|
||||
{/* 회원가입 승인 알람 (관리자 전용) */}
|
||||
{(user?.role === 'admin' || user?.role === 'system') && pendingSignupCount > 0 && (
|
||||
<button
|
||||
onClick={() => navigateToPage('user-management')}
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '44px',
|
||||
height: '44px',
|
||||
background: '#fef3c7',
|
||||
border: '2px solid #f59e0b',
|
||||
borderRadius: '50%',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '20px',
|
||||
transition: 'all 0.2s ease',
|
||||
animation: 'pulse 2s ease-in-out infinite'
|
||||
}}
|
||||
title={`회원가입 승인 대기: ${pendingSignupCount}명`}
|
||||
onMouseEnter={(e) => {
|
||||
e.target.style.background = '#fde68a';
|
||||
e.target.style.transform = 'scale(1.1)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.target.style.background = '#fef3c7';
|
||||
e.target.style.transform = 'scale(1)';
|
||||
}}
|
||||
>
|
||||
🔔
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '-4px',
|
||||
right: '-4px',
|
||||
background: '#ef4444',
|
||||
color: 'white',
|
||||
borderRadius: '50%',
|
||||
width: '22px',
|
||||
height: '22px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '11px',
|
||||
fontWeight: '700',
|
||||
border: '2px solid white',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
|
||||
}}>
|
||||
{pendingSignupCount}
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* 사용자 메뉴 */}
|
||||
<div className="user-menu-container" style={{ position: 'relative' }}>
|
||||
<button
|
||||
onClick={() => setShowUserMenu(!showUserMenu)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
|
||||
Reference in New Issue
Block a user