Skip to content

Conversation

@YouD0313
Copy link
Collaborator

@YouD0313 YouD0313 commented Jun 1, 2025

구현내용

관리자 페이지 공지사항

연관이슈

close #323

Summary by CodeRabbit

  • 신규 기능

    • 관리자 공지사항 목록, 상세, 작성, 수정, 삭제 기능이 추가되었습니다.
    • 관리자 전용 검색바와 공지사항 관리 UI가 도입되었습니다.
    • 공지사항 상세에서 관리자는 수정/삭제 드롭다운 메뉴를 사용할 수 있습니다.
  • 버그 수정

    • 공지사항 상세, 목록, 버튼 등에서 관리자/사용자별로 경로 및 UI가 올바르게 분기 처리됩니다.
  • 스타일

    • 공지사항, 검색바, 버튼 등 다양한 컴포넌트의 스타일이 개선되고, 가로폭 등 레이아웃이 유연하게 조정되었습니다.
  • 기타

    • 공지사항 관련 API 및 커스텀 훅이 추가되어 관리자 기능이 강화되었습니다.
    • 일부 불필요한 콘솔 로그 및 사용하지 않는 코드가 정리되었습니다.

@YouD0313 YouD0313 requested a review from layout-SY June 1, 2025 13:44
@YouD0313 YouD0313 self-assigned this Jun 1, 2025
@YouD0313 YouD0313 linked an issue Jun 1, 2025 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Jun 1, 2025

Walkthrough

관리자 공지사항 기능이 신규로 구현되었습니다. 공지사항 목록, 상세, 작성, 수정, 삭제를 위한 페이지, 라우트, API, 훅, 스타일 컴포넌트가 추가 및 수정되었으며, 어드민과 유저의 공지사항 흐름이 분리되어 관리됩니다. 관련 라우팅, 상태 관리, 모달 메시지, UI 컴포넌트가 포함되어 있습니다.

Changes

파일/그룹 변경 요약
src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.tsx, .styled.ts 관리자 공지사항 리스트 컴포넌트 및 스타일 신규 추가
src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx, .styled.ts 관리자 공지사항 작성/수정 컴포넌트 및 스타일 신규 추가
src/pages/admin/adminNoticeDetail/AdminNoticeDetail.tsx 관리자 공지사항 상세 컴포넌트 신규 추가
src/components/admin/searchBar/SearchBar.tsx, .styled.ts 관리자용 검색바 컴포넌트 및 스타일 신규 추가
src/api/admin/customerService/Notice.api.ts 관리자 공지사항 등록, 수정, 삭제 API 함수 신규 추가
src/hooks/admin/useAdminNotice.ts 관리자 공지사항 등록/수정/삭제 커스텀 훅 신규 추가
src/constants/admin/adminModal.ts 관리자 공지사항 관련 모달 메시지 상수 신규 추가
src/models/customerService.ts 관리자 공지사항 작성용 인터페이스 WriteBody 신규 추가
src/constants/routes.ts ADMIN_ROUTE에 detail, write, modification 경로 상수 추가
src/routes/AdminRoutes.tsx 관리자 공지사항 라우트 및 중첩 라우트 추가
src/pages/admin/adminNotice/AdminNotice.tsx, .styled.ts 관리자 공지사항 페이지 내 Outlet 추가, 스타일 분리
src/components/user/customerService/noticeDetail/NoticeDetailBundle.tsx, .styled.ts $width prop 추가, admin 여부에 따라 헤더 렌더링 분기
src/pages/user/customerService/notice/noticeItem/NoticeItem.tsx, .styled.ts 공지사항 리스트 UI 분리 및 admin 경로 대응
src/pages/user/customerService/notice/Notice.tsx 공지사항 리스트 렌더링 NoticeItem 컴포넌트로 위임
src/pages/user/customerService/noticeDetail/NoticeDetail.tsx NoticeDetailBundle에 $width prop 전달
src/components/user/customerService/noticeDetail/content/NoticeDetailContent.tsx, .styled.ts 관리자 권한 시 드롭다운 메뉴(수정/삭제) 및 삭제 핸들러 추가, 관련 스타일 추가
src/components/user/customerService/noticeDetail/bottom/button/ListButton.tsx admin 경로 여부에 따라 목록 버튼 라우트 분기
src/components/common/admin/sidebar/AdminSidebar.styled.ts LayoutContainer에 max-width 속성 추가
src/components/common/admin/title/AdminTitle.styled.ts TitleContainer에 하단 마진 추가
src/components/common/dropDown/DropDown.tsx comment prop을 optional로 변경, 기본값 false 지정
src/hooks/user/useGetNoticeDetail.ts id 없을 시 쿼리 비활성화 및 early return 추가
src/hooks/user/useMyInfo.ts useMyProfileInfo 훅의 캐시/리패치 옵션 조정
src/routes/MergeRoutes.tsx ProtectAdminRoute 제거, NotificationProvider/Initializer 추가
src/routes/ProtectAdminRoute.tsx 관리자 인증 로직 강화, storage 이벤트 및 모달 처리 추가
src/main.tsx MSW(mock service worker) 구동 코드 주석 처리
src/pages/admin/adminMain/AdminMain.tsx 불필요한 React import 제거
src/components/user/customerService/CustomerServiceHeader.tsx keyword prop → value로 네이밍 변경 및 처리 방식 단순화

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: (성공시) 목록으로 이동, (실패시) 에러 안내
Loading
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: 상세 페이지 이동
Loading

Assessment against linked issues

Objective Addressed Explanation
공지사항 페이지 구현 (#323)
공지사항 작성 페이지 구현 (#323)
공지사항 작성/삭제 API 연결 (#323)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
MSW(mock service worker) 구동 코드 주석 처리 (src/main.tsx) 관리자 공지사항과 직접 관련 없는 개발 환경 설정 변경입니다.
useMyProfileInfo 훅의 캐시/리패치 옵션 변경 (src/hooks/user/useMyInfo.ts) 공지사항 기능과 직접적인 연관이 없는 사용자 정보 훅의 캐시 정책 변경입니다.
NotificationProvider/Initializer 추가 및 ProtectAdminRoute 제거 (src/routes/MergeRoutes.tsx) 공지사항 기능과 무관한 전체 알림 시스템 및 인증 경로 구조 변경입니다.
DropDown 컴포넌트 comment prop optional화 (src/components/common/dropDown/DropDown.tsx) 공지사항 기능과 직접적인 연관이 없는 드롭다운 컴포넌트의 prop 타입 개선입니다.

Suggested labels

⛏기능, 🛠개선

Suggested reviewers

  • layout-SY

Poem

📝🐇
새 공지 올리고, 수정도 척척
삭제 버튼 누르면 모달이 번쩍
어드민 토끼, 일 잘한다
API도, 라우트도, 스타일도 완벽하다
공지사항 세상, 이제 더 반짝!
🥕🎉

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

npm error Exit handler never called!
npm error This is an error with npm itself. Please report this error at:
npm error https://github.com/npm/cli/issues
npm error A complete log of this run can be found in: /.npm/_logs/2025-06-03T16_10_23_219Z-debug-0.log


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fbd88f4 and a808d89.

📒 Files selected for processing (7)
  • src/components/admin/searchBar/SearchBar.tsx (1 hunks)
  • src/components/user/customerService/CustomerServiceHeader.tsx (2 hunks)
  • src/components/user/customerService/noticeDetail/content/NoticeDetailContent.tsx (3 hunks)
  • src/hooks/user/useMyInfo.ts (1 hunks)
  • src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.styled.ts (1 hunks)
  • src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx (1 hunks)
  • src/routes/ProtectAdminRoute.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/components/user/customerService/noticeDetail/content/NoticeDetailContent.tsx
  • src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.tsx
  • src/components/admin/searchBar/SearchBar.tsx
  • src/pages/admin/adminNotice/adminNoticeWrite/AdminNoticeWrite.styled.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/components/user/customerService/CustomerServiceHeader.tsx (1)
src/hooks/useModal.ts (1)
  • useModal (3-36)
src/routes/ProtectAdminRoute.tsx (3)
src/hooks/user/useMyInfo.ts (1)
  • useMyProfileInfo (21-35)
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: accessibility-test
🔇 Additional comments (10)
src/components/user/customerService/CustomerServiceHeader.tsx (3)

18-18: prop destructuring 개선 승인

keyword prop을 value로 rename하여 더 범용적인 이름을 사용하는 것은 좋은 변경입니다. SearchBar 컴포넌트와의 일관성을 유지할 수 있습니다.


24-24: 키워드 결정 로직 개선 승인

외부에서 전달된 value prop이 있으면 우선 사용하고, 없으면 내부 상태인 inputValue를 사용하는 로직이 명확하고 합리적입니다. 이는 제어/비제어 컴포넌트 패턴을 모두 지원할 수 있게 해줍니다.


68-68: input value 속성 단순화 승인

조건문을 제거하고 미리 계산된 keyword 상수를 직접 사용하는 것이 코드를 더 읽기 쉽게 만듭니다. 로직의 중앙집중화로 유지보수성도 향상되었습니다.

src/routes/ProtectAdminRoute.tsx (7)

8-8: 필요한 훅 임포트가 추가되었습니다.

사용자 프로필 정보와 로딩 상태를 관리하기 위한 useMyProfileInfo 훅이 적절히 임포트되었습니다.


25-25: 사용자 데이터와 로딩 상태를 적절히 추출했습니다.

useMyProfileInfo 훅에서 myDataisLoading 상태를 가져와 인증 상태 관리에 활용할 수 있게 되었습니다.


28-28: 타이머 변수 선언이 적절합니다.

NodeJS.Timeout 타입으로 타이머 변수를 선언하여 메모리 누수를 방지할 수 있는 구조를 마련했습니다.


41-47: 로그인되지 않은 사용자 처리가 개선되었습니다.

타이머를 활용하여 1초 후 메인 페이지로 리디렉션하는 방식으로 변경되어 사용자 경험이 향상되었습니다.


50-52: 관리자가 아닌 사용자 처리의 일관성이 개선되었습니다.

타이머 방식을 사용하여 다른 케이스들과 일관된 처리 패턴을 유지하고, 1초의 충분한 지연 시간을 제공합니다.


61-66: 이벤트 리스너 관리가 적절합니다.

스토리지 이벤트 리스너를 마운트 시 등록하고 언마운트 시 정리하는 패턴이 올바르게 구현되었습니다. 타이머도 함께 정리하여 메모리 누수를 방지합니다.


68-68: 의존성 배열에 myData 추가가 필요했습니다.

useEffect 내에서 myData를 사용하므로 의존성 배열에 포함시킨 것이 올바른 React 훅 사용법입니다.

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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: 코드 중복 가능성 확인

SpinnerWrappersrc/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

📥 Commits

Reviewing files that changed from the base of the PR and between 3f6df80 and 7808d6a.

📒 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_ROUTE import가 제거되었습니다. 이 파일에서 실제로 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_ROUTE import 삭제
  • 검증 결과: 파일 내에 ADMIN_ROUTE 사용이 없어 정상적인 정리입니다.
src/constants/routes.ts (1)

44-44: 관리자 상세 페이지 라우트 추가 승인

관리자 공지사항 상세 페이지를 위한 detail 라우트가 적절하게 추가되었습니다. 명명 규칙과 값이 일관성 있고 명확합니다.

src/components/common/admin/title/AdminTitle.styled.ts (1)

3-5:

Details

❓ Verification inconclusive

스타일 간격 중복 가능성 검토

TitleContainermargin-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 tsx

Length 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% 너비에서 동적 $width prop을 사용하도록 변경하여 컴포넌트의 재사용성과 유연성이 향상되었습니다. 다양한 레이아웃 요구사항에 대응할 수 있어 좋은 개선사항입니다.

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 파일을 찾을 수 없습니다."
fi

Length of output: 875


관리자 라우트가 실제로 어디에서 보호되고 있는지 정확히 파악하기 위해 AdminRoutes.tsx에서 보호 래퍼 사용 부분을 확인해보겠습니다.


🏁 Script executed:

#!/bin/bash
# AdminRoutes.tsx에서 ProtectAdminRoute 래퍼가 감싸는 구간 컨텍스트 출력
rg -n -A3 -B3 "ProtectAdminRoute" src/routes/AdminRoutes.tsx

Length 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 로딩이 적절하게 추가되었습니다.

NoticeListNoticeDetail 컴포넌트에 대한 lazy 로딩이 추가되어 코드 스플리팅을 통한 성능 최적화가 적용되었습니다.


36-39: 인덱스 라우트 포맷팅이 개선되었습니다.

인덱스 라우트의 포맷팅이 일관성 있게 개선되어 가독성이 향상되었습니다.


43-50: 중첩 라우팅 구조가 올바르게 구현되었습니다.

공지사항에 대한 중첩 라우팅이 RESTful 패턴에 따라 잘 구성되었습니다:

  • 인덱스 라우트: 목록 페이지 (NoticeList)
  • 상세 라우트: 개별 공지사항 페이지 (NoticeDetail)

detail/:noticeId 경로 패턴이 직관적이고 확장 가능한 구조입니다.

src/pages/user/customerService/notice/noticeItem/NoticeItem.styled.ts (2)

4-8: 동적 너비 설정과 flex 레이아웃이 적절하게 구현되었습니다.

Wrapper 컴포넌트에 $width prop을 통한 동적 너비 설정과 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 인터페이스와 컴포넌트 시그니처가 적절합니다

$width prop을 통한 동적 너비 제어가 잘 구현되었고, TypeScript 인터페이스로 타입 안전성도 확보되었습니다.


60-60: 조건부 헤더 렌더링이 적절합니다

관리자 페이지에서 헤더를 숨기는 로직이 명확하고 사용자 경험을 고려한 구현입니다.

src/pages/admin/adminNotice/adminNoticeList/AdminNoticeList.tsx (3)

20-27: URL 검색 파라미터 동기화 로직이 적절합니다

컴포넌트 마운트 시 URL의 keyword 파라미터를 상태에 반영하는 로직이 잘 구현되었습니다.


29-35: 이벤트 핸들러 함수들이 잘 설계되었습니다

handleGetKeywordhandleChangePagination 함수들이 명확하고 단일 책임 원칙을 잘 따르고 있습니다.


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 인터페이스가 컴포넌트의 요구사항을 잘 정의하고 있습니다. $width prop의 기본값 설정도 적절합니다.


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.subscribeisLoggedIn 상태 변화를 구독해 자동 리다이렉트 처리
  • 또는 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: 의존성 개선과 컴포넌트 분리가 잘 되었습니다.

useSearchParams hook 도입으로 URL 파라미터 처리가 더 직관적이 되었고, NoticeItem 컴포넌트 분리로 코드 구조가 개선되었습니다.


18-18: 쿼리 파라미터 처리 방식이 크게 개선되었습니다.

기존의 useLocation과 수동 파싱 방식에서 useSearchParams hook을 사용하는 방식으로 변경하여 코드가 더 명확하고 안전해졌습니다.

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;
Copy link

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.

Comment on lines 14 to 17
export default function SearchBar({ onGetKeyword, value }: SearchBarProps) {
const [keyword, setKeyword] = useState<string>('');
const { isOpen, message, handleModalOpen, handleModalClose } = useModal();
const [searchParams, setSearchParams] = useSearchParams();
Copy link

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.

Copy link

@coderabbitai coderabbitai bot left a 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 헤더 설정이 적절합니다. 하지만 몇 가지 개선사항이 있습니다:

  1. 에러 로깅이 구체적이지 않아 디버깅이 어려울 수 있습니다
  2. 응답 데이터를 반환하지 않아 성공 여부나 추가 정보를 활용할 수 없습니다

다음과 같이 개선을 고려해보세요:

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7808d6a and 847b43c.

📒 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 ts

Length 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_ROUTE import가 제거되었습니다. 이는 관리자 라우트가 별도 파일로 분리되었음을 의미하는 것 같습니다.

다음 스크립트를 실행하여 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.tsx

Length of output: 2577


관리자 라우트 분리 확인됨

  • AppRoutes.tsx에서 ADMIN_ROUTE import가 제거되어 사용자용 라우트만 남아 있습니다.
  • 관리자용 경로는 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 패턴도 올바르게 사용되었습니다.

Comment on lines 39 to 55
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;
}
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

함수 반환 타입 명시와 일관된 에러 처리 개선 필요

인증 토큰 체크 로직 추가는 보안상 좋은 개선이지만, 몇 가지 개선사항이 있습니다:

  1. 함수 반환 타입이 명시되지 않아 타입 안정성이 떨어집니다
  2. 토큰이 없을 때 undefined 반환보다는 명시적인 에러나 null 반환을 고려해보세요
  3. 다른 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.

Comment on lines 27 to 60
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: '',
});
};
Copy link

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.

Suggested change
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.

Copy link

@coderabbitai coderabbitai bot left a 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

폼 제출 로직 개선 필요

현재 구현에 여러 문제점이 있습니다:

  1. FormData를 생성하지만 실제로는 사용하지 않음
  2. 검증은 form 상태로 하지만 제출은 formDataObj로 함
  3. 파일 업로드가 없는데 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

📥 Commits

Reviewing files that changed from the base of the PR and between 847b43c and fbd88f4.

📒 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: 좋은 개선사항입니다!

comment prop을 선택적으로 만들고 기본값을 제공한 것은 컴포넌트의 사용성을 향상시킵니다. 이전 버전과의 호환성도 유지되며, 컴포넌트 사용 시 불필요한 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)

다음과 같은 개선을 고려하세요:

  1. CSS 변수나 테마 값을 사용하여 위치 값 관리
  2. 반응형 디자인을 위한 상대적 단위 사용
  3. 드롭다운 위치를 동적으로 계산하는 로직 추가
 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;
 `;

@YouD0313 YouD0313 merged commit 0958d33 into develop Jun 3, 2025
4 checks passed
@YouD0313 YouD0313 deleted the feat/#323 branch June 3, 2025 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

관리자 공지사항

3 participants