From e89a7b0c912c9af3e5a18e830792487b4c627389 Mon Sep 17 00:00:00 2001 From: constantly-dev Date: Thu, 26 Feb 2026 22:48:49 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20JobPinsBottomNotice=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/JobPinsBottomNotice.tsx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 apps/client/src/pages/jobPins/components/JobPinsBottomNotice.tsx diff --git a/apps/client/src/pages/jobPins/components/JobPinsBottomNotice.tsx b/apps/client/src/pages/jobPins/components/JobPinsBottomNotice.tsx new file mode 100644 index 0000000..4e70ff9 --- /dev/null +++ b/apps/client/src/pages/jobPins/components/JobPinsBottomNotice.tsx @@ -0,0 +1,26 @@ +import JobPinsBottomSpinner from './JobPinsBottomSpinner'; + +interface JobPinsBottomNoticeProps { + visible: boolean; +} + +const JobPinsBottomNotice = ({ visible }: JobPinsBottomNoticeProps) => { + return ( +
+
+

+ 관심 직무 핀은 계속 업데이트 돼요! +

+ +
+
+ ); +}; + +export default JobPinsBottomNotice; From 39186dc2d6329853bc82e99e9af2a6ba9c44e771 Mon Sep 17 00:00:00 2001 From: constantly-dev Date: Thu, 26 Feb 2026 22:49:10 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20JobPinsBottomNotice=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/JobPinsBottomSpinner.tsx | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 apps/client/src/pages/jobPins/components/JobPinsBottomSpinner.tsx diff --git a/apps/client/src/pages/jobPins/components/JobPinsBottomSpinner.tsx b/apps/client/src/pages/jobPins/components/JobPinsBottomSpinner.tsx new file mode 100644 index 0000000..0d36d42 --- /dev/null +++ b/apps/client/src/pages/jobPins/components/JobPinsBottomSpinner.tsx @@ -0,0 +1,42 @@ +import { useId } from 'react'; + +const JobPinsBottomSpinner = () => { + const gradientId = useId(); + + return ( + + + + + + + + + + + ); +}; + +export default JobPinsBottomSpinner; From 1b2d83ee159225930399efe0859308206a0cc5bf Mon Sep 17 00:00:00 2001 From: constantly-dev Date: Thu, 26 Feb 2026 23:06:17 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20useJobPinsBottomNotice=20hook=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jobPins/hooks/useJobPinsBottomNotice.ts | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 apps/client/src/pages/jobPins/hooks/useJobPinsBottomNotice.ts diff --git a/apps/client/src/pages/jobPins/hooks/useJobPinsBottomNotice.ts b/apps/client/src/pages/jobPins/hooks/useJobPinsBottomNotice.ts new file mode 100644 index 0000000..e28862f --- /dev/null +++ b/apps/client/src/pages/jobPins/hooks/useJobPinsBottomNotice.ts @@ -0,0 +1,59 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; + +export const useJobPinsBottomNotice = () => { + const scrollContainerRef = useRef(null); + const hideTimerRef = useRef | null>(null); + const [isBottomNoticeVisible, setIsBottomNoticeVisible] = useState(false); + + const showBottomNoticeTemporarily = useCallback(() => { + if (isBottomNoticeVisible) { + return; + } + + setIsBottomNoticeVisible(true); + if (hideTimerRef.current) { + clearTimeout(hideTimerRef.current); + } + + hideTimerRef.current = setTimeout(() => { + setIsBottomNoticeVisible(false); + hideTimerRef.current = null; + }, 2000); + }, [isBottomNoticeVisible]); + + useEffect(() => { + return () => { + if (hideTimerRef.current) { + clearTimeout(hideTimerRef.current); + } + }; + }, []); + + const handleBottomWheel = useCallback( + (e: React.WheelEvent) => { + if (e.deltaY <= 0) { + return; + } + + const container = scrollContainerRef.current; + if (!container) { + return; + } + + const isAtBottom = + container.scrollTop + container.clientHeight >= + container.scrollHeight - 1; + + if (isAtBottom) { + showBottomNoticeTemporarily(); + } + }, + [showBottomNoticeTemporarily] + ); + + return { + scrollContainerRef, + isBottomNoticeVisible, + handleBottomWheel, + }; +}; From 4c34c4822a04610dbecff6ed576293de2c6bf550 Mon Sep 17 00:00:00 2001 From: constantly-dev Date: Thu, 26 Feb 2026 23:11:18 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20job=20update=20notice=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/pages/jobPins/JobPins.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/client/src/pages/jobPins/JobPins.tsx b/apps/client/src/pages/jobPins/JobPins.tsx index 4bb7e49..46b31f3 100644 --- a/apps/client/src/pages/jobPins/JobPins.tsx +++ b/apps/client/src/pages/jobPins/JobPins.tsx @@ -1,11 +1,13 @@ import { useGetJobPinsArticles } from '@pages/jobPins/apis/queries'; +import JobPinsBottomNotice from '@pages/jobPins/components/JobPinsBottomNotice'; +import { useJobPinsBottomNotice } from '@pages/jobPins/hooks/useJobPinsBottomNotice'; import Footer from '@pages/myBookmark/components/footer/Footer'; import { Card } from '@pinback/design-system/ui'; import { useInfiniteScroll } from '@shared/hooks/useInfiniteScroll'; -import { useRef } from 'react'; const JobPins = () => { - const scrollContainerRef = useRef(null); + const { scrollContainerRef, isBottomNoticeVisible, handleBottomWheel } = + useJobPinsBottomNotice(); const { data, isPending, fetchNextPage, hasNextPage } = useGetJobPinsArticles(); @@ -43,6 +45,7 @@ const JobPins = () => { ) : articlesToDisplay.length > 0 ? (
{articlesToDisplay.map((article) => ( @@ -60,6 +63,7 @@ const JobPins = () => { /> ))} +
) : (