Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 30 additions & 35 deletions src/pages/post/PostContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import DeleteModal from '@/assets/components/modal/DeleteModal';

interface PostDetailType {
id: number;
Expand Down Expand Up @@ -61,6 +62,8 @@
const [comments, setComments] = useState<CommentType[]>([]);
const [isCommentLoading, setIsCommentLoading] = useState(false);

const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);

const calculateTimeAgo = (createdAt: string) => {
const now = new Date();
const created = new Date(createdAt);
Expand Down Expand Up @@ -93,7 +96,7 @@
};

fetchPostDetail();
}, [postId]);

Check warning on line 99 in src/pages/post/PostContent.tsx

View workflow job for this annotation

GitHub Actions / build-and-lint

React Hook useEffect has a missing dependency: 'fetchComments'. Either include it or remove the dependency array

const handleLikeToggle = async () => {
if (!postId || !postData || isLikeLoading) return;
Expand Down Expand Up @@ -186,23 +189,30 @@
const handleAdminDelete = async () => {
if (!postId) return;

const confirmDelete = window.confirm('게시글을 삭제하시겠습니까?');
if (!confirmDelete) return;

try {
await instance.delete(`/api/admin/post/${postId}`);

toast.success('게시글이 삭제되었습니다.');
navigate(-1);
} catch (error: unknown) {
if (typeof error === 'object' && error !== null && 'response' in error) {
const status = (error as { response?: { status?: number } }).response
?.status;

if (status === 401) toast.error('로그인이 필요합니다.');
else if (status === 403) toast.error('관리자 권한이 없습니다.');
else if (status === 404) toast.error('게시글을 찾을 수 없습니다.');
else toast.error('게시글 삭제에 실패했습니다.');
if (status === 401) {
toast.error('로그인이 필요합니다.');
} else if (status === 403) {
toast.error('관리자 권한이 없습니다.');
} else if (status === 404) {
toast.error('게시글을 찾을 수 없습니다.');
} else {
toast.error('게시글 삭제에 실패했습니다.');
}
} else {
toast.error('서버와 통신할 수 없습니다.');
}
Comment on lines 198 to 213
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Axios 에러를 처리할 때 axios.isAxiosError 타입 가드를 사용하는 것이 더 안정적이고 간결합니다. 현재의 typeof error === 'object' && error !== null && 'response' in error 방식 대신 axios.isAxiosError(error)를 사용하면 코드가 더 명확해지고, Axios가 제공하는 타입 안전성을 활용할 수 있습니다. 이를 위해 파일 상단에 import axios from 'axios';를 추가해야 합니다.

      if (axios.isAxiosError(error)) {
        const status = error.response?.status;

        if (status === 401) {
          toast.error('로그인이 필요합니다.');
        } else if (status === 403) {
          toast.error('관리자 권한이 없습니다.');
        } else if (status === 404) {
          toast.error('게시글을 찾을 수 없습니다.');
        } else {
          toast.error('게시글 삭제에 실패했습니다.');
        }
      } else {
        toast.error('서버와 통신할 수 없습니다.');
      }

} finally {
setIsDeleteModalOpen(false);
}
};

Expand Down Expand Up @@ -231,29 +241,6 @@
}
};

const isAdmin = () => {
try {
const token = localStorage.getItem('accessToken');
if (!token) return false;

const payload = JSON.parse(atob(token.split('.')[1]));

console.log('JWT payload:', payload);

return (
payload.role === 'ADMIN' ||
payload.role === 'ROLE_ADMIN' ||
payload.auth === 'ROLE_ADMIN' ||
payload.authority === 'ADMIN' ||
payload.roles?.includes('ADMIN') ||
payload.roles?.includes('ROLE_ADMIN')
);
} catch (e) {
console.error('isAdmin error', e);
return false;
}
};

if (isLoading || !postData) {
return (
<>
Expand Down Expand Up @@ -363,11 +350,12 @@
<Report />
</button>

{isAdmin() && (
<button onClick={handleAdminDelete} className="cursor-pointer">
<Delete />
</button>
)}
<button
onClick={() => setIsDeleteModalOpen(true)}
className="cursor-pointer"
>
<Delete />
</button>
Comment on lines +353 to +358
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

삭제 버튼이 이제 모든 사용자에게 표시됩니다. 권한이 없는 사용자가 삭제 버튼을 클릭하고 모달에서 확인을 누르면 '관리자 권한이 없습니다'라는 오류 메시지를 받게 됩니다. 이는 사용자 경험에 좋지 않습니다.

이전의 isAdmin 함수는 컴포넌트 내에서 JWT를 직접 파싱하는 등 문제가 있어 제거한 것은 좋은 결정입니다. 하지만 사용자 역할에 따라 버튼을 조건부로 렌더링하는 로직은 유지하는 것이 좋습니다.

사용자 정보를 관리하는 전역 상태(Context API, Redux 등)나 커스텀 훅(예: useUser)을 사용하여 사용자의 역할을 확인하고, 관리자일 경우에만 이 삭제 버튼이 보이도록 수정하는 것을 권장합니다.

</div>
</div>

Expand Down Expand Up @@ -413,6 +401,13 @@
onReport={() => setIsModalOpen(false)}
/>
)}

{isDeleteModalOpen && (
<DeleteModal
onClose={() => setIsDeleteModalOpen(false)}
onDelete={handleAdminDelete}
/>
)}
</>
);
}
Loading