Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 60 additions & 45 deletions src/pages/map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,80 +2,95 @@ import Image from 'next/image';
import { cn } from '@/shared/lib';
import { ControlBar } from '@/shared/components';
import { BottomNav } from '@/shared/components/tab/BottomNav';
import {
purposes,
stays,
moves,
} from '@/shared/constants/course/courseOptions';
import { useCourseSelection } from '@/shared/hooks/useCourseSelection';
import CourseSelectSection from '@/pages/map/components/CourseSelectSection';
import CourseInputSection from '@/pages/map/components/CourseInputSection';
import CourseSelectGroup from '@/shared/components/map/components/CourseSelectGroup';
import CourseInputSection from '@/shared/components/map/components/CourseInputSection';
import { useRouter } from 'next/router';
import { useRecommendCourse } from '@/shared/api/course/queries/useRecommendCourse';
import { useState } from 'react';

export default function CourseSettingPage() {
const router = useRouter();
const { purpose, setPurpose, stay, setStay, move, setMove } =
useCourseSelection();
const { mutate, isPending } = useRecommendCourse();

const { purpose, setPurpose, stay, setStay, move, setMove } = useCourseSelection();
const [mustVisitPlace, setMustVisitPlace] = useState('');

const canProceed = Boolean(purpose && stay && move);

const handleNext = () => {
if (canProceed) router.push('/map/result');
if (!canProceed) return;

mutate(
{
travelPurpose: purpose!,
stayDuration: stay!,
transportation: move!,
userLatitude: 37.4985,
userLongitude: 126.7822,
Comment on lines +29 to +30
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

하드코딩된 좌표 사용

사용자 위치 대신 고정된 좌표 (37.4985, 126.7822)를 전송하고 있어, 모든 사용자에게 동일한 기준점에서 코스가 추천됩니다. 이는 사용자의 실제 위치 기반 추천이라는 기능 의도와 맞지 않습니다.

다음 중 하나를 선택하세요:

방안 1 (권장): Geolocation API로 실제 사용자 위치 사용

const [userLocation, setUserLocation] = useState<{lat: number; lng: number} | null>(null);

useEffect(() => {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
      (position) => {
        setUserLocation({
          lat: position.coords.latitude,
          lng: position.coords.longitude,
        });
      },
      (error) => {
        console.error('위치 정보 조회 실패:', error);
        // 폴백 또는 에러 처리
      }
    );
  }
}, []);

// mutate 호출 시
userLatitude: userLocation?.lat ?? 37.4985,
userLongitude: userLocation?.lng ?? 126.7822,

방안 2: 하드코딩된 값이 의도된 것이라면 상수로 분리하고 주석 추가

// 임시: 개발/테스트용 기본 위치 (서울 인근)
const DEFAULT_LAT = 37.4985;
const DEFAULT_LNG = 126.7822;
🤖 Prompt for AI Agents
In src/pages/map/index.tsx around lines 29-30 the userLatitude/userLongitude are
hardcoded to 37.4985/126.7822; replace this by obtaining the real user position
via the Geolocation API (add a state for userLocation, call
navigator.geolocation.getCurrentPosition in a useEffect, set the state, and pass
userLocation?.lat / userLocation?.lng with a fallback to defaults when calling
mutate), and if you instead intend to keep hardcoded values for testing, extract
them to named constants (e.g., DEFAULT_LAT/DEFAULT_LNG) with a comment stating
they are temporary dev/test defaults and ensure permission/fallback errors are
handled.

mustVisitPlace: mustVisitPlace || '',
},
{
onSuccess: (res) => {
if (res.success) {
router.push(`/map/result?sessionId=${res.data.sessionId}`);
}
},
onError: (err) => {
console.error('AI 코스 추천 실패:', err);
},
},
Comment on lines +34 to +42
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

에러 및 실패 응답 처리 부족

현재 에러 처리에 두 가지 문제가 있습니다:

  1. 네트워크 에러 시 사용자 피드백 없음: onError에서 console.error만 하고 사용자에게는 아무 알림이 없어 버튼이 동작하지 않는 것처럼 보입니다.
  2. res.isSuccess === false 케이스 미처리: 서버가 200 응답을 주지만 isSuccess: false인 경우 아무 동작도 하지 않아 사용자가 무한 대기하게 됩니다.

다음과 같이 수정하세요:

  onSuccess: (res) => {
    if (res.isSuccess) {
      router.push(`/map/result?sessionId=${res.result.sessionId}`);
+   } else {
+     alert('코스 추천에 실패했습니다. 다시 시도해 주세요.');
    }
  },
  onError: (err) => {
    console.error('AI 코스 추천 실패:', err);
+   alert('네트워크 오류가 발생했습니다. 다시 시도해 주세요.');
  },

더 나은 UX를 위해 toast 라이브러리나 모달을 사용하는 것을 권장합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onSuccess: (res) => {
if (res.isSuccess) {
router.push(`/map/result?sessionId=${res.result.sessionId}`);
}
},
onError: (err) => {
console.error('AI 코스 추천 실패:', err);
},
},
onSuccess: (res) => {
if (res.isSuccess) {
router.push(`/map/result?sessionId=${res.result.sessionId}`);
} else {
alert('코스 추천에 실패했습니다. 다시 시도해 주세요.');
}
},
onError: (err) => {
console.error('AI 코스 추천 실패:', err);
alert('네트워크 오류가 발생했습니다. 다시 시도해 주세요.');
},
},
🤖 Prompt for AI Agents
In src/pages/map/index.tsx around lines 34 to 42, the response/error handling is
incomplete: add user-facing feedback and state cleanup for both network errors
and successful HTTP responses where res.isSuccess === false. Update onError to
stop any loading state, log the error, and show a toast/modal with a friendly
message and optional retry; update onSuccess to handle the false case by
clearing loading, logging the server error details, and showing a toast/modal
(and only navigate when res.isSuccess is true). Ensure any disabled buttons are
re-enabled and include enough context in logs for debugging.

);
};

return (
<div
className={cn(
'relative px-[2.4rem] bg-white flex flex-col h-full pt-[1.3rem] pb-[12rem]',
)}
role='form'
aria-labelledby='course-setting-title'
aria-describedby='course-setting-desc'
role="form"
aria-labelledby="course-setting-title"
aria-describedby="course-setting-desc"
>
<ControlBar className='fixed top-[1rem] left-0 right-0 z-50 px-[2rem]' />
<ControlBar className="fixed top-[1rem] left-0 right-0 z-50 px-[2rem]" />

<main
className='w-full pt-[3.4rem] flex flex-col overflow-auto'
aria-live='polite'
className="w-full pt-[3.4rem] flex flex-col overflow-auto"
aria-live="polite"
>
<section className='mb-[3.6rem] text-center'>
<h1 id='course-setting-title' className='sr-only'>
<section className="mb-[3.6rem] text-center">
<h1 id="course-setting-title" className="sr-only">
여행 코스 설정
</h1>

<Image
src='/assets/bannerMap.svg'
alt='여행 코스 추천 배너 이미지'
src="/assets/bannerMap.svg"
alt="여행 코스 추천 배너 이미지"
width={354}
height={79}
className='w-full h-auto object-cover block'
className="w-full h-auto object-cover block"
/>
<p id='course-setting-desc' className='sr-only'>
여행 목적, 체류 시간, 이동 방식을 선택하고, 원하는 장소를 입력할 수
있습니다.

<p id="course-setting-desc" className="sr-only">
여행 목적, 체류 시간, 이동 방식을 선택하고, 원하는 장소를 입력할 수 있습니다.
</p>
</section>

<div className='flex flex-col gap-[1.9rem] mb-[8rem]'>
<CourseSelectSection
title='여행 목적을 선택해 주세요'
options={purposes}
selected={purpose}
onSelect={setPurpose}
/>
<CourseSelectSection
title='체류 시간을 선택해 주세요'
options={stays}
selected={stay}
onSelect={setStay}
/>
<CourseSelectSection
title='이동 방식을 선택해 주세요'
options={moves}
selected={move}
onSelect={setMove}
/>
<CourseInputSection onNext={handleNext} />
</div>
<CourseSelectGroup
purpose={purpose}
stay={stay}
move={move}
setPurpose={setPurpose}
setStay={setStay}
setMove={setMove}
/>

<CourseInputSection
value={mustVisitPlace}
onChange={setMustVisitPlace}
onNext={handleNext}
isLoading={isPending}
/>
</main>

<BottomNav />
Expand Down
39 changes: 39 additions & 0 deletions src/pages/map/location/[placeId].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client';
import { useRouter } from 'next/router';
import Image from 'next/image';
import { Header, LocationCard, AddressCopy } from '@/shared/components';

export default function LocationPage() {
const router = useRouter();
const { name, imageSrc, description, address } = router.query;

if (!router.isReady || !name) {
return <p className="text-center mt-10">불러오는 중...</p>;
}

return (
<div className="relative w-full h-[100vh] overflow-auto px-[2.4rem]">
<Header title={String(name)} onClick={() => router.back()} />

<main className="relative pt-[14.3rem] gap-[1.2rem] flex flex-col">
<Image
src={typeof imageSrc === 'string' ? imageSrc : '/assets/board.svg'}
alt={String(name)}
width={354}
height={436}
className="w-full h-auto object-cover block rounded-[16px]"
/>

<LocationCard
name={String(name)}
address={String(address)}
description={String(description)}
variant="mint"
size="large"
/>

<AddressCopy variant="mint" value={String(address)} />
</main>
</div>
);
}
23 changes: 20 additions & 3 deletions src/pages/map/result/Map.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Header } from '@/shared/components';
import { useRouter } from 'next/router';
import FullMap from '@/pages/map/result/components/FullMap';
import FullMap from '@/shared/components/map/result/components/FullMap';

const MapPage = () => {
const router = useRouter();

const sessionId = Array.isArray(router.query.sessionId)
? router.query.sessionId[0]
: router.query.sessionId;

return (
<main
className="w-full h-[100vh] bg-mint-300 overflow-hidden"
Expand All @@ -13,9 +17,22 @@ const MapPage = () => {
>
<Header
title="코스 추천"
onClick={() => router.push('/map/result?from=map')}
onClick={() => {
if (window.history.length > 1) {
router.back();
} else {
router.push('/map');
}
}}
/>
<FullMap />

{sessionId ? (
<FullMap sessionId={sessionId} />
) : (
<div className="flex h-full items-center justify-center text-gray-600 text-title-sm bg-gray-200">
코스 정보가 없습니다
</div>
)}
</main>
);
};
Expand Down
19 changes: 0 additions & 19 deletions src/pages/map/result/components/FullMap.tsx

This file was deleted.

31 changes: 0 additions & 31 deletions src/pages/map/result/components/ResultList.tsx

This file was deleted.

32 changes: 0 additions & 32 deletions src/pages/map/result/components/ResultMap.tsx

This file was deleted.

Loading
Loading