Feat(client): 직무 아티클 주기적 업데이트 공지 로딩 추가#289
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
✅ Storybook chromatic 배포 확인: |
Walkthrough관심 직무 리스트 페이지에 스크롤 시 하단 공지를 표시하는 기능을 추가합니다. 새로운 커스텀 훅으로 공지 표시 상태를 관리하고, 휠 이벤트를 처리하며, 로딩 스피너가 포함된 UI 컴포넌트를 렌더링합니다. Changes
Sequence DiagramsequenceDiagram
actor User
participant JobPins as JobPins Page
participant Hook as useJobPinsBottomNotice
participant Notice as JobPinsBottomNotice
participant Spinner as JobPinsBottomSpinner
User->>JobPins: 마우스 휠 스크롤
JobPins->>Hook: handleBottomWheel(e) 호출
Hook->>Hook: 스크롤 위치 확인<br/>(컨테이너가 하단인지)
alt 하단 도달 시
Hook->>Hook: showBottomNoticeTemporarily() 실행<br/>(isBottomNoticeVisible = true, 2초 타이머)
end
Hook-->>JobPins: isBottomNoticeVisible, scrollContainerRef 반환
JobPins->>Notice: visible 상태 전달
Notice->>Spinner: 중첩된 스피너 렌더링
Spinner-->>Notice: 로딩 애니메이션 표시
Notice-->>User: "관심 직무 핀은 계속 업데이트 돼요!" 공지 표시
Note over Hook: 2초 후<br/>타이머 자동 정리<br/>isBottomNoticeVisible = false
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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: 3
🧹 Nitpick comments (1)
apps/client/src/pages/jobPins/components/JobPinsBottomSpinner.tsx (1)
7-14: 장식용 스피너는 접근성 트리에서 제외해 주세요.Line 7의
<svg>는 정보 전달용이 아니라 시각 장식이라, 스크린리더 노출을 막는 편이 좋습니다 (aria-hidden,focusable).제안 diff
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30" fill="none" + aria-hidden="true" + focusable="false" className="animate-spin-smooth h-[3rem] w-[3rem]" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/client/src/pages/jobPins/components/JobPinsBottomSpinner.tsx` around lines 7 - 14, The <svg> in JobPinsBottomSpinner (JobPinsBottomSpinner.tsx) is decorative and should be removed from the accessibility tree: add aria-hidden="true" and focusable="false" attributes to the <svg> element (ensure there are no accessible child text/title elements), so screen readers won't announce or focus this spinner.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/client/src/pages/jobPins/components/JobPinsBottomNotice.tsx`:
- Around line 9-15: The wrapper DIV that toggles visibility visually (the JSX
block in JobPinsBottomNotice where the className depends on `visible`) doesn't
update accessibility tree; add an `aria-hidden` attribute bound to the same
`visible` state (e.g., `aria-hidden={!visible}`) on that wrapper so assistive
technologies respect the hidden state, keeping the attribute logic consistent
with the existing `visible` conditional used in the className.
In `@apps/client/src/pages/jobPins/hooks/useJobPinsBottomNotice.ts`:
- Around line 8-22: The early return in showBottomNoticeTemporarily prevents
updating the hide timer while the notice is already visible, so additional wheel
events won't extend the display; remove the return and always reset/clear
hideTimerRef and set a new timeout, ensuring you still call
setIsBottomNoticeVisible(true) when appropriate; update the logic in
showBottomNoticeTemporarily (referencing isBottomNoticeVisible, hideTimerRef,
setIsBottomNoticeVisible) to clear any existing hideTimerRef, set visibility if
not already true, and then assign a fresh setTimeout to hideTimerRef so repeated
calls extend the notice duration.
In `@apps/client/src/pages/jobPins/JobPins.tsx`:
- Around line 66-67: The sentinel re-enters while JobPinsBottomNotice animates
height (see JobPinsBottomNotice and the div using observerRef), causing
duplicate fetches because useInfiniteScroll only checks hasNextPage; update the
intersection callback inside useInfiniteScroll (or the place where fetchNextPage
is called) to guard with entry.isIntersecting and also check in-flight state
(either isFetching or better isFetchingNextPage passed in from TanStack Query)
before calling fetchNextPage so it only runs when intersecting and not already
fetching.
---
Nitpick comments:
In `@apps/client/src/pages/jobPins/components/JobPinsBottomSpinner.tsx`:
- Around line 7-14: The <svg> in JobPinsBottomSpinner (JobPinsBottomSpinner.tsx)
is decorative and should be removed from the accessibility tree: add
aria-hidden="true" and focusable="false" attributes to the <svg> element (ensure
there are no accessible child text/title elements), so screen readers won't
announce or focus this spinner.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/client/src/pages/jobPins/JobPins.tsxapps/client/src/pages/jobPins/components/JobPinsBottomNotice.tsxapps/client/src/pages/jobPins/components/JobPinsBottomSpinner.tsxapps/client/src/pages/jobPins/hooks/useJobPinsBottomNotice.ts
| <div | ||
| className={`w-full overflow-hidden transition-all duration-300 ease-out ${ | ||
| visible | ||
| ? 'max-h-[10rem] translate-y-0 py-[1.2rem] opacity-100' | ||
| : 'max-h-0 translate-y-2 py-0 opacity-0' | ||
| }`} | ||
| > |
There was a problem hiding this comment.
숨김 상태를 접근성 트리에도 반영해 주세요.
Line 10-14는 시각적 접힘만 처리해서, 숨김 상태 문구가 보조기기에 노출될 수 있습니다. 래퍼에 aria-hidden={!visible}를 같이 두는 게 안전합니다.
제안 diff
<div
+ aria-hidden={!visible}
className={`w-full overflow-hidden transition-all duration-300 ease-out ${
visible
? 'max-h-[10rem] translate-y-0 py-[1.2rem] opacity-100'
: 'max-h-0 translate-y-2 py-0 opacity-0'
}`}
>📝 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.
| <div | |
| className={`w-full overflow-hidden transition-all duration-300 ease-out ${ | |
| visible | |
| ? 'max-h-[10rem] translate-y-0 py-[1.2rem] opacity-100' | |
| : 'max-h-0 translate-y-2 py-0 opacity-0' | |
| }`} | |
| > | |
| <div | |
| aria-hidden={!visible} | |
| className={`w-full overflow-hidden transition-all duration-300 ease-out ${ | |
| visible | |
| ? 'max-h-[10rem] translate-y-0 py-[1.2rem] opacity-100' | |
| : 'max-h-0 translate-y-2 py-0 opacity-0' | |
| }`} | |
| > |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/client/src/pages/jobPins/components/JobPinsBottomNotice.tsx` around
lines 9 - 15, The wrapper DIV that toggles visibility visually (the JSX block in
JobPinsBottomNotice where the className depends on `visible`) doesn't update
accessibility tree; add an `aria-hidden` attribute bound to the same `visible`
state (e.g., `aria-hidden={!visible}`) on that wrapper so assistive technologies
respect the hidden state, keeping the attribute logic consistent with the
existing `visible` conditional used in the className.
| const showBottomNoticeTemporarily = useCallback(() => { | ||
| if (isBottomNoticeVisible) { | ||
| return; | ||
| } | ||
|
|
||
| setIsBottomNoticeVisible(true); | ||
| if (hideTimerRef.current) { | ||
| clearTimeout(hideTimerRef.current); | ||
| } | ||
|
|
||
| hideTimerRef.current = setTimeout(() => { | ||
| setIsBottomNoticeVisible(false); | ||
| hideTimerRef.current = null; | ||
| }, 2000); | ||
| }, [isBottomNoticeVisible]); |
There was a problem hiding this comment.
공지 재노출 타이머가 갱신되지 않습니다.
Line 9의 조기 return 때문에, 공지가 보이는 동안 추가 휠 입력이 와도 숨김 타이머가 연장되지 않습니다. 연속 스크롤 시 공지가 너무 빨리 꺼질 수 있습니다.
제안 diff
const showBottomNoticeTemporarily = useCallback(() => {
- if (isBottomNoticeVisible) {
- return;
- }
-
setIsBottomNoticeVisible(true);
if (hideTimerRef.current) {
clearTimeout(hideTimerRef.current);
}
hideTimerRef.current = setTimeout(() => {
setIsBottomNoticeVisible(false);
hideTimerRef.current = null;
}, 2000);
- }, [isBottomNoticeVisible]);
+ }, []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const showBottomNoticeTemporarily = useCallback(() => { | |
| if (isBottomNoticeVisible) { | |
| return; | |
| } | |
| setIsBottomNoticeVisible(true); | |
| if (hideTimerRef.current) { | |
| clearTimeout(hideTimerRef.current); | |
| } | |
| hideTimerRef.current = setTimeout(() => { | |
| setIsBottomNoticeVisible(false); | |
| hideTimerRef.current = null; | |
| }, 2000); | |
| }, [isBottomNoticeVisible]); | |
| const showBottomNoticeTemporarily = useCallback(() => { | |
| setIsBottomNoticeVisible(true); | |
| if (hideTimerRef.current) { | |
| clearTimeout(hideTimerRef.current); | |
| } | |
| hideTimerRef.current = setTimeout(() => { | |
| setIsBottomNoticeVisible(false); | |
| hideTimerRef.current = null; | |
| }, 2000); | |
| }, []); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/client/src/pages/jobPins/hooks/useJobPinsBottomNotice.ts` around lines 8
- 22, The early return in showBottomNoticeTemporarily prevents updating the hide
timer while the notice is already visible, so additional wheel events won't
extend the display; remove the return and always reset/clear hideTimerRef and
set a new timeout, ensuring you still call setIsBottomNoticeVisible(true) when
appropriate; update the logic in showBottomNoticeTemporarily (referencing
isBottomNoticeVisible, hideTimerRef, setIsBottomNoticeVisible) to clear any
existing hideTimerRef, set visibility if not already true, and then assign a
fresh setTimeout to hideTimerRef so repeated calls extend the notice duration.
| <JobPinsBottomNotice visible={isBottomNoticeVisible} /> | ||
| <div ref={observerRef} style={{ height: '1px', width: '100%' }} /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "[1] useInfiniteScroll 구현 파일 탐색"
fd 'useInfiniteScroll\.(ts|tsx|js|jsx)$' || echo "No useInfiniteScroll hook file found by exact name"
echo
echo "[2] InfiniteScroll 관련 파일 탐색"
fd -t f -e ts -e tsx -e js -e jsx | xargs rg -l 'useInfiniteScroll|InfiniteScroll' | head -20
echo
echo "[3] JobPins 파일 내용 확인"
cat -n apps/client/src/pages/jobPins/JobPins.tsx | head -100
echo
echo "[4] JobPins 전체 구조"
wc -l apps/client/src/pages/jobPins/JobPins.tsxRepository: Pinback-Team/pinback-client
Length of output: 3697
🏁 Script executed:
echo "[5] 디렉터리 구조 확인"
find apps/client/src -type f -name "*.ts" -o -name "*.tsx" | grep -i "infinite\|scroll\|hook" | head -20Repository: Pinback-Team/pinback-client
Length of output: 676
🏁 Script executed:
echo "[6] useInfiniteScroll 또는 관련 훅 검색"
rg -t ts -t tsx 'export.*useInfiniteScroll|function useInfiniteScroll|const useInfiniteScroll' apps/Repository: Pinback-Team/pinback-client
Length of output: 148
🏁 Script executed:
echo "[1] useInfiniteScroll 구현 확인"
cat -n apps/client/src/shared/hooks/useInfiniteScroll.tsRepository: Pinback-Team/pinback-client
Length of output: 1509
🏁 Script executed:
echo "[2] JobPinsBottomNotice 컴포넌트 확인"
cat -n apps/client/src/pages/jobPins/components/JobPinsBottomNotice.tsxRepository: Pinback-Team/pinback-client
Length of output: 1005
🏁 Script executed:
echo "[3] useJobPinsBottomNotice 훅 확인"
cat -n apps/client/src/pages/jobPins/hooks/useJobPinsBottomNotice.tsRepository: Pinback-Team/pinback-client
Length of output: 1948
🏁 Script executed:
echo "[4] useGetJobPinsArticles 쿼리 함수 확인"
fd 'useGetJobPinsArticles' apps/ --exec cat -n {} \;Repository: Pinback-Team/pinback-client
Length of output: 105
공지 높이 애니메이션으로 인한 센티넬 재교차 가능성이 있습니다.
Line 66-67에서 JobPinsBottomNotice가 300ms 전환 애니메이션으로 높이가 변하면(max-h: 0 ↔ 10rem), 센티넬이 재진입할 수 있습니다. 현재 useInfiniteScroll은 hasNextPage 가드만 사용하며, 옵저버 콜백 내부에 isFetching 체크가 없어 진행 중인 페치 중 레이아웃 변동 시 중복 호출 위험이 있습니다.
개선안: useInfiniteScroll 콜백에서 다음과 같이 추가 가드를 적용하세요.
if (entry.isIntersecting && hasNextPage) {
fetchNextPage();
}
또는 TanStack Query의 isFetchingNextPage 상태를 props로 받아 콜백 내에서 체크하면 더욱 견고합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/client/src/pages/jobPins/JobPins.tsx` around lines 66 - 67, The sentinel
re-enters while JobPinsBottomNotice animates height (see JobPinsBottomNotice and
the div using observerRef), causing duplicate fetches because useInfiniteScroll
only checks hasNextPage; update the intersection callback inside
useInfiniteScroll (or the place where fetchNextPage is called) to guard with
entry.isIntersecting and also check in-flight state (either isFetching or better
isFetchingNextPage passed in from TanStack Query) before calling fetchNextPage
so it only runs when intersecting and not already fetching.
📌 Related Issues
📄 Tasks
⭐ PR Point (To Reviewer)
📷 Screenshot
2026-02-26.11.06.39.mov
Summary by CodeRabbit
릴리스 노트