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() { />