[feat] 학교별 동아리 목록 페이지 구현#307
Hidden character warning
Conversation
* 205 feat 이미지 전처리 기능 구현 (#206) * feat: 전처리 로직 및 WebWorker 구현 * feat: 전처리 적용 및 preview 동시성 제어 로직 추가 * refactor: 리뷰 반영 * [hotfix] 하단바 너비 수정 (#208) * hotfix: 하단바 너비 수정 * chore: 불필요한 값 제거 * refactor: 고정 gap 제거 * Reapply "[feat] 광고 배너 추가 (#200)" This reverts commit c51ec85. * [feat] 하단바 리디자인 (#213) * chore: asset 추가 * feat: 하단바 리디자인 반영 및 레이아웃 수정 * [refactor] 광고 카드 레이아웃 밀림 수정 (#215) * refactor: 광고 개수 측정 시기 변경 및 기본값 제거 * feat: 스켈레톤 UI 추가 * feat: 인앱 알림 페이지 및 토스트 구현 (#217) * feat: 알림 API 및 스트림 기반 추가 * feat: 인앱 알림 레이어 추가 * feat: 알림 페이지 및 헤더 진입 구현 * fix: 알림 스트림 401 재시도 조건 정리 * fix: 알림 목록 이동 차단 제거 * refactor: 알림 공용 훅 위치 정리 * fix: 알림 재연결 캐시 동기화 추가 * fix: 알림 목록 토스트 큐 누적 방지 * fix: 알림 읽음 카운트 감소 조건 보강 * [refactor] 도메인별 TanStack Query 훅 정리 (#219) * chore: pwa용 이미지 제거 * refactor: auth 도메인 쿼리와 뮤테이션 정리 * refactor: council과 schedule 조회 훅 정리 * refactor: chat과 notification 캐시 처리 정리 * refactor: club 조회와 지원 플로우 정리 * refactor: manager 도메인 캐시 처리 정리 * refactor: studyTime 도메인 쿼리와 뮤테이션 정리 * refactor: 광고와 업로드 도메인 훅 정리 * [refactor] mutaton query 및 hook 추가 수정 (#221) * refactor: auth와 user myInfo 훅 정리 * refactor: club과 schedule 조회 훅 정리 * refactor: chat과 notification 훅 구조 정리 * refactor: club 지원 뮤테이션 훅 정리 * refactor: manager 뮤테이션 훅 구조 정리 * refactor: mutation 훅 cache 정리 * refactor: 컨벤션 통일 * refactor: isRead 조건 정리 * fix: 채팅 스크롤 문제 수정 * refactor: 불필요한 코드 제거 * [fix] 모바일 환경 입력창과 키보드 간의 간격이 큰 문제 수정 (#223) * chore: 가공용 safeArea 변수 선언 * refactor: 고정 패딩 값 수정 및 safeArea 적용 변경 * feat: 키보드 활성화 감지 및 safeArea 적용 여부 기능 추가 * refactor: 매직넘버 상수화 및 가로모드 처리 * [fix] 키보드 활성화 시 화면 흔들림 문제 수정 (#225) * refactor: 채팅 viewport 훅 네이밍 정리 * refactor: viewport 높이 잠금 훅 적용 시점 조정 * [fix] 키보드 활성화 시 채팅 화면 전체가 흔들리는 문제 수정 (#227) * refactor: 채팅 viewport 훅 네이밍 정리 * refactor: viewport 높이 잠금 훅 적용 시점 조정 * fix: 채팅 화면 스크롤 잠금으로 키보드 흔들림 완화 * fix: 입력 포커스 중 viewport offset 고정 (#229) * fix: 문서 루트 스크롤 잠금으로 빈 공간 잔류 방지 (#232) * [fix] 키보드 활성화 시 채팅 화면 상단 고정이 깨지고 빈 공간이 남는 문제 수정 (#234) * fix: 채팅 화면 상단 고정 깨짐과 빈 공간 잔류 수정 * refactor: 라우트 조건 수정 * fix: 문서 스크롤 위치 감지 보강 * refactor: 입력 요소 판별 유틸과 스크롤 주석 정리 * [fix] 키보드 활성화 시 채팅 화면에서 문서 스크롤이 발생하는 문제 수정 (#236) * fix: 채팅 문서 스크롤 제스처 차단 * fix: 입력 요소 터치 동작 예외 처리 * [fix] 키보드 활성화 시 채팅방이 마지막 메시지 위치를 유지하지 못하는 문제 수정 (#238) * fix: 키보드 활성화 시 채팅 하단 정렬 유지 * refactor: 채팅 리사이즈 관찰 안정화 * fix: mypage 연계 약관 페이지 뒤로가기 수정 (#240) * refactor: alias import 경로 정리 * fix: query 설정과 suspense 분기 정리 * refactor: 관리자 화면 스타일 유틸 정리 * fix: 이미지 전처리 예외 처리 보강 * fix: 헤더와 회비 화면 동작 정리 * fix: 공통 유틸 안정성 개선 * fix: 이미지 전처리 실패 처리를 보정 * fix: 모집 공고 저장 후 설정 반영 순서 조정 * fix: 부원 직책 변경 실패 처리를 보강 * fix: 약관 링크 접근성을 개선 * fix: 공통 쿼리와 유틸 안정성을 보완 * [feat] 동적 버전 정보 표시 구현 (#211) * feat: 동적 버전 정보 표시 구현 * refactor: 버전 정보 미 존재시 v 표시 제거 * [feat] 메인화면 동아리 카드 디자인 수정 반영 (#242) * feat: 메인화면 동아리 카드 디자인 수정 * chore: 하단바 아이콘 수정 * refactor: 코드래빗 리뷰 반영 * refactor: and 연산자로 변경 * apiClient 코드 중복 제거 및 네이티브 브릿지 인증 동기화 중앙화 (#244) * refactor: apiClient 코드 중복 제거 및 네이티브 브릿지 인증 동기화 중앙화 * refactor: body 직렬화 가드를 plain object/array로 한정 * fix: body 읽기 중 AbortError가 ParseError로 오분류되는 문제 수정 * [refactor] 에러 처리 유틸 및 utils 구조 정리 (#246) * refactor: 에러 처리 유틸 및 공통 토스트 흐름 정리 * refactor: utils 폴더 구조를 역할별로 정리 * refactor: 코드래빗 리뷰 반영 * refactor: 코드래빗 리뷰 반영 * Update src/pages/Home/components/HomeClubSection.tsx * fix: 인증 세션 복구 흐름 정리 * fix: 홈 동아리 카드 레이아웃 정리 * [feat] 총동아리 페이지 리디자인 및 하단 오버레이 정리 (#249) * refactor: 하단 오버레이 처리 공통화 * feat: 총동아리 페이지와 헤더 리디자인 반영 * fix: 채팅 하단 여백과 외부 링크 속성 수정 * refactor: 총동아리 헤더 설정 정리 * fix: 총동아리 상세 접근성과 스타일 보완 * [feat] 마이페이지 관리자 카드 분리 및 채팅 미확인 배지 반영 (#251) * feat: 하단 채팅 배지 표시 및 조회 조건 보완 * refactor: 관리자 정보 카드 컴포넌트 분리 * feat: 채팅 페이지 리디자인 (#252) * feat: 채팅 페이지 리디자인 * fix:tailwind 문법 수정 * fix: 코드 수정 * fix: 폰트 색상 및 위치 수정 * fix: 채팅방 사람수 정렬 * fix: 오타 수정 * chore: conflict 해결 중 누락된 부분 수정 * [refactor] 광고 렌더링 조건 수정 (#254) * refactor: 광고 렌더링 조건 수정 * docs: 문서명 변경 --------- Co-authored-by: 박성주 <145267904+ParkSungju01@users.noreply.github.com>
Walkthrough이 PR은 대학별 동아리 목록 페이지를 구현합니다. 새 API 데이터 모델(ClubCategory, UniversityClub, UniversityClubListResponse)과 getUniversityClubs 클라이언트 함수를 정의하고, React Query 설정으로 단일/무한 페이지 쿼리를 구성합니다. 352줄의 UniversityClubList 페이지는 라우트 파라미터 검증, URL 기반 검색/필터링, 디바운싱된 검색 입력, IntersectionObserver 무한 스크롤, 최근 동아리 섹션, 카테고리 필터 버튼, 빈 상태 메시지를 구현합니다. App.tsx에서 라우트를 추가하고 Home 페이지 대학 카드를 Link로 변경하여 새 페이지로 라우팅합니다. Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
apps/web/src/apis/universityClub/index.ts (1)
1-2: ⚡ Quick win내부 import는 상대경로 대신 경로 별칭(
@/*)으로 통일해주세요.현재 상대경로 import는 프로젝트 컨벤션과 맞지 않습니다.
As per coding guidelines, `**/*.{ts,tsx}`: Use path alias `@/*` for internal app imports (preferred over relative paths).diff 제안
-import { apiClient } from '../client'; -import type { UniversityClubListRequestParams, UniversityClubListResponse } from './entity'; +import { apiClient } from '`@/apis/client`'; +import type { UniversityClubListRequestParams, UniversityClubListResponse } from '`@/apis/universityClub/entity`';🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/apis/universityClub/index.ts` around lines 1 - 2, Summary: The file uses relative imports; replace them with the project's path alias. Fix: change the import of apiClient from '../client' to the path-alias equivalent (e.g., import { apiClient } from '`@/apis/client`') and change the entity types import from './entity' to the alias form (e.g., import type { UniversityClubListRequestParams, UniversityClubListResponse } from '`@/apis/universityClub/entity`'); ensure you update both import statements to use the '`@/`...' alias and keep the same identifiers (apiClient, UniversityClubListRequestParams, UniversityClubListResponse).apps/web/src/apis/universityClub/queries.ts (1)
3-4: ⚡ Quick win여기도 내부 import를
@/*별칭으로 맞춰주세요.쿼리 파일만 상대경로를 쓰면 import 규칙 일관성이 깨집니다.
As per coding guidelines, `**/*.{ts,tsx}`: Use path alias `@/*` for internal app imports (preferred over relative paths).diff 제안
-import type { UniversityClubListRequestParams, UniversityClubListResponse } from './entity'; -import { getUniversityClubs } from '.'; +import type { UniversityClubListRequestParams, UniversityClubListResponse } from '`@/apis/universityClub/entity`'; +import { getUniversityClubs } from '`@/apis/universityClub`';🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/apis/universityClub/queries.ts` around lines 3 - 4, The import uses a relative path for an internal module which violates the alias rule; change the imports in this file to use the app path alias: replace the relative import of getUniversityClubs from '.' with the alias form (e.g., import { getUniversityClubs } from '`@/apis/universityClub`' or the correct alias root for that module) and likewise ensure the type imports (UniversityClubListRequestParams, UniversityClubListResponse) use the '`@/`...' alias; update the import statements that reference the symbols getUniversityClubs, UniversityClubListRequestParams, and UniversityClubListResponse to the `@/`* path alias.apps/web/src/App.tsx (1)
6-6: ⚡ Quick win내부 import는 alias(
@/)로 맞춰주세요.상대 경로 대신 alias를 사용해 import 일관성을 맞추는 편이 좋습니다.
As per coding guidelines, "Use path alias@/*for internal app imports (preferred over relative paths)".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/App.tsx` at line 6, The import in App.tsx uses a relative path for UniversityClubList; replace the relative import with the project path alias format used across the app (e.g. import UniversityClubList from '`@/pages/UniversityClubList`') so internal imports follow the "`@/`..." convention and stay consistent with the codebase; update the import line that references UniversityClubList accordingly.apps/web/src/pages/UniversityClubList/index.tsx (1)
33-97: 🏗️ Heavy lift페이지 비즈니스 로직을 커스텀 훅으로 분리해주세요.
UniversityClubListContent에 URL 상태, 쿼리 파라미터 조합, observer 로직이 밀집되어 있어 라우트 엔트리 페이지 책임이 과합니다.useUniversityClubListState같은 훅으로 분리하면 유지보수성이 좋아집니다.
As per coding guidelines, "apps/*/src/pages/**/index.tsx: 라우트 엔트리 페이지 패턴을 확인해주세요: - 비즈니스 로직은 커스텀 훅으로 분리되어 있는지".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/pages/UniversityClubList/index.tsx` around lines 33 - 97, Extract the business logic from UniversityClubListContent into a new custom hook (e.g., useUniversityClubListState) that accepts universityId and encapsulates URL/search param handling (useSearchParams, selectedCategory, query, updateSearchQuery), local state (searchKeyword, setSearchKeyword), requestParams creation, data fetching via useSuspenseInfiniteQuery(universityClubQueries.infiniteList(...)), observer setup (observerRef and the IntersectionObserver effect), and handlers (handleSearchKeywordChange, handleCategoryChange); have the hook return observerRef plus derived values (university, clubs, total counts, recentClubs, hasNextPage, isFetchingNextPage, fetchNextPage) and the handler setters so UniversityClubListContent keeps only JSX and presentation logic while the new useUniversityClubListState contains all moved logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web/src/pages/Home/index.tsx`:
- Line 137: The Tailwind class string in the JSX div's className contains a typo
"xl: mt-7" which leaves "xl:" isolated; update the class value used in the Home
page component (the div with className "xl: mt-7 grid ...") by removing the
space so the token reads "xl:mt-7" to enable the intended responsive margin.
In `@apps/web/src/pages/UniversityClubList/index.tsx`:
- Around line 207-238: RecentClubCard and ClubCard render inert <button>
elements with no onClick; replace them with an appropriate non-interactive
container or a navigable link: if the cards are purely presentational, swap the
<button> in RecentClubCard and ClubCard for a semantic <article> or <div>
(remove type="button") and keep existing classes; if the cards are intended to
navigate, wrap the card contents with the app's Link component (e.g., next/link)
and move the className to the link element so keyboard users get correct
behavior. Ensure to update imports (add Link if used) and remove any
button-specific attributes (type="button", focus styles tied to button) or adapt
them for the chosen element.
---
Nitpick comments:
In `@apps/web/src/apis/universityClub/index.ts`:
- Around line 1-2: Summary: The file uses relative imports; replace them with
the project's path alias. Fix: change the import of apiClient from '../client'
to the path-alias equivalent (e.g., import { apiClient } from '`@/apis/client`')
and change the entity types import from './entity' to the alias form (e.g.,
import type { UniversityClubListRequestParams, UniversityClubListResponse } from
'`@/apis/universityClub/entity`'); ensure you update both import statements to use
the '`@/`...' alias and keep the same identifiers (apiClient,
UniversityClubListRequestParams, UniversityClubListResponse).
In `@apps/web/src/apis/universityClub/queries.ts`:
- Around line 3-4: The import uses a relative path for an internal module which
violates the alias rule; change the imports in this file to use the app path
alias: replace the relative import of getUniversityClubs from '.' with the alias
form (e.g., import { getUniversityClubs } from '`@/apis/universityClub`' or the
correct alias root for that module) and likewise ensure the type imports
(UniversityClubListRequestParams, UniversityClubListResponse) use the '`@/`...'
alias; update the import statements that reference the symbols
getUniversityClubs, UniversityClubListRequestParams, and
UniversityClubListResponse to the `@/`* path alias.
In `@apps/web/src/App.tsx`:
- Line 6: The import in App.tsx uses a relative path for UniversityClubList;
replace the relative import with the project path alias format used across the
app (e.g. import UniversityClubList from '`@/pages/UniversityClubList`') so
internal imports follow the "`@/`..." convention and stay consistent with the
codebase; update the import line that references UniversityClubList accordingly.
In `@apps/web/src/pages/UniversityClubList/index.tsx`:
- Around line 33-97: Extract the business logic from UniversityClubListContent
into a new custom hook (e.g., useUniversityClubListState) that accepts
universityId and encapsulates URL/search param handling (useSearchParams,
selectedCategory, query, updateSearchQuery), local state (searchKeyword,
setSearchKeyword), requestParams creation, data fetching via
useSuspenseInfiniteQuery(universityClubQueries.infiniteList(...)), observer
setup (observerRef and the IntersectionObserver effect), and handlers
(handleSearchKeywordChange, handleCategoryChange); have the hook return
observerRef plus derived values (university, clubs, total counts, recentClubs,
hasNextPage, isFetchingNextPage, fetchNextPage) and the handler setters so
UniversityClubListContent keeps only JSX and presentation logic while the new
useUniversityClubListState contains all moved logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 960310f5-3a66-457a-865a-2a88297610b9
📒 Files selected for processing (6)
apps/web/src/App.tsxapps/web/src/apis/universityClub/entity.tsapps/web/src/apis/universityClub/index.tsapps/web/src/apis/universityClub/queries.tsapps/web/src/pages/Home/index.tsxapps/web/src/pages/UniversityClubList/index.tsx
| > | ||
| <section className="min-h-0 overflow-hidden" aria-hidden={isSearching}> | ||
| <SectionTitle title="최근에 본 동아리" description="관심있게 봤던 동아리를 다시 확인해보세요." /> | ||
| <div className="xl: mt-7 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> |
There was a problem hiding this comment.
Tailwind 클래스 오타를 수정해주세요 (xl:).
Line 137의 className에 xl: mt-7가 있어 xl:가 단독 토큰으로 남습니다. xl:mt-7로 붙여야 의도한 반응형 마진이 적용됩니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/pages/Home/index.tsx` at line 137, The Tailwind class string in
the JSX div's className contains a typo "xl: mt-7" which leaves "xl:" isolated;
update the class value used in the Home page component (the div with className
"xl: mt-7 grid ...") by removing the space so the token reads "xl:mt-7" to
enable the intended responsive margin.
| function RecentClubCard({ club }: { club: UniversityClub }) { | ||
| return ( | ||
| <button | ||
| className="border-primary-200 bg-web-background hover:border-primary-500 focus-visible:outline-primary-500 flex h-[122px] max-h-[122px] min-h-[122px] w-full shrink-0 items-center gap-7 overflow-hidden rounded-[20px] border px-7.5 py-6.5 text-left transition-colors focus-visible:outline-2 focus-visible:outline-offset-2" | ||
| type="button" | ||
| > | ||
| <ClubImage className="size-[70px]" imageUrl={club.imageUrl} name={club.name} /> | ||
| <ClubMeta | ||
| club={club} | ||
| titleClassName="w-57 text-[20px]" | ||
| categoryClassName="text-[16px]" | ||
| descriptionClassName="text-[16px]" | ||
| /> | ||
| </button> | ||
| ); | ||
| } | ||
|
|
||
| function ClubCard({ club }: { club: UniversityClub }) { | ||
| return ( | ||
| <button | ||
| className="border-text-100 hover:border-primary-500 focus-visible:outline-primary-500 flex h-35 items-center gap-5 overflow-hidden rounded-[20px] border bg-white px-5.5 py-8 text-left transition-colors hover:shadow-[0_0_30px_0_rgba(105,191,223,0.18)] focus-visible:outline-2 focus-visible:outline-offset-2" | ||
| type="button" | ||
| > | ||
| <ClubImage className="size-[50px]" imageUrl={club.imageUrl} name={club.name} /> | ||
| <ClubMeta | ||
| club={club} | ||
| titleClassName="w-57 text-[20px]" | ||
| categoryClassName="text-[14px]" | ||
| descriptionClassName="text-[14px]" | ||
| /> | ||
| </button> | ||
| ); |
There was a problem hiding this comment.
동작 없는 button은 비대화형 요소로 바꿔주세요.
Line 207, Line 226의 카드가 button이지만 실제 액션이 없습니다. 키보드/스크린리더 사용자에게 “눌리지만 아무 일도 안 일어나는” 컨트롤로 노출됩니다. 클릭 동작이 없다면 article/div로 바꾸거나, 이동 의도가 있으면 Link로 연결해주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/web/src/pages/UniversityClubList/index.tsx` around lines 207 - 238,
RecentClubCard and ClubCard render inert <button> elements with no onClick;
replace them with an appropriate non-interactive container or a navigable link:
if the cards are purely presentational, swap the <button> in RecentClubCard and
ClubCard for a semantic <article> or <div> (remove type="button") and keep
existing classes; if the cards are intended to navigate, wrap the card contents
with the app's Link component (e.g., next/link) and move the className to the
link element so keyboard users get correct behavior. Ensure to update imports
(add Link if used) and remove any button-specific attributes (type="button",
focus styles tied to button) or adapt them for the chosen element.
✨ 요약
😎 해결한 이슈
Summary by CodeRabbit
릴리스 노트