2026. 6. 4. 19:35ㆍ프로젝트/Trip-Linker 팀 프로젝트
플래너 API 연동 및 UI 개선 — goPlanStep 검증, 날씨 팝업
📋 한줄 요약
플래너 3단계 흐름을 백엔드 API에 연결하고, 미로그인/미입력 상태에서 단계 이동을 막는 검증 로직을 추가했다.
🎯 왜 만들었나
기존 플래너는 MOCK 데이터로 동작하고 있었고, 로그인 없이도 3단계까지 이동이 가능했다.
실제 API 연동 + 단계별 입력값 검증으로 정상 흐름을 완성하는 작업이었다.
추가로 클라이언트 측에 있던 로그인 잠금 dead code를 제거해서 서버 응답 기반 단일 로직으로 정리했다.
📁 작업 순서 (파일별)
1. ApiResponse.java
📦 global
추가한 코드
// 기존 — 두 인자 필요
public static <T> ApiResponse<T> success(String message, T data)
// 추가 — 데이터만 넘길 때 쓸 수 있는 단일 인자 오버로드
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, "success", data);
}
왜 추가했나
TravelPlanController에서 메시지 없이 데이터만 반환하는 경우가 많아서
매번 success("success", data) 처럼 쓰는 게 불편했다.
2. TravelPlanController.java
📦 domain/plan/controller
변경한 코드
// 변경 전 — LoginUser라는 커스텀 어노테이션이 없어서 에러
@AuthenticationPrincipal LoginUser loginUser
loginUser.getId()
// 변경 후 — 실제 존재하는 CustomUserDetails 사용
@AuthenticationPrincipal CustomUserDetails userDetails
userDetails.getUser().getId()
왜 바꿨나
@AuthenticationPrincipal은 UserDetails 구현체 타입으로 정확히 맞춰야 주입된다.
프로젝트의 실제 구현체는 CustomUserDetails이고, 사용자 ID는 .getUser().getId()로 꺼낸다.
3. TravelPlanServiceImpl.java
📦 domain/plan/service
변경한 코드
// 변경 전
throw new CustomException(ErrorCode.PLAN_NOT_FOUND);
// 변경 후
throw new IllegalArgumentException("존재하지 않는 플랜입니다.");
throw new IllegalStateException("본인의 플랜만 수정할 수 있습니다.");
왜 바꿨나
CustomException과 ErrorCode가 아직 팀에서 구현되지 않은 상태라 컴파일 에러가 났다.
표준 Java 예외로 교체해서 일단 동작하게 만들었다.
4. app_main.js
📦 frontend
제거한 코드 (dead code)
// 클라이언트 측 로그인 잠금 — 서버와 이중으로 관리되어 불필요
let _loginFailCount = 0;
let _loginLockedUntil = null;
if (_loginLockedUntil && Date.now() < _loginLockedUntil) {
showLockMessage(_loginLockedUntil - Date.now());
return;
}
_loginFailCount++;
if (_loginFailCount >= 5) { ... }
유지한 코드 (서버 응답 기반)
if (res.data.locked) {
showLockMessage(res.data.remainSeconds);
}
왜 바꿨나
클라이언트 카운터는 새로고침하면 초기화되어 의미가 없고,
서버에서 locked, remainSeconds를 이미 내려주므로 클라이언트 로직은 불필요했다.
추가한 코드 — go() 함수 내 플래너 초기화
case 'planner':
setTimeout(initPlannerDateConstraints, 100);
break;
왜 추가했나
SPA는 fragment를 동적으로 삽입하기 때문에 DOMContentLoaded 이후에 DOM이 생긴다.
페이지 전환 시점에 직접 호출해야 달력 min 날짜가 정상 세팅된다.
5. page_planner.html
📦 frontend
추가한 함수 — goPlanStep() 검증
function goPlanStep(step) {
// 미로그인 차단
if (!Token.getAccess()) {
openLoginModal();
return;
}
// STEP 1 → 2: 필수 입력값 검증
if (currentStep === 1 && step === 2) {
if (!_validatePlanStep1()) return;
}
// STEP 2 → 3: 스타일 chip 최소 1개
if (currentStep === 2 && step === 3) {
if (!_validatePlanStep2()) return;
}
currentStep = step;
renderStep();
}
API 연동 추가
// MOCK 제거 후 실제 API 호출
// 플랜 생성
POST /api/trips
// 이전 취향 불러오기
POST /api/trips/{tripId}/load-preference
// MBTI 기반 일정 밀도 자동 선택
GET /api/users/me → res.data.mbti 로 density 자동 세팅
토큰 참조 통일
// 변경 전 — 혼재
localStorage.getItem('accessToken')
// 변경 후 — 통일
Token.getAccess()
6. app_community.js
📦 frontend
제거한 코드
// _reportAction 함수가 파일 내에 두 번 선언되어 있었음
// 두 번째 선언 제거
function _reportAction(type, targetId) { ... } // ← 이 줄 삭제
⚠️ 트러블슈팅
문제 — 날씨 팝업이 display:flex로 켜도 안 보임
CSS가 기본적으로 opacity: 0; pointer-events: none이고
.overlay.open 클래스가 붙어야 보이는 구조였는데,
element.style.display = 'flex'로 직접 조작하고 있었다.
// 변경 전
weatherPopup.style.display = 'flex';
// 변경 후
weatherPopup.classList.add('open');
💡 배운 점
SPA에서 fragment를 동적 삽입하면 DOMContentLoaded가 이미 지난 뒤라서
초기화 함수를 페이지 전환 시점에 직접 호출해야 한다.
@AuthenticationPrincipal은 SecurityContext에 저장된 UserDetails 구현체 타입과 정확히 일치해야 주입된다.
'프로젝트 > Trip-Linker 팀 프로젝트' 카테고리의 다른 글
| Trip-Linker 개발일지 5 - User.java — updateNickname·withdraw 메서드 추가 (0) | 2026.06.04 |
|---|---|
| Trip-Linker 개발일지 4 - auth 도메인 전체 구현 — 로그인·토큰·비밀번호 재설정 (0) | 2026.06.04 |
| Trip-Linker 개발일지 3 - 프론트 리팩토링 — MOCK 위치, go() 렌더러, CSS 버그 (0) | 2026.06.04 |
| Trip-Linker 개발일지 2 - 계정 보안 이력 관리 Entity·Repository 구현 (0) | 2026.06.04 |