diff --git a/src/app/router/index.tsx b/src/app/router/index.tsx index 05df072..4589659 100644 --- a/src/app/router/index.tsx +++ b/src/app/router/index.tsx @@ -7,11 +7,14 @@ import { ProtectedRoute } from "@/app/router/ProtectedRoute"; import AuthCallbackPage from "@/pages/AuthCallbackPage"; import DevClickPlacePage from "@/pages/dev/DevClickPlacePage"; import DevSelectOptionPage from "@/pages/dev/DevSelectOptionPage"; +import EditPlacePage from "@/pages/EditPlacePage"; import EntryPage from "@/pages/EntryPage"; import LoginPage from "@/pages/LoginPage"; import { mapHomeLoader } from "@/pages/map/map-home-loader"; import NicknamePage from "@/pages/onboarding/NicknamePage"; import TermsAgreementPage from "@/pages/onboarding/TermsAgreementPage"; +import ReelsPlaceSelectPage from "@/pages/ReelsPlaceSelectPage"; +import RegisterSelectRoomPage from "@/pages/RegisterSelectRoomPage"; import SplashScreenPage from "@/pages/SplashScreenPage"; import { APP_ROUTES } from "@/shared/config/routes"; @@ -30,6 +33,9 @@ export const router = createBrowserRouter([ { path: "dev/splash", element: }, { path: "dev/click_place", element: }, { path: "dev/SelectOption", element: }, + { path: "dev/register_place", element: }, + { path: "edit_place", element: }, + { path: "register-select-room", element: }, { path: "dev/list", element: ( diff --git a/src/components/common/SearchField.tsx b/src/components/common/SearchField.tsx index 081e6aa..4a220e8 100644 --- a/src/components/common/SearchField.tsx +++ b/src/components/common/SearchField.tsx @@ -9,6 +9,7 @@ export type SearchFieldProps = Omit, "type"> & { inputClassName?: string; searchButtonLabel?: string; onSubmitSearch?: () => void; + searchButtonDisabled?: boolean; }; export const SearchField = React.forwardRef( @@ -18,6 +19,7 @@ export const SearchField = React.forwardRef( inputClassName, searchButtonLabel = "검색", onSubmitSearch, + searchButtonDisabled = false, id, placeholder = SEARCH_FIELD_DEFAULT_PLACEHOLDER, "aria-label": ariaLabel, @@ -51,8 +53,9 @@ export const SearchField = React.forwardRef( /> {!readOnly ? ( diff --git a/src/components/reels/EditPlaceResultCard.tsx b/src/components/reels/EditPlaceResultCard.tsx new file mode 100644 index 0000000..983d948 --- /dev/null +++ b/src/components/reels/EditPlaceResultCard.tsx @@ -0,0 +1,34 @@ +import { RoundSelectionCheck } from "@/components/ui/RoundSelectionCheck"; +import type { SavedPlace } from "@/shared/types/map-home"; + +type EditPlaceResultCardProps = { + place: SavedPlace; + selected: boolean; + onSelect: () => void; +}; + +export function EditPlaceResultCard({ place, selected, onSelect }: EditPlaceResultCardProps) { + return ( +
  • + +
  • + ); +} diff --git a/src/components/reels/PlaceSelectCard.tsx b/src/components/reels/PlaceSelectCard.tsx new file mode 100644 index 0000000..20af514 --- /dev/null +++ b/src/components/reels/PlaceSelectCard.tsx @@ -0,0 +1,81 @@ +import { MapPin, Pencil } from "lucide-react"; + +import { RoundSelectionCheck } from "@/components/ui/RoundSelectionCheck"; +import { cn } from "@/lib/utils"; +import type { SavedPlace } from "@/shared/types/map-home"; + +type PlaceSelectCardProps = { + place: SavedPlace; + selected: boolean; + disabled: boolean; + onSelect: () => void; + onEdit: () => void; +}; + +export function PlaceSelectCard({ + place, + selected, + disabled, + onSelect, + onEdit, +}: PlaceSelectCardProps) { + const handleSelect = () => { + if (!disabled) { + onSelect(); + } + }; + + return ( +
  • +
    +
    +
    + {place.name} + +
    +

    + + {place.address} +

    +
    + + {disabled ? ( + + 이미 저장된 장소입니다 + + ) : ( + + )} +
    +
  • + ); +} diff --git a/src/components/room/FriendRoomItemView.tsx b/src/components/room/FriendRoomItemView.tsx index 642e65f..535cafb 100644 --- a/src/components/room/FriendRoomItemView.tsx +++ b/src/components/room/FriendRoomItemView.tsx @@ -29,6 +29,7 @@ export const FriendRoomItemView = memo(function FriendRoomItemView({ onKeyDown, }: FriendRoomItemViewProps) { const pinned = Boolean(row.isPinned); + const placeCountText = row.placeCount >= 1000 ? "1k+개 장소" : `${row.placeCount}개 장소`; return (
    - {row.placeCount}개 장소 + {placeCountText}
    diff --git a/src/components/room/RoomConfirmModal.tsx b/src/components/room/RoomConfirmModal.tsx new file mode 100644 index 0000000..846e7aa --- /dev/null +++ b/src/components/room/RoomConfirmModal.tsx @@ -0,0 +1,85 @@ +import { PillButton } from "@/components/ui/PillButton"; +import { cn } from "@/lib/utils"; + +import { RoomModalShell } from "./RoomModalShell"; + +type RoomConfirmModalProps = { + open: boolean; + message: string; + cancelLabel?: string; + confirmLabel: string; + onCancel?: () => void; + onConfirm: () => void; +}; + +export function RoomConfirmModal({ + open, + message, + cancelLabel, + confirmLabel, + onCancel, + onConfirm, +}: RoomConfirmModalProps) { + if (!open) { + return null; + } + + const isSingleAction = !cancelLabel; + + return ( + { + if (!isSingleAction) { + onCancel?.(); + } + }} + className="z-60" + > + {isSingleAction ? ( +
    +

    + {message} +

    + + {confirmLabel} + +
    + ) : ( + <> +
    +

    + {message} +

    +
    + +
    + + +
    + + )} +
    + ); +} diff --git a/src/components/room/link-add/PlaceSelectionScreen.tsx b/src/components/room/link-add/PlaceSelectionScreen.tsx index 52eb6d3..a820fdc 100644 --- a/src/components/room/link-add/PlaceSelectionScreen.tsx +++ b/src/components/room/link-add/PlaceSelectionScreen.tsx @@ -1,4 +1,5 @@ import { PillButton } from "@/components/ui/PillButton"; +import { RoundSelectionCheck } from "@/components/ui/RoundSelectionCheck"; import type { MockPlaceCandidate } from "@/features/room/link-add"; import { cn } from "@/lib/utils"; @@ -52,12 +53,7 @@ export function PlaceSelectionScreen({ onClick={() => onSelectPlace(place.id)} > {place.name} - + ); diff --git a/src/components/ui/RoundSelectionCheck.tsx b/src/components/ui/RoundSelectionCheck.tsx new file mode 100644 index 0000000..f9c6e74 --- /dev/null +++ b/src/components/ui/RoundSelectionCheck.tsx @@ -0,0 +1,23 @@ +import { Check } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +export type RoundSelectionCheckProps = { + selected: boolean; + className?: string; +}; + +export function RoundSelectionCheck({ selected, className }: RoundSelectionCheckProps) { + return ( + + {selected ? : null} + + ); +} diff --git a/src/features/onboarding/components/AgreementItem.tsx b/src/features/onboarding/components/AgreementItem.tsx index fce2a3b..c229377 100644 --- a/src/features/onboarding/components/AgreementItem.tsx +++ b/src/features/onboarding/components/AgreementItem.tsx @@ -1,5 +1,4 @@ -import { Check } from "lucide-react"; - +import { RoundSelectionCheck } from "@/components/ui/RoundSelectionCheck"; import { cn } from "@/lib/utils"; type AgreementItemProps = { @@ -39,15 +38,7 @@ export function AgreementItem({ onClick={onToggle} className="text-foreground active:bg-tap-highlight flex w-full cursor-pointer items-start gap-3 rounded-xl py-2.5 text-left text-[1rem] leading-snug" > - - {checked ? : null} - + {label} {showBadge ? ( diff --git a/src/pages/EditPlacePage.tsx b/src/pages/EditPlacePage.tsx new file mode 100644 index 0000000..0247e53 --- /dev/null +++ b/src/pages/EditPlacePage.tsx @@ -0,0 +1,168 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; + +import { SearchField } from "@/components/common/SearchField"; +import { EditPlaceResultCard } from "@/components/reels/EditPlaceResultCard"; +import { PillButton } from "@/components/ui/PillButton"; +import { SAVED_PLACE_MOCKS } from "@/shared/mocks/place-mocks"; +import { useEditPlaceStore } from "@/store/editPlaceStore"; + +const REELS_LINK_MOCK = "https://www.instagram.com/reel/DNp9tqSz6rT/?igsh=MW4yOGd6aGNzMmRsYw=="; + +type EditPlaceLocationState = { + placeId?: string; + placeName?: string; +}; + +export default function EditPlacePage() { + const navigate = useNavigate(); + const location = useLocation(); + const routeState = (location.state ?? {}) as EditPlaceLocationState; + const editingPlaceId = useEditPlaceStore((state) => state.editingPlaceId); + const searchKeyword = useEditPlaceStore((state) => state.searchKeyword); + const selectedResultId = useEditPlaceStore((state) => state.selectedResultId); + const setEditingPlace = useEditPlaceStore((state) => state.setEditingPlace); + const setKeyword = useEditPlaceStore((state) => state.setKeyword); + const setSelectedResult = useEditPlaceStore((state) => state.setSelectedResult); + const reset = useEditPlaceStore((state) => state.reset); + const [copyLabel, setCopyLabel] = useState("복사"); + + useEffect(() => { + const nextPlaceId = routeState.placeId ?? editingPlaceId; + const nextKeyword = routeState.placeName ?? searchKeyword; + + setEditingPlace(nextPlaceId ?? null); + setKeyword(nextKeyword); + setSelectedResult(null); + // This page is entered with router state, so initialization should only run on entry. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const trimmedKeyword = searchKeyword.trim(); + const canSearch = trimmedKeyword.length > 0; + const canConfirm = selectedResultId != null; + + const searchResults = useMemo(() => { + if (!trimmedKeyword) { + return []; + } + + return SAVED_PLACE_MOCKS.filter( + (place) => place.name.includes(trimmedKeyword) || place.address.includes(trimmedKeyword), + ); + }, [trimmedKeyword]); + + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(REELS_LINK_MOCK); + setCopyLabel("복사됨"); + window.setTimeout(() => setCopyLabel("복사"), 1500); + } catch { + setCopyLabel("실패"); + window.setTimeout(() => setCopyLabel("복사"), 1500); + } + }, []); + + const handleBack = () => { + reset(); + navigate(-1); + }; + + const handleConfirm = () => { + if (!selectedResultId) { + return; + } + + navigate("/dev/register_place"); + }; + + return ( +
    +
    +
    +
    +

    + 장소 위치 정보를 변경하시겠습니까? +

    +

    + 해당 장소를 직접 입력해주세요 +

    +
    + +
    +

    {REELS_LINK_MOCK}

    + +
    + + + + {trimmedKeyword ? ( +
      + {searchResults.length === 0 ? ( +
    • + 검색 결과가 없습니다 +
    • + ) : ( + searchResults.map((place) => ( + setSelectedResult(place.id)} + /> + )) + )} +
    + ) : null} +
    +
    + +
    +
    + + 취소 + + + 확인 + +
    +
    +
    + ); +} diff --git a/src/pages/ReelsPlaceSelectPage.tsx b/src/pages/ReelsPlaceSelectPage.tsx new file mode 100644 index 0000000..c1e45da --- /dev/null +++ b/src/pages/ReelsPlaceSelectPage.tsx @@ -0,0 +1,146 @@ +import { useCallback, useMemo, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import { PlaceSelectCard } from "@/components/reels/PlaceSelectCard"; +import { PillButton } from "@/components/ui/PillButton"; +import { SAVED_PLACE_MOCKS } from "@/shared/mocks/place-mocks"; +import { useEditPlaceStore } from "@/store/editPlaceStore"; +import { useReelsPlaceSelectStore } from "@/store/reelsPlaceSelectStore"; +import { useRegisterRoomStore } from "@/store/registerRoomStore"; + +const REELS_LINK_MOCK = "https://www.instagram.com/reel/DNp9tqSz6rT/?igsh=MW4yOGd6aGNzMmRsYw=="; +const PLACE_RENDER_ORDER = [ + "restaurant-1", + "restaurant-2", + "restaurant-3", + "restaurant-4", + "restaurant-5", + "cafe-1", +]; +const SAVED_PLACE_ID = "restaurant-3"; + +export default function ReelsPlaceSelectPage() { + const navigate = useNavigate(); + const selectedPlaceIds = useReelsPlaceSelectStore((state) => state.selectedPlaceIds); + const togglePlace = useReelsPlaceSelectStore((state) => state.togglePlace); + const clearSelection = useReelsPlaceSelectStore((state) => state.clearSelection); + const editingPlaceId = useEditPlaceStore((state) => state.editingPlaceId); + const selectedResultId = useEditPlaceStore((state) => state.selectedResultId); + const setSelectedPlacesForRegister = useRegisterRoomStore((state) => state.setSelectedPlaces); + const [copyLabel, setCopyLabel] = useState("복사"); + + const placeRows = useMemo( + () => + PLACE_RENDER_ORDER.map((placeId) => { + const originalPlace = SAVED_PLACE_MOCKS.find((place) => place.id === placeId); + const editedPlace = + editingPlaceId === placeId && selectedResultId + ? SAVED_PLACE_MOCKS.find((place) => place.id === selectedResultId) + : null; + + const place = editedPlace ?? originalPlace; + + return place ? { slotId: placeId, place } : null; + }).filter((row) => row != null), + [editingPlaceId, selectedResultId], + ); + const canConfirm = selectedPlaceIds.length > 0; + + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(REELS_LINK_MOCK); + setCopyLabel("복사됨"); + window.setTimeout(() => setCopyLabel("복사"), 1500); + } catch { + setCopyLabel("실패"); + window.setTimeout(() => setCopyLabel("복사"), 1500); + } + }, []); + + return ( +
    +
    +
    +
    +

    + 장소가 인식되었습니다. +

    +

    + 어느 장소를 등록하시겠습니까? +

    +
    + +
    +

    {REELS_LINK_MOCK}

    + +
    +
    +
    + +
    +
      + {placeRows.map(({ slotId, place }) => { + const disabled = slotId === SAVED_PLACE_ID; + return ( + togglePlace(slotId)} + onEdit={() => + navigate("/edit_place", { + state: { + placeId: slotId, + placeName: place.name, + }, + }) + } + /> + ); + })} +
    +
    + +
    +
    + + 취소 + + { + setSelectedPlacesForRegister(selectedPlaceIds); + navigate("/register-select-room", { + state: { + selectedPlaceIds, + selectedPlaceCount: selectedPlaceIds.length, + }, + }); + }} + > + 확인 + +
    +
    +
    + ); +} diff --git a/src/pages/RegisterSelectRoomPage.tsx b/src/pages/RegisterSelectRoomPage.tsx new file mode 100644 index 0000000..ac5968f --- /dev/null +++ b/src/pages/RegisterSelectRoomPage.tsx @@ -0,0 +1,87 @@ +import { useEffect, useMemo } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; + +import { BottomNavigationBar } from "@/components/common/BottomNavigationBar"; +import { FloatingActionButton } from "@/components/common/FloatingActionButton"; +import { FriendRoomList } from "@/components/room/FriendRoomList"; +import { RoomConfirmModal } from "@/components/room/RoomConfirmModal"; +import { RoomMainHeader } from "@/components/room/RoomMainHeader"; +import { RoomMainShell } from "@/components/room/RoomMainShell"; +import { FRIEND_ROOM_MOCK_ROWS } from "@/pages/room/friend-room-mock"; +import type { FriendRoomRow } from "@/shared/types/room"; +import { useRegisterRoomStore } from "@/store/registerRoomStore"; + +type RegisterSelectRoomLocationState = { + selectedPlaceIds?: string[]; + selectedPlaceCount?: number; +}; + +export default function RegisterSelectRoomPage() { + const navigate = useNavigate(); + const location = useLocation(); + const routeState = (location.state ?? {}) as RegisterSelectRoomLocationState; + const selectedRoomId = useRegisterRoomStore((state) => state.selectedRoomId); + const selectedPlaceCount = useRegisterRoomStore((state) => state.selectedPlaceCount); + const confirmModalOpen = useRegisterRoomStore((state) => state.confirmModalOpen); + const roomPlaceCountDeltas = useRegisterRoomStore((state) => state.roomPlaceCountDeltas); + const setSelectedPlaces = useRegisterRoomStore((state) => state.setSelectedPlaces); + const setSelectedRoom = useRegisterRoomStore((state) => state.setSelectedRoom); + const openConfirm = useRegisterRoomStore((state) => state.openConfirm); + const closeConfirm = useRegisterRoomStore((state) => state.closeConfirm); + const completeRegister = useRegisterRoomStore((state) => state.completeRegister); + + useEffect(() => { + if (routeState.selectedPlaceIds && selectedPlaceCount === 0) { + setSelectedPlaces(routeState.selectedPlaceIds); + } + // Router state is only used as an entry payload. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const rows = useMemo( + () => + FRIEND_ROOM_MOCK_ROWS.map((row) => ({ + ...row, + placeCount: row.placeCount + (roomPlaceCountDeltas[row.id] ?? 0), + })), + [roomPlaceCountDeltas], + ); + const selectedRoom = useMemo( + () => rows.find((row) => row.id === selectedRoomId) ?? null, + [rows, selectedRoomId], + ); + + const handleSelectRoom = (row: FriendRoomRow) => { + setSelectedRoom(row.id); + openConfirm(); + }; + + return ( + + } + fab={} + bottomNav={ undefined} />} + > + + + { + if (completeRegister()) { + navigate("/room", { state: { showPlacesRegisteredToast: true } }); + } + }} + /> + + ); +} diff --git a/src/pages/room/RoomMainPage.tsx b/src/pages/room/RoomMainPage.tsx index 712aa21..46caa1f 100644 --- a/src/pages/room/RoomMainPage.tsx +++ b/src/pages/room/RoomMainPage.tsx @@ -1,5 +1,5 @@ -import { lazy, Suspense, useCallback, useState } from "react"; -import { useNavigate } from "react-router-dom"; +import { lazy, Suspense, useCallback, useEffect, useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; import { BottomNavigationBar } from "@/components/common/BottomNavigationBar"; import { BottomNavToast } from "@/components/common/BottomNavToast"; @@ -11,8 +11,10 @@ import { useRoomActionModalHistory, useRoomMainModals } from "@/features/room"; import type { RoomActionType } from "@/features/room/roomActionTypes"; import { useBottomNavController } from "@/hooks/use-bottom-nav-controller"; import { APP_ROUTES } from "@/shared/config/routes"; +import { REGISTER_SELECT_ROOM_TEXT } from "@/shared/config/text"; import type { FriendRoomRow } from "@/shared/types/room"; import { useAuthStore } from "@/store/auth-store"; +import { useRegisterRoomStore } from "@/store/registerRoomStore"; import { useRoomSelectionStore } from "@/store/room-selection-store"; const RoomActionModal = lazy(() => @@ -42,9 +44,15 @@ const EditRoomNameModal = lazy(() => })), ); +type RoomMainLocationState = { + showPlacesRegisteredToast?: boolean; +}; + export default function RoomMainPage() { const navigate = useNavigate(); + const location = useLocation(); const selectRoom = useRoomSelectionStore((s) => s.selectRoom); + const roomPlaceCountDeltas = useRegisterRoomStore((state) => state.roomPlaceCountDeltas); const nickname = useAuthStore((s) => s.nickname); const roomMainHeaderTitle = nickname != null && nickname.trim().length > 0 @@ -79,6 +87,21 @@ export default function RoomMainPage() { const [isLinkAddModalLoaded, setIsLinkAddModalLoaded] = useState(linkAddRoom != null); const [isRoomAddModalLoaded, setIsRoomAddModalLoaded] = useState(isAddRoomOpen); + useEffect(() => { + const state = (location.state ?? null) as RoomMainLocationState | null; + if (!state?.showPlacesRegisteredToast) { + return; + } + + showToast(REGISTER_SELECT_ROOM_TEXT.placesRegisteredToast, 2000); + navigate(location.pathname, { replace: true, state: {} }); + }, [location.pathname, location.state, navigate, showToast]); + + const displayRows = sortedRows.map((row) => ({ + ...row, + placeCount: row.placeCount + (roomPlaceCountDeltas[row.id] ?? 0), + })); + const handleRoomNavigate = useCallback( (row: FriendRoomRow) => { selectRoom({ id: row.id, name: row.displayName, memberCount: row.memberCount }); @@ -125,7 +148,7 @@ export default function RoomMainPage() { bottomNav={} > diff --git a/src/pages/room/friend-room-mock.ts b/src/pages/room/friend-room-mock.ts index ba4fd45..0060afc 100644 --- a/src/pages/room/friend-room-mock.ts +++ b/src/pages/room/friend-room-mock.ts @@ -5,7 +5,7 @@ export type { FriendRoomRow } from "@/shared/types/room"; export const FRIEND_ROOM_MOCK_ROWS: FriendRoomRow[] = [ { id: "1", - displayName: "내꺼♥️", + displayName: "내꺼♥", memberCount: 2, placeCount: 5, isPinned: true, diff --git a/src/shared/config/text.ts b/src/shared/config/text.ts index 14c02a9..51796a6 100644 --- a/src/shared/config/text.ts +++ b/src/shared/config/text.ts @@ -6,3 +6,7 @@ export const PLACE_LIST_TEXT = { export const BOTTOM_NAV_TEXT = { roomRequiredToast: "방을 먼저 선택해주세요.", } as const; + +export const REGISTER_SELECT_ROOM_TEXT = { + placesRegisteredToast: "등록되었습니다", +} as const; diff --git a/src/store/editPlaceStore.ts b/src/store/editPlaceStore.ts new file mode 100644 index 0000000..d7ae683 --- /dev/null +++ b/src/store/editPlaceStore.ts @@ -0,0 +1,26 @@ +import { create } from "zustand"; + +type EditPlaceState = { + editingPlaceId: string | null; + searchKeyword: string; + selectedResultId: string | null; + setEditingPlace: (placeId: string | null) => void; + setKeyword: (keyword: string) => void; + setSelectedResult: (resultId: string | null) => void; + reset: () => void; +}; + +export const useEditPlaceStore = create((set) => ({ + editingPlaceId: null, + searchKeyword: "", + selectedResultId: null, + setEditingPlace: (placeId) => set({ editingPlaceId: placeId }), + setKeyword: (keyword) => set({ searchKeyword: keyword }), + setSelectedResult: (resultId) => set({ selectedResultId: resultId }), + reset: () => + set({ + editingPlaceId: null, + searchKeyword: "", + selectedResultId: null, + }), +})); diff --git a/src/store/reelsPlaceSelectStore.ts b/src/store/reelsPlaceSelectStore.ts new file mode 100644 index 0000000..a7233d7 --- /dev/null +++ b/src/store/reelsPlaceSelectStore.ts @@ -0,0 +1,18 @@ +import { create } from "zustand"; + +type ReelsPlaceSelectState = { + selectedPlaceIds: string[]; + togglePlace: (id: string) => void; + clearSelection: () => void; +}; + +export const useReelsPlaceSelectStore = create((set) => ({ + selectedPlaceIds: [], + togglePlace: (id) => + set((state) => ({ + selectedPlaceIds: state.selectedPlaceIds.includes(id) + ? state.selectedPlaceIds.filter((placeId) => placeId !== id) + : [...state.selectedPlaceIds, id], + })), + clearSelection: () => set({ selectedPlaceIds: [] }), +})); diff --git a/src/store/registerRoomStore.ts b/src/store/registerRoomStore.ts new file mode 100644 index 0000000..ae18cdd --- /dev/null +++ b/src/store/registerRoomStore.ts @@ -0,0 +1,57 @@ +import { create } from "zustand"; + +type RegisterRoomState = { + selectedPlaceIds: string[]; + selectedPlaceCount: number; + selectedRoomId: string | null; + confirmModalOpen: boolean; + roomPlaceCountDeltas: Record; + setSelectedPlaces: (placeIds: string[]) => void; + setSelectedRoom: (roomId: string | null) => void; + openConfirm: () => void; + closeConfirm: () => void; + completeRegister: () => boolean; + resetFlow: () => void; +}; + +export const useRegisterRoomStore = create((set, get) => ({ + selectedPlaceIds: [], + selectedPlaceCount: 0, + selectedRoomId: null, + confirmModalOpen: false, + roomPlaceCountDeltas: {}, + setSelectedPlaces: (placeIds) => + set({ + selectedPlaceIds: placeIds, + selectedPlaceCount: placeIds.length, + }), + setSelectedRoom: (roomId) => set({ selectedRoomId: roomId }), + openConfirm: () => set({ confirmModalOpen: true }), + closeConfirm: () => set({ confirmModalOpen: false }), + completeRegister: () => { + const { selectedRoomId, selectedPlaceCount, roomPlaceCountDeltas } = get(); + + if (!selectedRoomId || selectedPlaceCount <= 0) { + return false; + } + + set({ + roomPlaceCountDeltas: { + ...roomPlaceCountDeltas, + [selectedRoomId]: (roomPlaceCountDeltas[selectedRoomId] ?? 0) + selectedPlaceCount, + }, + selectedPlaceIds: [], + selectedPlaceCount: 0, + selectedRoomId: null, + confirmModalOpen: false, + }); + return true; + }, + resetFlow: () => + set({ + selectedPlaceIds: [], + selectedPlaceCount: 0, + selectedRoomId: null, + confirmModalOpen: false, + }), +}));