diff --git a/src/pages/store/Store.tsx b/src/pages/store/Store.tsx index 87db0e7..74e398b 100644 --- a/src/pages/store/Store.tsx +++ b/src/pages/store/Store.tsx @@ -1,3 +1,189 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { getUser } from '@/api/userApi'; +import type { ShopItem } from '@/api/shopApi'; +import { getShopNotices, type NoticeInfo } from '@/api/noticeApi'; +import Modal from '@/components/common/Modal'; +import RegisterLayout from '@/components/layout/RegisterLayout'; +import Button from '@/components/common/Button'; +import ic_location from '@/assets/icons/location-red.svg'; +import Post from '@/components/common/Post'; +import Footer from '@/components/layout/Footer'; + +const NOTICES_LIMIT = 12; + export default function Store() { - return
내 가게 정보 상세(사장님)
; + const navigate = useNavigate(); + const [shop, setShop] = useState(null); + const [notices, setNotices] = useState([]); + const noticesOffset = useRef(0); + const [isModalOpen, setIsModalOpen] = useState(false); + const [modalContent, setModalContent] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const observerRef = useRef(null); + const [isMobile, setIsMobile] = useState(window.innerWidth < 768); + + const handleClose = useCallback(() => { + if (modalContent.startsWith('로그인')) { + navigate('/login'); + } else { + setIsModalOpen(false); + } + }, [navigate, modalContent]); + + const loadNotices = useCallback(async () => { + if (!shop || noticesOffset.current < 0) return; + setIsLoading(true); + try { + const noticesResponse = await getShopNotices(shop.id, { + offset: noticesOffset.current, + limit: NOTICES_LIMIT, + }); + setNotices((prev) => [...prev, ...noticesResponse.items]); + if (noticesResponse.hasNext) noticesOffset.current += NOTICES_LIMIT; + else noticesOffset.current = -1; + setIsLoading(false); + } catch { + setIsLoading('error'); + } + }, [shop]); + + useEffect(() => { + const userId = localStorage.getItem('userId'); + if (!userId) { + setIsModalOpen(true); + setModalContent('로그인이 필요합니다.'); + return; + } + + try { + (async () => { + const userResponse = await getUser(userId); + setShop(userResponse.item.shop?.item ?? null); + })(); + } catch (error) { + setIsModalOpen(true); + setModalContent((error as Error).message); + } + }, []); + + // 무한 스크롤 + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && !isLoading) { + loadNotices(); + } + }, + { + rootMargin: '300px', + }, + ); + + const { current } = observerRef; + if (current) observer.observe(current); + return () => { + if (current) observer.unobserve(current); + }; + }, [loadNotices, isLoading]); + + // 반응형 + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth < 768); + }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + return ( +
+
+
+

내 가게

+ {shop ? ( +
+
+
+
+
+
+ 식당 +
+
{shop.name}
+
+
+ + {shop.address1} +
+
{shop.description}
+
+
+ + +
+
+
+ ) : ( + + )} +
+
+ {shop && ( +
+
+

+ {notices[0] && '내가 '}등록한 공고 +

+ {notices[0] ? ( +
+ {notices.map((notice) => ( + + ))} +
+ ) : ( + + )} +
+ {/* 스크롤 감지 요소 */} +
+ {isLoading && ( +
+ {isLoading === 'error' + ? '데이터를 불러오는데 실패했습니다.' + : '로딩 중...'} +
+ )} +
+
+ )} +
+ ); }