Skip to content

Commit

Permalink
Merge branch 'main' into fix/load-mappin
Browse files Browse the repository at this point in the history
  • Loading branch information
choihooo authored Nov 3, 2024
2 parents 6c049af + 3104159 commit 5da4241
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 96 deletions.
22 changes: 22 additions & 0 deletions fe/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions fe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"axios": "^1.7.7",
"dotenv": "^16.4.5",
"express": "^4.21.0",
"lodash.debounce": "^4.0.8",
"nanoid": "^5.0.7",
"next": "^14.2.15",
"next-auth": "^4.24.8",
Expand All @@ -28,6 +29,7 @@
"zustand": "^5.0.0"
},
"devDependencies": {
"@types/lodash.debounce": "^4.0.9",
"@types/navermaps": "^3.7.8",
"@types/node": "^20",
"@types/react": "^18",
Expand Down
8 changes: 5 additions & 3 deletions fe/src/app/eventcreate-page/components/EventNameInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react";
import Image from "next/image";
import { EventNameInputProps } from "@/app/eventcreate-page/types/types";

// 현재 날짜를 "MM.DD" 형식으로 반환하는 함수
const getCurrentDate = () => {
const today = new Date();
const month = String(today.getMonth() + 1).padStart(2, "0");
Expand Down Expand Up @@ -44,11 +45,13 @@ function EventNameInput({
};

const borderClass = isFocused ? "border-[#2C2C2C]" : "border-transparent";

// 기본 값일 때는 #8e8e8e 색상으로, 그렇지 않을 경우 #2c2c2c 색상으로 설정
const textColorClass =
value === `${currentDate} 모임` ||
value === `${currentDate} ${selectedLocation} 모임`
? "text-mediumGray"
: "text-[#8e8e8e]";
? "text-[#8e8e8e]"
: "text-[#2c2c2c]";

const charCount = value.length;
const showWarning = charCount < 1 || charCount > 20;
Expand Down Expand Up @@ -96,7 +99,6 @@ function EventNameInput({
)}
</div>

{/* 로딩 중이 아닐 때 글자 수 검사 결과 표시 */}
{!isLoading && (
<>
{showWarning ? (
Expand Down
179 changes: 94 additions & 85 deletions fe/src/app/eventcreate-page/components/LocationInput.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,92 @@
"use client";

import React, { useState } from "react";
import React, { useState, useEffect, useCallback } from "react";
import debounce from "lodash.debounce";
import Image from "next/image";
import SearchResults from "./SearchResults";

interface Place {
name: string;
address: string;
px?: number;
py?: number;
}

interface LocationInputProps {
className?: string;
onSelect: (place: {
name: string;
address: string;
px?: number;
py?: number;
}) => void;
onSelect: (place: Place) => void;
}

function LocationInput({ className, onSelect }: LocationInputProps) {
const [location, setLocation] = useState<string>("");
const [results, setResults] = useState<
{ name: string; address: string; px?: number; py?: number }[]
>([]);
const [results, setResults] = useState<Place[]>([]);
const [isFetching, setIsFetching] = useState(false);

const fetchPlacesBySearch = async (query: string) => {
if (isFetching) return;
setIsFetching(true);

try {
const apiUrl = `${process.env.NEXT_PUBLIC_API_BASE_URL}/places/search?keyword=${encodeURIComponent(query)}`;
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.code === 200) {
setResults(data.data);
} else {
setResults([]);
alert(`장소 검색에 실패했습니다: ${data.message}`);
// 검색 API 호출 함수, useCallback으로 감싸서 의존성 안정화
const fetchPlacesBySearch = useCallback(
async (query: string) => {
if (isFetching) return;
setIsFetching(true);

try {
const apiUrl = `${process.env.NEXT_PUBLIC_API_BASE_URL}/places/search?keyword=${encodeURIComponent(
query
)}`;
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.code === 200) {
const formattedResults = data.data.map((place: Place) => ({
...place,
px: place.px ? parseFloat((place.px / 1e7).toFixed(7)) : undefined,
py: place.py ? parseFloat((place.py / 1e7).toFixed(7)) : undefined,
}));
setResults(formattedResults);
} else {
setResults([]);
alert(`장소 검색에 실패했습니다: ${data.message}`);
}
} catch (error) {
alert("서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요.");
} finally {
setIsFetching(false);
}
} catch (error) {
alert("서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요.");
} finally {
setIsFetching(false);
}
};
},
[isFetching]
);

const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const inputValue = e.target.value;
setLocation(inputValue);
if (inputValue.length > 0) {
fetchPlacesBySearch(inputValue);
// fetchPlacesBySearch에 debounce 적용
useEffect(() => {
const debouncedFetch = debounce((query: string) => {
fetchPlacesBySearch(query);
}, 300);

if (location.length > 0) {
debouncedFetch(location);
} else {
setResults([]);
onSelect({ name: "", address: "", px: undefined, py: undefined });
}

return () => {
debouncedFetch.cancel();
};
}, [location, fetchPlacesBySearch, onSelect]); // fetchPlacesBySearch 포함

// 검색 인풋 핸들러
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setLocation(e.target.value);
};

const handleSelectPlace = (place: {
name: string;
address: string;
px?: number;
py?: number;
}) => {
// 장소 선택 핸들러
const handleSelectPlace = (place: Place) => {
setLocation(place.name);
setResults([]);
onSelect(place);
};

// 현재 위치 핸들러
const handleCurrentLocation = async () => {
if (isFetching) return;
setIsFetching(true);
Expand All @@ -78,29 +98,19 @@ function LocationInput({ className, onSelect }: LocationInputProps) {
}

navigator.geolocation.getCurrentPosition(
async ({ coords }) => {
({ 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(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], px, py };
setResults(data.data);
setLocation(selectedPlace.name);
handleSelectPlace(selectedPlace);
} else {
alert("현재 위치에 대한 장소를 찾을 수 없습니다.");
}
} catch (error) {
alert("서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요.");
} finally {
setIsFetching(false);
}
const coordinatesString = `위도: ${py}, 경도: ${px}`;
setLocation(coordinatesString);

onSelect({
name: coordinatesString,
address: coordinatesString,
px,
py,
});

setIsFetching(false);
},
() => {
alert("위치 정보를 가져오는 중 오류가 발생했습니다.");
Expand All @@ -114,26 +124,25 @@ function LocationInput({ className, onSelect }: LocationInputProps) {
<div className="text-black text-xl font-semibold leading-loose mb-[12px]">
어떤 공간을 찾고 계신가요?
</div>
<div className="relative w-[328px] h-14 p-4 bg-background-light rounded-lg flex justify-between items-center">
<div className="flex items-center gap-3">
<Image
src="/images/Search.svg"
alt="돋보기 아이콘"
width={24}
height={24}
/>
<input
type="text"
value={location}
onChange={handleSearch}
placeholder="장소를 입력해주세요"
className="bg-transparent border-none grow text-base text-[#8e8e8e] placeholder-[#8e8e8e] outline-none font-['Pretendard']"
/>
</div>
<div className="relative w-[328px] h-14 p-4 bg-background-light rounded-lg flex items-center">
<Image
src="/images/Search.svg"
alt="돋보기 아이콘"
width={24}
height={24}
className="mr-3"
/>
<input
type="text"
value={location}
onChange={handleSearch}
placeholder="장소를 입력해주세요"
className="bg-transparent border-none flex-grow text-base font-medium font-['Pretendard'] text-[#2c2c2c] placeholder-[#8e8e8e] outline-none"
/>
<div
role="button"
tabIndex={0}
className="w-[38px] h-[38px] bg-darkGray rounded flex items-center justify-center cursor-pointer"
className="w-[38px] h-[38px] bg-darkGray rounded flex items-center justify-center cursor-pointer ml-3"
onClick={handleCurrentLocation}
onKeyDown={(e) => {
if (e.key === "Enter") handleCurrentLocation();
Expand Down
13 changes: 5 additions & 8 deletions fe/src/app/eventcreate-page/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,16 @@ function EventCreatePage() {
const [uuid, setUuid] = useState<string | null>(null);
const router = useRouter();

const adjustedPx = px ? px / 1e7 : null;
const adjustedPy = py ? py / 1e7 : null;

useEffect(() => {
setIsFormComplete(
selectedLocation.trim() !== "" &&
eventName.trim() !== "" &&
eventName.length >= 1 &&
eventName.length <= 20 &&
adjustedPx !== null &&
adjustedPy !== null
px !== null &&
py !== null
);
}, [selectedLocation, eventName, adjustedPx, adjustedPy]);
}, [selectedLocation, eventName, px, py]);

const handleLocationSelect = (place: {
name: string;
Expand Down Expand Up @@ -59,8 +56,8 @@ function EventCreatePage() {
},
body: JSON.stringify({
neighborhood: selectedLocation,
px: adjustedPx,
py: adjustedPy,
px,
py,
eventName,
}),
});
Expand Down

0 comments on commit 5da4241

Please sign in to comment.