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 = () => { /> ))} +
) : ( 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; 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; 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, + }; +};