Skip to content

Commit 33ec022

Browse files
authored
Merge pull request #110 from RunChuck/SCRUM-292-대한민국-판별-API-연결
[SCRUM-292] 대한민국 판별 API 연결
2 parents 3113795 + 46c6215 commit 33ec022

File tree

10 files changed

+70
-65
lines changed

10 files changed

+70
-65
lines changed

public/sw.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
const CACHE_NAME = 'running-handai-v1758375692343';
2-
const STATIC_CACHE_NAME = 'static-v1758375692343';
1+
const CACHE_NAME = 'running-handai-v1758873664909';
2+
const STATIC_CACHE_NAME = 'static-v1758873664909';
33

44

55
// 정적 리소스만 캐시 (이미지, 아이콘 등)

src/api/create.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
CourseNameCheckResponse,
44
LocationCheckRequest,
55
LocationCheckResponse,
6+
CheckMarkersInKoreaRequest,
67
CourseCreateRequest,
78
CourseCreateResponse,
89
MyCoursesRequest,
@@ -13,6 +14,11 @@ import type {
1314

1415
const PREFIX = '/api/members/me/courses';
1516

17+
export const checkMarkersInKorea = async (request: CheckMarkersInKoreaRequest): Promise<LocationCheckResponse> => {
18+
const response = await http.post<LocationCheckResponse>('/api/locations/korea', request);
19+
return response.data;
20+
};
21+
1622
export const checkCourseName = async (name: string): Promise<CourseNameCheckResponse> => {
1723
const response = await http.get<CourseNameCheckResponse>(`/api/courses/name/exists`, { params: { name } });
1824
return response.data;

src/api/openroute.ts

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -121,49 +121,6 @@ export const calculateRoute = async (coordinates: Coordinate[], profile: RoutePr
121121
}
122122
};
123123

124-
// 마커 좌표가 한국 내에 있는지 확인
125-
export const isLocationInKorea = async (lat: number, lng: number): Promise<boolean> => {
126-
try {
127-
const url = `${ORS_BASE_URL}/geocode/reverse?api_key=${ORS_API_KEY}&point.lon=${lng}&point.lat=${lat}&size=1`;
128-
129-
const response = await fetch(url);
130-
if (!response.ok) {
131-
throw new Error(`Reverse geocoding API error: ${response.status}`);
132-
}
133-
134-
const data = await response.json();
135-
136-
if (data.features && data.features.length > 0) {
137-
const properties = data.features[0].properties;
138-
139-
// country_a가 "KOR"이거나 country가 "한국"인 경우
140-
return properties.country_a === 'KOR' || properties.country === '한국';
141-
}
142-
143-
// 국가 정보 없으면 false
144-
return false;
145-
} catch (error) {
146-
console.error('Location validation error:', error);
147-
return false;
148-
}
149-
};
150-
151-
// 마커들이 모두 대한민국 내에 있는지 확인
152-
export const validateMarkersInKorea = async (coordinates: Coordinate[]): Promise<boolean> => {
153-
try {
154-
for (const coord of coordinates) {
155-
const isInKorea = await isLocationInKorea(coord.lat, coord.lng);
156-
if (!isInKorea) {
157-
return false;
158-
}
159-
}
160-
return true;
161-
} catch (error) {
162-
console.error('Marker validation error:', error);
163-
return false;
164-
}
165-
};
166-
167124
// Polyline 디코딩 함수
168125
const decodePolyline = (encoded: string, is3D: boolean = false): Array<[number, number] | [number, number, number]> => {
169126
const coordinates: Array<[number, number] | [number, number, number]> = [];

src/constants/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const APP_VERSION = '0.0.3';
1+
export const APP_VERSION = '0.0.4';

src/contexts/CourseCreationContext.tsx

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,23 @@ export const CourseCreationProvider = ({ children }: CourseCreationProviderProps
343343
mapInstance.clearRoute(); // 경로도 함께 제거
344344
};
345345

346+
// 에러 발생 시 마커 초기화 & 현재 위치로 이동
347+
const handleErrorAndReset = async (toastMessage: string) => {
348+
showErrorToast(toastMessage, { position: 'top' });
349+
handleDelete();
350+
351+
// 현재 위치로 이동
352+
if (mapInstance) {
353+
try {
354+
const { getUserLocation } = await import('@/utils/geolocation');
355+
const location = await getUserLocation({ maximumAge: 0 });
356+
mapInstance.moveToLocation(location.lat, location.lng, 7);
357+
} catch (error) {
358+
console.warn('현재 위치로 이동 실패:', error);
359+
}
360+
}
361+
};
362+
346363
const handleCourseCreate = async () => {
347364
if ((!isGpxUploaded && markers.length < 2) || !mapInstance) return;
348365

@@ -357,11 +374,18 @@ export const CourseCreationProvider = ({ children }: CourseCreationProviderProps
357374

358375
try {
359376
// 1. 모든 마커가 한국 내에 있는지 확인
360-
const { validateMarkersInKorea } = await import('@/api/openroute');
361-
const isInKorea = await validateMarkersInKorea(markers);
377+
const { checkMarkersInKorea } = await import('@/api/create');
378+
const requestBody = {
379+
coordinateDtoList: markers.map(coord => ({
380+
latitude: coord.lat,
381+
longitude: coord.lng,
382+
})),
383+
};
384+
const result = await checkMarkersInKorea(requestBody);
385+
const isInKorea = result.data;
362386

363387
if (!isInKorea) {
364-
showErrorToast(t('toast.courseCreation.NotKorea'), { position: 'top' });
388+
await handleErrorAndReset(t('toast.courseCreation.NotKorea'));
365389
return;
366390
}
367391

@@ -398,15 +422,13 @@ export const CourseCreationProvider = ({ children }: CourseCreationProviderProps
398422
}
399423
}
400424

401-
showErrorToast(toastMessage, { position: 'top' });
402-
handleDelete();
425+
await handleErrorAndReset(toastMessage);
403426
console.error('Course creation error:', err);
404427
} finally {
405428
setIsLoading(false);
406429
}
407430
};
408431

409-
410432
const handleCourseValidation = async () => {
411433
const courseName = `${startPoint}-${endPoint}`;
412434

@@ -442,7 +464,6 @@ export const CourseCreationProvider = ({ children }: CourseCreationProviderProps
442464
}
443465
};
444466

445-
446467
const submitCourse = async (courseData: { startPoint: string; endPoint: string; thumbnailBlob: Blob; isInBusan: boolean }): Promise<number> => {
447468
setIsSavingCourse(true);
448469
setError(null);

src/locales/ko.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"courseDeleteFailed": "코스 삭제 중 문제가 발생했어요. 다시 시도해주세요.",
5555
"gpxDownloaded": "GPX 파일이 다운로드 되었어요.",
5656
"gpxDownloadFailed": "GPX 다운로드 중 문제가 발생했어요. 다시 시도해주세요.",
57+
"courseCreation.MaxMarkers": "마커는 최대 30개까지만 추가할 수 있어요.",
5758
"courseCreation.NotKorea": "코스 생성이 가능한 지역을 벗어났어요.",
5859
"courseCreation.InvalidLocation": "선택한 위치에서는 코스를 생성할 수 없어요.",
5960
"courseCreation.ServerError": "코스 생성 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.",

src/pages/courseCreation/CourseCreation.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,13 @@ const CourseCreation = () => {
122122
maxAltitude={gpxData?.maxAltitude || 0}
123123
minAltitude={gpxData?.minAltitude || 0}
124124
/>
125-
<RouteView ref={routeViewRef} onMapLoad={setMapInstance} onMarkersChange={handleMarkersChange} isRouteGenerated={isRouteGenerated} />
125+
<RouteView
126+
ref={routeViewRef}
127+
onMapLoad={setMapInstance}
128+
onMarkersChange={handleMarkersChange}
129+
isRouteGenerated={isRouteGenerated}
130+
maxMarkers={30}
131+
/>
126132
<CreationBar onCreateCourse={isRouteGenerated ? () => setIsCourseCreationModalOpen(true) : handleCourseCreateWrapper} />
127133

128134
<OnboardingModal isOpen={isOnboardingOpen} onClose={handleOnboardingClose} />

src/pages/courseCreation/components/RouteView.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState, forwardRef } from 'react';
22
import ReactDOMServer from 'react-dom/server';
33
import { useTranslation } from 'react-i18next';
44
import styled from '@emotion/styled';
5+
import { useToast } from '@/hooks/useToast';
56
import { theme } from '@/styles/theme';
67
import Lottie from 'lottie-react';
78
import { BUSAN_CITY_HALL, DEFAULT_MAP_LEVEL } from '@/constants/locations';
@@ -34,10 +35,12 @@ interface RouteViewProps {
3435
onMapLoad?: (map: RouteViewMapInstance) => void;
3536
onMarkersChange?: (markers: { lat: number; lng: number }[]) => void;
3637
isRouteGenerated?: boolean;
38+
maxMarkers?: number;
3739
}
3840

39-
const RouteView = forwardRef<HTMLDivElement, RouteViewProps>(({ onMapLoad, onMarkersChange, isRouteGenerated = false }, ref) => {
41+
const RouteView = forwardRef<HTMLDivElement, RouteViewProps>(({ onMapLoad, onMarkersChange, isRouteGenerated = false, maxMarkers = 30 }, ref) => {
4042
const [t] = useTranslation();
43+
const { showWarningToast } = useToast();
4144
const mapContainer = useRef<HTMLDivElement>(null);
4245
const mapInstance = useRef<kakao.maps.Map | null>(null);
4346
const markersRef = useRef<MarkerData[]>([]);
@@ -134,9 +137,14 @@ const RouteView = forwardRef<HTMLDivElement, RouteViewProps>(({ onMapLoad, onMar
134137
};
135138

136139
const addMarker = (position: kakao.maps.LatLng) => {
137-
console.log('🔍 addMarker 호출:', { isRouteGenerated: isRouteGeneratedRef.current, hasMapInstance: !!mapInstance.current });
138140
if (!mapInstance.current || isRouteGeneratedRef.current) return;
139141

142+
// 마커 개수 제한 체크
143+
if (markersRef.current.length >= maxMarkers) {
144+
showWarningToast(t('toast.courseCreation.MaxMarkers'), { position: 'top' });
145+
return;
146+
}
147+
140148
const currentIndex = markersRef.current.length;
141149
const markerData = createRouteMarker(position, currentIndex);
142150
markerData.marker.setMap(mapInstance.current);

src/types/create.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ export interface LocationCheckResponse {
1717
data: boolean;
1818
}
1919

20+
export interface CheckMarkersInKoreaRequest {
21+
coordinateDtoList: Array<{
22+
latitude: number;
23+
longitude: number;
24+
}>;
25+
}
26+
2027
export interface CourseCreateRequest {
2128
startPointName: string;
2229
endPointName: string;

src/utils/geolocation.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,23 +56,22 @@ export const getUserLocation = (
5656
const errorMsg = error.code === 1 ? '권한 거부' : error.code === 2 ? '위치 불가' : '타임아웃';
5757
console.warn(`위치 조회 실패 (${retryCount + 1}회):`, errorMsg, error.message);
5858

59-
// 권한 거부가 아니고 재시도 가능한 경우
60-
if (!hasSucceeded && retryCount < maxRetries && error.code !== 1) {
59+
if (!hasSucceeded && retryCount < maxRetries) {
6160
retryCount++;
6261

63-
// 1-2초 후 재시도 (브라우저 권한 처리 시간 고려)
6462
setTimeout(() => {
6563
if (!hasSucceeded) {
6664
attemptGetLocation();
6765
}
6866
}, retryCount * 1000);
6967
} else if (!hasSucceeded) {
70-
// 권한 거부이거나 재시도 모두 실패
71-
const finalError = error.code === 1
72-
? new Error('위치 권한이 거부되었습니다. 브라우저 설정에서 위치 권한을 허용해주세요.')
73-
: error.code === 2
74-
? new Error('현재 위치를 확인할 수 없습니다. 인터넷 연결을 확인해주세요.')
75-
: new Error('위치 조회에 실패했습니다. 잠시 후 다시 시도해주세요.');
68+
// 모든 재시도 실패
69+
const finalError =
70+
error.code === 1
71+
? new Error('위치 권한이 거부되었습니다. 브라우저 설정에서 위치 권한을 허용해주세요.')
72+
: error.code === 2
73+
? new Error('현재 위치를 확인할 수 없습니다. 인터넷 연결을 확인해주세요.')
74+
: new Error('위치 조회에 실패했습니다. 잠시 후 다시 시도해주세요.');
7675

7776
reject(finalError);
7877
}

0 commit comments

Comments
 (0)