Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
Walkthrough여행 기능을 리팩토링하여 기존 TravelRoute/TravelViewModel/TravelScreen을 새로운 MyTravelRoute/MyTravelViewModel/MyTravelScreen으로 교체하고, 데이터 레이어에 예정된 여행 목록 조회 기능을 추가했습니다. 여행 카드, 여행 목록, 추천 여행 섹션 등 새로운 UI 컴포넌트를 추가하고 해당 리소스와 유틸리티 함수를 포함합니다. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (9)
feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelContract.kt (2)
27-35:UpcomingTravel.Upcoming과UpcomingTravelItem의 구조가 완전히 동일합니다두 클래스의 프로퍼티(
travelId,title,startDate,endDate,imageUrl,dDay)가 동일합니다. 의도적으로 카드(card) 상태와 리스트 아이템을 분리한 경우라면 주석으로 명시하는 것이 좋고, 그렇지 않다면UpcomingTravelItem대신UpcomingTravel.Upcoming을 재사용하거나 공통 추상 타입을 도출하는 방향을 검토해 보세요.Also applies to: 57-65
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelContract.kt` around lines 27 - 35, UpcomingTravel.Upcoming and UpcomingTravelItem define identical properties (travelId, title, startDate, endDate, imageUrl, dDay), causing duplicated model definitions; either reuse UpcomingTravel.Upcoming wherever UpcomingTravelItem is used or extract a shared data type (e.g., UpcomingTravelModel or an interface) and have both UpcomingTravel.Upcoming and UpcomingTravelItem inherit/alias it, and add a clarifying comment if the separation is intentional. Locate the classes by their names UpcomingTravel.Upcoming and UpcomingTravelItem and consolidate or refactor to a single source of truth to remove duplication.
20-21:UpcomingTravelsealed class의@Stable어노테이션을@Immutable로 변경하는 것을 권장합니다
MyTravelState는@Immutable로 선언되어 있으며, 이 계약이 유효하려면 클래스 내 모든 타입 역시@Stable또는@Immutable이어야 합니다.UpcomingTravel의 모든 구체적인 서브클래스(Upcoming,InProgress)가 이미@Immutabledata class이므로, 값이 생성 후 변경되지 않는다는 더 강력한 계약을 사용하려면, 모든 프로퍼티가val이고 immutable 타입인지 확인한 뒤 sealed class 자체도@Immutable로 선언하는 것이 더 정확한 의미론적 계약입니다.♻️ 제안 변경 사항
- `@Stable` + `@Immutable` sealed class UpcomingTravel {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelContract.kt` around lines 20 - 21, Change the annotation on the sealed class UpcomingTravel from `@Stable` to `@Immutable`; update the declaration of UpcomingTravel to use `@Immutable` and ensure its subclasses Upcoming and InProgress remain immutable data classes (all properties are val and use immutable types) to satisfy the MyTravelState immutability contract.data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/UserTravelRepository.kt (1)
28-30: 페이지네이션 파라미터가 활용되지 않음API는
page/size파라미터를 지원하고,UpcomingTravelList모델에hasNext: Boolean이 포함되어 있으나, 현재 repository에서는 항상 서버 기본값으로 호출합니다. 서버의 기본 페이지 크기를 초과하는 예정 여행이 있을 경우 전체 목록을 표시하지 못합니다. 향후 무한 스크롤 또는 페이지네이션 구현 시hasNext값을 ViewModel에서 활용하는 구조를 고려해 주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/UserTravelRepository.kt` around lines 28 - 30, The repository method getUpcomingTravelList() currently ignores pagination parameters and always calls userTravelApi.getUpcomingTravelList() with server defaults; update the repository to accept page and size params (or default constants) and forward them to userTravelApi.getUpcomingTravelList(page, size), returning UpcomingTravelList (which contains hasNext) so ViewModel can use hasNext for pagination/infinite-scroll; update the function signature (getUpcomingTravelList(page: Int = 0, size: Int = DEFAULT_PAGE_SIZE)) and ensure any callers are adjusted to supply page/size as needed.feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/RecommendedTravelSection.kt (1)
91-98:AsyncImage에 로딩 플레이스홀더 및 오류 상태 추가 고려현재 썸네일 이미지가 로드되는 동안 또는 로드 실패 시 빈 영역이 표시됩니다. Coil 3의
placeholder/error파라미터를 활용하면 UX를 개선할 수 있습니다.♻️ 개선 제안
AsyncImage( model = travel.thumbnail, contentDescription = travel.title, modifier = Modifier .fillMaxWidth() .height(140.dp), contentScale = ContentScale.Crop, + placeholder = painterResource(CoreR.drawable.img_empty_suitcase), + error = painterResource(CoreR.drawable.img_empty_suitcase), )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/RecommendedTravelSection.kt` around lines 91 - 98, The AsyncImage usage in RecommendedTravelSection.kt currently shows an empty area on load/failure; update the AsyncImage call in this component to provide placeholder and error painters (and optionally a fallback) so a drawable is shown while loading or on error—use painterResource(...) or a remembered Painter tied to your placeholder/error drawables and pass them to the AsyncImage parameters (placeholder=..., error=..., fallback=...) so the thumbnail (model = travel.thumbnail) displays a proper placeholder and an error image when loading fails.feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/UpcomingTravelCardSection.kt (1)
120-144:DayTag컴포저블이UpcomingTravelListSection.kt와 완전히 중복됩니다.두 파일의
DayTag가 동일한 로직과 구조를 가지고 있습니다(스타일/색상/조건 모두 동일). 공통 컴포넌트로 추출하여 재사용하는 것을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/UpcomingTravelCardSection.kt` around lines 120 - 144, Duplicate DayTag composable logic should be extracted to a single reusable component: create a shared DayTag composable (same signature: DayTag(dDay: Int, modifier: Modifier = Modifier)) in a common UI module or package, move the Box/Text implementation (preserving stringResource keys R.string.my_travel_upcoming_travel_d_day_minus and _plus, NDGLTheme styles/colors, and the conditional dDay logic) into that shared function, then replace the duplicate DayTag definitions in UpcomingTravelCardSection.kt and UpcomingTravelListSection.kt with imports and calls to the new shared DayTag to avoid duplication.feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelScreen.kt (2)
65-67: FIXME: 인기 여행지 리스트 네비게이션이 미구현 상태입니다.
NavigateToPopularTravelListside effect가 발생해도 실제 네비게이션이 수행되지 않습니다. 후속 작업으로 구현 예정인지 확인해주세요.이 항목을 추적하기 위한 이슈를 생성해 드릴까요?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelScreen.kt` around lines 65 - 67, The NavigateToPopularTravelList side effect in MyTravelScreen (MyTravelSideEffect.NavigateToPopularTravelList) is unimplemented; update the side-effect handler to perform the actual navigation (e.g., call the NavController.navigate(...) with the popular-travel route or invoke the existing navigation callback such as navigateToPopularTravelList/navigateToPopularTravelListScreen) so the app transitions to the Popular Travel List when this side effect is emitted; ensure any required parameters or navigation options are passed and unit-tested if applicable.
46-46: 불필요한 이중 괄호가 있습니다.
(MyTravelIntent.ClickTravel(travelId = travelId))→ 외부 괄호가 불필요합니다.🧹 수정 제안
- viewModel.onIntent((MyTravelIntent.ClickTravel(travelId = travelId))) + viewModel.onIntent(MyTravelIntent.ClickTravel(travelId = travelId))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelScreen.kt` at line 46, The call to viewModel.onIntent uses an unnecessary outer pair of parentheses around MyTravelIntent.ClickTravel; update the invocation in MyTravelScreen so you pass the intent directly to viewModel.onIntent (i.e., replace viewModel.onIntent((MyTravelIntent.ClickTravel(travelId = travelId))) with a single-parenthesis call viewModel.onIntent(MyTravelIntent.ClickTravel(travelId = travelId))) to remove the redundant parentheses.feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelViewModel.kt (2)
50-89:LocalDate.now()가 여러 곳에서 독립적으로 호출되어 미세한 날짜 불일치 가능성이 있습니다.
mapToUpcomingTravel(Line 51)과loadUpcomingTravelList(Line 96)에서 각각LocalDate.now()를 호출합니다. 자정 전후로 실행될 경우 서로 다른 날짜를 기준으로 D-Day가 계산될 수 있습니다.today를init블록에서 한 번만 구하고 파라미터로 전달하면 일관성이 보장됩니다.♻️ 수정 제안
init { viewModelScope.launch { + val today = LocalDate.now() - val upcomingDeferred = async { loadUpcomingTravel() } - val listDeferred = async { loadUpcomingTravelList() } + val upcomingDeferred = async { loadUpcomingTravel(today) } + val listDeferred = async { loadUpcomingTravelList(today) } ... } } -private fun mapToUpcomingTravel(travel: UpcomingTravelResponse): MyTravelState.UpcomingTravel? { - val today = LocalDate.now() +private fun mapToUpcomingTravel(travel: UpcomingTravelResponse, today: LocalDate): MyTravelState.UpcomingTravel? {Also applies to: 91-108
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelViewModel.kt` around lines 50 - 89, mapToUpcomingTravel and loadUpcomingTravelList independently call LocalDate.now(), causing potential date mismatch; capture LocalDate.now() once in the ViewModel init (e.g., val todayAtInit = LocalDate.now()) and use that single value everywhere: change mapToUpcomingTravel to accept a LocalDate parameter (or reference the stored todayAtInit) and update all callers (including loadUpcomingTravelList) to pass or use the same today value so D-Day/dayCount calculations are consistent across methods.
26-39: 초기 데이터 로딩 시 에러 및 로딩 상태가 UI에 반영되지 않습니다.모든 API 호출이 실패할 경우, 사용자에게 에러 피드백 없이 빈 화면만 보여집니다. 현재 빈 상태 UI에 CTA가 있어 최소한의 UX는 보장되지만, 네트워크 에러와 실제로 데이터가 없는 경우를 구분할 수 없습니다.
추후
isLoading/isError상태를MyTravelState에 추가하여 스켈레톤 UI 또는 재시도 UI를 제공하는 것을 고려해주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelViewModel.kt` around lines 26 - 39, The init block in MyTravelViewModel launches parallel loads but doesn't update loading/error state, so UI can't show skeletons or error feedback; add isLoading and isError fields to MyTravelState and update them from the init coroutine: set isLoading=true before starting async calls (in the viewModelScope.launch), set isLoading=false after await, and set isError=true when any loadUpcomingTravel() or loadUpcomingTravelList() throws (catch exceptions around the async/await or use try/catch inside each loader), then reduce to emit the new state; ensure loadRecommendedTravels() is only invoked when no data and isError is false (or preserve separate handling) so UI can show retry when isError=true.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/UserTravelRepository.kt`:
- Around line 28-30: getUpcomingTravelList() currently lets
HttpResponseException bubble up on a 204 response; wrap the
userTravelApi.getUpcomingTravelList() call in a try/catch for
HttpResponseException (same pattern as getUpcomingTravel()) and if the caught
exception indicates HTTP 204 return an empty UpcomingTravelList (or the same
null/empty sentinel used by getUpcomingTravel()), otherwise rethrow the
exception; reference functions: UserTravelRepository.getUpcomingTravelList(),
userTravelApi.getUpcomingTravelList(), and HttpResponseException.
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelContract.kt`:
- Around line 52-54: The model objects have two issues: clarify the unit for
estimatedDuration and unify thumbnail naming; update the
TravelPlace/RecommendedTravel definitions so estimatedDuration is explicit
(either rename estimatedDuration to estimatedDurationMinutes or add KDoc on the
estimatedDuration property indicating "minutes"), and rename
RecommendedTravel.thumbnail to thumbnailUrl to match TravelPlace (or vice versa
across both classes), updating all usages of RecommendedTravel.thumbnail to
RecommendedTravel.thumbnailUrl and any constructors/serializers to preserve
compatibility.
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelScreen.kt`:
- Around line 142-152: The MyTravelScreenPreview currently calls MyTravelScreen
directly and is missing the NDGLTheme wrapper, causing the preview to render
without app theme colors/typography; update MyTravelScreenPreview to wrap the
MyTravelScreen invocation inside NDGLTheme { ... } so the preview uses the app's
theme (referencing MyTravelScreenPreview, MyTravelScreen and MyTravelState).
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/UpcomingTravelCardSection.kt`:
- Line 221: 파일의 UpcomingTravelCardSection.kt 내부에 남아있는 standalone 표현식
place.category는 아무 동작을 하지 않는 불필요한 코드입니다; 해당 표현식을 삭제하여 정리하세요. 만약 의도적으로 카테고리 값을
사용하려던 것이라면 place.category를 반환하거나 변수에 할당하거나 UI에 바인드하는 방식으로 의도한 사용처로 대체하고, 그렇지 않다면
해당 라인(단독 place.category 표현)을 제거하면 됩니다.
- Around line 103-105: The DateTimeFormatter instances in
UpcomingTravelCardSection.kt are recreated on every recomposition; wrap the
DateTimeFormatter.ofPattern(...) calls with Compose's remember to cache them
(same approach as in UpcomingTravelListSection.kt). Update the dateFormatter
declaration(s) used in UpcomingTravelCardSection (and the formatter in
InProgressTravelCard) to use remember {
DateTimeFormatter.ofPattern(stringResource(...)) } so they are not reallocated
each recomposition.
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/UpcomingTravelListSection.kt`:
- Around line 199-205: The ripple extends beyond the rounded corners because the
modifier order on the Row applies .clickable before .clip; in
UpcomingTravelListSection (the Row with
modifier.wrapContentSize().clickable(onClick =
onClick).clip(RoundedCornerShape(8.dp)).background(...)), move
.clip(RoundedCornerShape(8.dp)) before .clickable so the clickable ripple is
clipped to the rounded shape and remains inside the background bounds.
- Around line 149-153: The D-Day rendering logic in UpcomingTravelListSection is
inverted: swap the condition so future days (dDay > 0) use the "minus" format
and past/zero use the "plus" format; specifically update the conditional around
dDay in UpcomingTravelListSection to use
stringResource(R.string.my_travel_upcoming_list_d_day_minus, dDay) when dDay > 0
and stringResource(R.string.my_travel_upcoming_list_d_day_plus, dDay) otherwise.
Also change the string resource my_travel_upcoming_list_d_day_minus to include
the minus sign (e.g., "D-%d") so it matches UpcomingTravelCardSection and
produces "D-7" for a 7-day future travel.
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/navigation/TravelEntry.kt`:
- Around line 28-31: The navigateToTravelDetail lambda currently calls
navigator.navigate(Route.TravelDetail(travelId.toInt())) which can truncate Long
IDs and cause wrong routing; update the code so Route.TravelDetail accepts a
Long (or add an explicit range check and error handling before conversion) and
pass travelId as Long instead of calling toInt(), i.e., change the
Route.TravelDetail constructor/signature to accept Long and adjust all usages
(or implement a guard in navigateToTravelDetail that validates travelId <=
Int.MAX_VALUE and logs/handles out-of-range IDs) to prevent silent truncation.
In `@feature/travel/src/main/res/values/strings.xml`:
- Line 16: The string resource my_travel_upcoming_list_d_day_minus is missing
the hyphen in its format and should match the card section; update the value
from "D%d" to "D-%d" so list items render as "D-3" consistently (edit the string
named my_travel_upcoming_list_d_day_minus in strings.xml).
---
Nitpick comments:
In
`@data/travel/src/main/java/com/yapp/ndgl/data/travel/repository/UserTravelRepository.kt`:
- Around line 28-30: The repository method getUpcomingTravelList() currently
ignores pagination parameters and always calls
userTravelApi.getUpcomingTravelList() with server defaults; update the
repository to accept page and size params (or default constants) and forward
them to userTravelApi.getUpcomingTravelList(page, size), returning
UpcomingTravelList (which contains hasNext) so ViewModel can use hasNext for
pagination/infinite-scroll; update the function signature
(getUpcomingTravelList(page: Int = 0, size: Int = DEFAULT_PAGE_SIZE)) and ensure
any callers are adjusted to supply page/size as needed.
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelContract.kt`:
- Around line 27-35: UpcomingTravel.Upcoming and UpcomingTravelItem define
identical properties (travelId, title, startDate, endDate, imageUrl, dDay),
causing duplicated model definitions; either reuse UpcomingTravel.Upcoming
wherever UpcomingTravelItem is used or extract a shared data type (e.g.,
UpcomingTravelModel or an interface) and have both UpcomingTravel.Upcoming and
UpcomingTravelItem inherit/alias it, and add a clarifying comment if the
separation is intentional. Locate the classes by their names
UpcomingTravel.Upcoming and UpcomingTravelItem and consolidate or refactor to a
single source of truth to remove duplication.
- Around line 20-21: Change the annotation on the sealed class UpcomingTravel
from `@Stable` to `@Immutable`; update the declaration of UpcomingTravel to use
`@Immutable` and ensure its subclasses Upcoming and InProgress remain immutable
data classes (all properties are val and use immutable types) to satisfy the
MyTravelState immutability contract.
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelScreen.kt`:
- Around line 65-67: The NavigateToPopularTravelList side effect in
MyTravelScreen (MyTravelSideEffect.NavigateToPopularTravelList) is
unimplemented; update the side-effect handler to perform the actual navigation
(e.g., call the NavController.navigate(...) with the popular-travel route or
invoke the existing navigation callback such as
navigateToPopularTravelList/navigateToPopularTravelListScreen) so the app
transitions to the Popular Travel List when this side effect is emitted; ensure
any required parameters or navigation options are passed and unit-tested if
applicable.
- Line 46: The call to viewModel.onIntent uses an unnecessary outer pair of
parentheses around MyTravelIntent.ClickTravel; update the invocation in
MyTravelScreen so you pass the intent directly to viewModel.onIntent (i.e.,
replace viewModel.onIntent((MyTravelIntent.ClickTravel(travelId = travelId)))
with a single-parenthesis call
viewModel.onIntent(MyTravelIntent.ClickTravel(travelId = travelId))) to remove
the redundant parentheses.
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/MyTravelViewModel.kt`:
- Around line 50-89: mapToUpcomingTravel and loadUpcomingTravelList
independently call LocalDate.now(), causing potential date mismatch; capture
LocalDate.now() once in the ViewModel init (e.g., val todayAtInit =
LocalDate.now()) and use that single value everywhere: change
mapToUpcomingTravel to accept a LocalDate parameter (or reference the stored
todayAtInit) and update all callers (including loadUpcomingTravelList) to pass
or use the same today value so D-Day/dayCount calculations are consistent across
methods.
- Around line 26-39: The init block in MyTravelViewModel launches parallel loads
but doesn't update loading/error state, so UI can't show skeletons or error
feedback; add isLoading and isError fields to MyTravelState and update them from
the init coroutine: set isLoading=true before starting async calls (in the
viewModelScope.launch), set isLoading=false after await, and set isError=true
when any loadUpcomingTravel() or loadUpcomingTravelList() throws (catch
exceptions around the async/await or use try/catch inside each loader), then
reduce to emit the new state; ensure loadRecommendedTravels() is only invoked
when no data and isError is false (or preserve separate handling) so UI can show
retry when isError=true.
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/RecommendedTravelSection.kt`:
- Around line 91-98: The AsyncImage usage in RecommendedTravelSection.kt
currently shows an empty area on load/failure; update the AsyncImage call in
this component to provide placeholder and error painters (and optionally a
fallback) so a drawable is shown while loading or on error—use
painterResource(...) or a remembered Painter tied to your placeholder/error
drawables and pass them to the AsyncImage parameters (placeholder=...,
error=..., fallback=...) so the thumbnail (model = travel.thumbnail) displays a
proper placeholder and an error image when loading fails.
In
`@feature/travel/src/main/java/com/yapp/ndgl/feature/travel/mytravel/UpcomingTravelCardSection.kt`:
- Around line 120-144: Duplicate DayTag composable logic should be extracted to
a single reusable component: create a shared DayTag composable (same signature:
DayTag(dDay: Int, modifier: Modifier = Modifier)) in a common UI module or
package, move the Box/Text implementation (preserving stringResource keys
R.string.my_travel_upcoming_travel_d_day_minus and _plus, NDGLTheme
styles/colors, and the conditional dDay logic) into that shared function, then
replace the duplicate DayTag definitions in UpcomingTravelCardSection.kt and
UpcomingTravelListSection.kt with imports and calls to the new shared DayTag to
avoid duplication.
5a6eb7c to
8fc085b
Compare
8fc085b to
94f0187
Compare
94f0187 to
17f0475
Compare
NDGL-89 내 여행 탭 구현
연관 문서
디자인
스크린샷 (Optional)
변경사항
내 여행 탭 UI 구현
다가오는 여행 카드 섹션 (UpcomingTravelCardSection)
다가오는 여행 목록 섹션 (UpcomingTravelListSection)
예정된 여행 목록을 리스트로 표시
여행이 없을 경우 빈 상태 UI + "새로운 여행지 찾아보기" CTA 버튼 노출
추천 여행 템플릿 섹션 (RecommendedTravelSection)
다가오는 여행 카드와 목록이 모두 비어있을 때 추천 여행 템플릿을 가로 스크롤로 표시
async/await로 두 API를 병렬 호출 후 결과 확인하여 조건부 로딩
API 연동
UserTravelApi에 GET /api/v1/travels/upcoming/list 추가
UserTravelRepository에 getUpcomingTravelList() 추가
UpcomingTravelList 응답 모델 추가
TravelTemplateRepository.getRecommendTravelTemplates() 연동
기타
테스트 체크 리스트
Summary by CodeRabbit
릴리스 노트