사용자 계정 관리 API 연동 및 기능 개선

- 사용자 비밀번호 변경 API 추가
- 메뉴 권한 관리 API 추가 (조회/저장)
- 인증 방법 수정 API 추가
- 사용자 권한 업데이트 API 추가
- 계정 관리 UI 컴포넌트 개선
- Docker 및 Makefile 설정 업데이트

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jay Sheen
2025-09-26 14:22:37 +09:00
parent 43e7eefefa
commit dd2fa9d6f3
25 changed files with 999 additions and 261 deletions

View File

@@ -11,8 +11,12 @@ import { useState, useEffect, useRef } from 'react';
import { VerificationItem } from '@/entities/account/model/types';
import { useUserCreateMutation } from '@/entities/user/api/use-user-create-mutation';
import { useUserExistsUseridQuery } from '@/entities/user/api/use-user-exists-userid-query';
import { useLocation } from 'react-router';
export const UserAddAccountPage = () => {
const { navigate } = useNavigate();
const location = useLocation();
const { mid } = location.state || {};
const { mutateAsync: userCreate, isPending } = useUserCreateMutation();
// 폼 상태 관리
@@ -143,6 +147,16 @@ export const UserAddAccountPage = () => {
setNewPhones(updated);
};
// 비밀번호 검증 함수
const validatePassword = (password: string) => {
if (!password.trim()) {
return '비밀번호를 입력해 주세요';
} else if (password.length < 8) {
return '8자리 이상 입력해 주세요';
}
return '';
};
// 폼 입력 핸들러
const handleInputChange = (field: string, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
@@ -174,6 +188,14 @@ export const UserAddAccountPage = () => {
}
};
// 비밀번호 blur 핸들러
const handlePasswordBlur = () => {
const passwordError = validatePassword(formData.password);
if (passwordError) {
setErrors(prev => ({ ...prev, password: passwordError }));
}
};
// 컴포넌트 언마운트 시 타이머 정리
useEffect(() => {
return () => {
@@ -241,6 +263,44 @@ export const UserAddAccountPage = () => {
return totalCount > 1;
};
// 저장 버튼 활성화 조건 체크
const isSaveButtonEnabled = () => {
// 1. 사용자 ID가 입력되고 중복되지 않아야 함
if (!formData.usrid.trim()) return false;
if (errors.usrid) return false;
if (isCheckingUsrid || isUserExistsLoading) return false;
// 2. 비밀번호가 8자리 이상
if (!formData.password.trim() || formData.password.length < 8) return false;
// 3. 입력된 모든 이메일과 휴대폰 번호가 유효한 형식이어야 함
const nonEmptyEmails = newEmails.filter(email => email.trim());
const nonEmptyPhones = newPhones.filter(phone => phone.trim());
// 입력된 이메일이 있으면 모두 유효한 형식이어야 함
if (nonEmptyEmails.length > 0) {
const invalidEmails = nonEmptyEmails.filter(email => !isValidEmail(email));
if (invalidEmails.length > 0) return false;
}
// 입력된 휴대폰 번호가 있으면 모두 유효한 형식이어야 함
if (nonEmptyPhones.length > 0) {
const invalidPhones = nonEmptyPhones.filter(phone => !isValidPhone(phone));
if (invalidPhones.length > 0) return false;
}
// 4. 유효한 이메일 또는 휴대폰 번호가 최소 1개 이상
const validEmails = nonEmptyEmails.filter(email => isValidEmail(email));
const validPhones = nonEmptyPhones.filter(phone => isValidPhone(phone));
if (validEmails.length === 0 && validPhones.length === 0) return false;
// 5. 중복이 없어야 함
if (hasDuplicateEmail() || hasDuplicatePhone()) return false;
return true;
};
// 폼 검증
const validateForm = () => {
const newErrors = { usrid: '', password: '' };
@@ -302,10 +362,11 @@ export const UserAddAccountPage = () => {
});
const request = {
userId: formData.usrid,
mid: mid,
usrid: formData.usrid,
password: formData.password,
loginRange: formData.loginRange,
verification: verifications
verifications: verifications
};
const response = await userCreate(request);
@@ -365,7 +426,7 @@ export const UserAddAccountPage = () => {
)}
</div>
</div>
{errors.usrid && <div className="ua-help error">{errors.usrid}</div>}
{errors.usrid && <div className="ua-help error pt-10">{errors.usrid}</div>}
{!errors.usrid && formData.usrid && userExistsData && !userExistsData.exists && (
<div className="ua-help" style={{ color: '#78D197' }}> ID입니다.</div>
)}
@@ -378,9 +439,10 @@ export const UserAddAccountPage = () => {
placeholder="8자리 이상 입력해 주세요"
value={formData.password}
onChange={(e) => handleInputChange('password', e.target.value)}
onBlur={handlePasswordBlur}
/>
</div>
{errors.password && <div className="ua-help error">{errors.password}</div>}
{errors.password && <div className="ua-help error pt-10">{errors.password}</div>}
<div className="ua-row">
<div className="ua-label"> </div>
@@ -390,7 +452,7 @@ export const UserAddAccountPage = () => {
onChange={(e) => handleInputChange('loginRange', e.target.value)}
>
<option value="MID">MID</option>
<option value="MID + GID">MID + GID</option>
<option value="GID">MID + GID</option>
</select>
</div>
</div>
@@ -471,7 +533,7 @@ export const UserAddAccountPage = () => {
className="btn-50 btn-blue flex-1"
type="button"
onClick={handleSave}
disabled={isPending}
disabled={!isSaveButtonEnabled() || isPending}
>
{isPending ? '저장 중...' : '저장'}
</button>