From 4e0716c1cb3f97612c395a3ed7e8c5bf1d48a946 Mon Sep 17 00:00:00 2001 From: tolelom <98kimsungmin@naver.com> Date: Sun, 12 Apr 2026 19:17:17 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20components/=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ConfirmProvider useMemo 불필요한 래핑 제거 - DownloadAdmin useCallback 적용, toast 중복 제거, eslint-disable 정리 - UserAdmin useCallback 적용, PAGE_SIZE 컴포넌트 밖으로 이동, 페이지네이션 버튼 가독성 개선 - UploadForm 에러 처리 fail 헬퍼로 중복 제거 - DownloadSection 후행 빈 줄 제거 Co-Authored-By: Claude Sonnet 4.6 --- src/components/DownloadSection.jsx | 2 +- src/components/admin/DownloadAdmin.jsx | 23 ++++--------- src/components/admin/UploadForm.jsx | 27 +++++---------- src/components/admin/UserAdmin.jsx | 39 +++++++++++++++------- src/components/confirm/ConfirmProvider.jsx | 6 ++-- 5 files changed, 45 insertions(+), 52 deletions(-) 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() { ))}
- -
diff --git a/src/components/confirm/ConfirmProvider.jsx b/src/components/confirm/ConfirmProvider.jsx index 65a5601..66e147d 100644 --- a/src/components/confirm/ConfirmProvider.jsx +++ b/src/components/confirm/ConfirmProvider.jsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useMemo, useRef } from 'react'; +import { useState, useCallback, useRef } from 'react'; import { ConfirmContext } from './confirmContextValue'; import './Confirm.css'; @@ -25,10 +25,8 @@ export function ConfirmProvider({ children }) { setDialog(null); }, []); - const value = useMemo(() => confirm, [confirm]); - return ( - + {children} {dialog && (