diff --git a/src/components/DownloadSection.jsx b/src/components/DownloadSection.jsx index 9a7360d..2fba932 100644 --- a/src/components/DownloadSection.jsx +++ b/src/components/DownloadSection.jsx @@ -114,4 +114,4 @@ export default function DownloadSection() { ); -} +} \ No newline at end of file diff --git a/src/components/admin/DownloadAdmin.jsx b/src/components/admin/DownloadAdmin.jsx index 94dab96..abd364a 100644 --- a/src/components/admin/DownloadAdmin.jsx +++ b/src/components/admin/DownloadAdmin.jsx @@ -1,33 +1,24 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { getDownloadInfo } from '../../api/download'; -import { useToast } from '../toast/useToast'; import UploadForm from './UploadForm'; import s from './AdminCommon.module.css'; export default function DownloadAdmin() { - const toast = useToast(); const [info, setInfo] = useState(null); const [loading, setLoading] = useState(true); const [loadError, setLoadError] = useState(''); - const load = () => { + const load = useCallback(() => { setLoading(true); setLoadError(''); getDownloadInfo() - .then((data) => { - setInfo(data); - setLoadError(''); - }) - .catch((err) => { - console.error('다운로드 정보 로드 실패:', err); - setLoadError('배포 정보를 불러올 수 없습니다.'); - toast.error('배포 정보를 불러올 수 없습니다.'); - }) + .then((data) => setInfo(data)) + .catch(() => setLoadError('배포 정보를 불러올 수 없습니다.')) .finally(() => setLoading(false)); - }; + }, []); - // eslint-disable-next-line react-hooks/set-state-in-effect, react-hooks/exhaustive-deps -- initial data fetch on mount - useEffect(() => { load(); }, []); + // eslint-disable-next-line react-hooks/set-state-in-effect + useEffect(() => { load(); }, [load]); if (loading) { return ( diff --git a/src/components/admin/UploadForm.jsx b/src/components/admin/UploadForm.jsx index f8339b9..bc94a53 100644 --- a/src/components/admin/UploadForm.jsx +++ b/src/components/admin/UploadForm.jsx @@ -22,10 +22,11 @@ export default function UploadForm({ title, hint, accept, endpoint, onSuccess }) if (!file) return; const path = `${endpoint}?filename=${encodeURIComponent(file.name)}`; - setUploading(true); setError(''); + const fail = (msg) => { setError(msg); toast.error(msg); setProgress(0); }; + try { const { status, body } = await apiUpload(path, file, (p) => setProgress(p)); if (status >= 200 && status < 300) { @@ -35,28 +36,16 @@ export default function UploadForm({ title, hint, accept, endpoint, onSuccess }) if (fileInputRef.current) fileInputRef.current.value = ''; setProgress(0); } else if (status === 413) { - const msg = '파일 크기가 너무 큽니다. 더 작은 파일을 선택해주세요.'; - setError(msg); - toast.error(msg); + fail('파일 크기가 너무 큽니다. 더 작은 파일을 선택해주세요.'); } else if (status === 409) { - const msg = '동일한 파일이 이미 존재합니다.'; - setError(msg); - toast.error(msg); + fail('동일한 파일이 이미 존재합니다.'); } else if (status >= 500) { - const msg = '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'; - setError(msg); - toast.error(msg); + fail('서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'); } else { - const msg = body.error || '업로드에 실패했습니다.'; - setError(msg); - toast.error(msg); - setProgress(0); + fail(body.error || '업로드에 실패했습니다.'); } } catch { - const msg = '네트워크 오류가 발생했습니다.'; - setError(msg); - toast.error(msg); - setProgress(0); + fail('네트워크 오류가 발생했습니다.'); } finally { setUploading(false); } @@ -93,4 +82,4 @@ export default function UploadForm({ title, hint, accept, endpoint, onSuccess }) ); -} +} \ No newline at end of file diff --git a/src/components/admin/UserAdmin.jsx b/src/components/admin/UserAdmin.jsx index 30df481..e886955 100644 --- a/src/components/admin/UserAdmin.jsx +++ b/src/components/admin/UserAdmin.jsx @@ -1,12 +1,13 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { getUsers, updateUserRole, deleteUser } from '../../api/users'; import { useAuth } from '../../context/useAuth'; import { useToast } from '../toast/useToast'; import { useConfirm } from '../confirm/useConfirm'; import s from './AdminCommon.module.css'; +const PAGE_SIZE = 20; + export default function UserAdmin() { - const PAGE_SIZE = 20; const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [fetchError, setFetchError] = useState(false); @@ -16,7 +17,7 @@ export default function UserAdmin() { const toast = useToast(); const confirm = useConfirm(); - const load = (off = offset) => { + const load = useCallback((off = 0) => { setLoading(true); setFetchError(false); getUsers(off, PAGE_SIZE) @@ -24,14 +25,12 @@ export default function UserAdmin() { setUsers(data); setHasMore(data.length === PAGE_SIZE); }) - .catch((err) => { - console.error('유저 목록 로드 실패:', err); - setFetchError(true); - }) + .catch(() => setFetchError(true)) .finally(() => setLoading(false)); - }; - // eslint-disable-next-line react-hooks/set-state-in-effect, react-hooks/exhaustive-deps -- initial data fetch on mount - useEffect(() => { load(0); }, []); + }, []); + + // eslint-disable-next-line react-hooks/set-state-in-effect + useEffect(() => { load(0); }, [load]); const handleRoleToggle = async (u) => { const newRole = u.role === 'admin' ? 'user' : 'admin'; @@ -86,10 +85,26 @@ export default function UserAdmin() { ))}