Skip to content

[Feat] feat:139 중 모임 홈#146

Closed
hongik-luke wants to merge 15 commits intodevfrom
feat-139
Closed

[Feat] feat:139 중 모임 홈#146
hongik-luke wants to merge 15 commits intodevfrom
feat-139

Conversation

@hongik-luke
Copy link
Contributor

📌 개요 (Summary)

[모임 홈화면]
0. 나의 상태 조회 GET /api/clubs/{clubId}/me -> 관리자 유무

  1. 모임 홈 화면 GET /api/clubs/{clubId}/home
  2. 가장 최근 공지사항 1개 조회 GET /api/clubs/{clubId}/notices/latest
  3. 이번 모임 바로가기 GET /api/clubs/{clubId}/meetings/next
    -> 넷 다 GET요청

[모임 관리]

page1 : 모임 가입 신청 관리
독서 모임 회원 관리(받아오기) : GET /api/clubs/{clubId}/members
독서 모임 회원 등급 수정(수정하기) : PATCH /api/clubs/{clubId}/members/{clubMemberId}

page2 : 모임 회원 관리
독서 모임 회원 관리(받아오기) : GET /api/clubs/{clubId}/members
독서 모임 회원 등급 수정(수정하기) : PATCH /api/clubs/{clubId}/members/{clubMemberId}

page3 : 모임 수정 (생성 페이지 사용 UI만들기)
[운영진] 독서 모임 상세 조회 GET /api/clubs/{clubId}
[운영진] 독서 모임 정보 수정 PUT /api/clubs/{clubId}

🛠️ 변경 사항 (Changes)

  • 새로운 기능 추가
  • 버그 수정
  • 코드 리팩토링
  • 문서 업데이트
  • 기타 (설명: )

📸 스크린샷 (Screenshots)

(UI 변경 사항이 있다면 첨부해주세요)

✅ 체크리스트 (Checklist)

  • 빌드가 성공적으로 수행되었나요? (pnpm build)
  • 린트 에러가 없나요? (pnpm lint)
  • 불필요한 콘솔 로그나 주석을 제거했나요?

Copilot AI review requested due to automatic review settings February 26, 2026 21:01
@vercel
Copy link

vercel bot commented Feb 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
check-mo Ready Ready Preview, Comment Feb 27, 2026 1:35am
checkmo Ready Ready Preview, Comment Feb 27, 2026 1:35am

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-139

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @hongik-luke, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 Pull Request는 모임 관련 페이지들의 백엔드 API 연동을 완료하고, 운영진을 위한 모임 관리 기능을 대폭 강화하는 데 중점을 두었습니다. 기존 더미 데이터를 실제 데이터로 대체하고, 모임 가입 신청 승인/거절, 회원 역할 변경, 모임 정보 수정 등 핵심 관리 기능을 사용자 인터페이스와 함께 구현하여 모임 운영의 편의성을 향상시켰습니다.

Highlights

  • 모임 관리 기능 구현: 모임 가입 신청 관리, 모임 회원 관리, 모임 정보 수정 등 운영진을 위한 다양한 관리 기능을 API 연동과 함께 구현했습니다.
  • API 연동 및 더미 데이터 제거: 기존에 더미 데이터를 사용하던 모임 관련 페이지들을 실제 API 엔드포인트에 연결하고, 더미 데이터를 완전히 제거하여 실제 서비스 데이터를 활용하도록 변경했습니다.
  • React Query 훅 도입: 모임 홈, 회원 목록, 관리자 상세 정보 등 새로운 API 연동을 위해 useInfiniteClubMembersQuery, useClubAdminDetailQuery, useClubhomeQueries 등 React Query 훅을 도입하여 데이터 관리 효율성을 높였습니다.
  • 카테고리 태그 컴포넌트 개선: 카테고리 태그 컴포넌트(ClubCategoryTags)가 숫자 배열뿐만 아니라 DTO 객체 배열도 처리할 수 있도록 유연성을 강화했습니다.
  • 모임 정보 수정 페이지 추가: 운영진이 모임의 이름, 설명, 프로필 이미지, 공개 여부, 카테고리, 활동 지역, SNS 링크 등을 수정할 수 있는 전용 페이지를 추가했습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/app/groups/[id]/admin/applicant/page.tsx
    • 더미 데이터를 제거하고 useInfiniteClubMembersQueryuseUpdateClubMemberStatusMutation 훅을 사용하여 실제 가입 신청 목록을 불러오고 상태를 업데이트하도록 변경했습니다.
    • 신청자 목록의 페이지네이션 로직을 개선하고, 데이터 로딩 및 에러 상태를 UI에 반영했습니다.
    • 외부 클릭 시 메뉴 닫기 로직을 수정하여 버튼 클릭 시 메뉴가 닫히지 않도록 개선했습니다.
  • src/app/groups/[id]/admin/edit/layout.tsx
    • 모임 수정 페이지를 위한 새로운 레이아웃 파일을 추가했습니다.
  • src/app/groups/[id]/admin/edit/page.tsx
    • 모임 정보 수정 페이지를 새로 추가했습니다.
    • 모임 상세 정보를 불러오고(GET), 수정된 정보를 저장(PUT)하는 API 연동 로직을 구현했습니다.
    • 모임 이름 중복 확인, 프로필 이미지 업로드, 카테고리 및 참여 대상 선택, 활동 지역 입력, SNS 링크 관리 등 모임 생성 페이지와 유사한 UI/UX를 제공합니다.
  • src/app/groups/[id]/admin/members/page.tsx
    • 더미 데이터를 제거하고 useInfiniteClubMembersQueryuseUpdateClubMemberStatusMutation 훅을 사용하여 실제 회원 목록을 불러오고 역할/상태를 업데이트하도록 변경했습니다.
    • 회원 상태(PENDING, WITHDRAWN, KICKED, OWNER)에 따라 역할 수정 드롭다운 메뉴의 동작을 다르게 처리하도록 로직을 추가했습니다.
    • 회원 목록 새로고침 버튼을 추가하고, 페이지네이션 로직을 개선하여 무한 스크롤과 호환되도록 조정했습니다.
  • src/app/groups/[id]/dummy.ts
    • 더미 데이터 파일을 제거했습니다.
  • src/app/groups/[id]/page.tsx
    • 더미 데이터를 제거하고 useClubhomeQueries 훅을 사용하여 모임 홈, 나의 상태, 최신 공지, 다음 모임 정보를 실제 API에서 불러오도록 변경했습니다.
    • 공지사항 및 Contact US 버튼 클릭 시 실제 데이터 유무에 따라 토스트 메시지를 표시하도록 개선했습니다.
    • Contact US 모달이 home.links 데이터를 기반으로 동적으로 링크를 표시하도록 변경했습니다.
  • src/app/groups/page.tsx
    • 카테고리 매핑 로직을 ClubCategoryDTO 타입에 맞춰 업데이트하고, 불필요한 CATEGORY_LABEL_TO_NUM 상수를 제거했습니다.
  • src/components/base-ui/Group-Search/search_clublist/search_club_category_tags.tsx
    • 카테고리 prop이 숫자 배열(number[]) 또는 CategoryDTO[] 타입을 모두 받을 수 있도록 타입을 확장했습니다.
    • DTO 배열이 전달될 경우 description 필드를 사용하여 태그를 렌더링하도록 로직을 추가했습니다.
  • src/components/base-ui/Group-Search/search_clublist/search_clublist_item.tsx
    • ClubSummary 타입의 category 필드를 number[]에서 ClubCategoryDTO[]로 변경했습니다.
  • src/components/base-ui/Group/group_admin_menu.tsx
    • 관리자 메뉴에 '모임 수정' 버튼을 추가하고, 해당 버튼 클릭 시 모임 수정 페이지로 이동하도록 라우팅 로직을 추가했습니다.
    • 메뉴 드롭다운의 높이를 '모임 수정' 버튼 추가에 맞춰 조정했습니다.
  • src/hooks/mutations/useClubAdminEditMutations.ts
    • 모임 관리자 정보 업데이트를 위한 useUpdateClubAdminMutation 훅을 새로 추가했습니다.
  • src/hooks/mutations/useClubMemberMutations.ts
    • 모임 회원 상태 업데이트를 위한 useUpdateClubMemberStatusMutation 훅을 새로 추가했습니다.
  • src/hooks/queries/useClubAdminEditQueries.ts
    • 모임 관리자 상세 정보를 불러오기 위한 useClubAdminDetailQuery 훅을 새로 추가했습니다.
  • src/hooks/queries/useClubMemberQueries.ts
    • 모임 회원 목록을 무한 스크롤 방식으로 불러오기 위한 useInfiniteClubMembersQuery 훅을 새로 추가했습니다.
  • src/hooks/queries/useClubhomeQueries.ts
    • 모임 홈 화면에 필요한 나의 상태, 모임 홈 정보, 최신 공지, 다음 모임 정보를 한 번에 관리하는 useClubhomeQueries 훅을 새로 추가했습니다.
  • src/lib/api/endpoints/Clubs.ts
    • 모임 홈, 나의 상태, 최신 공지, 다음 모임, 회원 목록/개별 회원, 모임 상세/수정 등 새로운 API 엔드포인트를 CLUBS 객체에 추가했습니다.
  • src/services/clubMemberService.ts
    • 모임 회원 관련 API 호출을 위한 clubMemberService를 새로 추가했습니다. (회원 목록 조회, 회원 상태 업데이트)
  • src/services/clubService.ts
    • 모임 홈 관련 API (나의 상태, 모임 홈, 최신 공지, 다음 모임) 및 관리자 모임 상세 조회/수정 API 호출 메서드를 추가했습니다.
    • API 응답에서 '공지사항 없음' 또는 '다음 정기모임 없음'과 같은 특정 에러 메시지를 처리하여 null을 반환하도록 예외 처리 로직을 추가했습니다.
  • src/types/groups/clubAdminEdit.ts
    • 모임 관리자 상세 조회 및 업데이트 요청/응답을 위한 새로운 타입들을 정의했습니다.
  • src/types/groups/clubMembers.ts
    • 모임 회원 상태, 명령어, 상세 정보, 목록 조회 파라미터 및 결과, 회원 상태 업데이트 요청 등 모임 회원 관리를 위한 새로운 타입들을 정의했습니다.
  • src/types/groups/grouphome.ts
    • 모임 홈 관련 타입들을 ApiResponse 인터페이스를 따르도록 재정의하고, 나의 상태, 최신 공지, 다음 모임에 대한 개별 응답 타입들을 추가했습니다.
Activity
  • hongik-luke 님이 새로운 모임 관리 기능과 API 연동을 구현했습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@hongik-luke hongik-luke changed the title Feat 139 [Feat] feat:139 중 모임 홈 Feb 26, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements group home and management features, transitioning from dummy data to actual API integration and utilizing react-query for clean asynchronous state management. The effort to improve code structure by grouping multiple API requests into a single custom hook is commendable. However, a critical security vulnerability was identified: a potential unsafe navigation issue where user-supplied nicknames are used in URL construction without proper encoding. This requires immediate attention. Additionally, the review highlighted areas for improvement concerning unfinished features, type safety, and enhancing hook reusability. Overall, this is a strong contribution, and other data handling practices, such as URL sanitization for external links, appear to follow security best practices.

Comment on lines +147 to +150
// TODO: useClubNameCheckQuery(name).refetch() 연결
// 임시: 무조건 available 처리
setNameCheck("available");
toast.success("사용 가능한 모임 이름입니다.");
Copy link
Contributor

Choose a reason for hiding this comment

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

high

모임 이름 중복 확인 기능이 완전히 구현되지 않았습니다. TODO 주석과 함께 임시로 항상 사용 가능한 것으로 처리하고 있습니다. 이대로는 중복된 모임 이름이 생성될 수 있어 데이터 무결성 문제가 발생할 수 있습니다. 실제 API를 호출하여 이름 중복을 확인하는 로직을 구현해야 합니다.

Comment on lines +233 to +235
const goProfile = (nickname: string) => {
router.push(`/profile/${nickname}`);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The nickname variable is used to construct a URL path without proper encoding. If a nickname contains special characters (e.g., .., /, ?, #), it could lead to unexpected navigation or path traversal within the application. It is recommended to use encodeURIComponent to ensure the URL is constructed safely, consistent with the implementation in src/app/groups/[id]/admin/members/page.tsx.

Suggested change
const goProfile = (nickname: string) => {
router.push(`/profile/${nickname}`);
};
const goProfile = (nickname: string) => {
router.push(`/profile/${encodeURIComponent(nickname)}`);
};

open: open === true,
};

await updateClub.mutateAsync(payload as any);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

payload as any를 사용하여 타입 에러를 우회하고 있습니다. payload.profileImageUrlnull이 될 수 있지만, UpdateClubAdminRequest 타입의 profileImageUrlstring으로 선언되어 타입이 일치하지 않습니다. src/types/groups/clubAdminEdit.ts 파일에서 UpdateClubAdminRequestprofileImageUrl 타입을 string | null로 수정하여 타입 정의를 바로잡고, as any 캐스팅을 제거하는 것이 좋습니다.

Comment on lines +22 to +27
onSuccess: async (_data, variables) => {
// 가입 신청 관리 페이지는 PENDING만 보면 되니까 이거 하나만 갱신해도 충분
await qc.invalidateQueries({
queryKey: clubMemberQueryKeys.members(variables.clubId, "PENDING"),
});
},
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

useUpdateClubMemberStatusMutationonSuccess 핸들러가 PENDING 상태의 쿼리만 무효화하도록 구현되어 있어 '가입 신청 관리' 페이지에 강하게 결합되어 있습니다. 이로 인해 다른 페이지(예: '모임 회원 관리' 페이지)에서 이 훅을 재사용하기 어렵고, 해당 페이지에서는 수동으로 refetch를 호출해야만 데이터가 갱신됩니다.

onSuccess 핸들러를 더 범용적으로 만드는 것이 좋습니다. 예를 들어, clubMemberQueryKeys.members(variables.clubId)와 같이 상위 쿼리 키를 무효화하여 모든 상태 필터에 대한 쿼리가 갱신되도록 할 수 있습니다. 이렇게 하면 훅의 재사용성이 높아지고 예기치 않은 데이터 불일치 문제를 방지할 수 있습니다.

    onSuccess: async (_data, variables) => {
      // PENDING, ALL 등 관련된 모든 멤버 목록 쿼리를 무효화합니다.
      await qc.invalidateQueries({
        queryKey: clubMemberQueryKeys.members(variables.clubId, "ALL"),
      });
      await qc.invalidateQueries({
        queryKey: clubMemberQueryKeys.members(variables.clubId, "PENDING"),
      });
    },

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds client-side support (types, services, React Query hooks, and pages) for the “모임 홈화면” and “모임 관리(가입 신청/회원 관리/모임 수정)” API endpoints described in the PR.

Changes:

  • Added new API types and endpoints for club home, latest notice, next meeting, member management, and admin club detail/update.
  • Implemented services + React Query hooks for fetching/updating club home/admin/member data.
  • Updated group detail/admin pages to replace dummy data with real API-backed UI flows (including new “모임 수정” route).

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
src/types/groups/grouphome.ts Defines response/result types for club home-related endpoints (and adds a category-code mapping).
src/types/groups/clubMembers.ts Adds member list/status/command request types for admin member management.
src/types/groups/clubAdminEdit.ts Adds types for admin club detail + update payload/response.
src/services/clubService.ts Extends club service with new home/admin endpoints and “latest notice/next meeting” helpers.
src/services/clubMemberService.ts Adds service for member list and member status patch API.
src/lib/api/endpoints/Clubs.ts Adds new club endpoints (me, home, latestNotice, nextMeeting, members, member, detail, update).
src/hooks/queries/useClubhomeQueries.ts Adds React Query hooks to fetch “me/home/latestNotice/nextMeeting” for club detail screen.
src/hooks/queries/useClubMemberQueries.ts Adds infinite-query hook for club members list.
src/hooks/queries/useClubAdminEditQueries.ts Adds query hook for admin club detail.
src/hooks/mutations/useClubMemberMutations.ts Adds mutation hook for member status updates + cache invalidation.
src/hooks/mutations/useClubAdminEditMutations.ts Adds mutation hook for admin club update + cache invalidation.
src/components/base-ui/Group/group_admin_menu.tsx Adds “모임 수정” navigation item to admin menu.
src/components/base-ui/Group-Search/search_clublist/search_clublist_item.tsx Updates club summary category type to DTO list.
src/components/base-ui/Group-Search/search_clublist/search_club_category_tags.tsx Updates category tag rendering to accept both legacy number[] and new DTO list.
src/app/groups/page.tsx Adapts group list mapping to the new category DTO shape and minor cleanup.
src/app/groups/[id]/page.tsx Replaces dummy club home with API-backed queries + link modal wiring.
src/app/groups/[id]/dummy.ts Removes dummy data used by the group detail page.
src/app/groups/[id]/admin/members/page.tsx Replaces dummy members with API-backed infinite list + PATCH actions.
src/app/groups/[id]/admin/edit/page.tsx Adds admin edit page (create UI reused) wired to admin detail/update APIs (currently with TODOs).
src/app/groups/[id]/admin/edit/layout.tsx Adds layout wrapper for the admin edit route.
src/app/groups/[id]/admin/applicant/page.tsx Replaces dummy applicants with API-backed PENDING list + approve/reject actions.
Comments suppressed due to low confidence (1)

src/services/clubService.ts:49

  • searchClubs 내부에서 any를 쓰고 있고, 현재 들여쓰기/블록 정렬이 깨져 있어(Prettier/린트 적용 시) CI에서 포맷 실패가 날 수 있습니다. const cleaned: Partial<ClubSearchParams> = { ...params }처럼 타입을 유지하면서 불필요한 필드만 제거하도록 정리해 주세요.
  searchClubs: async (params: ClubSearchParams) => {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const cleaned: any = { ...params };
  if (cleaned.cursorId == null) delete cleaned.cursorId;
  if (typeof cleaned.keyword === "string" && cleaned.keyword.trim() === "") {
    delete cleaned.keyword;
  }
  if (cleaned.inputFilter == null) delete cleaned.inputFilter;

  const res = await apiClient.get<ClubSearchResponse>(CLUBS.search, {
    params: cleaned,
  });
  return res.result;
},

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +41 to +42
profileImageUrl: string;
region: string;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

profileImageUrl/region를 non-null string으로 바꿨는데, 다른 DTO들(예: ClubDTO.profileImageUrl: string | null) 및 UI 코드에서는 null 가능성을 전제로 처리하고 있습니다. 실제 API가 null을 줄 수 있다면 런타임에서는 null이 오는데 타입이 이를 막아 잘못된 가정이 생기니, 응답 스펙에 맞게 string | null로 맞추는 게 좋습니다.

Suggested change
profileImageUrl: string;
region: string;
profileImageUrl: string | null;
region: string | null;

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +83
export const CLUB_CATEGORY_CODE_TO_NUM: Record<string, number> = {
TRAVEL: 1,
FOREIGN_LANGUAGE: 2,
CHILD_TEEN: 3,
RELIGION_PHILOSOPHY: 4,
FICTION_POETRY_DRAMA: 5,
ESSAY: 6,
HUMANITIES: 7,
SCIENCE: 8,
COMPUTER_IT: 9,
ECONOMY_BUSINESS: 10,
SELF_IMPROVEMENT: 11,
SOCIAL_SCIENCE: 12,
POLITICS_DIPLOMACY_DEFENSE: 13,
HISTORY_CULTURE: 14,
ART_POP_CULTURE: 15,
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

CLUB_CATEGORY_CODE_TO_NUM의 키가 clubCreate.tsClubCategoryCode(예: CHILDREN_BOOKS, ECONOMY_MANAGEMENT, SELF_DEVELOPMENT)와 다릅니다(CHILD_TEEN, ECONOMY_BUSINESS, SELF_IMPROVEMENT). 이 매핑을 실제로 사용할 경우 변환이 실패할 수 있으니, 백엔드 enum 값/프론트 공용 enum과 동일한 코드명으로 통일하거나, 정확한 매핑 규칙을 근거와 함께 맞춰 주세요.

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +45
export type ClubAdminDetail = {
clubId: number;
name: string;
description: string;
profileImageUrl: string;
region: string;
category: CodeDescItem[]; // [{code, description}]
participantTypes: CodeDescItem[]; // [{code, description}]
links: ClubLinkItem[]; // [{link, label}]
open: boolean;
};

export type ClubAdminDetailResponse = ApiResponse<ClubAdminDetail>;

/**
* [운영진] 독서 모임 정보 수정 PUT /api/clubs/{clubId}
*/
export type UpdateClubAdminRequest = {
name: string;
description: string;
profileImageUrl: string;
open: boolean;
region: string;
category: string[];
participantTypes: string[];
links: ClubLinkItem[];
};
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

ClubAdminDetail.profileImageUrlUpdateClubAdminRequest.profileImageUrlstring으로 고정되어 있는데, 생성 요청(CreateClubRequest)에서는 string | null로 정의되어 있고(edit 페이지에서도 default 프로필 선택 시 null을 보내도록 구현되어 있음) 타입/동작이 충돌합니다. API가 null을 허용한다면 여기 타입도 string | null로 맞추고, 허용하지 않는다면 edit 페이지에서 null 대신 기본 이미지 URL(또는 기존 값 유지)을 보내도록 수정해야 합니다.

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +81
getLatestNotice: async (clubId: number) => {
try {
const res = await apiClient.get<LatestNoticeResponse>(CLUBS.latestNotice(clubId));
return res.result;
} catch (e: any) {
const msg = e?.message ?? "";
if (
msg.includes("공지") && (msg.includes("없") || msg.includes("존재하지"))
) {
return null;
}
throw e;
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

catch (e: any)@typescript-eslint/no-explicit-any 규칙에 걸릴 가능성이 큽니다(같은 파일에서 이미 해당 룰을 disable하고 있음). catch (e: unknown)로 받고 e instanceof ApiError로 좁혀서 e.code/e.message를 사용하도록 바꾸는 게 안전합니다.

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +97
getLatestNotice: async (clubId: number) => {
try {
const res = await apiClient.get<LatestNoticeResponse>(CLUBS.latestNotice(clubId));
return res.result;
} catch (e: any) {
const msg = e?.message ?? "";
if (
msg.includes("공지") && (msg.includes("없") || msg.includes("존재하지"))
) {
return null;
}
throw e;
}
},

getNextMeeting: async (clubId: number) => {
try {
const res = await apiClient.get<NextMeetingResponse>(CLUBS.nextMeeting(clubId));
return res.result;
} catch (e: any) {
const msg = e?.message ?? "";
if (msg.includes("다음 정기모임이 존재하지 않습니다")) {
return null;
}
if (msg.includes("정기모임") && (msg.includes("없") || msg.includes("존재하지"))) {
return null;
}
throw e;
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

getLatestNotice/getNextMeeting에서 “공지/정기모임 없음”을 에러 메시지 한글 포함 여부로 판별하고 있는데, 메시지 문구가 바뀌면 바로 오동작합니다. apiClientApiErrorcode를 넣어주고 있으니(HTTP404 등), 가능하면 e instanceof ApiError + e.code(또는 e.response?.code) 기반으로 분기하도록 바꾸는 게 유지보수에 유리합니다.

Copilot uses AI. Check for mistakes.
import Link from 'next/link';
import { useRouter, useParams } from 'next/navigation';
import Image from "next/image";
import Link from "next/link";
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

현재 Link를 import 했지만 JSX에서는 사용하지 않습니다. next/link import는 제거하거나, 실제로 <Link>로 감싸서 사용할지 중 하나로 정리하지 않으면 린트(no-unused-vars) 에러가 날 수 있습니다.

Suggested change
import Link from "next/link";

Copilot uses AI. Check for mistakes.

const noticeText = latestNotice?.title ?? "공지사항이 없습니다.";
const hasNotice = Boolean(latestNotice?.id);
const noticeUrl = `/groups/${groupId}/notice`;
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

const noticeUrl = ...를 선언했지만 실제 이동은 하드코딩된 문자열로 router.push를 호출하고 있어 noticeUrl이 미사용 변수입니다. 선언을 제거하거나, 클릭/키다운 핸들러에서 noticeUrl을 사용하도록 통일해 주세요.

Suggested change
const noticeUrl = `/groups/${groupId}/notice`;

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +155
const onCheckName = async () => {
const name = clubName.trim();
if (!name) return;

// ✅ 이름이 원래랑 같으면 그냥 통과
if (name === initialName) {
setNameCheck("available");
toast.success("사용 가능한 모임 이름입니다.");
return;
}

// 여기서 실제 중복체크 훅이 있다면 create 페이지처럼 refetch하면 됨.
// 지금은 “UI 동일”이 목표라, 최소 동작만(나중에 hook 연결) 형태로 둠.
setNameCheck("checking");
try {
// TODO: useClubNameCheckQuery(name).refetch() 연결
// 임시: 무조건 available 처리
setNameCheck("available");
toast.success("사용 가능한 모임 이름입니다.");
} catch {
setNameCheck("idle");
toast.error("이름 중복 확인 실패");
}
};
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

모임 이름 중복 확인 로직이 현재는 TODO 상태로, 이름이 바뀐 경우에도 무조건 available로 처리합니다. 이 상태로는 UI가 잘못된 성공 메시지를 보여주고, 서버에서 중복으로 실패해도 원인을 알기 어렵습니다. create 페이지처럼 useClubNameCheckQuery를 연결해 실제 API 결과에 따라 available/duplicate를 설정하도록 구현해 주세요.

Copilot uses AI. Check for mistakes.
Comment on lines +241 to +253
const payload = {
name: clubName.trim(),
description: clubDescription.trim(),
profileImageUrl: profileMode === "upload" ? profileImageUrl : null,
region: activityArea.trim(),
category,
participantTypes,
links: linksPayload,
open: open === true,
};

await updateClub.mutateAsync(payload as any);
toast.success("모임 정보가 수정되었습니다.");
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

업데이트 payload에서 profileImageUrlnull을 넣을 수 있는데(profileMode !== "upload"), UpdateClubAdminRequest.profileImageUrl 타입은 string이고 호출부에서 payload as any로 우회하고 있습니다. 타입을 스펙에 맞게(string | null) 정리하고, as any 캐스팅 없이 mutateAsync를 호출하도록 수정해 주세요(서버가 null 미허용이면 기본값/기존값 유지로 처리 필요).

Copilot uses AI. Check for mistakes.
const endIndex = startIndex + itemsPerPage;
const currentApplicants = applicants.slice(startIndex, endIndex);
const goProfile = (nickname: string) => {
router.push(`/profile/${nickname}`);
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

프로필 페이지 이동에서 닉네임을 그대로 path에 붙이고 있어 공백/한글/특수문자 닉네임이면 라우팅이 깨질 수 있습니다. router.push(/profile/${encodeURIComponent(nickname)})처럼 인코딩해서 이동하는 쪽이 안전합니다.

Suggested change
router.push(`/profile/${nickname}`);
router.push(`/profile/${encodeURIComponent(nickname)}`);

Copilot uses AI. Check for mistakes.
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.

2 participants