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);
+ }
+ }}
+ >
@@ -210,11 +270,7 @@ export default function BottomDrawer({
handleButtonClick(member.nonMemberId)}
- className={`w-[68px] h-[68px] ${
- selectedButton === member.nonMemberId
- ? "border-2 rounded-lg border-primary-50"
- : ""
- }`}
+ className={`w-[68px] h-[68px] ${selectedButton === member.nonMemberId ? "border-2 rounded-lg border-primary-50" : ""}`}
>
{
- const size = isSelected ? 44 : 24; // 선택된 마커는 44px, 기본 마커는 24px
+ const size = isSelected ? 44 : 36; // 선택된 마커는 44px, 기본 마커는 24px
return {
url: `/pin/level${level}.svg`, // 레벨에 따라 다른 아이콘 파일 사용
size: new window.naver.maps.Size(size, size),
diff --git a/fe/src/app/event-maps/[id]/load-mappin/components/Form.tsx b/fe/src/app/event-maps/[id]/load-mappin/components/Form.tsx
index 741f6ff..f37eb56 100644
--- a/fe/src/app/event-maps/[id]/load-mappin/components/Form.tsx
+++ b/fe/src/app/event-maps/[id]/load-mappin/components/Form.tsx
@@ -17,12 +17,14 @@ export default function Form({ uuid }: FormProps) {
const [storeLinks, setStoreLinks] = useState([]);
const [isFormComplete, setIsFormComplete] = useState(false);
const [isTooltipVisible, setIsTooltipVisible] = useState(true);
+ const [nameErrorType, setNameErrorType] = useState<
+ "exists" | "invalid" | null
+ >(null);
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
- // 빈 문자열을 제거한 배열을 생성
const filteredMapLinks = mapLinks.filter((link) => link.trim() !== "");
const filteredStoreLinks = storeLinks.filter((link) => link.trim() !== "");
@@ -47,11 +49,11 @@ export default function Form({ uuid }: FormProps) {
);
if (response.ok) {
- console.log(requestBody);
console.log("성공적으로 처리되었습니다.");
router.push(`/event-maps/${uuid}`);
+ } else if (response.status === 409) {
+ setNameErrorType("exists");
} else {
- console.log(requestBody);
console.log("요청에 실패했습니다. 상태 코드:", response.status);
}
} catch (error) {
@@ -83,7 +85,7 @@ export default function Form({ uuid }: FormProps) {
return (
diff --git a/fe/src/app/event-maps/[id]/load-mappin/components/PinField.tsx b/fe/src/app/event-maps/[id]/load-mappin/components/PinField.tsx
index 633b522..4b58874 100644
--- a/fe/src/app/event-maps/[id]/load-mappin/components/PinField.tsx
+++ b/fe/src/app/event-maps/[id]/load-mappin/components/PinField.tsx
@@ -57,6 +57,7 @@ export default function PinField({ value, onChange }: PinFieldProps) {
("");
- const [results, setResults] = useState
([]);
+ const [results, setResults] = useState<
+ { name: string; address: string; px?: number; py?: number }[]
+ >([]);
const [isFetching, setIsFetching] = useState(false);
const fetchPlacesBySearch = async (query: string) => {
@@ -64,7 +65,12 @@ function LocationInput({ className, onSelect }: LocationInputProps) {
}
};
- const handleSelectPlace = (place: Place) => {
+ const handleSelectPlace = (place: {
+ name: string;
+ address: string;
+ px?: number;
+ py?: number;
+ }) => {
setLocation(place.name);
setResults([]);
onSelect(place);
diff --git a/fe/src/app/layout.tsx b/fe/src/app/layout.tsx
index 6521acb..d6d650f 100644
--- a/fe/src/app/layout.tsx
+++ b/fe/src/app/layout.tsx
@@ -2,8 +2,8 @@ import "@/styles/globals.css";
import { ReactNode } from "react";
export const metadata = {
- title: "My App",
- description: "This is my Next.js app",
+ title: "Moping!",
+ description: "Moping",
};
export default function RootLayout({ children }: { children: ReactNode }) {