diff --git a/src/app/router/index.tsx b/src/app/router/index.tsx index 4589659..6904a57 100644 --- a/src/app/router/index.tsx +++ b/src/app/router/index.tsx @@ -14,6 +14,7 @@ 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 RegisterPlaceInpersonPage from "@/pages/RegisterPlaceInpersonPage"; import RegisterSelectRoomPage from "@/pages/RegisterSelectRoomPage"; import SplashScreenPage from "@/pages/SplashScreenPage"; import { APP_ROUTES } from "@/shared/config/routes"; @@ -35,6 +36,7 @@ export const router = createBrowserRouter([ { path: "dev/SelectOption", element: }, { path: "dev/register_place", element: }, { path: "edit_place", element: }, + { path: "register-place-inperson", element: }, { path: "register-select-room", element: }, { path: "dev/list", diff --git a/src/pages/RegisterPlaceInpersonPage.tsx b/src/pages/RegisterPlaceInpersonPage.tsx new file mode 100644 index 0000000..5839de9 --- /dev/null +++ b/src/pages/RegisterPlaceInpersonPage.tsx @@ -0,0 +1,169 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { 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 { useInpersonPlaceStore } from "@/store/inpersonPlaceStore"; +import { useRegisterRoomStore } from "@/store/registerRoomStore"; + +const REELS_LINK_MOCK = "https://www.instagram.com/reel/DNp9tqSz6rT/?igsh=MW4yOGd6aGNzMmRsYw=="; + +export default function RegisterPlaceInpersonPage() { + const navigate = useNavigate(); + const keyword = useInpersonPlaceStore((state) => state.keyword); + const selectedPlaceId = useInpersonPlaceStore((state) => state.selectedPlaceId); + const setKeyword = useInpersonPlaceStore((state) => state.setKeyword); + const setSelectedPlace = useInpersonPlaceStore((state) => state.setSelectedPlace); + const reset = useInpersonPlaceStore((state) => state.reset); + const setSelectedPlacesForRegister = useRegisterRoomStore((state) => state.setSelectedPlaces); + const [copyLabel, setCopyLabel] = useState("복사"); + + useEffect(() => { + reset(); + }, [reset]); + + const trimmedKeyword = keyword.trim(); + const canSearch = trimmedKeyword.length > 0; + const canConfirm = selectedPlaceId !== null; + + const searchResults = useMemo(() => { + if (!trimmedKeyword) { + return []; + } + + return SAVED_PLACE_MOCKS.filter( + (place) => place.name.includes(trimmedKeyword) || place.address.includes(trimmedKeyword), + ); + }, [trimmedKeyword]); + + const selectedPlace = useMemo( + () => SAVED_PLACE_MOCKS.find((place) => place.id === selectedPlaceId) ?? null, + [selectedPlaceId], + ); + + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(REELS_LINK_MOCK); + setCopyLabel("복사됨"); + window.setTimeout(() => setCopyLabel("복사"), 1500); + } catch { + setCopyLabel("실패"); + window.setTimeout(() => setCopyLabel("복사"), 1500); + } + }, []); + + const handleCancel = () => { + reset(); + navigate(-1); + }; + + const handleConfirm = () => { + if (!selectedPlaceId) { + return; + } + + setSelectedPlacesForRegister([selectedPlaceId]); + navigate("/register-select-room", { + state: { + selectedPlaceIds: [selectedPlaceId], + selectedPlaceCount: 1, + selectedPlace, + }, + }); + }; + + return ( +
+
+
+
+

+ 장소 인식에 실패했습니다. +

+

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

+
+ +
+

{REELS_LINK_MOCK}

+ +
+ + + + {trimmedKeyword ? ( +
    + {searchResults.length === 0 ? ( +
  • + 검색 결과가 없습니다 +
  • + ) : ( + searchResults.map((place) => ( + setSelectedPlace(place.id)} + /> + )) + )} +
+ ) : null} +
+
+ +
+
+ + 취소 + + + 확인 + +
+
+
+ ); +} diff --git a/src/store/inpersonPlaceStore.ts b/src/store/inpersonPlaceStore.ts new file mode 100644 index 0000000..f1804f8 --- /dev/null +++ b/src/store/inpersonPlaceStore.ts @@ -0,0 +1,21 @@ +import { create } from "zustand"; + +type InpersonPlaceState = { + keyword: string; + selectedPlaceId: string | null; + setKeyword: (keyword: string) => void; + setSelectedPlace: (placeId: string | null) => void; + reset: () => void; +}; + +export const useInpersonPlaceStore = create((set) => ({ + keyword: "", + selectedPlaceId: null, + setKeyword: (keyword) => set({ keyword }), + setSelectedPlace: (placeId) => set({ selectedPlaceId: placeId }), + reset: () => + set({ + keyword: "", + selectedPlaceId: null, + }), +}));