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 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(() => { - setIsFormComplete( - mapLinks.some((link) => link.trim() !== "") && - storeLinks.some((link) => link.trim() !== "") - ); - }, [mapLinks, storeLinks]); + if (userData) { + setMapLinks( + userData.bookmarkUrls?.filter((link) => link.trim() !== "") || [] + ); + setStoreLinks( + userData.storeUrls?.filter((link) => link.trim() !== "") || [] + ); + } + }, [userData]); - 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 +69,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,17 +80,14 @@ export default function Form({ userName }: FormProps) { label="가게 정보 링크" placeholder="링크 붙여넣기" value={storeLinks} - onChange={setStoreLinks} + onChange={(links) => + setStoreLinks(links.filter((link) => link.trim() !== "")) + } /> 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 dd71f70..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,9 +1,12 @@ -import React, { useState } from "react"; +"use client"; + +import React, { useState, useEffect } from "react"; import { nanoid } from "nanoid"; import Image from "next/image"; +import { useUserDataStore } from "../../stores/useUserDataStore"; interface LinkFieldProps { - label: string; // label 속성 추가 + label: string; placeholder: string; value: string[]; onChange: (value: string[]) => void; @@ -19,16 +22,37 @@ export default function LinkField({ showTooltip = true, onInfoClick, }: LinkFieldProps) { + 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(() => { + const initialData = + label === "맵핀 모음 링크" ? userData.bookmarkUrls : userData.storeUrls; + 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) => { @@ -36,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 ( @@ -67,15 +101,28 @@ export default function LinkField({ /> {showTooltip && (
즐겨찾기 링크 복사 방법을 확인해보세요 -
+
)}
)} +
{inputFields.map((field) => ( @@ -85,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 4519489..ab03631 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 @@ -5,9 +5,11 @@ import Image from "next/image"; import { useRouter, useParams } from "next/navigation"; import ExitModal from "@/app/event-maps/[id]/[nonMemberId]/components/PinExitModal"; import Form from "./components/Form"; +import { useUserDataStore } from "../stores/useUserDataStore"; export default function Page() { - const [userName] = useState("규리"); + const userName = useUserDataStore((state) => state.userData.name); + const [isModalOpen, setIsModalOpen] = useState(false); const router = useRouter(); const { id } = useParams(); @@ -46,7 +48,7 @@ export default function Page() { {userName}님의 맵핀 모음이에요
)} -
+
{isModalOpen && ( diff --git a/fe/src/app/event-maps/[id]/[nonMemberId]/stores/useUserDataStore.ts b/fe/src/app/event-maps/[id]/[nonMemberId]/stores/useUserDataStore.ts new file mode 100644 index 0000000..a2ea29f --- /dev/null +++ b/fe/src/app/event-maps/[id]/[nonMemberId]/stores/useUserDataStore.ts @@ -0,0 +1,23 @@ +import { create } from "zustand"; + +interface UserData { + nonMemberId: number | null; + name: string; + bookmarkUrls: string[]; + storeUrls: string[]; +} + +interface UserDataStore { + userData: UserData; + setUserData: (data: UserData) => void; +} + +export const useUserDataStore = create((set) => ({ + userData: { + nonMemberId: null, + name: "", + bookmarkUrls: [], + storeUrls: [], + }, + setUserData: (data) => set({ userData: data }), +})); diff --git a/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx b/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx index 2f4f341..5c74ede 100644 --- a/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx +++ b/fe/src/app/event-maps/[id]/components/BottomDrawer.tsx @@ -24,16 +24,18 @@ interface BottomDrawerProps { } export default function BottomDrawer({ - nonMembers, - eventName, + nonMembers: initialNonMembers, + eventName: initialEventName, id, }: BottomDrawerProps) { + const [eventName, setEventName] = useState(initialEventName); const [selectedButton, setSelectedButton] = useState(null); + const [nonMembers, setNonMembers] = useState(initialNonMembers); const [memberProfiles, setMemberProfiles] = useState<{ [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 +45,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 +63,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 +91,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 +107,9 @@ 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 +137,64 @@ export default function BottomDrawer({ } }; + const handleRefresh = async () => { + try { + const response = await fetch( + `${apiUrl}/nonmembers/pings/refresh-all?uuid=${id}`, + { + method: "GET", + headers: { "Content-Type": "application/json" }, + } + ); + + if (response.ok) { + const data = await response.json(); + console.log(data); + setEventName(data.eventName); + setNonMembers(data.nonMembers); + setAllPings(data.pings || []); + setCustomMarkers(data.pings || []); + } else { + console.log("Failed to fetch refreshed data:", response.status); + } + } catch (error) { + console.log("Error refreshing data:", error); + } + }; + + const handleDrawerClick = ( + event: + | React.MouseEvent + | React.KeyboardEvent + ) => { + if (event.type === "keydown") { + const keyboardEvent = event as React.KeyboardEvent; + if (keyboardEvent.key === "Enter" || keyboardEvent.key === " ") { + setSelectedButton(null); + setCustomMarkers(allPings); + } + } else if (event.type === "click") { + const mouseEvent = event as React.MouseEvent; + const target = mouseEvent.target as HTMLElement; + if (!target.closest("button")) { + setSelectedButton(null); + setCustomMarkers(allPings); + } + } + }; + return ( -
+
{ + if (e.key === "Enter" || e.key === " ") { + handleDrawerClick(e); + } + }} + >
+ {selectedButton !== null ? ( + + ) : ( + + )}
@@ -210,11 +270,7 @@ export default function BottomDrawer({