-
Notifications
You must be signed in to change notification settings - Fork 0
관리자 페이지 공지사항 (#issue 323) #329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough관리자 공지사항 기능이 신규로 구현되었습니다. 공지사항 목록, 상세, 작성, 수정, 삭제를 위한 페이지, 라우트, API, 훅, 스타일 컴포넌트가 추가 및 수정되었으며, 어드민과 유저의 공지사항 흐름이 분리되어 관리됩니다. 관련 라우팅, 상태 관리, 모달 메시지, UI 컴포넌트가 포함되어 있습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Admin as 관리자
participant UI as AdminNoticeList/AdminNoticeWrite
participant API as Notice.api
participant Modal as Modal
Admin->>UI: 공지사항 작성/수정/삭제 시도
UI->>API: postNotice/putNotice/deleteNotice 호출
API-->>UI: 성공/실패 응답
UI->>Modal: 결과 메시지 표시
UI-->>Admin: (성공시) 목록으로 이동, (실패시) 에러 안내
sequenceDiagram
participant Admin as 관리자
participant AdminNoticeList as AdminNoticeList
participant SearchBar as SearchBar
participant NoticeItem as NoticeItem
participant NoticeDetail as AdminNoticeDetail
Admin->>AdminNoticeList: 페이지 진입
AdminNoticeList->>SearchBar: 검색어 입력
SearchBar-->>AdminNoticeList: 검색어 변경
AdminNoticeList->>NoticeItem: 공지사항 데이터 전달
Admin->>NoticeItem: 공지사항 선택
NoticeItem->>NoticeDetail: 상세 페이지 이동
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Suggested labels
Suggested reviewers
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
npm error Exit handler never called! 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (7)
🚧 Files skipped from review as they are similar to previous changes (4)
🧰 Additional context used🧬 Code Graph Analysis (2)src/components/user/customerService/CustomerServiceHeader.tsx (1)
src/routes/ProtectAdminRoute.tsx (3)
⏰ Context from checks skipped due to timeout of 90000ms (1)
🔇 Additional comments (10)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🔭 Outside diff range comments (1)
src/pages/admin/adminInquiries/AdminInquiries.tsx (1)
3-5:⚠️ Potential issue컴포넌트 명칭 오타로 인한 불일치
함수명AdminInquires가 파일명(AdminInquiries.tsx) 및 스타일 컴포넌트(AdminInquiriesContainer)와 맞지 않아 혼동을 초래할 수 있습니다. 컴포넌트명을AdminInquiries로 수정해 주세요.-export default function AdminInquires() { - return <S.AdminInquiriesContainer></S.AdminInquiriesContainer>; -} +export default function AdminInquiries() { + return <S.AdminInquiriesContainer />; +}
🧹 Nitpick comments (8)
src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.styled.ts (1)
4-4: 코드 중복 가능성 확인
SpinnerWrapper가src/pages/admin/adminNotice/AdminNotice.styled.ts에도 동일하게 정의되어 있는 것으로 보입니다. 공통 스타일 컴포넌트는 한 곳에서 정의하고 재사용하는 것이 좋겠습니다.// AdminNotice.styled.ts에서 export하고 여기서 import하는 방식 고려 import { SpinnerWrapper } from '../AdminNotice.styled';src/components/user/customerService/noticeDetail/bottom/button/ListButton.tsx (1)
11-12: 불필요한 nullish coalescing 연산자 제거
location.pathname.includes('admin')은 항상 boolean을 반환하므로?? false는 불필요합니다.- const includesAdmin = location.pathname.includes('admin') ?? false; + const includesAdmin = location.pathname.includes('admin');src/components/user/customerService/noticeDetail/NoticeDetailBundle.tsx (1)
21-21: 불필요한 nullish coalescing 연산자 제거이전 파일과 동일한 이슈입니다.
includes()메서드는 항상 boolean을 반환합니다.- const includesAdmin = location.pathname.includes('admin') ?? false; + const includesAdmin = location.pathname.includes('admin');src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.tsx (1)
24-25: 중복 로직 단순화 가능
setValue함수에서 삼항 연산자가 불필요합니다.searchKeyword가 이미 존재하는지 확인한 후에 실행되는 코드이므로 직접 할당하면 됩니다.- setValue((prev) => (searchKeyword ? searchKeyword : prev)); + setValue(searchKeyword);src/pages/admin/adminNotice/AdminNotice.styled.ts (1)
6-6: 빈 스타일 컨테이너를 구체화해보세요.
AdminNoticeContainer가 빈 styled div로 정의되어 있습니다. 향후 스타일링이 필요한 경우를 대비해 기본적인 레이아웃 스타일을 추가하는 것을 고려해보세요.예시:
-export const AdminNoticeContainer = styled.div``; +export const AdminNoticeContainer = styled.div` + padding: 1rem; + min-height: 100vh; +`;src/components/admin/searchBar/SearchBar.styled.ts (2)
10-18: 반응형 디자인을 고려해보세요.고정된 70% 너비가 모바일 환경에서 적절하지 않을 수 있습니다. 반응형 디자인을 위한 개선을 제안합니다.
export const AdminSearchBarWrapper = styled.div` display: flex; - width: 70%; + width: min(70%, 600px); + max-width: 90%; justify-content: space-between; padding: 0.5rem 0.5rem 0.5rem 1rem; border: 1px solid ${({ theme }) => theme.color.deepGrey}; border-radius: ${({ theme }) => theme.borderRadius.large} 0 0 ${({ theme }) => theme.borderRadius.large}; + + @media (max-width: 768px) { + width: 95%; + } `;
20-23: 테마 폰트 사이즈 사용을 고려해보세요.하드코딩된 폰트 사이즈 대신 테마 변수를 사용하면 일관성이 향상됩니다.
export const AdminSearchBarInput = styled.input` width: 100%; - font-size: 1.3rem; + font-size: ${({ theme }) => theme.fontSize?.large || '1.3rem'}; `;src/pages/user/customerService/notice/noticeItem/NoticeItem.tsx (1)
26-40: 성능 최적화를 고려해보세요.매 렌더링마다
map함수 내에서 라우트 문자열을 재생성하고 있습니다. 성능 개선을 위해useMemo를 사용하는 것을 고려해보세요.+import { useMemo } from 'react'; export default function NoticeItem({ noticeData, value, $width = '75%', }: NoticeItemProps) { const location = useLocation(); const includesAdmin = location.pathname.includes('admin'); + const baseRoute = useMemo(() => + includesAdmin + ? `${ADMIN_ROUTE.admin}/${ADMIN_ROUTE.notice}/${ADMIN_ROUTE.detail}` + : `${ROUTES.customerService}/${ROUTES.noticeDetail}`, + [includesAdmin] + ); + return ( <S.Wrapper $width={$width}> {noticeData.length > 0 && <ContentBorder />} {noticeData.length > 0 ? ( noticeData.map((list) => ( <S.NoticeDetailLink - to={ - includesAdmin - ? `${ADMIN_ROUTE.admin}/${ADMIN_ROUTE.notice}/${ADMIN_ROUTE.detail}/${list.id}` - : `${ROUTES.customerService}/${ROUTES.noticeDetail}/${list.id}` - } + to={`${baseRoute}/${list.id}`} state={{ id: list.id, keyword: value, includesAdmin }} key={list.id} >
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (23)
src/components/admin/searchBar/SearchBar.styled.ts(1 hunks)src/components/admin/searchBar/SearchBar.tsx(1 hunks)src/components/common/admin/sidebar/AdminSidebar.styled.ts(1 hunks)src/components/common/admin/title/AdminTitle.styled.ts(1 hunks)src/components/user/customerService/noticeDetail/NoticeDetailBundle.styled.ts(1 hunks)src/components/user/customerService/noticeDetail/NoticeDetailBundle.tsx(3 hunks)src/components/user/customerService/noticeDetail/bottom/button/ListButton.tsx(2 hunks)src/constants/routes.ts(1 hunks)src/hooks/user/useMyInfo.ts(1 hunks)src/pages/admin/adminInquiries/AdminInquiries.tsx(1 hunks)src/pages/admin/adminNotice/AdminNotice.styled.ts(1 hunks)src/pages/admin/adminNotice/AdminNotice.tsx(1 hunks)src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.styled.ts(1 hunks)src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.tsx(1 hunks)src/pages/admin/adminNoticeDetail/AdminNoticeDetail.tsx(1 hunks)src/pages/user/customerService/notice/Notice.tsx(3 hunks)src/pages/user/customerService/notice/noticeItem/NoticeItem.styled.ts(1 hunks)src/pages/user/customerService/notice/noticeItem/NoticeItem.tsx(1 hunks)src/pages/user/customerService/noticeDetail/NoticeDetail.tsx(1 hunks)src/routes/AdminRoutes.tsx(2 hunks)src/routes/AppRoutes.tsx(1 hunks)src/routes/MergeRoutes.tsx(1 hunks)src/routes/ProtectAdminRoute.tsx(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (11)
src/pages/admin/adminNoticeDetail/AdminNoticeDetail.tsx (1)
src/components/user/customerService/noticeDetail/NoticeDetailBundle.tsx (1)
NoticeDetailBundle(14-71)
src/pages/admin/adminNotice/AdminNotice.tsx (1)
src/components/common/admin/title/AdminTitle.tsx (1)
AdminTitle(8-17)
src/components/user/customerService/noticeDetail/NoticeDetailBundle.styled.ts (8)
src/pages/user/customerService/faq/FAQ.styled.ts (1)
Container(6-12)src/components/user/applyComponents/careersComponent/CareersComponent.styled.ts (1)
Container(4-6)src/components/user/projectFormComponents/projectInformationInput/positionComponent/PositionComponent.styled.ts (1)
Container(4-4)src/pages/user/customerService/notice/Notice.styled.ts (1)
Container(7-14)src/pages/user/apply/ApplyStep.styled.ts (1)
Container(4-11)src/pages/user/manage/myProjectParticipantsPass/MyProjectVolunteersPass.styled.ts (1)
Container(3-9)src/pages/user/manage/myProjectVolunteer/MyProjectVolunteer.styled.ts (1)
Container(3-7)src/pages/user/projectDetail/ProjectDetail.styled.ts (1)
Container(4-12)
src/components/user/customerService/noticeDetail/bottom/button/ListButton.tsx (1)
src/constants/routes.ts (2)
ADMIN_ROUTE(34-45)ROUTES(1-32)
src/routes/AdminRoutes.tsx (2)
src/constants/routes.ts (1)
ADMIN_ROUTE(34-45)src/pages/user/customerService/notice/Notice.tsx (1)
Notice(11-70)
src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.styled.ts (2)
src/pages/admin/adminNotice/AdminNotice.styled.ts (2)
SpinnerWrapper(4-4)NoticeItemWrapper(8-11)src/components/user/mypage/Spinner.styled.ts (1)
SpinnerWrapperStyled(29-35)
src/pages/user/customerService/notice/noticeItem/NoticeItem.tsx (2)
src/constants/routes.ts (2)
ADMIN_ROUTE(34-45)ROUTES(1-32)src/components/common/noResult/NoResult.tsx (1)
NoResult(8-17)
src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.tsx (5)
src/models/customerService.ts (1)
NoticeSearch(46-48)src/hooks/user/useGetNotice.ts (1)
useGetNotice(6-17)src/components/common/loadingSpinner/LoadingSpinner.styled.ts (1)
Spinner(21-28)src/components/admin/searchBar/SearchBar.tsx (1)
SearchBar(14-75)src/pages/user/customerService/notice/noticeItem/NoticeItem.tsx (1)
NoticeItem(15-46)
src/pages/admin/adminNotice/AdminNotice.styled.ts (2)
src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.styled.ts (2)
SpinnerWrapper(4-4)NoticeItemWrapper(6-9)src/components/user/mypage/Spinner.styled.ts (1)
SpinnerWrapperStyled(29-35)
src/routes/ProtectAdminRoute.tsx (2)
src/constants/user/modalMessage.ts (1)
MODAL_MESSAGE(1-30)src/constants/routes.ts (2)
ROUTES(1-32)ADMIN_ROUTE(34-45)
src/components/admin/searchBar/SearchBar.tsx (2)
src/hooks/useModal.ts (1)
useModal(3-36)src/constants/user/customerService.ts (1)
MODAL_MESSAGE_CUSTOMER_SERVICE(11-13)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: accessibility-test
🔇 Additional comments (39)
src/pages/admin/adminInquiries/AdminInquiries.tsx (1)
1-1: 올바른 import 경로 수정 확인
스타일 컴포넌트 파일 경로가AdminInquiries.styled로 정확히 수정되어 일관성이 유지되었습니다.src/routes/AppRoutes.tsx (1)
16-16:Details
✅ Verification successful
사용하지 않는 import 제거 확인
ADMIN_ROUTEimport가 제거되었습니다. 이 파일에서 실제로ADMIN_ROUTE를 사용하지 않는다면 올바른 정리입니다.다음 스크립트로 이 파일에서
ADMIN_ROUTE사용 여부를 확인해보겠습니다:
🏁 Script executed:
#!/bin/bash # 설명: AppRoutes.tsx 파일에서 ADMIN_ROUTE 사용 여부 확인 # 예상 결과: ADMIN_ROUTE 사용이 없어야 함 grep -n "ADMIN_ROUTE" src/routes/AppRoutes.tsx || echo "ADMIN_ROUTE 사용 없음 - 정상"Length of output: 101
불필요한 import
ADMIN_ROUTE제거 확인 완료
- 파일:
src/routes/AppRoutes.tsx(16행)- 변경 사항:
ADMIN_ROUTEimport 삭제- 검증 결과: 파일 내에
ADMIN_ROUTE사용이 없어 정상적인 정리입니다.src/constants/routes.ts (1)
44-44: 관리자 상세 페이지 라우트 추가 승인관리자 공지사항 상세 페이지를 위한
detail라우트가 적절하게 추가되었습니다. 명명 규칙과 값이 일관성 있고 명확합니다.src/components/common/admin/title/AdminTitle.styled.ts (1)
3-5:Details
❓ Verification inconclusive
스타일 간격 중복 가능성 검토
TitleContainer에margin-bottom: 1rem이 추가되었는데, 같은 파일의TitleWrapper에도margin-bottom: 2rem이 설정되어 있습니다.두 컴포넌트가 함께 사용될 때 의도한 간격이 나오는지 확인해주세요.
다음 스크립트로 이 컴포넌트들의 사용 패턴을 확인해보겠습니다:
🏁 Script executed:
#!/bin/bash # 설명: TitleContainer와 TitleWrapper의 사용 패턴 확인 # 예상 결과: 두 컴포넌트의 동시 사용 여부 파악 echo "=== TitleContainer 사용 패턴 ===" rg -A 5 -B 5 "TitleContainer" --type tsx echo "=== TitleWrapper 사용 패턴 ===" rg -A 5 -B 5 "TitleWrapper" --type tsxLength of output: 265
다음과 같이
tsx파일을 glob 패턴으로 지정해 다시 검색해 보겠습니다.
🏁 Script executed:
#!/bin/bash # 1) TitleWrapper 정의 확인 echo "=== TitleWrapper styled 정의 ===" rg -n "export const TitleWrapper" src/components/common/admin/title/AdminTitle.styled.ts # 2) TitleContainer 사용 위치 검색 echo "=== TitleContainer 사용 위치 (TSX) ===" rg -n "TitleContainer" -g '*.tsx' # 3) TitleWrapper 사용 위치 검색 echo "=== TitleWrapper 사용 위치 (TSX) ===" rg -n "TitleWrapper" -g '*.tsx'Length of output: 1872
AdminTitle 컴포넌트 마진 병합 확인
TitleContainer에
margin-bottom: 1rem을 추가했고, 내부에 이미margin-bottom: 2rem이 설정된 TitleWrapper가 존재합니다. CSS 상에서 두 마진이 병합되어 최종적으로 2rem이 적용될 수 있으니, AdminTitle 화면에서 의도한 간격이 반영되는지 확인해주세요.
- 확인 위치:
src/components/common/admin/title/AdminTitle.tsx(약 10–15라인)src/components/common/admin/sidebar/AdminSidebar.styled.ts (1)
4-4: 반응형 레이아웃 제약 추가가 적절합니다.최대 너비 1440px 제약은 대형 화면에서의 적절한 레이아웃 유지를 위한 좋은 관례입니다.
src/pages/admin/adminNoticeDetail/AdminNoticeDetail.tsx (1)
1-5: 기존 컴포넌트 재사용이 잘 구현되었습니다.
NoticeDetailBundle컴포넌트를 재사용하여 관리자와 사용자 간 일관된 로직을 유지하면서도 적절한 너비 차이(90% vs 75%)를 적용한 깔끔한 구현입니다.src/pages/user/customerService/noticeDetail/NoticeDetail.tsx (1)
4-4: 컴포넌트 인터페이스 일관성이 잘 유지되었습니다.
NoticeDetailBundle에$width프롭을 추가하여 컴포넌트 인터페이스를 일관되게 유지하고, 사용자 페이지와 관리자 페이지 간 적절한 너비 차이를 구현했습니다.src/pages/admin/adminNotice/AdminNotice.tsx (2)
1-1: 중첩 라우팅을 위한 적절한 import 추가.React Router v6의 중첩 라우팅 구현을 위해
Outlet을 올바르게 import했습니다.
7-10: 중첩 라우팅 구조가 올바르게 구현되었습니다.
AdminNoticeContainer사용과Outlet컴포넌트 추가로 하위 라우트(공지사항 목록, 상세)를 렌더링할 수 있는 적절한 구조를 구현했습니다. React Router v6의 모범 사례를 잘 따르고 있습니다.src/components/user/customerService/noticeDetail/NoticeDetailBundle.styled.ts (1)
6-7: 유연한 너비 설정으로 개선되었습니다.고정된 75% 너비에서 동적
$widthprop을 사용하도록 변경하여 컴포넌트의 재사용성과 유연성이 향상되었습니다. 다양한 레이아웃 요구사항에 대응할 수 있어 좋은 개선사항입니다.src/routes/MergeRoutes.tsx (1)
18-18:Details
❌ Incorrect review comment
관리자 라우트 보호 제거에 대한 보안 검증이 필요합니다.
ProtectAdminRoute래퍼가 제거되어 관리자 라우트의 접근 제어 계층이 사라졌습니다. 이로 인해 인증되지 않은 사용자가 관리자 페이지에 접근할 수 있는 보안 취약점이 발생할 수 있습니다.다른 곳에서 관리자 인증이 처리되는지 확인해주시기 바랍니다.
🏁 Script executed:
#!/bin/bash # 설명: 관리자 라우트 보호가 다른 곳에서 구현되었는지 확인 # ProtectAdminRoute가 AdminRoutes.tsx에서 여전히 사용되는지 확인 rg -A 5 "ProtectAdminRoute" --type tsx # 관리자 컴포넌트에서 인증 체크가 있는지 확인 ast-grep --pattern $'useEffect($$$) { $$$ auth$$$ $$$ }'Length of output: 130
아래 스크립트를 실행해
ProtectAdminRoute래퍼가 코드베이스 어디에 여전히 남아있는지, 또AdminRoutes.tsx파일이 존재하는지 확인해주세요.
🏁 Script executed:
#!/bin/bash # 1) ProtectAdminRoute 전역 검색 rg -n "ProtectAdminRoute" . # 2) AdminRoutes.tsx 파일 존재 여부 확인 및 해당 파일에서 ProtectAdminRoute 사용 확인 file=$(fd AdminRoutes.tsx) if [ -n "$file" ]; then echo "Found $file – checking for ProtectAdminRoute usage:" rg -n "ProtectAdminRoute" "$file" else echo "⚠️ AdminRoutes.tsx 파일을 찾을 수 없습니다." fiLength of output: 875
관리자 라우트가 실제로 어디에서 보호되고 있는지 정확히 파악하기 위해
AdminRoutes.tsx에서 보호 래퍼 사용 부분을 확인해보겠습니다.
🏁 Script executed:
#!/bin/bash # AdminRoutes.tsx에서 ProtectAdminRoute 래퍼가 감싸는 구간 컨텍스트 출력 rg -n -A3 -B3 "ProtectAdminRoute" src/routes/AdminRoutes.tsxLength of output: 560
ProtectAdminRoute는 AdminRoutes.tsx에서 여전히 적용됩니다.
- src/routes/AdminRoutes.tsx (31–33행):
<ProtectAdminRoute> <Sidebar /> </ProtectAdminRoute>보안 제어 계층이 유지되므로 추가 조치가 필요하지 않습니다.
Likely an incorrect or invalid review comment.
src/routes/AdminRoutes.tsx (3)
11-16: 공지사항 관련 컴포넌트의 lazy 로딩이 적절하게 추가되었습니다.
NoticeList와NoticeDetail컴포넌트에 대한 lazy 로딩이 추가되어 코드 스플리팅을 통한 성능 최적화가 적용되었습니다.
36-39: 인덱스 라우트 포맷팅이 개선되었습니다.인덱스 라우트의 포맷팅이 일관성 있게 개선되어 가독성이 향상되었습니다.
43-50: 중첩 라우팅 구조가 올바르게 구현되었습니다.공지사항에 대한 중첩 라우팅이 RESTful 패턴에 따라 잘 구성되었습니다:
- 인덱스 라우트: 목록 페이지 (
NoticeList)- 상세 라우트: 개별 공지사항 페이지 (
NoticeDetail)
detail/:noticeId경로 패턴이 직관적이고 확장 가능한 구조입니다.src/pages/user/customerService/notice/noticeItem/NoticeItem.styled.ts (2)
4-8: 동적 너비 설정과 flex 레이아웃이 적절하게 구현되었습니다.
Wrapper컴포넌트에$widthprop을 통한 동적 너비 설정과 flex 컨테이너 구성이 다른 컴포넌트들과 일관성 있게 구현되었습니다. 재사용성이 높은 좋은 구조입니다.
10-10: 기본 Link 스타일 컴포넌트가 추가되었습니다.현재는 기본
Link컴포넌트와 동일하지만, 향후 공지사항 링크에 특화된 스타일을 추가할 수 있는 확장 가능한 구조입니다.src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.styled.ts (1)
6-9: 스타일 컴포넌트 구현이 적절합니다
NoticeItemWrapper의 flexbox 중앙 정렬 구현이 깔끔하고 목적에 부합합니다.src/components/user/customerService/noticeDetail/bottom/button/ListButton.tsx (1)
20-24: 관리자/사용자 라우팅 로직이 적절합니다pathname에 'admin'이 포함되어 있는지를 확인하여 조건부 라우팅을 구현한 것이 명확하고 다른 컴포넌트들과 일관성을 유지합니다.
src/components/user/customerService/noticeDetail/NoticeDetailBundle.tsx (2)
10-16: Props 인터페이스와 컴포넌트 시그니처가 적절합니다
$widthprop을 통한 동적 너비 제어가 잘 구현되었고, TypeScript 인터페이스로 타입 안전성도 확보되었습니다.
60-60: 조건부 헤더 렌더링이 적절합니다관리자 페이지에서 헤더를 숨기는 로직이 명확하고 사용자 경험을 고려한 구현입니다.
src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.tsx (3)
20-27: URL 검색 파라미터 동기화 로직이 적절합니다컴포넌트 마운트 시 URL의 keyword 파라미터를 상태에 반영하는 로직이 잘 구현되었습니다.
29-35: 이벤트 핸들러 함수들이 잘 설계되었습니다
handleGetKeyword와handleChangePagination함수들이 명확하고 단일 책임 원칙을 잘 따르고 있습니다.
49-65: 컴포넌트 렌더링 구조가 우수합니다SearchBar, NoticeItem, Pagination 컴포넌트들의 통합과 props 전달이 적절하게 구현되었으며, 일관된 스타일링이 적용되었습니다.
src/pages/admin/adminNotice/AdminNotice.styled.ts (2)
2-2: 스피너 컴포넌트 재사용 패턴이 좋습니다!기존 사용자 페이지의
SpinnerWrapperStyled를 재사용하는 것은 일관성 있는 UI와 코드 중복 제거 측면에서 훌륭한 접근입니다.
8-11: 깔끔한 플렉스 레이아웃 구현입니다.
NoticeItemWrapper의 중앙 정렬 레이아웃이 적절하게 구현되었습니다.src/components/admin/searchBar/SearchBar.styled.ts (1)
3-8: 테마 변수 사용이 훌륭합니다!
AdminSearchBarContainer에서 적절한 레이아웃과 간격을 제공하고 있습니다.src/pages/user/customerService/notice/noticeItem/NoticeItem.tsx (3)
9-13: 인터페이스 정의가 명확합니다.
NoticeItemProps인터페이스가 컴포넌트의 요구사항을 잘 정의하고 있습니다.$widthprop의 기본값 설정도 적절합니다.
20-21: 위치 기반 라우팅 로직이 효율적입니다.
useLocation을 사용한 관리자 페이지 판별 로직이 깔끔하게 구현되었습니다.
42-42: NoResult 컴포넌트 사용이 적절합니다.고정 높이를 지정한
NoResult컴포넌트 사용이 UI 일관성 측면에서 좋습니다.src/routes/ProtectAdminRoute.tsx (5)
26-26: 타이머 변수 선언이 적절합니다.
NodeJS.Timeout타입을 사용한 타이머 변수 선언이 타입 안전성을 보장합니다.
39-45: 일관된 리다이렉트 지연시간이 UX를 향상시킵니다.1초 지연을 통해 사용자가 모달 메시지를 읽을 수 있는 충분한 시간을 제공하는 것이 좋습니다.
48-50: 타이머 설정 일관성이 좋습니다.로그인된 사용자의 권한 없음 케이스에서도 동일한 1초 지연을 적용한 것이 일관성 있는 UX를 제공합니다.
59-64: 메모리 누수 방지를 위한 정리 로직이 완벽합니다.이벤트 리스너와 타이머를 모두 정리하는 cleanup 함수가 메모리 누수를 방지합니다.
28-37:Details
✅ Verification successful
스토리지 이벤트 리스너가 세션 관리를 개선합니다.
외부에서 인증 정보가 삭제되었을 때 적절히 대응하는 로직이 훌륭합니다. 다만
storage이벤트는 동일한 윈도우에서는 트리거되지 않는다는 점을 고려해주세요.동일 윈도우 내에서의 localStorage 변경 감지가 필요한 경우를 확인해보세요:
🏁 Script executed:
#!/bin/bash # 프로젝트에서 localStorage.removeItem('auth-storage') 호출 지점 검색 rg -A 3 -B 3 "removeItem.*auth-storage"Length of output: 41
추가 검증을 위해 프로젝트 내에서
auth-storage사용 및removeItem,logout호출 지점을 확인해보세요:
🏁 Script executed:
#!/bin/bash # 'auth-storage' 키를 참조하는 모든 위치 검색 rg -n -C 3 "auth-storage" # localStorage.removeItem 호출 지점 검색 rg -n -C 3 "removeItem" # logout 함수 정의 및 호출 지점 검색 rg -n -C 3 "logout"Length of output: 7754
스토리지 이벤트 한계 안내 및 단일 윈도우 대응 방안
현재
handleStorageChange는 다른 탭에서auth-storage가 변경될 때만 트리거되며, 동일 창에서는 실행되지 않습니다. 또한 프로젝트 어디에서도localStorage.removeItem('auth-storage')를 직접 호출하지 않으므로 이 리스너는 크로스-탭 동기화를 위한 용도로만 동작합니다.단일 창에서 세션 만료나 로그아웃을 감지하려면 다음을 고려해주세요:
useAuthStore.subscribe로isLoggedIn상태 변화를 구독해 자동 리다이렉트 처리- 또는
logout()호출 지점(예:src/hooks/useAuth.ts,src/components/common/admin/sidebar/AdminSidebar.tsx등)에서 직접 모달 오픈 및 네비게이션 로직 수행점검 위치
- src/routes/ProtectAdminRoute.tsx:28–37 (
handleStorageChange구현부)- src/store/authStore.ts:43–46 (
logout구현부)위 주의사항만 참고하시면 추가 수정 없이도 현재 코드가 의도대로 동작합니다.
src/pages/user/customerService/notice/Notice.tsx (3)
8-9: 의존성 개선과 컴포넌트 분리가 잘 되었습니다.
useSearchParamshook 도입으로 URL 파라미터 처리가 더 직관적이 되었고,NoticeItem컴포넌트 분리로 코드 구조가 개선되었습니다.
18-18: 쿼리 파라미터 처리 방식이 크게 개선되었습니다.기존의
useLocation과 수동 파싱 방식에서useSearchParamshook을 사용하는 방식으로 변경하여 코드가 더 명확하고 안전해졌습니다.Also applies to: 21-27
57-61: 컴포넌트 구조 단순화가 잘 이루어졌습니다.복잡한 JSX 구조를
NoticeItem컴포넌트로 분리하여 Notice 컴포넌트의 책임이 명확해졌고, 재사용성도 높아졌습니다.src/components/admin/searchBar/SearchBar.tsx (2)
19-27: URL 파라미터 처리 로직이 잘 구현되었습니다.검색어가 있을 때는 파라미터를 설정하고, 없을 때는 삭제하는 로직이 적절하게 구현되어 있습니다.
29-39: 폼 제출 및 유효성 검증이 적절합니다.빈 검색어에 대한 유효성 검증과 모달 표시 로직이 잘 구현되어 있습니다.
trim()사용으로 공백만 있는 입력도 적절히 처리합니다.
| ); | ||
| } | ||
|
|
||
| if (!noticeData) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
빈 상태 처리 개선 필요
!noticeData일 때 단순히 return하는 것보다는 사용자에게 적절한 빈 상태 메시지를 보여주는 것이 더 나은 사용자 경험을 제공할 것입니다.
if (!noticeData) {
return (
<div>
<SearchBar onGetKeyword={handleGetKeyword} value={value} />
<div>데이터를 불러올 수 없습니다.</div>
</div>
);
}🤖 Prompt for AI Agents
In src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.tsx at line 45,
instead of simply returning when noticeData is falsy, update the code to render
a user-friendly empty state message. Include the SearchBar component and display
a message like "데이터를 불러올 수 없습니다." to inform users that data could not be loaded,
improving the user experience during empty or error states.
| export default function SearchBar({ onGetKeyword, value }: SearchBarProps) { | ||
| const [keyword, setKeyword] = useState<string>(''); | ||
| const { isOpen, message, handleModalOpen, handleModalClose } = useModal(); | ||
| const [searchParams, setSearchParams] = useSearchParams(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
상태 관리 구조를 개선해야 합니다.
컴포넌트가 로컬 keyword 상태와 외부 value prop을 모두 관리하고 있어 동기화 문제가 발생할 수 있습니다. 단일 진실 원칙(Single Source of Truth)을 위반합니다.
다음 중 하나의 방식으로 수정하는 것을 권장합니다:
방법 1: 완전한 제어 컴포넌트
- const [keyword, setKeyword] = useState<string>('');
+ // keyword 상태 제거하고 value prop만 사용방법 2: 로컬 상태만 사용
interface SearchBarProps {
- onGetKeyword: (value: string) => void;
- value: string;
+ onGetKeyword: (value: string) => void;
+ initialValue?: string;
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/admin/searchBar/SearchBar.tsx around lines 14 to 17, the
component manages both a local keyword state and an external value prop, causing
potential synchronization issues and violating the single source of truth
principle. To fix this, either convert the component into a fully controlled
component by removing the local keyword state and relying solely on the value
prop and onGetKeyword callback, or remove the value prop and onGetKeyword
callback and manage the keyword state locally within the component only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (5)
src/api/admin/customerService/Notice.api.ts (3)
4-15: postNotice 함수 구현 검토FormData와 multipart/form-data 헤더 설정이 적절합니다. 하지만 몇 가지 개선사항이 있습니다:
- 에러 로깅이 구체적이지 않아 디버깅이 어려울 수 있습니다
- 응답 데이터를 반환하지 않아 성공 여부나 추가 정보를 활용할 수 없습니다
다음과 같이 개선을 고려해보세요:
export const postNotice = async (formData: FormData) => { try { - await httpClient.post<ApiCommonBasicType>(`/notice`, formData, { + const response = await httpClient.post<ApiCommonBasicType>(`/notice`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); + return response.data; } catch (e) { - console.error(e); + console.error('공지사항 생성 실패:', e); throw e; } };
17-28: putNotice 함수의 중복 코드 개선 필요postNotice와 동일한 헤더 설정 로직이 중복됩니다. 공통 함수로 추출하거나 상수로 관리하는 것을 고려해보세요.
헤더 설정을 상수로 분리하여 중복을 제거할 수 있습니다:
+const MULTIPART_HEADERS = { + headers: { + 'Content-Type': 'multipart/form-data', + }, +}; export const putNotice = async (id: number, formData: FormData) => { try { - await httpClient.put<ApiCommonBasicType>(`/notice/${id}`, formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); + const response = await httpClient.put<ApiCommonBasicType>(`/notice/${id}`, formData, MULTIPART_HEADERS); + return response.data; } catch (e) { - console.error(e); + console.error('공지사항 수정 실패:', e); throw e; } };
30-37: deleteNotice 함수 구현 확인DELETE 요청 구현이 적절하지만, 다른 함수들과 마찬가지로 에러 메시지를 더 구체적으로 작성하는 것을 권장합니다.
} catch (e) { - console.error(e); + console.error('공지사항 삭제 실패:', e); throw e; }src/hooks/admin/useAdminNotice.ts (2)
21-48: handleButtonState 함수 리팩터링 고려함수가 복잡하고 여러 책임을 가지고 있습니다. 가독성과 유지보수성 향상을 위해 분리를 고려해보세요.
다음과 같이 분리하여 개선할 수 있습니다:
+ const getModalMessage = (state: State, isDeleteApi: boolean) => { + if (state === 'success') { + return isDeleteApi ? ADMIN_MODAL_MESSAGE.writeDeleteSuccess : ADMIN_MODAL_MESSAGE.writeSuccess; + } + if (state === 'fail') { + return isDeleteApi ? ADMIN_MODAL_MESSAGE.writeDeleteFail : ADMIN_MODAL_MESSAGE.writeFail; + } + return ADMIN_MODAL_MESSAGE.writeError; + }; + + const handleNavigation = () => { + setTimeout(() => { + pathname ? navigate(pathname) : navigate(-1); + }, 1000); + }; const handleButtonState = (state: State, isDeleteApi: boolean = false) => { - switch (state) { - case 'success': - if (!isDeleteApi) { - handleModalOpen(ADMIN_MODAL_MESSAGE.writeSuccess); - } else { - handleModalOpen(ADMIN_MODAL_MESSAGE.writeDeleteSuccess); - } - setTimeout(() => { - if (pathname) { - return navigate(pathname); - } else { - return navigate(-1); - } - }, 1000); - break; - case 'fail': - if (!isDeleteApi) { - handleModalOpen(ADMIN_MODAL_MESSAGE.writeFail); - } else { - handleModalOpen(ADMIN_MODAL_MESSAGE.writeDeleteFail); - } - break; - default: - handleModalOpen(ADMIN_MODAL_MESSAGE.writeError); - break; - } + const message = getModalMessage(state, isDeleteApi); + handleModalOpen(message); + + if (state === 'success') { + handleNavigation(); + } };
50-95: useMutation 훅들의 일관된 구현3개의 mutation 훅이 일관되게 잘 구현되었습니다. queryClient 무효화와 성공/실패 처리도 적절합니다.
다만 타입 안정성을 위해 타입 정의를 더 명확하게 할 수 있습니다.
타입 정의를 더 명확하게 개선할 수 있습니다:
+interface PutNoticeParams { + id: number; + formData: FormData; +} - const putNoticeMutate = useMutation< - void, - AxiosError, - { id: number; formData: FormData } - >({ + const putNoticeMutate = useMutation<void, AxiosError, PutNoticeParams>({ - mutationFn: ({ id, formData }: { id: number; formData: FormData }) => + mutationFn: ({ id, formData }: PutNoticeParams) => putNotice(id, formData),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
src/api/admin/customerService/Notice.api.ts(1 hunks)src/api/alarm.api.ts(2 hunks)src/components/admin/searchBar/SearchBar.styled.ts(1 hunks)src/components/admin/searchBar/SearchBar.tsx(1 hunks)src/constants/admin/adminModal.ts(1 hunks)src/constants/routes.ts(1 hunks)src/hooks/admin/useAdminNotice.ts(1 hunks)src/models/customerService.ts(1 hunks)src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.styled.ts(1 hunks)src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx(1 hunks)src/routes/AdminRoutes.tsx(2 hunks)src/routes/AppRoutes.tsx(2 hunks)src/routes/MergeRoutes.tsx(1 hunks)
✅ Files skipped from review due to trivial changes (3)
- src/constants/admin/adminModal.ts
- src/models/customerService.ts
- src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.styled.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- src/constants/routes.ts
- src/routes/MergeRoutes.tsx
- src/components/admin/searchBar/SearchBar.styled.ts
- src/routes/AdminRoutes.tsx
- src/components/admin/searchBar/SearchBar.tsx
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/api/alarm.api.ts (2)
src/api/http.api.ts (1)
httpClient(80-80)src/models/alarm.ts (1)
ApiAlarmList(3-5)
src/hooks/admin/useAdminNotice.ts (3)
src/constants/admin/adminModal.ts (1)
ADMIN_MODAL_MESSAGE(1-7)src/api/admin/customerService/Notice.api.ts (3)
postNotice(4-15)putNotice(17-28)deleteNotice(30-37)src/hooks/queries/user/keys.ts (1)
CustomerService(52-56)
src/api/admin/customerService/Notice.api.ts (2)
src/api/http.api.ts (1)
httpClient(80-80)src/models/apiCommon.ts (1)
ApiCommonBasicType(6-8)
🔇 Additional comments (9)
src/routes/AppRoutes.tsx (2)
387-387:Details
✅ Verification successful
라우터 아키텍처 변경 검증 필요
함수가
RouterProvider대신newRouteList를 직접 반환하도록 변경되었습니다. 이는 라우터 생성과 notification provider 래핑이 상위 레벨로 이동했음을 의미합니다.다음 스크립트를 실행하여 라우터 설정이 올바르게 상위 레벨로 이동되었는지 확인해보겠습니다:
🏁 Script executed:
#!/bin/bash # Description: 라우터 설정과 provider 래핑이 상위 레벨로 올바르게 이동되었는지 확인 # MergeRoutes.tsx에서 RouterProvider 사용 확인 echo "=== MergeRoutes.tsx에서 RouterProvider 사용 확인 ===" rg -A 10 -B 5 "RouterProvider\|createBrowserRouter" src/routes/MergeRoutes.tsx # NotificationProvider 래핑 확인 echo "=== NotificationProvider 래핑 확인 ===" rg -A 5 -B 5 "NotificationProvider\|ToastProvider" src/routes/MergeRoutes.tsx # AppRoutes 함수 사용처 확인 echo "=== AppRoutes 함수 사용처 확인 ===" rg -A 3 "AppRoutes" --type tsx --type tsLength of output: 476
추가 검증을 위해 다음 스크립트를 실행해주세요:
🏁 Script executed:
#!/bin/bash # MergeRoutes.tsx 파일이 존재하는지 확인 echo "=== 파일 존재 확인: MergeRoutes.tsx ===" fd MergeRoutes.tsx # RouterProvider 또는 createBrowserRouter 검색 (모든 .ts/.tsx 대상) echo "=== RouterProvider 검색 ===" rg -n "RouterProvider" --glob '*.ts' --glob '*.tsx' echo "=== createBrowserRouter 검색 ===" rg -n "createBrowserRouter" --glob '*.ts' --glob '*.tsx' # NotificationProvider 또는 ToastProvider 검색 (모든 .ts/.tsx 대상) echo "=== NotificationProvider 검색 ===" rg -n "NotificationProvider" --glob '*.ts' --glob '*.tsx' echo "=== ToastProvider 검색 ===" rg -n "ToastProvider" --glob '*.ts' --glob '*.tsx' # AppRoutes 함수 사용처 검색 echo "=== AppRoutes 사용처 검색 ===" rg -n "AppRoutes" --glob '*.ts' --glob '*.tsx'Length of output: 2369
라우터 아키텍처 변경 검증 완료
src/routes/MergeRoutes.tsx에서createBrowserRouter→<RouterProvider>로 라우터가 정상 이동- 같은 파일에서
<NotificationProvider>및<ToastProvider>래핑 확인AppRoutes()를 children으로 불러와 경로 리스트가 올바르게 적용됨위치와 동작 모두 기대한 대로 변경되었으므로 추가 조치 필요 없습니다.
16-16:Details
✅ Verification successful
관리자 라우트 import 제거 확인 필요
ADMIN_ROUTEimport가 제거되었습니다. 이는 관리자 라우트가 별도 파일로 분리되었음을 의미하는 것 같습니다.다음 스크립트를 실행하여
ADMIN_ROUTE사용처와 관리자 라우트 처리가 올바르게 이전되었는지 확인해보겠습니다:
🏁 Script executed:
#!/bin/bash # Description: ADMIN_ROUTE 사용처 확인 및 관리자 라우트 처리 검증 # ADMIN_ROUTE 사용처 검색 echo "=== ADMIN_ROUTE 사용처 검색 ===" rg -A 3 "ADMIN_ROUTE" # 관리자 관련 라우트 파일 검색 echo "=== 관리자 라우트 파일 검색 ===" fd -t f -e tsx -e ts | xargs rg -l -i "admin.*route" # AdminRoutes.tsx 파일 확인 echo "=== AdminRoutes.tsx 내용 확인 ===" cat src/routes/AdminRoutes.tsx 2>/dev/null || echo "AdminRoutes.tsx 파일이 존재하지 않습니다"Length of output: 11588
🏁 Script executed:
#!/bin/bash echo "=== AdminRoutes import 및 사용 확인 ===" rg -n "AdminRoutes" src/routes/AppRoutes.tsx || echo "AdminRoutes import/usage 없음" echo -e "\n=== AppRoutes.tsx 상단 1-50줄 ===" sed -n '1,50p' src/routes/AppRoutes.tsxLength of output: 2577
관리자 라우트 분리 확인됨
- AppRoutes.tsx에서
ADMIN_ROUTEimport가 제거되어 사용자용 라우트만 남아 있습니다.- 관리자용 경로는
src/routes/AdminRoutes.tsx에 별도 정의되어 있으며,ProtectAdminRoute로 접근이 보호됩니다.- AdminRoutes는 에러 처리(
errorElement)도 적용된 상태로 잘 구성되어 있습니다.위 변경은 의도된 아키텍처 분리로 보이며, 추가적인 수정은 필요하지 않습니다.
src/api/alarm.api.ts (1)
2-2: 인증 스토어 import 추가 확인관리자 기능 구현에 따른 인증 체크 로직 추가로 적절한 변경사항입니다.
src/api/admin/customerService/Notice.api.ts (1)
1-3: 적절한 타입 import와 HTTP 클라이언트 사용필요한 타입과 HTTP 클라이언트를 올바르게 import하고 있습니다.
src/hooks/admin/useAdminNotice.ts (2)
1-11: 적절한 dependency import와 타입 정의필요한 라이브러리와 모듈들을 올바르게 import하고 State 타입을 명확하게 정의했습니다.
14-20: 훅 매개변수와 초기 설정매개변수 타입과 훅 초기 설정이 적절합니다. pathname의 optional 처리도 잘 고려되었습니다.
src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx (3)
1-9: 적절한 import와 의존성 설정필요한 컴포넌트, 훅, 타입들을 올바르게 import하고 있습니다.
10-25: 컴포넌트 초기 설정과 상태 관리훅 사용과 초기 상태 설정이 적절합니다. location.state.from의 안전한 접근도 잘 처리되었습니다.
62-103: 컴포넌트 렌더링 구조JSX 구조와 이벤트 핸들러 연결이 적절하게 구현되었습니다. controlled component 패턴도 올바르게 사용되었습니다.
| export const testLiveAlarm = async () => { | ||
| try { | ||
| const response = await httpClient.get<ApiAlarmList>( | ||
| `/user/send-alarm?alarmFilter=0` | ||
| ); | ||
| const { accessToken } = useAuthStore.getState(); | ||
| if (accessToken) { | ||
| try { | ||
| const response = await httpClient.get<ApiAlarmList>( | ||
| '/user/send-alarm?alarmFilter=0' | ||
| ); | ||
|
|
||
| return response; | ||
| } catch (e) { | ||
| console.error(e); | ||
| throw e; | ||
| return response; | ||
| } catch (e) { | ||
| console.error(e); | ||
| throw e; | ||
| } | ||
| } else { | ||
| return; | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
함수 반환 타입 명시와 일관된 에러 처리 개선 필요
인증 토큰 체크 로직 추가는 보안상 좋은 개선이지만, 몇 가지 개선사항이 있습니다:
- 함수 반환 타입이 명시되지 않아 타입 안정성이 떨어집니다
- 토큰이 없을 때
undefined반환보다는 명시적인 에러나 null 반환을 고려해보세요 - 다른 API 함수들과 에러 처리 방식의 일관성을 유지해야 합니다
다음과 같이 개선하는 것을 권장합니다:
-export const testLiveAlarm = async () => {
+export const testLiveAlarm = async (): Promise<AxiosResponse<ApiAlarmList> | null> => {
const { accessToken } = useAuthStore.getState();
if (accessToken) {
try {
const response = await httpClient.get<ApiAlarmList>(
'/user/send-alarm?alarmFilter=0'
);
return response;
} catch (e) {
console.error(e);
throw e;
}
} else {
- return;
+ return null;
}
};🤖 Prompt for AI Agents
In src/api/alarm.api.ts around lines 39 to 55, the testLiveAlarm function lacks
an explicit return type, returns undefined when no accessToken is present, and
has inconsistent error handling compared to other API functions. To fix this,
explicitly declare the function's return type, replace the undefined return with
a null or a specific error to clearly indicate the absence of a token, and align
the error handling pattern with other API functions by possibly removing the
try-catch block here or handling errors uniformly.
| const handleSubmitInquiry = (e: React.FormEvent<HTMLFormElement>) => { | ||
| e.preventDefault(); | ||
|
|
||
| const formData = new FormData(e.currentTarget as HTMLFormElement); | ||
|
|
||
| const formDataObj: WriteBody = { | ||
| title: formData.get('title') as string, | ||
| content: formData.get('content') as string, | ||
| }; | ||
|
|
||
| const data = new Blob([JSON.stringify(formDataObj)], { | ||
| type: 'application/json', | ||
| }); | ||
|
|
||
| formData.append('inquiryDto', data); | ||
|
|
||
| const isValid = { | ||
| title: form.title.trim() !== '', | ||
| content: form.content.trim() !== '', | ||
| }; | ||
|
|
||
| if (!isValid.title) { | ||
| return handleModalOpen(INQUIRY_MESSAGE.writeTitle); | ||
| } | ||
| if (!isValid.content) { | ||
| return handleModalOpen(INQUIRY_MESSAGE.writeContent); | ||
| } | ||
|
|
||
| postNoticeMutate.mutate(formData); | ||
| setForm({ | ||
| title: '', | ||
| content: '', | ||
| }); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
폼 제출 로직 개선 필요
폼 데이터 처리가 복잡하고 중복이 있습니다. 또한 validation 후 상태 초기화 타이밍이 부적절합니다.
다음과 같이 개선하는 것을 권장합니다:
const handleSubmitInquiry = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
- const formData = new FormData(e.currentTarget as HTMLFormElement);
-
- const formDataObj: WriteBody = {
- title: formData.get('title') as string,
- content: formData.get('content') as string,
- };
-
- const data = new Blob([JSON.stringify(formDataObj)], {
- type: 'application/json',
- });
-
- formData.append('inquiryDto', data);
-
- const isValid = {
- title: form.title.trim() !== '',
- content: form.content.trim() !== '',
- };
-
- if (!isValid.title) {
+ // validation을 먼저 수행
+ if (!form.title.trim()) {
return handleModalOpen(INQUIRY_MESSAGE.writeTitle);
}
- if (!isValid.content) {
+ if (!form.content.trim()) {
return handleModalOpen(INQUIRY_MESSAGE.writeContent);
}
+ // validation 통과 후 FormData 생성
+ const formData = new FormData();
+ const data = new Blob([JSON.stringify(form)], {
+ type: 'application/json',
+ });
+ formData.append('inquiryDto', data);
+
postNoticeMutate.mutate(formData);
+
+ // mutation 성공 후 상태 초기화는 mutation의 onSuccess에서 처리하는 것이 좋습니다
- setForm({
- title: '',
- content: '',
- });
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleSubmitInquiry = (e: React.FormEvent<HTMLFormElement>) => { | |
| e.preventDefault(); | |
| const formData = new FormData(e.currentTarget as HTMLFormElement); | |
| const formDataObj: WriteBody = { | |
| title: formData.get('title') as string, | |
| content: formData.get('content') as string, | |
| }; | |
| const data = new Blob([JSON.stringify(formDataObj)], { | |
| type: 'application/json', | |
| }); | |
| formData.append('inquiryDto', data); | |
| const isValid = { | |
| title: form.title.trim() !== '', | |
| content: form.content.trim() !== '', | |
| }; | |
| if (!isValid.title) { | |
| return handleModalOpen(INQUIRY_MESSAGE.writeTitle); | |
| } | |
| if (!isValid.content) { | |
| return handleModalOpen(INQUIRY_MESSAGE.writeContent); | |
| } | |
| postNoticeMutate.mutate(formData); | |
| setForm({ | |
| title: '', | |
| content: '', | |
| }); | |
| }; | |
| const handleSubmitInquiry = (e: React.FormEvent<HTMLFormElement>) => { | |
| e.preventDefault(); | |
| // validation을 먼저 수행 | |
| if (!form.title.trim()) { | |
| return handleModalOpen(INQUIRY_MESSAGE.writeTitle); | |
| } | |
| if (!form.content.trim()) { | |
| return handleModalOpen(INQUIRY_MESSAGE.writeContent); | |
| } | |
| // validation 통과 후 FormData 생성 | |
| const formData = new FormData(); | |
| const data = new Blob([JSON.stringify(form)], { | |
| type: 'application/json', | |
| }); | |
| formData.append('inquiryDto', data); | |
| postNoticeMutate.mutate(formData); | |
| // mutation 성공 후 상태 초기화는 mutation의 onSuccess에서 처리하는 것이 좋습니다 | |
| }; |
🤖 Prompt for AI Agents
In src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx between
lines 27 and 60, the form submission logic is overly complex with redundant form
data handling and improper validation and state reset timing. Simplify by
validating the form fields directly from the form state before creating
FormData, perform validation checks first, and only if valid, create and submit
the FormData. Move the state reset to occur after a successful submission to
avoid clearing the form prematurely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🔭 Outside diff range comments (1)
src/api/customerService.api.ts (1)
32-41:⚠️ Potential issue에러 처리 일관성 문제가 있습니다.
getNoticeDetail함수에서 에러를 다시 throw하지 않도록 변경되었지만, 같은 파일의 다른 함수들(getFAQ,getNotice)은 여전히 에러를 throw하고 있습니다. 이러한 불일치는 예상치 못한 동작을 유발할 수 있습니다.또한 에러를 throw하지 않으면 함수가
undefined를 반환할 수 있어, 이를 사용하는 컴포넌트에서 적절한 에러 처리가 불가능해집니다.다음과 같이 수정하여 일관된 에러 처리를 유지하는 것을 권장합니다:
} catch (e) { console.error(e); - // throw e; + throw e; }
♻️ Duplicate comments (1)
src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx (1)
47-74:⚠️ Potential issue폼 제출 로직 개선 필요
현재 구현에 여러 문제점이 있습니다:
- FormData를 생성하지만 실제로는 사용하지 않음
- 검증은
form상태로 하지만 제출은formDataObj로 함- 파일 업로드가 없는데
multipart/form-data사용다음과 같이 개선하세요:
const handleSubmitInquiry = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); - const formData = new FormData(e.currentTarget as HTMLFormElement); - - const formDataObj: WriteBody = { - title: formData.get('title') as string, - content: formData.get('content') as string, - }; - - const isValid = { - title: form.title.trim() !== '', - content: form.content.trim() !== '', - }; - - if (!isValid.title) { + // 상태에서 직접 검증 + if (!form.title.trim()) { return handleModalOpen(INQUIRY_MESSAGE.writeTitle); } - if (!isValid.content) { + if (!form.content.trim()) { return handleModalOpen(INQUIRY_MESSAGE.writeContent); } if (!id) { - return postNoticeMutate.mutate(formDataObj); + return postNoticeMutate.mutate(form); } else { - return putNoticeMutate.mutate({ id, formDataObj }); + return putNoticeMutate.mutate({ id, formDataObj: form }); } };또한 폼에서
encType='multipart/form-data'를 제거하세요:- onSubmit={handleSubmitInquiry} - method='post' - encType='multipart/form-data' + onSubmit={handleSubmitInquiry}
🧹 Nitpick comments (5)
src/routes/ProtectAdminRoute.tsx (1)
28-30: 프로덕션 코드에서 console.log 제거 필요.디버깅용 console.log 구문이 프로덕션 코드에 남아있습니다.
다음과 같이 console.log를 제거하세요:
- let timer: NodeJS.Timeout; - console.log(myData); - + let timer: NodeJS.Timeout;src/hooks/user/useGetNoticeDetail.ts (1)
5-17: 중복된 조건 검사가 있습니다.
enabled: !!id옵션을 추가하여 이미 id가 falsy일 때 쿼리가 실행되지 않도록 처리했는데, 12-14번 줄의 early return은 불필요합니다. React Query는 enabled가 false일 때 자동으로isLoading: false를 반환합니다.다음과 같이 early return을 제거하는 것을 제안합니다:
export const useGetNoticeDetail = (id: string) => { const { data: noticeDetailData, isLoading } = useQuery({ queryKey: [CustomerService.noticeDetail, id], queryFn: () => getNoticeDetail(id), enabled: !!id, }); - if (!id) { - return { noticeDetail: undefined, isLoading: false }; - } - return { noticeDetail: noticeDetailData, isLoading }; };src/components/user/customerService/noticeDetail/content/NoticeDetailContent.tsx (1)
50-50: 불필요한 조건 검사입니다.
id는 props로 전달되는 number 타입이므로 항상 truthy 값입니다.Boolean(id)검사는 의미가 없습니다.props의 타입 정의를 보면
id는 항상 존재하므로 조건문을 제거할 수 있습니다:- {Boolean(id) && ( <S.InfoWrapper> <S.NoticeContentDate>{formatDate(createdAt)}</S.NoticeContentDate> <S.ViewWrapper> <S.ViewIcon> <EyeIcon /> </S.ViewIcon> <S.ViewCount>{viewCount}</S.ViewCount> </S.ViewWrapper> </S.InfoWrapper> - )}src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx (1)
21-21: 코드 간소화 제안불필요한 삼항 연산자와 함수 정의를 단순화할 수 있습니다.
다음과 같이 개선하세요:
- const id = noticeId ? noticeId : ''; + const id = noticeId || '';- const formDefault = () => { - setForm({ - title: '', - content: '', - }); - }; + const formDefault = () => setForm({ title: '', content: '' });Also applies to: 25-30
src/components/user/customerService/noticeDetail/content/NoticeDetailContent.styled.ts (1)
42-52: 버튼 접근성 및 사용성 개선
AdminAuthButton에 호버 효과와 접근성 기능을 추가하세요.export const AdminAuthButton = styled.button` height: fit-content; display: flex; justify-content: center; align-items: center; + padding: 0.25rem; + border-radius: ${({ theme }) => theme.borderRadius.primary}; + transition: background-color 0.2s ease; + cursor: pointer; + + &:hover { + background-color: ${({ theme }) => theme.color.lightgrey}; + } + + &:focus-visible { + outline: 2px solid ${({ theme }) => theme.color.primary}; + outline-offset: 2px; + } svg { width: 1rem; height: 1rem; } `;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
src/api/admin/customerService/Notice.api.ts(1 hunks)src/api/customerService.api.ts(1 hunks)src/components/admin/searchBar/SearchBar.styled.ts(1 hunks)src/components/common/dropDown/DropDown.tsx(1 hunks)src/components/user/customerService/noticeDetail/content/NoticeDetailContent.styled.ts(2 hunks)src/components/user/customerService/noticeDetail/content/NoticeDetailContent.tsx(3 hunks)src/constants/routes.ts(1 hunks)src/hooks/admin/useAdminNotice.ts(1 hunks)src/hooks/user/useGetNoticeDetail.ts(1 hunks)src/hooks/user/useMyInfo.ts(1 hunks)src/main.tsx(1 hunks)src/pages/admin/adminMain/AdminMain.tsx(0 hunks)src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx(1 hunks)src/routes/AdminRoutes.tsx(2 hunks)src/routes/ProtectAdminRoute.tsx(2 hunks)
💤 Files with no reviewable changes (1)
- src/pages/admin/adminMain/AdminMain.tsx
✅ Files skipped from review due to trivial changes (2)
- src/main.tsx
- src/components/admin/searchBar/SearchBar.styled.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- src/hooks/user/useMyInfo.ts
- src/constants/routes.ts
- src/routes/AdminRoutes.tsx
- src/api/admin/customerService/Notice.api.ts
- src/hooks/admin/useAdminNotice.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/routes/ProtectAdminRoute.tsx (3)
src/hooks/user/useMyInfo.ts (1)
useMyProfileInfo(21-37)src/constants/user/modalMessage.ts (1)
MODAL_MESSAGE(1-30)src/constants/routes.ts (2)
ROUTES(1-32)ADMIN_ROUTE(34-47)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Run Chromatic
🔇 Additional comments (9)
src/routes/ProtectAdminRoute.tsx (6)
8-8: useMyProfileInfo 훅 추가가 적절합니다.사용자 프로필 데이터를 통한 추가 인증 검증은 보안을 강화하는 좋은 접근입니다.
25-25: myData 추출이 적절합니다.useMyProfileInfo 훅에서 myData를 추출하여 인증 상태 검증에 활용하는 것이 좋습니다.
42-48: 타임아웃 증가가 사용자 경험을 개선합니다.200ms에서 1000ms로 타임아웃을 증가시켜 사용자가 모달 메시지를 읽을 충분한 시간을 제공하는 것이 좋습니다.
51-53: 일관된 타임아웃 적용이 좋습니다.모든 권한 없음 케이스에 동일한 1000ms 타임아웃을 적용하여 일관성을 유지하는 것이 좋습니다.
62-67: 이벤트 리스너 정리 로직이 적절합니다.storage 이벤트 리스너와 타이머에 대한 적절한 정리 로직이 메모리 누수를 방지합니다.
69-69: 의존성 배열에 myData 추가가 필요합니다.useEffect에서 myData를 사용하므로 의존성 배열에 추가하는 것이 올바릅니다.
src/components/common/dropDown/DropDown.tsx (1)
6-18: 좋은 개선사항입니다!
commentprop을 선택적으로 만들고 기본값을 제공한 것은 컴포넌트의 사용성을 향상시킵니다. 이전 버전과의 호환성도 유지되며, 컴포넌트 사용 시 불필요한 prop 전달을 줄일 수 있습니다.src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx (1)
76-78: 로딩 상태 처리가 적절합니다!공지사항 상세 정보를 불러오는 동안 스피너를 표시하는 것이 좋은 UX입니다.
src/components/user/customerService/noticeDetail/content/NoticeDetailContent.styled.ts (1)
58-69: 절대 위치 지정에 대한 우려사항
AdminLinkWrapper의 절대 위치 지정이 유지보수 문제를 야기할 수 있습니다:
- 음수 값 사용 (
top: -1rem,left: -5.5rem)- 고정 너비 (
5.5rem)다음과 같은 개선을 고려하세요:
- CSS 변수나 테마 값을 사용하여 위치 값 관리
- 반응형 디자인을 위한 상대적 단위 사용
- 드롭다운 위치를 동적으로 계산하는 로직 추가
export const AdminLinkWrapper = styled.nav` position: absolute; - top: -1rem; - left: -5.5rem; - width: 5.5rem; + top: 100%; + right: 0; + min-width: 5.5rem; display: flex; flex-direction: column; background: ${({ theme }) => theme.color.white}; border-radius: ${({ theme }) => theme.borderRadius.primary}; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); overflow: hidden; + z-index: 10; `;
구현내용
관리자 페이지 공지사항
연관이슈
close #323
Summary by CodeRabbit
신규 기능
버그 수정
스타일
기타