From d598c6d92ccb1c63c076f11339602e34707e983a Mon Sep 17 00:00:00 2001 From: choihooo Date: Thu, 31 Oct 2024 15:40:47 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[feat]=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20api=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PincheckInput.tsx | 17 ++++- .../load-mappin-edit/components/Form.tsx | 73 +++++++++++++++---- .../load-mappin-edit/components/LinkField.tsx | 13 +++- .../[nonMemberId]/load-mappin-edit/page.tsx | 3 +- .../[nonMemberId]/stores/useUserDataStore.ts | 23 ++++++ fe/src/app/event-maps/[id]/page.tsx | 1 - 6 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 fe/src/app/event-maps/[id]/[nonMemberId]/stores/useUserDataStore.ts 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 9428341..8e8f30e 100644 --- a/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx +++ b/fe/src/app/event-maps/[id]/[nonMemberId]/components/PincheckInput.tsx @@ -3,6 +3,7 @@ import React, { useRef, useState, useEffect, useCallback } from "react"; import { useRouter, useParams } from "next/navigation"; import { v4 as uuidv4 } from "uuid"; +import { useUserDataStore } from "../stores/useUserDataStore"; // zustand store import export default function PasswordInput() { const [password, setPassword] = useState(["", "", "", ""]); @@ -11,8 +12,8 @@ export default function PasswordInput() { const inputRefs = useRef<(HTMLInputElement | null)[]>([]); const router = useRouter(); const { id, nonMemberId } = useParams(); + const setUserData = useUserDataStore((state) => state.setUserData); // Use zustand's setUserData - // Define submitPassword before useEffect const submitPassword = useCallback(async () => { const fullPassword = password.join(""); @@ -38,6 +39,16 @@ export default function PasswordInput() { ); if (response.ok) { + const data = await response.json(); // Get the JSON response + + // Save data in zustand store + setUserData({ + nonMemberId: data.nonMemberId, + name: data.name, + bookmarkUrls: data.bookmarkUrls || [], + storeUrls: data.storeUrls || [], + }); + router.push(`/event-maps/${id}/${nonMemberId}/load-mappin-edit`); } else { setHasError(true); @@ -47,7 +58,7 @@ export default function PasswordInput() { } catch (error) { setHasError(true); } - }, [id, nonMemberId, password, router]); + }, [id, nonMemberId, password, router, setUserData]); useEffect(() => { if (password.every((digit) => digit !== "")) { @@ -98,7 +109,7 @@ export default function PasswordInput() {
{password.map((_, i) => (
([]); + const [storeLinks, setStoreLinks] = useState([]); const [isTooltipVisible, setIsTooltipVisible] = useState(true); - const [isFormComplete, setIsFormComplete] = useState(false); // isFormComplete 추가 + const [isFormComplete, setIsFormComplete] = useState(false); + const router = useRouter(); + const { id } = useParams(); // Retrieve `id` from the route parameters - // mapLinks와 storeLinks가 모두 입력되었을 때만 isFormComplete를 true로 설정 + // Load mapLinks and storeLinks from the store when the component mounts + useEffect(() => { + if (userData) { + setMapLinks( + userData.bookmarkUrls?.filter((link) => link.trim() !== "") || [] + ); + setStoreLinks( + userData.storeUrls?.filter((link) => link.trim() !== "") || [] + ); + } + }, [userData]); + + // Check if form is complete whenever mapLinks or storeLinks change useEffect(() => { setIsFormComplete( - mapLinks.some((link) => link.trim() !== "") && - storeLinks.some((link) => link.trim() !== "") + mapLinks.length > 0 && storeLinks.length > 0 // Check for non-empty lists ); }, [mapLinks, storeLinks]); - const handleSubmit = (e: FormEvent) => { + const handleSubmit = async (e: FormEvent) => { e.preventDefault(); + + // Filter out any empty strings just before submitting + const filteredMapLinks = mapLinks.filter((link) => link.trim() !== ""); + const filteredStoreLinks = storeLinks.filter((link) => link.trim() !== ""); + const formData = { - userName, - mapLinks, - storeLinks, + nonMemberId: userData?.nonMemberId, // Assuming nonMemberId is available in userData + bookmarkUrls: filteredMapLinks, + storeUrls: filteredStoreLinks, }; - console.log("폼 데이터:", formData); + + try { + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/nonmembers/pings`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(formData), + } + ); + + if (response.ok) { + console.log("데이터가 성공적으로 전송되었습니다."); + router.push(`/event-maps/${id}`); // Redirect to the specified route on success + } else { + console.error("데이터 전송 실패:", response.status); + } + } catch (error) { + console.error("서버 오류 발생:", error); + } }; return ( @@ -38,7 +81,9 @@ export default function Form({ userName }: FormProps) { label="맵핀 모음 링크" placeholder="링크 붙여넣기" value={mapLinks} - onChange={setMapLinks} + onChange={(links) => + setMapLinks(links.filter((link) => link.trim() !== "")) + } showTooltip={isTooltipVisible} onInfoClick={() => setIsTooltipVisible(true)} /> @@ -47,7 +92,9 @@ export default function Form({ userName }: FormProps) { label="가게 정보 링크" placeholder="링크 붙여넣기" value={storeLinks} - onChange={setStoreLinks} + onChange={(links) => + setStoreLinks(links.filter((link) => link.trim() !== "")) + } />
)} -
+
{isModalOpen && ( From 633186c4cfa51c20a046336fa159246304c00f1b Mon Sep 17 00:00:00 2001 From: choihooo Date: Thu, 31 Oct 2024 20:34:48 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[hotfix]=20=EC=A7=80=EB=8F=84=EB=9E=91=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=B6=94=EA=B0=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../load-mappin-edit/components/Form.tsx | 15 +---- .../load-mappin-edit/components/LinkField.tsx | 58 +++++++++++++++---- .../[nonMemberId]/load-mappin-edit/page.tsx | 2 +- .../[id]/components/BottomDrawer.tsx | 29 ++++++---- .../[id]/components/MapComponent.tsx | 2 +- .../[id]/load-mappin/components/Form.tsx | 10 ++-- .../[id]/load-mappin/components/NameField.tsx | 49 +++++++++++----- .../[id]/load-mappin/components/PinField.tsx | 4 ++ 8 files changed, 115 insertions(+), 54 deletions(-) diff --git a/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/Form.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/Form.tsx index e970d80..38726a5 100644 --- a/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/Form.tsx +++ b/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/Form.tsx @@ -10,7 +10,6 @@ export default function Form() { const [mapLinks, setMapLinks] = useState([]); const [storeLinks, setStoreLinks] = useState([]); const [isTooltipVisible, setIsTooltipVisible] = useState(true); - const [isFormComplete, setIsFormComplete] = useState(false); const router = useRouter(); const { id } = useParams(); // Retrieve `id` from the route parameters @@ -26,13 +25,6 @@ export default function Form() { } }, [userData]); - // Check if form is complete whenever mapLinks or storeLinks change - useEffect(() => { - setIsFormComplete( - mapLinks.length > 0 && storeLinks.length > 0 // Check for non-empty lists - ); - }, [mapLinks, storeLinks]); - const handleSubmit = async (e: FormEvent) => { e.preventDefault(); @@ -94,13 +86,8 @@ export default function Form() { /> diff --git a/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/LinkField.tsx b/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/LinkField.tsx index d9b3477..76a82a5 100644 --- a/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/LinkField.tsx +++ b/fe/src/app/event-maps/[id]/[nonMemberId]/load-mappin-edit/components/LinkField.tsx @@ -1,3 +1,5 @@ +"use client"; + import React, { useState, useEffect } from "react"; import { nanoid } from "nanoid"; import Image from "next/image"; @@ -20,24 +22,37 @@ export default function LinkField({ showTooltip = true, onInfoClick, }: LinkFieldProps) { - const userData = useUserDataStore((state) => state.userData); // Use zustand's userData + const userData = useUserDataStore((state) => state.userData); const [inputFields, setInputFields] = useState( - value.map((val) => ({ id: nanoid(), text: val })) + value.length > 0 + ? value.map((val) => ({ id: nanoid(), text: val })) + : [{ id: nanoid(), text: "" }] ); useEffect(() => { - // Update input fields with data from zustand if available const initialData = label === "맵핀 모음 링크" ? userData.bookmarkUrls : userData.storeUrls; - setInputFields(initialData.map((val) => ({ id: nanoid(), text: val }))); + setInputFields( + initialData.length > 0 + ? initialData.map((val) => ({ id: nanoid(), text: val })) + : [{ id: nanoid(), text: "" }] + ); }, [label, userData]); + useEffect(() => { + if (inputFields.length === 0) { + setInputFields([{ id: nanoid(), text: "" }]); + } + }, [inputFields]); + const handleInputChange = (id: string, inputValue: string) => { const newInputs = inputFields.map((field) => field.id === id ? { ...field, text: inputValue } : field ); setInputFields(newInputs); - onChange(newInputs.map((field) => field.text)); + onChange( + newInputs.map((field) => field.text).filter((text) => text.trim() !== "") + ); }; const clearInput = (id: string) => { @@ -45,14 +60,24 @@ export default function LinkField({ field.id === id ? { ...field, text: "" } : field ); setInputFields(newInputs); - onChange(newInputs.map((field) => field.text)); + onChange( + newInputs.map((field) => field.text).filter((text) => text.trim() !== "") + ); }; const addInputField = () => { const newField = { id: nanoid(), text: "" }; const updatedInputs = [...inputFields, newField]; setInputFields(updatedInputs); - onChange(updatedInputs.map((field) => field.text)); + onChange( + updatedInputs + .map((field) => field.text) + .filter((text) => text.trim() !== "") + ); + }; + + const handleNaverMove = () => { + window.open("https://m.place.naver.com/my/place"); }; return ( @@ -76,15 +101,28 @@ export default function LinkField({ /> {showTooltip && (
즐겨찾기 링크 복사 방법을 확인해보세요 -
+
)}
)} +
{inputFields.map((field) => ( @@ -94,7 +132,7 @@ export default function LinkField({ value={field.text} onChange={(e) => handleInputChange(field.id, e.target.value)} placeholder={placeholder} - className="w-full p-3 bg-gray-50 rounded-md focus:outline-none focus:ring-2 focus:ring-grayscale-80" + className="w-full p-3 pr-10 bg-gray-50 rounded-md focus:outline-none focus:ring-2 focus:ring-grayscale-80" style={{ border: "none", }} 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 00a8312..75caf76 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 @@ -8,7 +8,7 @@ import Form from "./components/Form"; import { useUserDataStore } from "../stores/useUserDataStore"; export default function Page() { - const userName = useUserDataStore((state) => state.userData.name); // Get userName from zustand + const userName = useUserDataStore((state) => state.userData.name); const [isModalOpen, setIsModalOpen] = useState(false); const router = useRouter(); const { id } = useParams(); diff --git a/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx b/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx index 2f4f341..af66644 100644 --- a/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx +++ b/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx @@ -33,7 +33,7 @@ export default function BottomDrawer({ [key: number]: string; }>({}); const [allPings, setAllPings] = useState([]); - const { setCustomMarkers } = useMarkerStore(); // useMarkerStore에서 setCustomMarkers 가져오기 + const { setCustomMarkers } = useMarkerStore(); const moveToLocation = useLocationStore((state) => state.moveToLocation); const router = useRouter(); const profileImagesRef = useRef([ @@ -43,10 +43,9 @@ export default function BottomDrawer({ "/profile/profil4.svg", ]); - const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL; // 환경 변수로부터 API URL 가져오기 + const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL; useEffect(() => { - // 프로필 이미지 랜덤 할당 const profiles = nonMembers.reduce( (acc, member) => { const randomImage = @@ -62,14 +61,13 @@ export default function BottomDrawer({ }, [nonMembers]); useEffect(() => { - // 전체 pings 데이터를 처음 로드할 때 가져옴 const fetchAllPings = async () => { try { const response = await fetch(`${apiUrl}/nonmembers/pings?uuid=${id}`); if (response.ok) { const data = await response.json(); setAllPings(data.pings || []); - setCustomMarkers(data.pings || []); // 모든 핑을 setCustomMarkers에 설정 + setCustomMarkers(data.pings || []); } } catch (error) { console.log("Error:", error); @@ -91,15 +89,12 @@ export default function BottomDrawer({ }; const handleButtonClick = async (nonMemberId: number) => { - // 선택된 버튼 토글 const isDeselect = selectedButton === nonMemberId; setSelectedButton(isDeselect ? null : nonMemberId); if (isDeselect) { - // 선택 해제 시 전체 핑 표시 setCustomMarkers(allPings); } else { - // 특정 nonMemberId에 대한 핑 요청 try { const response = await fetch( `${apiUrl}/nonmembers/pings/${nonMemberId}`, @@ -110,10 +105,10 @@ export default function BottomDrawer({ const data = await response.json(); const filteredPings = data.pings.map((ping: Ping) => ({ ...ping, - iconLevel: 1, // 선택된 nonMember에 대한 핑을 level1로 설정 + iconLevel: 1, })); console.log(filteredPings); - setCustomMarkers(filteredPings); // 필터링된 핑을 setCustomMarkers에 설정 + setCustomMarkers(filteredPings); } else { console.log("Failed to fetch data:", response.status); } @@ -141,8 +136,20 @@ export default function BottomDrawer({ } }; + // 드로워 내부에서 이미지가 아닌 다른 요소 클릭 시 선택 해제 + const handleDrawerClick = (event: React.MouseEvent) => { + const target = event.target as HTMLElement; + if (!target.closest("button")) { + setSelectedButton(null); + setCustomMarkers(allPings); // 선택 해제 시 전체 마커 표시 + } + }; + return ( -
+
+ {selectedButton !== null ? ( + + ) : ( + + )}
@@ -237,11 +270,7 @@ export default function BottomDrawer({