From 1a1cefcfbe57712c509b15fcbf8b4f9b340d2b6f Mon Sep 17 00:00:00 2001 From: choihooo Date: Thu, 31 Oct 2024 05:01:40 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[feat]=20#40=20=ED=95=91=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8B=B0=EB=A5=BC=20=EC=A0=9C=EC=99=B8?= =?UTF-8?q?=ED=95=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=97=B0=EA=B2=B0=20?= =?UTF-8?q?=EB=B0=8F=20api=20=EC=97=B0=EB=8F=99=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PincheckInput.tsx | 182 ++++++++++++++++++ .../load-mappin-edit/components/Form.tsx | 0 .../load-mappin-edit/components/LinkField.tsx | 0 .../[nonMemberId]}/load-mappin-edit/page.tsx | 5 +- .../[id]/[nonMemberId]}/page.tsx | 2 +- .../[id]/components/BottomDrawer.tsx | 12 +- .../[id]/load-mappin/components/Form.tsx | 14 +- .../app/event-maps/[id]/load-mappin/page.tsx | 3 +- fe/src/app/event-maps/[id]/page.tsx | 19 +- fe/src/app/eventcreate-page/page.tsx | 2 +- .../components/PincheckInput.tsx | 114 ----------- 11 files changed, 223 insertions(+), 130 deletions(-) create mode 100644 fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx rename fe/src/app/{ => event-maps/[id]/[nonMemberId]}/load-mappin-edit/components/Form.tsx (100%) rename fe/src/app/{ => event-maps/[id]/[nonMemberId]}/load-mappin-edit/components/LinkField.tsx (100%) rename fe/src/app/{ => event-maps/[id]/[nonMemberId]}/load-mappin-edit/page.tsx (92%) rename fe/src/app/{pincheck-page => event-maps/[id]/[nonMemberId]}/page.tsx (89%) delete mode 100644 fe/src/app/pincheck-page/components/PincheckInput.tsx diff --git a/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx new file mode 100644 index 0000000..d3683c6 --- /dev/null +++ b/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx @@ -0,0 +1,182 @@ +"use client"; + +import React, { useRef, useState, useEffect } from "react"; +import { useRouter, useParams } from "next/navigation"; + +export default function PasswordInput() { + const [password, setPassword] = useState(["", "", "", ""]); + const [currentIndex, setCurrentIndex] = useState(0); + const [error, setError] = useState(false); + const inputRefs = useRef<(HTMLInputElement | null)[]>([]); + const router = useRouter(); + const { id, nonMemberId } = useParams(); + + const submitPassword = async () => { + const fullPassword = password.join(""); + console.log(password); + console.log("Password submitted:", fullPassword); + + try { + const response = await fetch( + "http://110.165.17.236:8081/api/v1/nonmembers/login", + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify({ + nonMemberId, + password: fullPassword, + }), + } + ); + + if (response.ok) { + router.push(`/event-maps/${id}/${nonMemberId}/load-mappin-edit`); + } else { + setError(true); + setPassword(["", "", "", ""]); + setCurrentIndex(0); + } + } catch (error) { + console.error("서버 오류:", error); + setError(true); + } + }; + + const handleInputChange = ( + e: React.ChangeEvent, + index: number + ) => { + const inputValue = e.target.value; + + if (/^\d$/.test(inputValue)) { + const newPass = [...password]; + newPass[index] = inputValue; + setPassword(newPass); + + if (index < password.length - 1) { + setCurrentIndex(index + 1); + } + } + }; + + const handleKeyDown = ( + e: React.KeyboardEvent, + index: number + ) => { + if (e.key === "Backspace") { + const newPass = [...password]; + newPass[index] = ""; + + if (index > 0) { + setCurrentIndex(index - 1); + } + + setPassword(newPass); + setError(false); + } + }; + + useEffect(() => { + inputRefs.current[currentIndex]?.focus(); + }, [currentIndex]); + + return ( +
+
+
+ { + inputRefs.current[0] = el; + }} + type="text" + inputMode="numeric" + pattern="[0-9]*" + className="grow text-center w-full h-full bg-transparent outline-none text-2xl" + maxLength={1} + value={password[0]} + onChange={(e) => handleInputChange(e, 0)} + onKeyDown={(e) => handleKeyDown(e, 0)} + /> +
+
+ { + inputRefs.current[1] = el; + }} + type="text" + inputMode="numeric" + pattern="[0-9]*" + className="grow text-center w-full h-full bg-transparent outline-none text-2xl" + maxLength={1} + value={password[1]} + onChange={(e) => handleInputChange(e, 1)} + onKeyDown={(e) => handleKeyDown(e, 1)} + /> +
+
+ { + inputRefs.current[2] = el; + }} + type="text" + inputMode="numeric" + pattern="[0-9]*" + className="grow text-center w-full h-full bg-transparent outline-none text-2xl" + maxLength={1} + value={password[2]} + onChange={(e) => handleInputChange(e, 2)} + onKeyDown={(e) => handleKeyDown(e, 2)} + /> +
+
+ { + inputRefs.current[3] = el; + }} + type="text" + inputMode="numeric" + pattern="[0-9]*" + className="grow text-center w-full h-full bg-transparent outline-none text-2xl" + maxLength={1} + value={password[3]} + onChange={(e) => handleInputChange(e, 3)} + onKeyDown={(e) => handleKeyDown(e, 3)} + /> +
+
+ + {error && ( +

+ 비밀번호가 일치하지 않아요 +

+ )} + + +
+ ); +} diff --git a/fe/src/app/load-mappin-edit/components/Form.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/Form.tsx similarity index 100% rename from fe/src/app/load-mappin-edit/components/Form.tsx rename to fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/Form.tsx diff --git a/fe/src/app/load-mappin-edit/components/LinkField.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/LinkField.tsx similarity index 100% rename from fe/src/app/load-mappin-edit/components/LinkField.tsx rename to fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/LinkField.tsx diff --git a/fe/src/app/load-mappin-edit/page.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/page.tsx similarity index 92% rename from fe/src/app/load-mappin-edit/page.tsx rename to fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/page.tsx index daf4ba4..7e8a052 100644 --- a/fe/src/app/load-mappin-edit/page.tsx +++ b/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/page.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import Image from "next/image"; -import { useRouter } from "next/navigation"; +import { useRouter, useParams } from "next/navigation"; import ExitModal from "@/app/components/common/ExitModal"; import Form from "./components/Form"; @@ -10,6 +10,7 @@ export default function Page() { const [userName] = useState("규리"); const [isModalOpen, setIsModalOpen] = useState(false); const router = useRouter(); + const { id } = useParams(); const handleBackClick = () => { setIsModalOpen(true); @@ -20,7 +21,7 @@ export default function Page() { }; const handleExit = () => { - router.push("/map-page"); + router.push(`/event-maps/${id}`); }; return ( diff --git a/fe/src/app/pincheck-page/page.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/page.tsx similarity index 89% rename from fe/src/app/pincheck-page/page.tsx rename to fe/src/app/event-maps/[id]/[nonMemberId]/page.tsx index 30b1127..6d309a3 100644 --- a/fe/src/app/pincheck-page/page.tsx +++ b/fe/src/app/event-maps/[id]/[nonMemberId]/page.tsx @@ -1,7 +1,7 @@ "use client"; import Image from "next/image"; -import PasswordInput from "@/app/pincheck-page/components/PincheckInput"; +import PasswordInput from "./components/PincheckInput"; import NavBar from "@/app/components/common/Navigation"; export default function PasswordPage() { diff --git a/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx b/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx index 9478857..cea2095 100644 --- a/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx +++ b/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx @@ -127,6 +127,12 @@ export default function BottomDrawer({ router.push(`/event-maps/${id}/load-mappin`); }; + const handleEditBtn = () => { + if (selectedButton !== null) { + router.push(`/event-maps/${id}/${selectedButton}`); + } + }; + const handleShare = () => { if (navigator.share) { navigator.share({ url: window.location.href }).then().catch(); @@ -170,7 +176,11 @@ export default function BottomDrawer({
{eventName}
-
diff --git a/fe/src/app/eventcreate-page/page.tsx b/fe/src/app/eventcreate-page/page.tsx index 9b3f80b..7e3ad06 100644 --- a/fe/src/app/eventcreate-page/page.tsx +++ b/fe/src/app/eventcreate-page/page.tsx @@ -71,7 +71,7 @@ function EventCreatePage() { useEffect(() => { if (uuid && !isRedirecting) { setIsRedirecting(true); // 이동 시작 시 상태 설정 - router.push(`/map-page?uuid=${uuid}`); + router.push(`/event-maps/${uuid}`); } }, [uuid, isRedirecting, router]); diff --git a/fe/src/app/pincheck-page/components/PincheckInput.tsx b/fe/src/app/pincheck-page/components/PincheckInput.tsx deleted file mode 100644 index 423e649..0000000 --- a/fe/src/app/pincheck-page/components/PincheckInput.tsx +++ /dev/null @@ -1,114 +0,0 @@ -"use client"; - -import React, { - useRef, - useState, - useEffect, - useCallback, - useMemo, -} from "react"; -import { useRouter } from "next/navigation"; -import { v4 as uuidv4 } from "uuid"; - -export default function PasswordInput() { - const [password, setPassword] = useState(["", "", "", ""]); - const [currentIndex, setCurrentIndex] = useState(0); - const [error, setError] = useState(false); - const inputRefs = useRef<(HTMLInputElement | null)[]>([]); - const router = useRouter(); - - const correctPassword = useMemo(() => ["1", "2", "3", "4"], []); - - const checkPassword = useCallback( - (inputPassword: string[]) => { - if (inputPassword.every((char) => char !== "")) { - if (JSON.stringify(inputPassword) === JSON.stringify(correctPassword)) { - setError(false); - router.push("/editmappin-page"); - } else { - setError(true); - } - } - }, - [correctPassword, router] - ); - - const handleInputChange = ( - e: React.ChangeEvent, - index: number - ) => { - const inputValue = e.target.value; - - // 숫자만 허용 - if (/^\d$/.test(inputValue)) { - const newPass = [...password]; - newPass[index] = inputValue; - setPassword(newPass); - - if (index < password.length - 1) { - setCurrentIndex(index + 1); - } - - checkPassword(newPass); - } - }; - - const handleKeyDown = ( - e: React.KeyboardEvent, - index: number - ) => { - if (e.key === "Backspace") { - const newPass = [...password]; - newPass[index] = ""; - - if (index > 0) { - setCurrentIndex(index - 1); - } - - setPassword(newPass); - setError(false); - } - }; - - // 현재 인덱스의 입력 칸에 포커스 설정 - useEffect(() => { - inputRefs.current[currentIndex]?.focus(); - }, [currentIndex]); - - const inputKeys = useMemo(() => password.map(() => uuidv4()), [password]); - - return ( -
-
- {password.map((char, index) => ( -
- { - inputRefs.current[index] = el; - }} - type="text" - inputMode="numeric" - pattern="[0-9]*" - className="grow shrink basis-0 text-center w-full h-full bg-transparent outline-none text-2xl text-text-default" - maxLength={1} - value={char} - onChange={(e) => handleInputChange(e, index)} - onKeyDown={(e) => handleKeyDown(e, index)} - /> -
- ))} -
- - {error && ( -

- 비밀번호가 일치하지 않아요 -

- )} -
- ); -} From ab33840359f6388553588b3f317b7a484d98e4fa Mon Sep 17 00:00:00 2001 From: choihooo Date: Thu, 31 Oct 2024 05:04:40 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[feat]=20#40=20eslint=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[nonMemberId]/components/PincheckInput.tsx | 18 +++++++++--------- .../app/event-maps/[id]/[nonMemberId]/page.tsx | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx index d3683c6..27029d9 100644 --- a/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx +++ b/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx @@ -6,7 +6,7 @@ import { useRouter, useParams } from "next/navigation"; export default function PasswordInput() { const [password, setPassword] = useState(["", "", "", ""]); const [currentIndex, setCurrentIndex] = useState(0); - const [error, setError] = useState(false); + const [hasError, setHasError] = useState(false); // 변수명 변경 const inputRefs = useRef<(HTMLInputElement | null)[]>([]); const router = useRouter(); const { id, nonMemberId } = useParams(); @@ -35,13 +35,13 @@ export default function PasswordInput() { if (response.ok) { router.push(`/event-maps/${id}/${nonMemberId}/load-mappin-edit`); } else { - setError(true); + setHasError(true); // hasError로 변경 setPassword(["", "", "", ""]); setCurrentIndex(0); } } catch (error) { console.error("서버 오류:", error); - setError(true); + setHasError(true); // hasError로 변경 } }; @@ -75,7 +75,7 @@ export default function PasswordInput() { } setPassword(newPass); - setError(false); + setHasError(false); // hasError로 변경 } }; @@ -88,7 +88,7 @@ export default function PasswordInput() {
- {error && ( + {hasError && (

비밀번호가 일치하지 않아요

diff --git a/fe/src/app/event-maps/[id]/[nonMemberId]/page.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/page.tsx index 6d309a3..f6ac537 100644 --- a/fe/src/app/event-maps/[id]/[nonMemberId]/page.tsx +++ b/fe/src/app/event-maps/[id]/[nonMemberId]/page.tsx @@ -1,8 +1,8 @@ "use client"; import Image from "next/image"; -import PasswordInput from "./components/PincheckInput"; import NavBar from "@/app/components/common/Navigation"; +import PasswordInput from "./components/PincheckInput"; export default function PasswordPage() { return ( From 30c95e38137cdbd35388b53805a7d8065196ec1d Mon Sep 17 00:00:00 2001 From: karnelll <165611407+karnelll@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:41:19 +0900 Subject: [PATCH 3/4] #40 Update API connection --- fe/src/app/dashboard/page.tsx | 36 ----- .../components/PinExitModal.tsx} | 0 .../components/PincheckInput.tsx | 138 ++++++------------ .../[nonMemberId]/load-mappin-edit/page.tsx | 2 +- .../[id]/components/EventMapExitModal.tsx | 52 +++++++ fe/src/app/event-maps/[id]/page.tsx | 45 ++++-- .../components/LocationInput.tsx | 87 ++++------- .../components/SearchResults.tsx | 9 +- fe/src/app/eventcreate-page/page.tsx | 43 ++++-- fe/src/app/eventcreate-page/types/types.tsx | 18 +++ 10 files changed, 214 insertions(+), 216 deletions(-) delete mode 100644 fe/src/app/dashboard/page.tsx rename fe/src/app/{components/common/ExitModal.tsx => event-maps/[id]/[nonMemberId]/components/PinExitModal.tsx} (100%) create mode 100644 fe/src/app/event-maps/[id]/components/EventMapExitModal.tsx diff --git a/fe/src/app/dashboard/page.tsx b/fe/src/app/dashboard/page.tsx deleted file mode 100644 index 64147dd..0000000 --- a/fe/src/app/dashboard/page.tsx +++ /dev/null @@ -1,36 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; - -interface UserInfo { - name: string; - email: string; -} - -export default function Dashboard() { - const [userInfo, setUserInfo] = useState(null); - - useEffect(() => { - const fetchUserInfo = async () => { - const response = await fetch("/api/userinfo"); - const data = await response.json(); - setUserInfo(data); - }; - - fetchUserInfo(); - }, []); - - return ( -
-

Dashboard

- {userInfo ? ( -
-

Name: {userInfo.name}

-

Email: {userInfo.email}

-
- ) : ( -

Loading user info...

- )} -
- ); -} diff --git a/fe/src/app/components/common/ExitModal.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/components/PinExitModal.tsx similarity index 100% rename from fe/src/app/components/common/ExitModal.tsx rename to fe/src/app/event-maps/[id]/[nonMemberId]/components/PinExitModal.tsx diff --git a/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx index 27029d9..9428341 100644 --- a/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx +++ b/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx @@ -1,24 +1,29 @@ "use client"; -import React, { useRef, useState, useEffect } from "react"; +import React, { useRef, useState, useEffect, useCallback } from "react"; import { useRouter, useParams } from "next/navigation"; +import { v4 as uuidv4 } from "uuid"; export default function PasswordInput() { const [password, setPassword] = useState(["", "", "", ""]); const [currentIndex, setCurrentIndex] = useState(0); - const [hasError, setHasError] = useState(false); // 변수명 변경 + const [hasError, setHasError] = useState(false); const inputRefs = useRef<(HTMLInputElement | null)[]>([]); const router = useRouter(); const { id, nonMemberId } = useParams(); - const submitPassword = async () => { + // Define submitPassword before useEffect + const submitPassword = useCallback(async () => { const fullPassword = password.join(""); - console.log(password); - console.log("Password submitted:", fullPassword); + + if (fullPassword.length !== 4) { + setHasError(true); + return; + } try { const response = await fetch( - "http://110.165.17.236:8081/api/v1/nonmembers/login", + `${process.env.NEXT_PUBLIC_API_BASE_URL}/nonmembers/login`, { method: "PUT", headers: { @@ -35,15 +40,20 @@ export default function PasswordInput() { if (response.ok) { router.push(`/event-maps/${id}/${nonMemberId}/load-mappin-edit`); } else { - setHasError(true); // hasError로 변경 + setHasError(true); setPassword(["", "", "", ""]); setCurrentIndex(0); } } catch (error) { - console.error("서버 오류:", error); - setHasError(true); // hasError로 변경 + setHasError(true); } - }; + }, [id, nonMemberId, password, router]); + + useEffect(() => { + if (password.every((digit) => digit !== "")) { + submitPassword(); + } + }, [password, submitPassword]); const handleInputChange = ( e: React.ChangeEvent, @@ -75,7 +85,7 @@ export default function PasswordInput() { } setPassword(newPass); - setHasError(false); // hasError로 변경 + setHasError(false); } }; @@ -86,82 +96,28 @@ export default function PasswordInput() { return (
-
- { - inputRefs.current[0] = el; - }} - type="text" - inputMode="numeric" - pattern="[0-9]*" - className="grow text-center w-full h-full bg-transparent outline-none text-2xl" - maxLength={1} - value={password[0]} - onChange={(e) => handleInputChange(e, 0)} - onKeyDown={(e) => handleKeyDown(e, 0)} - /> -
-
- { - inputRefs.current[1] = el; - }} - type="text" - inputMode="numeric" - pattern="[0-9]*" - className="grow text-center w-full h-full bg-transparent outline-none text-2xl" - maxLength={1} - value={password[1]} - onChange={(e) => handleInputChange(e, 1)} - onKeyDown={(e) => handleKeyDown(e, 1)} - /> -
-
- { - inputRefs.current[2] = el; - }} - type="text" - inputMode="numeric" - pattern="[0-9]*" - className="grow text-center w-full h-full bg-transparent outline-none text-2xl" - maxLength={1} - value={password[2]} - onChange={(e) => handleInputChange(e, 2)} - onKeyDown={(e) => handleKeyDown(e, 2)} - /> -
-
- { - inputRefs.current[3] = el; - }} - type="text" - inputMode="numeric" - pattern="[0-9]*" - className="grow text-center w-full h-full bg-transparent outline-none text-2xl" - maxLength={1} - value={password[3]} - onChange={(e) => handleInputChange(e, 3)} - onKeyDown={(e) => handleKeyDown(e, 3)} - /> -
+ {password.map((_, i) => ( +
+ { + inputRefs.current[i] = el; + }} + type="text" + inputMode="numeric" + pattern="[0-9]*" + className="grow text-center w-full h-full bg-transparent outline-none text-2xl" + maxLength={1} + value={password[i]} + onChange={(e) => handleInputChange(e, i)} + onKeyDown={(e) => handleKeyDown(e, i)} + /> +
+ ))}
{hasError && ( @@ -169,14 +125,6 @@ export default function PasswordInput() { 비밀번호가 일치하지 않아요

)} - -
); } diff --git a/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/page.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/page.tsx index 7e8a052..4519489 100644 --- a/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/page.tsx +++ b/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/page.tsx @@ -3,7 +3,7 @@ import React, { useState } from "react"; import Image from "next/image"; import { useRouter, useParams } from "next/navigation"; -import ExitModal from "@/app/components/common/ExitModal"; +import ExitModal from "@/app/event-maps/[id]/[nonMemberId]/components/PinExitModal"; import Form from "./components/Form"; export default function Page() { diff --git a/fe/src/app/event-maps/[id]/components/EventMapExitModal.tsx b/fe/src/app/event-maps/[id]/components/EventMapExitModal.tsx new file mode 100644 index 0000000..6ca4183 --- /dev/null +++ b/fe/src/app/event-maps/[id]/components/EventMapExitModal.tsx @@ -0,0 +1,52 @@ +import React from "react"; + +interface ExitModalProps { + onCancel: () => void; + onExit: () => void; +} + +function ExitModal({ onCancel, onExit }: ExitModalProps) { + return ( +
+
+
+ {" "} + {/* pt 값을 줄여서 위로 올림 */} +
+
+ 이벤트를 저장하지 않고 나갈까요? +
+
+ 이대로 나가면 +
발급된 이벤트가 초기화됩니다. +
+
+
+ +
+ + + +
+
+
+ ); +} + +export default ExitModal; diff --git a/fe/src/app/event-maps/[id]/page.tsx b/fe/src/app/event-maps/[id]/page.tsx index 17ca291..ef3f8c6 100644 --- a/fe/src/app/event-maps/[id]/page.tsx +++ b/fe/src/app/event-maps/[id]/page.tsx @@ -2,14 +2,15 @@ import React, { useEffect, useState } from "react"; import { useParams, useRouter } from "next/navigation"; -import Image from "next/image"; // 외부 라이브러리 -import { a } from "@react-spring/web"; // 외부 라이브러리 -import { useDrag } from "@use-gesture/react"; // 외부 라이브러리 +import Image from "next/image"; +import { a } from "@react-spring/web"; +import { useDrag } from "@use-gesture/react"; import MapComponent from "./components/MapComponent"; import BottomDrawer from "./components/BottomDrawer"; -import useDrawer from "./hooks/useDrawer"; // 내부 모듈 -import { useLocationStore } from "./stores/useLocationStore"; // 내부 모듈 +import useDrawer from "./hooks/useDrawer"; +import { useLocationStore } from "./stores/useLocationStore"; import { useMarkerStore } from "./load-mappin/stores/useMarkerStore"; +import ExitModal from "./components/EventMapExitModal"; interface NonMember { nonMemberId: number; @@ -38,15 +39,20 @@ export default function Page() { const { id } = useParams(); const parsedId = Array.isArray(id) ? id[0] : id; const [data, setData] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); // 모달 열림 상태 추가 const moveToLocation = useLocationStore((state) => state.moveToLocation); const setCustomMarkers = useMarkerStore((state) => state.setCustomMarkers); const router = useRouter(); useEffect(() => { + console.log("useEffect triggered with id:", id); + const fetchData = async () => { try { + console.log("Fetching data for id:", id); + const response = await fetch( - `http://110.165.17.236:8081/api/v1/nonmembers/pings?uuid=${id}`, + `${process.env.NEXT_PUBLIC_API_BASE_URL}/nonmembers/pings?uuid=${id}`, { method: "GET", headers: { @@ -58,22 +64,25 @@ export default function Page() { if (response.ok) { const result = await response.json(); setData(result); - console.log("Response Data:", JSON.stringify(result, null, 2)); + console.log("API Response Data:", JSON.stringify(result, null, 2)); - // 처음 px, py 값을 useLocationStore에 저장 if (result.px && result.py) { + console.log("Moving to location:", result.py, result.px); moveToLocation(result.py, result.px); } - // pings 데이터를 useMarkerStore에 저장 if (result.pings) { + console.log("Setting custom markers:", result.pings); setCustomMarkers(result.pings); } } else { - console.error("데이터 가져오기에 실패했습니다."); + console.error( + "Failed to fetch data from API. Status:", + response.status + ); } } catch (error) { - console.error("서버 오류:", error); + console.error("Server error:", error); } }; @@ -83,8 +92,18 @@ export default function Page() { }, [id, data, moveToLocation, setCustomMarkers]); const handleBackbtn = () => { - router.push(`eventcreate-page`); + setIsModalOpen(true); // 뒤로 가기 버튼 클릭 시 모달 열기 + }; + + const handleExit = () => { + setIsModalOpen(false); + router.replace("/eventcreate-page"); // UUID 초기화 후 이벤트 생성 페이지로 이동 }; + + const handleCancel = () => { + setIsModalOpen(false); // 모달 닫기 + }; + const bind = useDrag( ({ last, movement: [, my], memo = y.get() }) => { if (last) { @@ -131,6 +150,8 @@ export default function Page() { )} + {/* isModalOpen 상태에 따라 모달을 조건부 렌더링 */} + {isModalOpen && }
); } diff --git a/fe/src/app/eventcreate-page/components/LocationInput.tsx b/fe/src/app/eventcreate-page/components/LocationInput.tsx index 870a195..a4a58a4 100644 --- a/fe/src/app/eventcreate-page/components/LocationInput.tsx +++ b/fe/src/app/eventcreate-page/components/LocationInput.tsx @@ -1,38 +1,25 @@ -// LocationInput.tsx +"use client"; import React, { useState } from "react"; +import { LocationInputProps, Place } from "@/app/eventcreate-page/types/types"; // 최상단으로 이동 import Image from "next/image"; import SearchResults from "./SearchResults"; -interface LocationInputProps { - className?: string; - onSelect: (place: { name: string; address: string }) => void; // 타입 수정 -} - function LocationInput({ className, onSelect }: LocationInputProps) { const [location, setLocation] = useState(""); - const [results, setResults] = useState<{ name: string; address: string }[]>( - [] - ); - const [isFetching, setIsFetching] = useState(false); // 중복 요청 방지 상태 + const [results, setResults] = useState([]); + const [isFetching, setIsFetching] = useState(false); const fetchPlacesBySearch = async (query: string) => { if (isFetching) return; setIsFetching(true); try { - const response = await fetch( - `${process.env.NEXT_PUBLIC_API_BASE_URL}/places/search?keyword=${encodeURIComponent(query)}`, - { - headers: { accept: "*/*" }, - } - ); + const apiUrl = `${process.env.NEXT_PUBLIC_API_BASE_URL}/places/search?keyword=${encodeURIComponent(query)}`; + const response = await fetch(apiUrl, { headers: { accept: "*/*" } }); - if (!response.ok) { - const errorText = await response.text(); - console.error("Error fetching search results:", errorText); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - } const data = await response.json(); if (data.code === 200) { @@ -42,32 +29,26 @@ function LocationInput({ className, onSelect }: LocationInputProps) { alert(`장소 검색에 실패했습니다: ${data.message}`); } } catch (error) { - if (error instanceof Error) { - alert(`API 요청 중 오류가 발생했습니다: ${error.message}`); - } else { - alert("API 요청 중 알 수 없는 오류가 발생했습니다."); - } + alert("서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요."); } finally { setIsFetching(false); } }; const handleSearch = (e: React.ChangeEvent) => { - const { value } = e.target; - setLocation(value); - - if (value.length > 0) { - fetchPlacesBySearch(value); + const inputValue = e.target.value; + setLocation(inputValue); + if (inputValue.length > 0) { + fetchPlacesBySearch(inputValue); } else { setResults([]); } }; - const handleSelectPlace = (place: { name: string; address: string }) => { - // 매개변수 타입 수정 - setLocation(place.name); // 입력 필드를 선택된 장소의 이름으로 업데이트 + const handleSelectPlace = (place: Place) => { + setLocation(place.name); setResults([]); - onSelect(place); // place 객체 전달 + onSelect(place); }; const handleCurrentLocation = async () => { @@ -83,34 +64,24 @@ function LocationInput({ className, onSelect }: LocationInputProps) { navigator.geolocation.getCurrentPosition( async ({ coords }) => { const { latitude: py, longitude: px } = coords; + const apiUrl = `${process.env.NEXT_PUBLIC_API_BASE_URL}/places/geocode?py=${py}&px=${px}`; try { - const response = await fetch( - `${process.env.NEXT_PUBLIC_API_BASE_URL}/places/geocode?py=${py}&px=${px}`, - { - headers: { accept: "*/*" }, - } - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error("Error fetching places:", errorText); + const response = await fetch(apiUrl, { headers: { accept: "*/*" } }); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - } const data = await response.json(); - if (data.data && data.data.length > 0) { - const selectedPlace = data.data[0]; + const selectedPlace: Place = { ...data.data[0], px, py }; setResults(data.data); setLocation(selectedPlace.name); - handleSelectPlace(selectedPlace); // 선택된 장소를 전달 + handleSelectPlace(selectedPlace); } else { alert("현재 위치에 대한 장소를 찾을 수 없습니다."); } } catch (error) { - console.error("Error fetching places:", error); - alert("위치 정보를 가져오는 중 오류가 발생했습니다."); + alert("서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요."); } finally { setIsFetching(false); } @@ -122,6 +93,12 @@ function LocationInput({ className, onSelect }: LocationInputProps) { ); }; + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" || e.key === " ") { + handleCurrentLocation(); + } + }; + return (
@@ -140,17 +117,15 @@ function LocationInput({ className, onSelect }: LocationInputProps) { value={location} onChange={handleSearch} placeholder="장소를 입력해주세요" - className="bg-transparent border-none grow shrink basis-0 text-text-default text-base font-medium font-['Pretendard'] leading-normal outline-none flex-1 placeholder-mediumGray" + className="bg-transparent border-none grow text-base placeholder-mediumGray outline-none" />
{ - if (e.key === "Enter" || e.key === " ") handleCurrentLocation(); - }} + onKeyDown={handleKeyDown} > )}
diff --git a/fe/src/app/eventcreate-page/components/SearchResults.tsx b/fe/src/app/eventcreate-page/components/SearchResults.tsx index d8bbe70..6c0b72b 100644 --- a/fe/src/app/eventcreate-page/components/SearchResults.tsx +++ b/fe/src/app/eventcreate-page/components/SearchResults.tsx @@ -2,9 +2,14 @@ import React from "react"; import SearchResultItem from "./SearchResultsItem"; interface SearchResultsProps { - results: { name: string; address: string }[]; + results: { name: string; address: string; px?: number; py?: number }[]; searchTerm: string; - onSelect: (place: { name: string; address: string }) => void; + onSelect: (place: { + name: string; + address: string; + px?: number; + py?: number; + }) => void; } function SearchResults({ results, searchTerm, onSelect }: SearchResultsProps) { diff --git a/fe/src/app/eventcreate-page/page.tsx b/fe/src/app/eventcreate-page/page.tsx index 7e3ad06..e8a38d7 100644 --- a/fe/src/app/eventcreate-page/page.tsx +++ b/fe/src/app/eventcreate-page/page.tsx @@ -9,21 +9,37 @@ import Button from "@/app/components/common/Button"; function EventCreatePage() { const [selectedLocation, setSelectedLocation] = useState(""); + const [px, setPx] = useState(null); + const [py, setPy] = useState(null); const [eventName, setEventName] = useState(""); const [isFormComplete, setIsFormComplete] = useState(false); - const [isSubmitting, setIsSubmitting] = useState(false); // 중복 요청 방지 상태 - const [isRedirecting, setIsRedirecting] = useState(false); // 중복 라우팅 방지 상태 + const [isSubmitting, setIsSubmitting] = useState(false); + const [isRedirecting, setIsRedirecting] = useState(false); const [uuid, setUuid] = useState(null); const router = useRouter(); + // Adjust coordinates by dividing by 10^7 to match standard map coordinates + const adjustedPx = px ? px / 1e7 : null; + const adjustedPy = py ? py / 1e7 : null; + useEffect(() => { setIsFormComplete( - selectedLocation.trim() !== "" && eventName.trim() !== "" + selectedLocation.trim() !== "" && + eventName.trim() !== "" && + adjustedPx !== null && + adjustedPy !== null ); - }, [selectedLocation, eventName]); + }, [selectedLocation, eventName, adjustedPx, adjustedPy]); - const handleLocationSelect = (place: { name: string; address: string }) => { + const handleLocationSelect = (place: { + name: string; + address: string; + px?: number; + py?: number; + }) => { setSelectedLocation(place.name); + if (place.px) setPx(place.px); + if (place.py) setPy(place.py); }; const handleEventNameChange = (name: string) => { @@ -31,10 +47,10 @@ function EventCreatePage() { }; const createEvent = async () => { - if (!isFormComplete || isSubmitting) return; // 이미 요청 중이면 실행 방지 + if (!isFormComplete || isSubmitting) return; try { - setIsSubmitting(true); // 요청 시작 시 상태 설정 + setIsSubmitting(true); const response = await fetch("/api/event", { method: "POST", headers: { @@ -42,8 +58,8 @@ function EventCreatePage() { }, body: JSON.stringify({ neighborhood: selectedLocation, - px: 126.978, - py: 37.5665, + px: adjustedPx, + py: adjustedPy, eventName, }), }); @@ -55,7 +71,7 @@ function EventCreatePage() { const data = await response.json(); if (data.code === 200 && data.data?.shareUrl) { const extractedUuid = data.data.shareUrl.split("/").pop(); - setUuid(extractedUuid); // UUID를 상태로 저장 + setUuid(extractedUuid); } else { alert("이벤트 생성에 실패했습니다."); } @@ -63,14 +79,13 @@ function EventCreatePage() { console.error("Event creation error:", error); alert("이벤트 생성 중 오류가 발생했습니다."); } finally { - setIsSubmitting(false); // 요청 완료 후 상태 초기화 + setIsSubmitting(false); } }; - // UUID가 설정되면 페이지 이동 (중복 이동 방지를 위해 isRedirecting 사용) useEffect(() => { if (uuid && !isRedirecting) { - setIsRedirecting(true); // 이동 시작 시 상태 설정 + setIsRedirecting(true); router.push(`/event-maps/${uuid}`); } }, [uuid, isRedirecting, router]); @@ -91,7 +106,7 @@ function EventCreatePage() { />
))} -
{hasError && (