- 회원가입 username 검증을 서버와 동일하게 맞춤 - 비밀번호 maxLength를 bcrypt 제한(72)에 맞춤 - 공지사항 줄바꿈 CSS 처리 (pre→white-space) - 어드민 페이지 에러 로깅 추가 - 다운로드 섹션 로딩 스켈레톤 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,8 @@
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: rgba(255, 255, 255, 0.6);
|
color: rgba(255, 255, 255, 0.6);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.announcement-error {
|
.announcement-error {
|
||||||
|
|||||||
@@ -62,7 +62,16 @@ export default function DownloadSection() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!ready) return null;
|
if (!ready) {
|
||||||
|
return (
|
||||||
|
<section className="download-section">
|
||||||
|
<div className="download-content">
|
||||||
|
<h2 className="download-title">One of the plans</h2>
|
||||||
|
<p className="download-meta">불러오는 중...</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="download-section">
|
<section className="download-section">
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ export default function AnnouncementAdmin() {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
const load = useCallback(() => {
|
const load = useCallback(() => {
|
||||||
getAnnouncements().then(setList).catch(() => {});
|
getAnnouncements().then(setList).catch((err) => {
|
||||||
|
console.error('공지사항 로드 실패:', err);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => { load(); }, [load]);
|
useEffect(() => { load(); }, [load]);
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,9 @@ export default function DownloadAdmin() {
|
|||||||
const [info, setInfo] = useState(null);
|
const [info, setInfo] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDownloadInfo().then(setInfo).catch(() => {});
|
getDownloadInfo().then(setInfo).catch((err) => {
|
||||||
|
console.error('다운로드 정보 로드 실패:', err);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default function UserAdmin() {
|
|||||||
const load = useCallback(() => {
|
const load = useCallback(() => {
|
||||||
getUsers()
|
getUsers()
|
||||||
.then(setUsers)
|
.then(setUsers)
|
||||||
.catch(() => {})
|
.catch((err) => { console.error('유저 목록 로드 실패:', err); })
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
useEffect(() => { load(); }, [load]);
|
useEffect(() => { load(); }, [load]);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function LoginPage() {
|
|||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="비밀번호를 입력하세요"
|
placeholder="비밀번호를 입력하세요"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
maxLength={100}
|
maxLength={72}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,19 @@ export default function RegisterPage() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError('');
|
setError('');
|
||||||
|
|
||||||
if (!username.trim()) {
|
const trimmed = username.trim().toLowerCase();
|
||||||
|
if (!trimmed) {
|
||||||
setError('아이디를 입력해주세요.');
|
setError('아이디를 입력해주세요.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (trimmed.length < 3) {
|
||||||
|
setError('아이디는 3자 이상이어야 합니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^[a-z0-9_-]+$/.test(trimmed)) {
|
||||||
|
setError('아이디는 영문 소문자, 숫자, _, -만 사용 가능합니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (password !== confirm) {
|
if (password !== confirm) {
|
||||||
setError('비밀번호가 일치하지 않습니다.');
|
setError('비밀번호가 일치하지 않습니다.');
|
||||||
return;
|
return;
|
||||||
@@ -46,7 +55,7 @@ export default function RegisterPage() {
|
|||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await register(username, password);
|
await register(trimmed, password);
|
||||||
navigate('/login', { state: { registered: true } });
|
navigate('/login', { state: { registered: true } });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.message || '회원가입에 실패했습니다.');
|
setError(err.message || '회원가입에 실패했습니다.');
|
||||||
@@ -86,7 +95,7 @@ export default function RegisterPage() {
|
|||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="6자 이상 입력하세요"
|
placeholder="6자 이상 입력하세요"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
maxLength={100}
|
maxLength={72}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -105,7 +114,7 @@ export default function RegisterPage() {
|
|||||||
onChange={(e) => setConfirm(e.target.value)}
|
onChange={(e) => setConfirm(e.target.value)}
|
||||||
placeholder="비밀번호를 다시 입력하세요"
|
placeholder="비밀번호를 다시 입력하세요"
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
maxLength={100}
|
maxLength={72}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user