회원가입 신청 기능 완성 (간소화)
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

백엔드:
- signup_routes.py 신규 생성
- POST /auth/signup-request: 회원가입 신청
- GET /auth/signup-requests: 승인 대기 목록 (관리자)
- POST /auth/approve-signup/{id}: 승인 (관리자)
- DELETE /auth/reject-signup/{id}: 거부 (관리자)
- main.py에 signup_router 등록

프론트엔드:
- SimpleLogin에 회원가입 폼 추가
- 필수 항목만: 사용자명, 비밀번호, 비밀번호 확인, 이름
- 간단하고 깔끔한 UI
- 비밀번호 일치 검사 및 최소 길이 검사
- 제출 후 승인 대기 안내 메시지
This commit is contained in:
Hyungi Ahn
2025-10-14 07:28:06 +09:00
parent dfb6c7e8a4
commit e14f8b69c7
4 changed files with 438 additions and 0 deletions

View File

@@ -9,6 +9,18 @@ const SimpleLogin = ({ onLoginSuccess }) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const [showSignup, setShowSignup] = useState(false);
const [signupData, setSignupData] = useState({
username: '',
password: '',
confirmPassword: '',
name: '',
email: '',
department: '',
position: '',
phone: '',
reason: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
@@ -19,6 +31,73 @@ const SimpleLogin = ({ onLoginSuccess }) => {
if (error) setError('');
};
const handleSignupChange = (e) => {
const { name, value } = e.target;
setSignupData(prev => ({
...prev,
[name]: value
}));
if (error) setError('');
};
const handleSignupSubmit = async (e) => {
e.preventDefault();
// 유효성 검사
if (!signupData.username || !signupData.password || !signupData.name) {
setError('필수 항목을 모두 입력해주세요.');
return;
}
if (signupData.password !== signupData.confirmPassword) {
setError('비밀번호가 일치하지 않습니다.');
return;
}
if (signupData.password.length < 6) {
setError('비밀번호는 최소 6자 이상이어야 합니다.');
return;
}
setIsLoading(true);
setError('');
try {
const response = await api.post('/auth/signup-request', {
username: signupData.username,
password: signupData.password,
name: signupData.name,
email: signupData.email || null,
department: signupData.department || null,
position: signupData.position || null,
phone: signupData.phone || null,
reason: signupData.reason || null
});
if (response.data.success) {
setSuccess('회원가입 요청이 전송되었습니다. 관리자 승인 후 이용 가능합니다.');
setShowSignup(false);
// 폼 초기화
setSignupData({
username: '',
password: '',
confirmPassword: '',
name: '',
email: '',
department: '',
position: '',
phone: '',
reason: ''
});
}
} catch (err) {
const errorMsg = err.response?.data?.detail || '회원가입 요청 중 오류가 발생했습니다.';
setError(errorMsg);
} finally {
setIsLoading(false);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
@@ -175,6 +254,55 @@ const SimpleLogin = ({ onLoginSuccess }) => {
{isLoading ? '로그인 중...' : '로그인'}
</button>
</form>
{/* 회원가입 버튼 */}
<div style={{ marginTop: '20px', textAlign: 'center' }}>
<button
type="button"
onClick={() => {
setShowSignup(!showSignup);
setError('');
setSuccess('');
}}
style={{
width: '100%',
padding: '14px',
backgroundColor: showSignup ? '#6b7280' : '#10b981',
color: 'white',
border: 'none',
borderRadius: '8px',
fontSize: '16px',
fontWeight: '600',
cursor: 'pointer',
transition: 'background-color 0.3s ease'
}}
>
{showSignup ? '✕ 닫기' : ' 회원가입 신청'}
</button>
</div>
{/* 회원가입 폼 */}
{showSignup && (
<div style={{
marginTop: '20px',
padding: '20px',
background: '#f0fdf4',
border: '2px solid #10b981',
borderRadius: '12px',
maxHeight: '500px',
overflowY: 'auto'
}}>
<form onSubmit={handleSignupSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
<input type="text" name="username" value={signupData.username} onChange={handleSignupChange} placeholder="사용자명*" required style={{ padding: '12px', border: '1px solid #d1d5db', borderRadius: '6px', fontSize: '14px' }} />
<input type="password" name="password" value={signupData.password} onChange={handleSignupChange} placeholder="비밀번호 (최소 6자)*" required style={{ padding: '12px', border: '1px solid #d1d5db', borderRadius: '6px', fontSize: '14px' }} />
<input type="password" name="confirmPassword" value={signupData.confirmPassword} onChange={handleSignupChange} placeholder="비밀번호 확인*" required style={{ padding: '12px', border: '1px solid #d1d5db', borderRadius: '6px', fontSize: '14px' }} />
<input type="text" name="name" value={signupData.name} onChange={handleSignupChange} placeholder="이름*" required style={{ padding: '12px', border: '1px solid #d1d5db', borderRadius: '6px', fontSize: '14px' }} />
<button type="submit" disabled={isLoading} style={{ padding: '14px', background: isLoading ? '#9ca3af' : '#10b981', color: 'white', border: 'none', borderRadius: '8px', fontSize: '16px', fontWeight: '600', cursor: isLoading ? 'not-allowed' : 'pointer', marginTop: '8px' }}>
{isLoading ? '처리 중...' : '✓ 회원가입 신청'}
</button>
</form>
</div>
)}
{error && (
<div style={{