diff --git a/apps/nowait-user/src/pages/order/orderList/components/CartList.tsx b/apps/nowait-user/src/pages/order/orderList/components/CartList.tsx index 7f5c5497..e88f4733 100644 --- a/apps/nowait-user/src/pages/order/orderList/components/CartList.tsx +++ b/apps/nowait-user/src/pages/order/orderList/components/CartList.tsx @@ -26,7 +26,7 @@ const CartList = ({ cart, storeId }: Props) => { className="py-5 border-none" > 메뉴 추가하기 - + diff --git a/apps/nowait-user/src/pages/waiting/WaitingSummary/WaitingSummaryPage.tsx b/apps/nowait-user/src/pages/waiting/WaitingSummary/WaitingSummaryPage.tsx index a5f1df8a..01544380 100644 --- a/apps/nowait-user/src/pages/waiting/WaitingSummary/WaitingSummaryPage.tsx +++ b/apps/nowait-user/src/pages/waiting/WaitingSummary/WaitingSummaryPage.tsx @@ -1,20 +1,16 @@ import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { Button } from "@repo/ui"; -import { - createReservation, - getMyReservations, - getStore, -} from "../../../api/reservation"; import PageFooterButton from "../../../components/order/PageFooterButton"; import useThrottle from "../../../hooks/useThrottle"; import { useState } from "react"; -import { useQuery } from "@tanstack/react-query"; import BackOnlyHeader from "../../../components/BackOnlyHeader"; -import { WAITING_GUIDE } from "../../../constants/guides"; import FullPageLoader from "../../../components/FullPageLoader"; import LoadingSpinner from "../../../components/LoadingSpinner"; import useModal from "../../../hooks/useModal"; -import Portal from "../../../components/common/modal/Portal"; +import MaxReservationModal from "./components/MaxReservationModal"; +import WaitingGuide from "./components/WaitingGuide"; +import WaitingStoreSummary from "./components/WaitingStoreSummary"; +import { useWaitingSummary } from "./hooks/useWaitingSummary"; const WaitingSummaryPage = () => { const navigate = useNavigate(); @@ -23,41 +19,32 @@ const WaitingSummaryPage = () => { const partySize = Number(searchParams.get("partySize")); const [reservationIsLoading, setReservationIsLoading] = useState(false); const modal = useModal(); - const MAX_RESERVATIONS = 3; + + const { + store, + isStoreLoading, + myReservations, + canCreateReservation, + createWaiting, + MAX_RESERVATIONS, + } = useWaitingSummary(storeId); - const { data: store, isLoading } = useQuery({ - queryKey: ["store", storeId], - queryFn: () => getStore(storeId!), - select: (data) => data?.response, - }); - - const { data: myReservations } = useQuery({ - queryKey: ["myReservations"], - queryFn: getMyReservations, - select: (data) => data.response, - }); - - const handleSubmitReservation = useThrottle(async () => { - if (myReservations?.length >= MAX_RESERVATIONS) { + const handleSubmit = useThrottle(async () => { + if (!canCreateReservation) { modal.open(); return; } + try { setReservationIsLoading(true); - const payload = { - partySize, - }; - const res = await createReservation(Number(store?.storeId!), payload); - console.log(res, "예약 응답"); + await createWaiting(partySize); navigate(`/store/${storeId}/waiting/success`, { replace: true }); - } catch (error) { - console.log(error); } finally { setReservationIsLoading(false); } }, 3000); - if (isLoading) return ; + if (isStoreLoading) return ; return ( <> @@ -71,46 +58,16 @@ const WaitingSummaryPage = () => {
대기하고 있어요 -
-
-

부스

-
-

{store?.name} /

- 14 ? "block" : "inline" - } w-full truncate`} - > - {store?.departmentName} - -
-
-
-

입장 인원

-

{partySize}명

-
-
- -
-

- 대기 등록 전 꼭 확인해주세요 -

-
    - {WAITING_GUIDE.map((guide, i) => { - return ( -
  • - {guide.description} -
  • - ); - })} -
+
+ - - - - + navigate(`/store/${storeId}`, { replace: true })} + /> )} ); diff --git a/apps/nowait-user/src/pages/waiting/WaitingSummary/components/MaxReservationModal.tsx b/apps/nowait-user/src/pages/waiting/WaitingSummary/components/MaxReservationModal.tsx new file mode 100644 index 00000000..3912a8e4 --- /dev/null +++ b/apps/nowait-user/src/pages/waiting/WaitingSummary/components/MaxReservationModal.tsx @@ -0,0 +1,37 @@ +import Portal from "../../../../components/common/modal/Portal"; +import { Button } from "@repo/ui"; + +interface Props { + isOpen: boolean; + max: number; + onClose: () => void; + onGoHome: () => void; +} + +const MaxReservationModal = ({ isOpen, max, onClose, onGoHome }: Props) => { + if (!isOpen) return null; + + return ( + +
+
e.stopPropagation()} + > +

+ 주점 웨이팅은
+ {max}개까지 가능 합니다 +

+ + +
+
+
+ ); +}; + +export default MaxReservationModal; diff --git a/apps/nowait-user/src/pages/waiting/WaitingSummary/components/WaitingGuide.tsx b/apps/nowait-user/src/pages/waiting/WaitingSummary/components/WaitingGuide.tsx new file mode 100644 index 00000000..9078b16a --- /dev/null +++ b/apps/nowait-user/src/pages/waiting/WaitingSummary/components/WaitingGuide.tsx @@ -0,0 +1,23 @@ +import { WAITING_GUIDE } from "../../../../constants/guides"; + +const WaitingGuide = () => { + return ( +
+

+ 대기 등록 전 꼭 확인해주세요 +

+
    + {WAITING_GUIDE.map((guide, i) => ( +
  • + {guide.description} +
  • + ))} +
+
+ ); +}; + +export default WaitingGuide; diff --git a/apps/nowait-user/src/pages/waiting/WaitingSummary/components/WaitingStoreSummary.tsx b/apps/nowait-user/src/pages/waiting/WaitingSummary/components/WaitingStoreSummary.tsx new file mode 100644 index 00000000..3764caf2 --- /dev/null +++ b/apps/nowait-user/src/pages/waiting/WaitingSummary/components/WaitingStoreSummary.tsx @@ -0,0 +1,30 @@ +interface Props { + name: string; + departmentName: string; + partySize: number; +} + +const WaitingStoreSummary = ({ name, departmentName, partySize }: Props) => { + return ( +
+
+

부스

+
+

{name} /

+ 14 ? "block" : "inline"} truncate`} + > + {departmentName} + +
+
+ +
+

입장 인원

+

{partySize}명

+
+
+ ); +}; + +export default WaitingStoreSummary; diff --git a/apps/nowait-user/src/pages/waiting/WaitingSummary/hooks/useWaitingSummary.ts b/apps/nowait-user/src/pages/waiting/WaitingSummary/hooks/useWaitingSummary.ts new file mode 100644 index 00000000..1f437fed --- /dev/null +++ b/apps/nowait-user/src/pages/waiting/WaitingSummary/hooks/useWaitingSummary.ts @@ -0,0 +1,39 @@ +import { useQuery } from "@tanstack/react-query"; +import { + createReservation, + getMyReservations, + getStore, +} from "../../../../api/reservation"; + +const MAX_RESERVATIONS = 3; + +export const useWaitingSummary = (storeId: string | undefined) => { + const storeQuery = useQuery({ + queryKey: ["store", storeId], + queryFn: () => getStore(storeId!), + select: (data) => data?.response, + enabled: !!storeId, + }); + + const myReservationsQuery = useQuery({ + queryKey: ["myReservations"], + queryFn: getMyReservations, + select: (data) => data?.response, + }); + + const canCreateReservation = + (myReservationsQuery.data?.length ?? 0) < MAX_RESERVATIONS; + + const createWaiting = (partySize: number) => { + return createReservation(Number(storeQuery.data?.storeId!), { partySize }); + }; + + return { + store: storeQuery.data, + isStoreLoading: storeQuery.isLoading, + myReservations: myReservationsQuery.data, + canCreateReservation, + createWaiting, + MAX_RESERVATIONS, + }; +}; diff --git a/apps/nowait-user/src/pages/waiting/bookmark/BookmarkPage.tsx b/apps/nowait-user/src/pages/waiting/bookmark/BookmarkPage.tsx index 176ba343..82510400 100644 --- a/apps/nowait-user/src/pages/waiting/bookmark/BookmarkPage.tsx +++ b/apps/nowait-user/src/pages/waiting/bookmark/BookmarkPage.tsx @@ -6,6 +6,7 @@ import { getBookmark } from "../../../api/reservation"; import { useQuery } from "@tanstack/react-query"; import { useParams } from "react-router-dom"; import BookmarkListItemSkeleton from "./components/BookmarkListItemSkeleton"; +import NotFound from "../../NotFound/NotFound"; const BookmarkPage = () => { const { id: storeId } = useParams(); @@ -19,7 +20,8 @@ const BookmarkPage = () => { select: (data) => data.response, }); - if (!bookmarkList && !isLoading && isError) return
에러....
; + if (!bookmarkList && !isLoading && isError) return ; + return (
diff --git a/apps/nowait-user/src/pages/waiting/storeDetail/StoreDetailPage.tsx b/apps/nowait-user/src/pages/waiting/storeDetail/StoreDetailPage.tsx index e1315ce9..c68cafa9 100644 --- a/apps/nowait-user/src/pages/waiting/storeDetail/StoreDetailPage.tsx +++ b/apps/nowait-user/src/pages/waiting/storeDetail/StoreDetailPage.tsx @@ -1,173 +1,55 @@ -import Arrow from "../../../assets/icon/arrow-right.svg?react"; -import SubStract from "../../../assets/icon/subtract.svg?react"; -import Clock from "../../../assets/icon/clock.svg?react"; -import PageFooterButton from "../../../components/order/PageFooterButton"; -import { Button } from "@repo/ui"; -import { useNavigate, useParams } from "react-router-dom"; -import MenuList from "../../../components/common/MenuList"; -import { useBookmarkMutation } from "../../../hooks/mutate/useBookmark"; -import BookmarkIcon from "../../../components/common/BookmarkIcon"; -import { useBookmarkState } from "../../../hooks/useBookmarkState"; -import { useQuery } from "@tanstack/react-query"; -import { getStore } from "../../../api/reservation"; -import CommonSwiper from "../../../components/CommonSwiper"; -import SectionDivider from "../../../components/SectionDivider"; -import { formatTimeRange } from "../../../utils/formatTimeRange"; -import DepartmentImage from "../../../components/DepartmentImage"; -import NotFound from "../../NotFound/NotFound"; import FullPageLoader from "../../../components/FullPageLoader"; -import { useEffect } from "react"; +import NotFound from "../../NotFound/NotFound"; +import SectionDivider from "../../../components/SectionDivider"; +import MenuList from "../../../components/common/MenuList"; import { useStoreMenus } from "../../../hooks/order/useStoreMenus"; +import { useStoreDetail } from "./hooks/useStoreDetail"; +import StoreHeaderSection from "./components/StoreHeaderSection"; +import StoreInfoSection from "./components/StoreInfoSection"; +import StoreActionSection from "./components/StoreActionSection"; const StoreDetailPage = () => { - const navigate = useNavigate(); - const { id: storeId } = useParams(); - const { data: menus, isLoading: menusIsLoading } = useStoreMenus(storeId); - - const { - data: store, - isLoading: storeIsLoading, - isError, - } = useQuery({ - queryKey: ["store", storeId], - queryFn: () => getStore(storeId!), - select: (data) => data?.response, - }); + const { storeId, store, storeQuery, isBookmarked, toggleBookmark } = + useStoreDetail(); - //맨위로 위치 초기화 - useEffect(() => { - window.scrollTo(0, 0); - }, []); + const { data: menus, isLoading: menusLoading } = useStoreMenus(storeId); - const { createBookmarkMutate, deleteBookmarkMutate } = useBookmarkMutation( - { withInvalidate: true }, - Number(store?.storeId!) - ); - const { isBookmarked } = useBookmarkState(Number(store?.storeId!)); + if (storeQuery.isLoading) return ; + if (storeQuery.isError) return ; - const handleBookmarkButton = async () => { - try { - if (!isBookmarked) { - await createBookmarkMutate.mutate(); - } else { - await deleteBookmarkMutate.mutate(); - } - } catch (error) { - console.log(error); - } - }; - if (storeIsLoading) return ; - if (isError) return ; return (
-
- {/* 주점 배너 이미지 */} - - {/* 학과 정보 섹션 */} -
-
-
-

- {store?.departmentName} -

-

{store?.name}

-
- -
- {/* 주점 대기팀 인원 수 */} - {store?.waitingCount !== 0 && ( -
-

- 대기 {store?.waitingCount}팀 -

-
- )} -
- {/* 주점 정보(위치,운영 시간, 공지사항) */} -
-
-

- - - - {store?.location} -

-

- - - - {formatTimeRange(store?.openTime)} -

-
-

- {store?.description} -

- {/* 공지사항 */} - {store?.noticeTitle && ( - - )} -
+
+ + - {/* 주점 메뉴 리스트 */}
- - - {/* 주점 미오픈 시 버튼 */} - {!store?.isActive ? ( - - ) : ( - // 내 웨이팅 등록 현황에 따른 버튼 - - )} - +
); }; diff --git a/apps/nowait-user/src/pages/waiting/storeDetail/components/StoreActionSection.tsx b/apps/nowait-user/src/pages/waiting/storeDetail/components/StoreActionSection.tsx new file mode 100644 index 00000000..0c4c73e4 --- /dev/null +++ b/apps/nowait-user/src/pages/waiting/storeDetail/components/StoreActionSection.tsx @@ -0,0 +1,55 @@ +import { Button } from "@repo/ui"; +import { useNavigate } from "react-router-dom"; +import PageFooterButton from "../../../../components/order/PageFooterButton"; +import BookmarkIcon from "../../../../components/common/BookmarkIcon"; + +interface Props { + storeId: string; + storeName?: string; + isActive?: boolean; + isWaiting?: boolean; + isBookmarked: boolean; + onToggleBookmark: () => void; +} + +const StoreActionSection = ({ + storeId, + storeName, + isActive, + isWaiting, + isBookmarked, + onToggleBookmark, +}: Props) => { + const navigate = useNavigate(); + + return ( + + + + {!isActive ? ( + + ) : ( + + )} + + ); +}; + +export default StoreActionSection; diff --git a/apps/nowait-user/src/pages/waiting/storeDetail/components/StoreHeaderSection.tsx b/apps/nowait-user/src/pages/waiting/storeDetail/components/StoreHeaderSection.tsx new file mode 100644 index 00000000..b28a10bb --- /dev/null +++ b/apps/nowait-user/src/pages/waiting/storeDetail/components/StoreHeaderSection.tsx @@ -0,0 +1,45 @@ +import CommonSwiper from "../../../../components/CommonSwiper"; +import DepartmentImage from "../../../../components/DepartmentImage"; +import type { BannerImages } from "../../../../types/wait/store"; + +interface PropsType { + bannerImages?: BannerImages[]; + departmentName?: string; + name?: string; + waitingCount?: number; + profileImage?: string; +} + +const StoreHeaderSection = ({ + bannerImages, + departmentName, + name, + waitingCount, + profileImage, +}: PropsType) => { + return ( + <> + + +
+
+
+

{departmentName}

+

{name}

+
+ +
+ + {waitingCount !== 0 && ( +
+

+ 대기 {waitingCount}팀 +

+
+ )} +
+ + ); +}; + +export default StoreHeaderSection; diff --git a/apps/nowait-user/src/pages/waiting/storeDetail/components/StoreInfoSection.tsx b/apps/nowait-user/src/pages/waiting/storeDetail/components/StoreInfoSection.tsx new file mode 100644 index 00000000..5562c2c1 --- /dev/null +++ b/apps/nowait-user/src/pages/waiting/storeDetail/components/StoreInfoSection.tsx @@ -0,0 +1,60 @@ +import SubStract from "../../../../assets/icon/subtract.svg?react"; +import Clock from "../../../../assets/icon/clock.svg?react"; +import Arrow from "../../../../assets/icon/arrow-right.svg?react"; +import { useNavigate } from "react-router-dom"; +import { formatTimeRange } from "../../../../utils/formatTimeRange"; + +interface PropsType { + storeId: string; + location?: string; + openTime?: string; + description?: string; + noticeTitle?: string; + noticeContent?: string; +} + +const StoreInfoSection = ({ + storeId, + location, + openTime, + description, + noticeTitle, + noticeContent, +}: PropsType) => { + const navigate = useNavigate(); + + return ( +
+
+

+ + {location} +

+

+ + {formatTimeRange(openTime)} +

+
+ +

+ {description} +

+ + {noticeTitle && ( + + )} +
+ ); +}; + +export default StoreInfoSection; \ No newline at end of file diff --git a/apps/nowait-user/src/pages/waiting/storeDetail/hooks/useStoreDetail.ts b/apps/nowait-user/src/pages/waiting/storeDetail/hooks/useStoreDetail.ts new file mode 100644 index 00000000..e69108f2 --- /dev/null +++ b/apps/nowait-user/src/pages/waiting/storeDetail/hooks/useStoreDetail.ts @@ -0,0 +1,44 @@ +import { useEffect } from "react"; +import { useParams } from "react-router-dom"; +import { useQuery } from "@tanstack/react-query"; +import { getStore } from "../../../../api/reservation"; +import { useBookmarkState } from "../../../../hooks/useBookmarkState"; +import { useBookmarkMutation } from "../../../../hooks/mutate/useBookmark"; + + +export const useStoreDetail = () => { + const { id: storeId } = useParams(); + + const storeQuery = useQuery({ + queryKey: ["store", storeId], + queryFn: () => getStore(storeId!), + select: (data) => data?.response, + enabled: !!storeId, + }); + + const storeIdNumber = Number(storeQuery.data?.storeId); + + const { isBookmarked } = useBookmarkState(storeIdNumber); + const { createBookmarkMutate, deleteBookmarkMutate } = + useBookmarkMutation({ withInvalidate: true }, storeIdNumber); + + const toggleBookmark = async () => { + if (isBookmarked) { + await deleteBookmarkMutate.mutate(); + } else { + await createBookmarkMutate.mutate(); + } + }; + + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + return { + storeId, + store: storeQuery.data, + storeQuery, + isBookmarked, + toggleBookmark, + }; +}; \ No newline at end of file diff --git a/apps/nowait-user/src/types/wait/store.ts b/apps/nowait-user/src/types/wait/store.ts index b3c19631..98b6cb4e 100644 --- a/apps/nowait-user/src/types/wait/store.ts +++ b/apps/nowait-user/src/types/wait/store.ts @@ -31,7 +31,8 @@ export interface StoreResponse { isActive: boolean; deleted: boolean; createdAt: string; - isBookmarked: boolean; + bookmarkId: number; + isBookmark: boolean; }; } @@ -57,7 +58,7 @@ export interface StoreType { departmentName: string; description: string; isActive: boolean; - isBookmark: boolean; + isBookmarked: boolean; location: string; name: string; noticeTitle: string | null; diff --git a/packages/ui/src/button.tsx b/packages/ui/src/button.tsx index 43be608b..a014d4b2 100644 --- a/packages/ui/src/button.tsx +++ b/packages/ui/src/button.tsx @@ -76,7 +76,7 @@ export const Button: React.FC = ({ ? "var(--black-25)" : backgroundColor || buttonConfig.defaultBg, color: disabled ? "var(--black-55)" : textColor || buttonConfig.defaultText, - borderColor: borderColor || undefined, + borderColor: borderColor, width: fullWidth ? "100%" : buttonConfig.width, height: buttonConfig.height, };