Skip to content

176 feat 홈화면 리디자인#177

Merged
ff1451 merged 11 commits intodevelopfrom
176-feat-홈화면-리디자인
Mar 10, 2026

Hidden character warning

The head ref may contain hidden characters: "176-feat-\ud648\ud654\uba74-\ub9ac\ub514\uc790\uc778"
Merged

176 feat 홈화면 리디자인#177
ff1451 merged 11 commits intodevelopfrom
176-feat-홈화면-리디자인

Conversation

@ff1451
Copy link
Copy Markdown
Collaborator

@ff1451 ff1451 commented Mar 10, 2026

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 홈 화면에 주간 일정 미리보기 및 일정 섹션 추가
    • 공의회 공지 섹션 추가 (최신 3건 표시)
    • 동아리 추천 무한 카루셀 및 추천 카드 추가
  • 스타일

    • 하단 네비게이션 중앙 정렬·둥근 컨테이너·활성색 조정
    • 헤더 시각적 개선(그림자·패딩·타이포그래피)
    • 전체 레이아웃에 스크롤바 숨김 스타일 적용
  • 버그 수정 / 안정성

    • 로딩 스켈레톤 및 오류 폴백 추가로 섹션별 로드 안정성 강화
  • 정리

    • 사용되지 않는 일부 간단 카드 컴포넌트 제거 및 구성 간소화

@ff1451 ff1451 self-assigned this Mar 10, 2026
@ff1451 ff1451 linked an issue Mar 10, 2026 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 10, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1269f811-48d1-4d86-ad22-72ce0b8e5eff

📥 Commits

Reviewing files that changed from the base of the PR and between 9282227 and da4c330.

📒 Files selected for processing (21)
  • src/apis/club/queries.ts
  • src/components/layout/Header/components/InfoHeader.tsx
  • src/components/layout/Header/components/NotificationBell.tsx
  • src/components/layout/index.tsx
  • src/pages/Club/Application/hooks/useApplyToClub.ts
  • src/pages/Club/Application/hooks/useClubApply.ts
  • src/pages/Club/Application/hooks/useGetClubFee.ts
  • src/pages/Club/ClubDetail/hooks/useGetClubDetail.ts
  • src/pages/Club/ClubDetail/hooks/useGetClubMembers.ts
  • src/pages/Club/ClubDetail/hooks/useGetClubRecruitment.ts
  • src/pages/Club/ClubList/hooks/useGetClubs.ts
  • src/pages/Home/components/CouncilNoticeCard.tsx
  • src/pages/Home/components/HomeClubSection.tsx
  • src/pages/Home/components/InfiniteClubCarousel.tsx
  • src/pages/Home/components/MiniSchedulePreview.tsx
  • src/pages/Home/components/RecommendedClubCard.tsx
  • src/pages/Home/components/SectionErrorFallback.tsx
  • src/pages/Home/components/SectionTitle.tsx
  • src/pages/Home/hooks/useGetHomeClubs.ts
  • src/pages/Home/hooks/useInfiniteClubCarousel.ts
  • src/pages/Manager/hooks/useManagedClubs.ts

Walkthrough

홈 페이지를 모듈화하여 CouncilNoticeSection, HomeClubSection, ScheduleSection 등 섹션 컴포넌트를 추가했고, 각 섹션을 SectionAsyncBoundary로 래핑해 Suspense 및 Sentry ErrorBoundary 처리를 통합했습니다. BottomNav를 데이터 드리븐으로 리팩터링해 NavLink 기반 렌더링과 BottomNavItem 구성으로 대체했고, 여러 홈 관련 훅(useGetHomeMyClubs, useGetHomeRecruitingClubs, useInfiniteClubCarousel 등)과 카루셀/미니 스케줄 컴포넌트를 추가했습니다. SimpleClubCard·SimpleAppliedClubCard와 useGetAppliedClubs·useGetJoinedClubs 파일은 삭제되었고, SCHEDULE_DAYS와 clubQueryKeys가 새로 추가되어 여러 파일의 import 경로가 변경되었습니다.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 홈화면 리디자인이라는 주요 변경사항을 명확하게 요약하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 176-feat-홈화면-리디자인

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.

Copy link
Copy Markdown

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

홈 화면 리디자인을 위해 기존 Home 페이지의 섹션 렌더링/로딩/에러 처리 방식을 컴포넌트 단위로 재구성하고, 주간(미니) 일정 미리보기·공의회 공지·동아리 캐러셀 UI를 추가/개선합니다. 동시에 레이아웃(스크롤바 숨김), 헤더/하단 네비게이션 스타일도 함께 업데이트합니다.

Changes:

  • Home을 섹션 단위 컴포넌트(AsyncBoundary + Skeleton/ErrorFallback)로 분리하고 새 UI 구성 적용
  • 동아리 무한 캐러셀 훅/컴포넌트 추가 및 모집중/내 동아리 섹션 구성 변경
  • 미니 일정 프리뷰/공지 카드/레이아웃 스크롤바/헤더·하단 네비게이션 스타일 업데이트, 요일 상수 공통화

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/pages/Schedule/index.tsx 요일 라벨을 공통 상수로 교체
src/pages/Home/types.ts 홈 캐러셀 카드용 타입 추가
src/pages/Home/index.tsx Home 섹션을 SectionAsyncBoundary 기반 구성으로 재작성
src/pages/Home/hooks/useInfiniteClubCarousel.ts 무한 캐러셀 스크롤/인디케이터/루프 보정 로직 추가
src/pages/Home/hooks/useGetJoinedClubs.ts 기존 단일 joined 훅 제거
src/pages/Home/hooks/useGetHomeClubs.ts 홈 전용(내 동아리 + 모집중) 쿼리 훅 추가
src/pages/Home/hooks/useGetAppliedClubs.ts 기존 단일 applied 훅 제거
src/pages/Home/components/SimpleClubCard.tsx 기존 단순 카드 컴포넌트 제거
src/pages/Home/components/SimpleAppliedClubCard.tsx 기존 단순 카드 컴포넌트 제거
src/pages/Home/components/SectionTitle.tsx 섹션 헤더(제목/더보기) 컴포넌트 추가
src/pages/Home/components/SectionErrorFallback.tsx 홈 섹션 공용 에러 폴백 UI 추가
src/pages/Home/components/SectionAsyncBoundary.tsx 섹션 단위 Suspense + Sentry ErrorBoundary 래퍼 추가
src/pages/Home/components/ScheduleSection.tsx 일정 섹션(타이틀 + 미니 프리뷰) 컴포넌트화
src/pages/Home/components/RecommendedClubCard.tsx 캐러셀 카드 UI 추가
src/pages/Home/components/MiniSchedulePreview.tsx 홈 미니 일정 프리뷰 추가
src/pages/Home/components/InfiniteClubCarousel.tsx 캐러셀 렌더/인디케이터 UI 추가
src/pages/Home/components/HomeClubSection.tsx 내 동아리/모집중 동아리 섹션 구성 변경
src/pages/Home/components/CouncilNoticeSection.tsx 공의회 공지 섹션 추가(스켈레톤/폴백 포함)
src/pages/Home/components/CouncilNoticeCard.tsx 공지 카드 스타일/타입 적용 변경
src/constants/schedule.ts 요일 상수(SCHEDULE_DAYS) 추가
src/components/layout/index.tsx main 스크롤바 숨김 클래스 추가
src/components/layout/Header/components/NotificationBell.tsx 알림 아이콘 교체
src/components/layout/Header/components/InfoHeader.tsx 헤더 스타일(패딩/그림자/라운드) 변경
src/components/layout/BottomNav/index.tsx BottomNav를 NavLink 기반으로 리팩터링 및 스타일 변경

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

Comment on lines +171 to +183
useEffect(() => {
const scrollNode = scrollRef.current;
if (!scrollNode) return;

const handleScrollEnd = () => {
if (isAdjustingLoopRef.current) return;
syncCarouselPosition();
};

scrollNode.addEventListener('scrollend', handleScrollEnd);
return () => {
scrollNode.removeEventListener('scrollend', handleScrollEnd);
clearRestoreSnapFrame();
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

scrollend 이벤트에만 의존해 루프 위치 보정(syncCarouselPosition)을 실행하고 있는데, scrollend는 브라우저 지원이 제한적이라(특히 iOS Safari) 보정 로직이 아예 동작하지 않을 수 있습니다. 이 경우 3세그먼트 복제 리스트의 끝에 도달하면 무한 캐러셀처럼 동작하지 않습니다. scroll 이벤트에 debounce/timeout(예: 마지막 스크롤 후 N ms)으로 sync를 호출하거나, pointerup/touchend 등 보조 트리거를 추가해 호환성 있는 종료 감지를 구현해 주세요.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +73
const dateList = getMonthDateList();
const weeks: Date[][] = [];

for (let index = 0; index < dateList.length; index += 7) {
weeks.push(dateList.slice(index, index + 7));
}

const handleDateClick = (date: Date) => {
navigate(`/schedule?year=${date.getFullYear()}&month=${date.getMonth() + 1}&day=${date.getDate()}`);
};

return (
<div className="h-[193px] overflow-hidden rounded-[20px] bg-white px-3 pt-3 shadow-[0_0_3px_rgba(0,0,0,0.2)]">
<ul className="grid grid-cols-7 justify-items-center text-center text-[13px] leading-5 font-bold text-indigo-600">
{SCHEDULE_DAYS.map((dayLabel) => (
<li key={dayLabel}>{dayLabel}</li>
))}
</ul>

<div className="mt-1">
{weeks.map((weekDates) => (
<CalendarWeekRow
key={weekDates[0].toISOString()}
dates={weekDates}
schedules={data.schedules}
isCurrentMonth={isCurrentMonth}
isSelectedDay={isSelectedDay}
isSunday={isSunday}
onDateClick={handleDateClick}
/>
))}
</div>
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

현재 getMonthDateList()로 월 전체(최대 6주)를 만든 뒤 모든 weeks를 렌더링하지만, 컨테이너가 h-[193px] overflow-hidden이라 대부분의 주가 잘려서(특히 오늘이 월 후반인 경우) 사용자가 ‘이번 주/오늘’이 보이지 않는 미리보기를 보게 됩니다. 주간 미리보기 의도라면 ‘오늘이 포함된 주(또는 ±1주)’만 slice해서 렌더링하거나, 월 전체를 보여줄 거라면 높이/overflow 정책을 조정(세로 스크롤 허용 등)해 주세요.

Copilot uses AI. Check for mistakes.
Comment thread src/components/layout/BottomNav/index.tsx
Comment thread src/pages/Home/components/InfiniteClubCarousel.tsx
Comment thread src/pages/Home/index.tsx
Comment on lines 6 to +9
<Link
to={`/council/notice/${id}`}
key={id}
className="bg-indigo-0 border-indigo-5 block rounded-lg border-b px-3 py-3"
className="block rounded-lg border border-[#f4f6f9] bg-white px-3 py-3 shadow-[0_0_3px_rgba(0,0,0,0.2)]"
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

key는 리스트 렌더링 시 부모가 부여하는 용도라, 단일 컴포넌트 내부의 <Link>key={id}를 둘 필요가 없습니다(오해만 유발). 맵핑하는 쪽에서 이미 key를 주고 있으니 여기서는 제거해 주세요.

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +106
const { data, hasNextPage, isFetchingNextPage, fetchNextPage } = useGetHomeRecruitingClubs({
limit: CLUB_CAROUSEL_LIMIT,
});

useEffect(() => {
if (hasNextPage && !isFetchingNextPage) {
void fetchNextPage();
}
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

useEffect에서 hasNextPage인 동안 자동으로 fetchNextPage()를 반복 호출하고 있어, 홈 진입 시 전체 모집중 동아리 페이지를 끝까지 모두 프리페치하게 됩니다(페이지 수가 많으면 초기 로딩/트래픽 증가). 홈 캐러셀 목적이 ‘일부 추천 노출’이라면 1페이지만 사용하거나, 사용자 인터랙션(캐러셀 끝 접근) 시에만 다음 페이지를 가져오도록 제한해 주세요.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (10)
src/components/layout/Header/components/InfoHeader.tsx (1)

11-13: text-caption1 대신 프로젝트 타이포 토큰을 써주세요.

가이드에 있는 캡션 토큰은 text-cap1 / text-cap2라서, 지금 클래스는 타이포 스케일에서 벗어날 가능성이 있습니다.

As per coding guidelines, **/*.{ts,tsx}: Use typography tokens (text-h1 through text-h5, text-sub1 through text-sub4, text-body1 through text-body3, text-cap1 through text-cap2) from src/styles/theme.css.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/layout/Header/components/InfoHeader.tsx` around lines 11 - 13,
The JSX in InfoHeader (component InfoHeader.tsx) uses a non-standard class
"text-caption1"; replace it with the project's typography token (either
"text-cap1" or "text-cap2" as appropriate for the visual weight) in the div that
renders {myInfo.name} {myInfo.studentNumber}, i.e., update the className on that
element to use the theme's typography token from src/styles/theme.css so it
conforms to the typography scale.
src/pages/Home/components/SectionTitle.tsx (1)

11-12: 타이포그래피 토큰으로 맞춰주세요.

text-[16px], text-[14px] 대신 공용 text-sub* / text-body* / text-cap* 토큰을 쓰는 편이 홈 섹션 간 스케일이 덜 흔들립니다.

As per coding guidelines, **/*.{ts,tsx}: Use typography tokens (text-h1 through text-h5, text-sub1 through text-sub4, text-body1 through text-body3, text-cap1 through text-cap2) from src/styles/theme.css.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Home/components/SectionTitle.tsx` around lines 11 - 12, Replace the
hard-coded pixel typography classes in SectionTitle (the h2 rendering {title}
and the Link to={to}) with the project typography tokens from
src/styles/theme.css; specifically swap text-[16px] on the h2 for the
appropriate text-sub*/text-body* token (e.g., text-sub2 or text-body1 as per
design) and replace text-[14px] on the Link with the matching smaller token
(e.g., text-sub4 or text-body2), ensuring you only use the permitted tokens
(text-h1..h5, text-sub1..sub4, text-body1..body3, text-cap1..cap2) in the
SectionTitle component.
src/components/layout/index.tsx (1)

26-31: twMerge 대신 cn()으로 통일해주세요.

이 파일도 조건부 Tailwind 조합을 하고 있어서 공용 cn() 유틸로 맞춰두는 편이 클래스 병합 방식이 일관됩니다.

♻️ 제안 수정
-import { twMerge } from 'tailwind-merge';
+import { cn } from '@/utils/ts/cn';
...
-          className={twMerge(
+          className={cn(

As per coding guidelines, **/*.{ts,tsx}: Use cn() utility from src/utils/ts/cn.ts to merge Tailwind CSS classes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/layout/index.tsx` around lines 26 - 31, Replace the twMerge
call with the shared cn() utility to make Tailwind class merging consistent:
import cn from the cn utility, then swap twMerge(...) for cn(...) where the
className is built (the expression that currently references twMerge and the
variables isInfoHeader, showBottomNav, contentClassName); keep the same
conditional class strings ('pt-15' vs 'pt-11', 'pb-19', base classes) and ensure
the final className uses cn(content strings, contentClassName) so
contentClassName still merges correctly.
src/pages/Home/hooks/useGetHomeClubs.ts (1)

2-4: 공용 query key는 페이지 훅 밖으로 빼고 infinite 전용 key를 분리해 주세요.

clubQueryKeys@/pages/Club/ClubList/hooks/useGetClubs에서 가져오면 Home 훅이 다른 페이지 내부 구현에 묶입니다. 같은 list key가 일반 query에도 쓰이면 useInfiniteQuery와 캐시 shape가 충돌할 수 있으니 분리 여부를 확인해 주세요. As per coding guidelines, "Use React Query with useSuspenseQuery and query key factory pattern for server state management" and "Separate page-specific logic into page-dedicated hooks/ directories instead of keeping it in the page component".

Also applies to: 29-31

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Home/hooks/useGetHomeClubs.ts` around lines 2 - 4, The Home hook is
importing clubQueryKeys from another page hook which couples Home to ClubList
and risks query key collisions between normal and infinite queries; update
useGetHomeClubs to stop importing clubQueryKeys from
'@/pages/Club/ClubList/hooks/useGetClubs' and instead create or import a shared
query key factory (e.g., clubQueryKeysFactory) outside page-specific hooks, and
define a distinct key for infinite lists (e.g., clubQueryKeys.infinite.list or
clubInfiniteQueryKeys.list) so useGetHomeClubs and useGetClubs each consume the
appropriate key shape and avoid mixing useInfiniteQuery keys with standard list
keys; ensure the new key factory is colocated in a shared queries module and
update references in useGetHomeClubs and useGetClubs accordingly.
src/pages/Home/components/RecommendedClubCard.tsx (1)

18-20: 색상/타이포를 raw 값보다 theme token으로 맞춰 주세요.

border-[#f4f6f9], text-[#5a6b7f], text-[16px] 같은 값이 많아서 이 카드만 디자인 시스템에서 벗어납니다. theme color/typography token으로 맞추면 이후 유지보수가 쉬워집니다. As per coding guidelines, "Prioritize color tokens from src/styles/theme.css" and "Use typography tokens (text-h1 through text-cap2) from src/styles/theme.css".

Also applies to: 30-41

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Home/components/RecommendedClubCard.tsx` around lines 18 - 20, The
RecommendedClubCard component is using raw color and typography utilities (e.g.,
border-[`#f4f6f9`], text-[`#5a6b7f`], text-[16px])—replace those with the design
system theme tokens instead: swap border-[`#f4f6f9`] for the appropriate border
color token, replace raw hex text colors with the theme color tokens, and change
text-[16px]/other raw size classes to the corresponding typography tokens
(text-h1…text-cap2) used across the project; update the className on the root
(className={cn(...)} in RecommendedClubCard) and any inner element classes
referenced around the same block (lines ~30–41) to use the theme tokens so the
card conforms to the design system.
src/pages/Home/components/InfiniteClubCarousel.tsx (1)

4-4: 상대 경로 대신 alias import로 맞춰 주세요.

이 파일만 ./RecommendedClubCard를 써서 현재 import 규칙에서 벗어납니다. As per coding guidelines, "Use @/* alias for import paths instead of relative paths".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Home/components/InfiniteClubCarousel.tsx` at line 4, The import in
InfiniteClubCarousel.tsx uses a relative path for RecommendedClubCard; replace
the relative import ("./RecommendedClubCard") with the project alias form (use
the "@/..." alias) so it follows the coding guideline to use `@/`* aliases for
imports, ensuring the import statement references RecommendedClubCard via the
alias instead of a relative path.
src/pages/Home/components/MiniSchedulePreview.tsx (1)

55-55: 커스텀 폰트 사이즈 대신 typography 토큰 사용 권장

text-[13px]는 커스텀 값입니다. 가능하다면 text-cap1 또는 text-cap2 같은 typography 토큰 사용을 고려해주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Home/components/MiniSchedulePreview.tsx` at line 55, The className
on the ul in MiniSchedulePreview uses a custom font size token "text-[13px]";
replace it with the project's typography token (e.g., text-cap1 or text-cap2) to
align with design tokens. Update the class string in the MiniSchedulePreview
component (the ul with className "... text-[13px] ...") to use the appropriate
token (text-cap1 or text-cap2) and ensure visual parity with the previous 13px
size before committing.
src/pages/Home/components/HomeClubSection.tsx (1)

61-67: normalizeAppliedClub에서 subLabel 누락

normalizeJoinedClubnormalizeRecruitingClubsubLabel을 설정하지만, normalizeAppliedClubcategoryName이 있음에도 subLabel을 설정하지 않습니다. 의도된 것인지 확인해주세요.

♻️ 제안
 function normalizeAppliedClub(club: AppliedClub): HomeClubCardItem {
   return {
     id: club.id,
     name: club.name,
     imageUrl: club.imageUrl,
+    subLabel: club.categoryName,
     badgeLabel: '승인 대기',
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Home/components/HomeClubSection.tsx` around lines 61 - 67, The
normalizeAppliedClub function is missing the subLabel property while
normalizeJoinedClub and normalizeRecruitingClub set subLabel from categoryName;
update normalizeAppliedClub (returning HomeClubCardItem for AppliedClub) to
include subLabel: club.categoryName (or formatted equivalent) alongside id,
name, imageUrl, and badgeLabel so the applied-club cards display the category
like the others.
src/pages/Home/hooks/useInfiniteClubCarousel.ts (2)

171-185: useEffect 의존성 배열 검토

빈 의존성 배열 []로 설정되어 있으나, syncCarouselPosition을 내부에서 호출합니다. useEffectEvent로 감싸져 있어 의존성에 포함하지 않아도 되지만, ESLint 규칙에 따라 경고가 발생할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Home/hooks/useInfiniteClubCarousel.ts` around lines 171 - 185, The
effect adds a 'scrollend' listener but uses syncCarouselPosition, which triggers
ESLint dependency warnings; wrap the handler in a stable reference using
useEffectEvent (e.g., create handleScrollEnd via useEffectEvent that calls
syncCarouselPosition and checks isAdjustingLoopRef.current) and then
register/remove that stable handler in the useEffect so the dependency array can
remain empty; ensure clearRestoreSnapFrame is still called in the cleanup and
reference scrollRef, isAdjustingLoopRef, syncCarouselPosition, and
clearRestoreSnapFrame when locating the code to modify.

91-108: jumpToLoopPosition의 double rAF 패턴

두 번의 requestAnimationFrame을 중첩하여 snap 복원을 지연시키는 패턴입니다. 브라우저에 따라 동작이 다를 수 있으므로, 필요시 테스트를 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Home/hooks/useInfiniteClubCarousel.ts` around lines 91 - 108, The
double requestAnimationFrame in jumpToLoopPosition (which sets
restoreSnapFrameRef.current) can be unreliable across browsers; replace the
nested rAF pattern with a single requestAnimationFrame followed by
setTimeout(..., 0) to defer restoring scrollSnapType to the next macrotask, and
update clearRestoreSnapFrame and restoreSnapFrameRef handling to cancel both the
RAF (via cancelAnimationFrame) and the timeout (via clearTimeout) — or store
separate refs (e.g., restoreSnapRafRef and restoreSnapTimeoutRef) — so
isAdjustingLoopRef, scrollRef, and scrollNode.style.scrollSnapType are restored
reliably and cancellations work correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/layout/Header/components/NotificationBell.tsx`:
- Around line 9-10: The NotificationBell's Link currently renders only an icon
so screen readers can't convey its purpose; update the Link (the element
rendering Link to {'chats'}) to include an accessible name by adding an
aria-label (e.g., aria-label="Open chats" or aria-label="Notifications") or
adding visually hidden text inside the Link next to <ChatCatIcon />; ensure the
label accurately describes the action and that keyboard focus/role remains on
the Link (no extra interactive elements), so screen readers can announce the
link purpose.

In `@src/pages/Home/components/CouncilNoticeCard.tsx`:
- Around line 11-13: 'truncate' on the title div (the element rendering {title}
in CouncilNoticeCard.tsx) can fail inside a flex row; update the element with
either min-w-0 or flex-1 (e.g., add the Tailwind class "min-w-0" or "flex-1") so
the truncation/ellipsis works and the unread dot isn't pushed out of view.

In `@src/pages/Home/components/InfiniteClubCarousel.tsx`:
- Around line 24-25: The root div in the InfiniteClubCarousel component
currently uses className="gap-1" which has no effect because the div is not a
flex/grid container; update the root div (the returned top-level element in
InfiniteClubCarousel) to use either a container utility like "flex flex-col
gap-1" or the spacing utility "space-y-1" (e.g., replace "gap-1" with "flex
flex-col" plus gap or with "space-y-1") so the intended vertical spacing between
the indicator and the list is applied.

In `@src/pages/Home/components/RecommendedClubCard.tsx`:
- Around line 23-26: The card image in RecommendedClubCard currently uses
alt={club.name}, causing screen readers to read the link label twice; change the
img to a decorative image by setting alt="" (empty string) and add
aria-hidden="true" (or role="presentation") on the same <img> element (which
uses club.imageUrl and className with cn(...)) so the adjacent club.name text
remains the accessible label.

In `@src/pages/Home/components/SectionErrorFallback.tsx`:
- Around line 3-4: The error message in SectionErrorFallback is currently only a
visual span and should be exposed to assistive tech; modify the component
(SectionErrorFallback) so the error container or the message element includes
role="alert" (and optionally aria-live="assertive") on the same element that
renders the span with text "불러오는 중 오류가 발생했어요", keeping the existing className
composition and layout intact; ensure you add the attribute to the outer div or
the span within the component so screen readers receive the update immediately
without changing visual styling.

In `@src/pages/Home/components/SectionTitle.tsx`:
- Around line 12-14: The "더보기" Link in SectionTitle is missing an accessible
label; update the Link element in the SectionTitle component to add an
aria-label that includes the section title (e.g., aria-label={`${title} 더보기`}),
ensuring you reference the component prop name title and keep the existing
className and to prop unchanged; verify the SectionTitle props type includes
title as a string so the label is type-safe and consistent across usages.

---

Nitpick comments:
In `@src/components/layout/Header/components/InfoHeader.tsx`:
- Around line 11-13: The JSX in InfoHeader (component InfoHeader.tsx) uses a
non-standard class "text-caption1"; replace it with the project's typography
token (either "text-cap1" or "text-cap2" as appropriate for the visual weight)
in the div that renders {myInfo.name} {myInfo.studentNumber}, i.e., update the
className on that element to use the theme's typography token from
src/styles/theme.css so it conforms to the typography scale.

In `@src/components/layout/index.tsx`:
- Around line 26-31: Replace the twMerge call with the shared cn() utility to
make Tailwind class merging consistent: import cn from the cn utility, then swap
twMerge(...) for cn(...) where the className is built (the expression that
currently references twMerge and the variables isInfoHeader, showBottomNav,
contentClassName); keep the same conditional class strings ('pt-15' vs 'pt-11',
'pb-19', base classes) and ensure the final className uses cn(content strings,
contentClassName) so contentClassName still merges correctly.

In `@src/pages/Home/components/HomeClubSection.tsx`:
- Around line 61-67: The normalizeAppliedClub function is missing the subLabel
property while normalizeJoinedClub and normalizeRecruitingClub set subLabel from
categoryName; update normalizeAppliedClub (returning HomeClubCardItem for
AppliedClub) to include subLabel: club.categoryName (or formatted equivalent)
alongside id, name, imageUrl, and badgeLabel so the applied-club cards display
the category like the others.

In `@src/pages/Home/components/InfiniteClubCarousel.tsx`:
- Line 4: The import in InfiniteClubCarousel.tsx uses a relative path for
RecommendedClubCard; replace the relative import ("./RecommendedClubCard") with
the project alias form (use the "@/..." alias) so it follows the coding
guideline to use `@/`* aliases for imports, ensuring the import statement
references RecommendedClubCard via the alias instead of a relative path.

In `@src/pages/Home/components/MiniSchedulePreview.tsx`:
- Line 55: The className on the ul in MiniSchedulePreview uses a custom font
size token "text-[13px]"; replace it with the project's typography token (e.g.,
text-cap1 or text-cap2) to align with design tokens. Update the class string in
the MiniSchedulePreview component (the ul with className "... text-[13px] ...")
to use the appropriate token (text-cap1 or text-cap2) and ensure visual parity
with the previous 13px size before committing.

In `@src/pages/Home/components/RecommendedClubCard.tsx`:
- Around line 18-20: The RecommendedClubCard component is using raw color and
typography utilities (e.g., border-[`#f4f6f9`], text-[`#5a6b7f`],
text-[16px])—replace those with the design system theme tokens instead: swap
border-[`#f4f6f9`] for the appropriate border color token, replace raw hex text
colors with the theme color tokens, and change text-[16px]/other raw size
classes to the corresponding typography tokens (text-h1…text-cap2) used across
the project; update the className on the root (className={cn(...)} in
RecommendedClubCard) and any inner element classes referenced around the same
block (lines ~30–41) to use the theme tokens so the card conforms to the design
system.

In `@src/pages/Home/components/SectionTitle.tsx`:
- Around line 11-12: Replace the hard-coded pixel typography classes in
SectionTitle (the h2 rendering {title} and the Link to={to}) with the project
typography tokens from src/styles/theme.css; specifically swap text-[16px] on
the h2 for the appropriate text-sub*/text-body* token (e.g., text-sub2 or
text-body1 as per design) and replace text-[14px] on the Link with the matching
smaller token (e.g., text-sub4 or text-body2), ensuring you only use the
permitted tokens (text-h1..h5, text-sub1..sub4, text-body1..body3,
text-cap1..cap2) in the SectionTitle component.

In `@src/pages/Home/hooks/useGetHomeClubs.ts`:
- Around line 2-4: The Home hook is importing clubQueryKeys from another page
hook which couples Home to ClubList and risks query key collisions between
normal and infinite queries; update useGetHomeClubs to stop importing
clubQueryKeys from '@/pages/Club/ClubList/hooks/useGetClubs' and instead create
or import a shared query key factory (e.g., clubQueryKeysFactory) outside
page-specific hooks, and define a distinct key for infinite lists (e.g.,
clubQueryKeys.infinite.list or clubInfiniteQueryKeys.list) so useGetHomeClubs
and useGetClubs each consume the appropriate key shape and avoid mixing
useInfiniteQuery keys with standard list keys; ensure the new key factory is
colocated in a shared queries module and update references in useGetHomeClubs
and useGetClubs accordingly.

In `@src/pages/Home/hooks/useInfiniteClubCarousel.ts`:
- Around line 171-185: The effect adds a 'scrollend' listener but uses
syncCarouselPosition, which triggers ESLint dependency warnings; wrap the
handler in a stable reference using useEffectEvent (e.g., create handleScrollEnd
via useEffectEvent that calls syncCarouselPosition and checks
isAdjustingLoopRef.current) and then register/remove that stable handler in the
useEffect so the dependency array can remain empty; ensure clearRestoreSnapFrame
is still called in the cleanup and reference scrollRef, isAdjustingLoopRef,
syncCarouselPosition, and clearRestoreSnapFrame when locating the code to
modify.
- Around line 91-108: The double requestAnimationFrame in jumpToLoopPosition
(which sets restoreSnapFrameRef.current) can be unreliable across browsers;
replace the nested rAF pattern with a single requestAnimationFrame followed by
setTimeout(..., 0) to defer restoring scrollSnapType to the next macrotask, and
update clearRestoreSnapFrame and restoreSnapFrameRef handling to cancel both the
RAF (via cancelAnimationFrame) and the timeout (via clearTimeout) — or store
separate refs (e.g., restoreSnapRafRef and restoreSnapTimeoutRef) — so
isAdjustingLoopRef, scrollRef, and scrollNode.style.scrollSnapType are restored
reliably and cancellations work correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 99537938-fe7e-43ae-8994-c9d6af7444f1

📥 Commits

Reviewing files that changed from the base of the PR and between 69f4f16 and 9282227.

⛔ Files ignored due to path filters (1)
  • src/assets/svg/chat-cat.svg is excluded by !**/*.svg, !src/assets/** and included by **
📒 Files selected for processing (24)
  • src/components/layout/BottomNav/index.tsx
  • src/components/layout/Header/components/InfoHeader.tsx
  • src/components/layout/Header/components/NotificationBell.tsx
  • src/components/layout/index.tsx
  • src/constants/schedule.ts
  • src/pages/Home/components/CouncilNoticeCard.tsx
  • src/pages/Home/components/CouncilNoticeSection.tsx
  • src/pages/Home/components/HomeClubSection.tsx
  • src/pages/Home/components/InfiniteClubCarousel.tsx
  • src/pages/Home/components/MiniSchedulePreview.tsx
  • src/pages/Home/components/RecommendedClubCard.tsx
  • src/pages/Home/components/ScheduleSection.tsx
  • src/pages/Home/components/SectionAsyncBoundary.tsx
  • src/pages/Home/components/SectionErrorFallback.tsx
  • src/pages/Home/components/SectionTitle.tsx
  • src/pages/Home/components/SimpleAppliedClubCard.tsx
  • src/pages/Home/components/SimpleClubCard.tsx
  • src/pages/Home/hooks/useGetAppliedClubs.ts
  • src/pages/Home/hooks/useGetHomeClubs.ts
  • src/pages/Home/hooks/useGetJoinedClubs.ts
  • src/pages/Home/hooks/useInfiniteClubCarousel.ts
  • src/pages/Home/index.tsx
  • src/pages/Home/types.ts
  • src/pages/Schedule/index.tsx
💤 Files with no reviewable changes (4)
  • src/pages/Home/components/SimpleClubCard.tsx
  • src/pages/Home/components/SimpleAppliedClubCard.tsx
  • src/pages/Home/hooks/useGetAppliedClubs.ts
  • src/pages/Home/hooks/useGetJoinedClubs.ts

Comment thread src/components/layout/Header/components/NotificationBell.tsx Outdated
Comment thread src/pages/Home/components/CouncilNoticeCard.tsx
Comment on lines +24 to +25
return (
<div className="gap-1">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

gap-1이 현재는 적용되지 않습니다.

루트 div가 flex/grid 컨테이너가 아니라 indicator와 리스트 사이 간격이 생기지 않습니다. flex flex-col 또는 space-y-1로 바꿔 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Home/components/InfiniteClubCarousel.tsx` around lines 24 - 25, The
root div in the InfiniteClubCarousel component currently uses className="gap-1"
which has no effect because the div is not a flex/grid container; update the
root div (the returned top-level element in InfiniteClubCarousel) to use either
a container utility like "flex flex-col gap-1" or the spacing utility
"space-y-1" (e.g., replace "gap-1" with "flex flex-col" plus gap or with
"space-y-1") so the intended vertical spacing between the indicator and the list
is applied.

Comment thread src/pages/Home/components/RecommendedClubCard.tsx
Comment thread src/pages/Home/components/SectionErrorFallback.tsx Outdated
Comment thread src/pages/Home/components/SectionTitle.tsx Outdated
ff1451 added 6 commits March 10, 2026 17:28
- src/apis/club/queries.ts에 clubQueryKeys 팩토리 이동
- infinite.list 키 네임스페이스 추가로 일반 쿼리와 무한 쿼리 키 충돌 방지
- useGetClubs, useGetHomeClubs 포함 전체 참조를 공유 모듈로 일원화
normalizeAppliedClub에 subLabel: club.categoryName 추가
- NotificationBell Link에 aria-label="채팅 열기" 추가
- CouncilNoticeCard 제목에 min-w-0 추가로 flex 컨테이너 내 truncate 정상화
- RecommendedClubCard 이미지를 장식용(alt="", aria-hidden)으로 변경해 중복 읽기 방지
- SectionErrorFallback 컨테이너에 role="alert" 추가
- SectionTitle 더보기 Link에 aria-label 추가
scrollend를 지원하지 않는 iOS Safari < 26에서 루프 보정이
동작하지 않는 문제를 scroll 이벤트 + 150ms debounce로 해결
월 전체 weeks를 렌더링하면 overflow-hidden으로 오늘이
잘려 보이지 않는 문제를 오늘이 포함된 주부터 2주 slice로 수정
- layout/index.tsx: twMerge 직접 호출을 cn 유틸로 통일
- InfoHeader.tsx: text-caption1을 text-cap1으로 수정
@ff1451 ff1451 force-pushed the 176-feat-홈화면-리디자인 branch from 0eeb4ef to da4c330 Compare March 10, 2026 08:33
@ff1451 ff1451 merged commit b55dc14 into develop Mar 10, 2026
1 check passed
@ff1451 ff1451 deleted the 176-feat-홈화면-리디자인 branch April 7, 2026 09:33
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.

[feat] 홈화면 리디자인

2 participants