Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit Hold shift + click to select a range
ce3d906
fix: #21 - ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ ๋กœ๊น… ์ถ”๊ฐ€ ๋ฐ api nullable ๋ฏธ๋ฐ˜์˜ ์ˆ˜์ •
KimNahun Feb 9, 2026
0b66922
fix: #21 - planB response ์‘๋‹ต api์— ๋งž๊ฒŒ ์ˆ˜์ •
KimNahun Feb 9, 2026
4d4d41a
del: #21 - ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒŒ ๊ธฐํš ๋ณ€๊ฒฝ๋œ budgetview ์‚ญ์ œ (1์ผ ๊ธฐ์ค€ ์˜ˆ์‚ฐ)
KimNahun Feb 9, 2026
f56b991
feat: #21 - ๊ธธ์ฐพ๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ์‹œ ์™ธ๋ถ€ ๋ธŒ๋ผ์šฐ์ €๋กœ ์ด๋™๋˜๋„๋ก ๊ตฌํ˜„
KimNahun Feb 9, 2026
010eec2
feat: #21 - placeDetail RIB ์ถ”๊ฐ€ ๋ฐ chevronbutton๋ˆ„๋ฅผ ์‹œ ํ•ด๋‹น ํŽ˜์ด์ง€ ์ด๋™ ๊ตฌํ˜„
KimNahun Feb 9, 2026
36c5e29
feat: #21 - ๊ตฌ๊ธ€๋งต ๊ด€๋ จ api ์—ฐ๊ฒฐ ๋ฐ ๊ธฐ์กด api์—๋Ÿฌ ์‘๋‹ต์„ ๋ชจ๋“ˆ ๋‹จ์œ„์—์„œ api ๋‹จ์œ„๋กœ ๋ฆฌํŒฉํ† ๋ง
KimNahun Feb 9, 2026
1b87b01
feat: #21 - ๊ตฌ๊ธ€๋งต ๊ด€๋ จ api ์‹ค์ œ Interactor์—์„œ ํ˜ธ์ถœ ๊ตฌํ˜„
KimNahun Feb 9, 2026
fc7dde2
design: #21 - ๊ฐ€๊ฒŒ ์ƒ์„ธ์ •๋ณด API๋ฅผ ํ™œ์šฉํ•œ UI ๊ตฌํ˜„
KimNahun Feb 9, 2026
b2bfa71
design: #21 - ๊ฐ€๊ฒŒ ์ƒ์„ธ์ •๋ณด ์ด๋ฏธ์ง€ ์ปฌ๋ ‰์…˜๋ทฐ ๊ตฌํ˜„
KimNahun Feb 9, 2026
11928c8
refactor: #21 - Domain ๋ชจ๋ธ(PlaceDetail, TravelPlace, PlacePhoto) ์ง์ ‘ ์ „๋‹ฌ
KimNahun Feb 9, 2026
e018ce9
chore: #23: ์ƒ‰์ƒ ํ‘œ์‹œ ๋ฐฉ์‹ ๋ณ€๊ฒฝ hexcolor ์ง€์ • -> DSKit
KimNahun Feb 13, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ import Foundation

public protocol FollowServiceProtocol: Sendable {
/// ์—ฌํ–‰ ์ƒ์„ธ ์ •๋ณด ์กฐํšŒ
func fetchTravelDetail(id: Int) async -> Result<TravelDetail, FollowError>
func fetchTravelDetail(id: Int) async -> Result<TravelDetail, ContentCardError>

/// ์ผ์ฐจ๋ณ„ ์žฅ์†Œ ๋ชฉ๋ก ์กฐํšŒ
func fetchPlaces(travelId: Int, day: Int) async -> Result<[TravelPlace], FollowError>
func fetchPlaces(travelId: Int, day: Int) async -> Result<[TravelPlace], ItineraryError>

/// ์žฅ์†Œ ์ƒ์„ธ ์กฐํšŒ
func fetchPlaceDetail(googlePlaceId: String) async -> Result<PlaceDetail, PlaceDetailError>

/// ์žฅ์†Œ ์‚ฌ์ง„ ์กฐํšŒ
func fetchPlacePhotos(googlePlaceId: String) async -> Result<[PlacePhoto], PlacePhotosError>
}
19 changes: 19 additions & 0 deletions Projects/Domain/Sources/Model/Follow/Error/ContentCardError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// ContentCardError.swift
// Domain
//
// Created by kimnahun on 2026-02-09.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

/// ์—ฌํ–‰ ํ…œํ”Œ๋ฆฟ ์ƒ์„ธ ์กฐํšŒ API ์—๋Ÿฌ
public enum ContentCardError: Error, Sendable {
/// ์—ฌํ–‰ ํ…œํ”Œ๋ฆฟ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ (TRAVEL-02-001)
case notFound(message: String)
/// ์„œ๋ฒ„ ์—๋Ÿฌ (COMM-08-001)
case serverError(message: String)
/// ์•Œ ์ˆ˜ ์—†๋Š” ์—๋Ÿฌ
case unknown(code: String, message: String)
}
19 changes: 19 additions & 0 deletions Projects/Domain/Sources/Model/Follow/Error/ItineraryError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// ItineraryError.swift
// Domain
//
// Created by kimnahun on 2026-02-09.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

/// ์—ฌํ–‰ ํ…œํ”Œ๋ฆฟ ์ผ์ • ์กฐํšŒ API ์—๋Ÿฌ
public enum ItineraryError: Error, Sendable {
/// ์—ฌํ–‰ ํ…œํ”Œ๋ฆฟ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ (TRAVEL-02-001)
case notFound(message: String)
/// ์„œ๋ฒ„ ์—๋Ÿฌ (COMM-08-001)
case serverError(message: String)
/// ์•Œ ์ˆ˜ ์—†๋Š” ์—๋Ÿฌ
case unknown(code: String, message: String)
}
19 changes: 19 additions & 0 deletions Projects/Domain/Sources/Model/Follow/Error/PlaceDetailError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// PlaceDetailError.swift
// Domain
//
// Created by kimnahun on 2026-02-09.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

/// ์žฅ์†Œ ์ƒ์„ธ ์กฐํšŒ API ์—๋Ÿฌ
public enum PlaceDetailError: Error, Sendable {
/// ํ•„์ˆ˜ ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Œ (COMM-01-006)
case missingParameter(message: String)
/// ์žฅ์†Œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ (PLACE-02-001)
case notFound(message: String)
/// ์•Œ ์ˆ˜ ์—†๋Š” ์—๋Ÿฌ
case unknown(code: String, message: String)
}
Comment on lines +12 to +19
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

rg -n 'serverError' --type=swift -C2

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 6052


๐Ÿ Script executed:

fd -e swift | xargs grep -l 'PlacePhotosError' | head -5

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 431


๐Ÿ Script executed:

fd -e swift -path "*/Error/*" | head -20

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 243


๐Ÿ Script executed:

fd -e swift -path "*PlaceDetailError*"

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 243


๐Ÿ Script executed:

cat -n "Projects/Domain/Sources/Model/Follow/Error/PlacePhotosError.swift"

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 764


๐Ÿ Script executed:

fd PlaceDetailError

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 225


๐Ÿ Script executed:

rg -l PlaceDetailError

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 431


๐Ÿ Script executed:

cat -n "Projects/Modules/Networks/Sources/ErrorMapping/Follow/PlaceDetailError+Mapping.swift"

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 806


๐Ÿ Script executed:

rg -A 20 -B 5 'PlaceDetailError' "Projects/Modules/Networks/Sources/Service/FollowService.swift" | head -50

Repository: YAPP-Github/27th-App-Team-1-iOS

Length of output: 1231


serverError ์ผ€์ด์Šค ๋ˆ„๋ฝ - ์ถ”๊ฐ€ ํ•„์š”

PlaceDetailError์—๋Š” PlacePhotosError, HomeError, ItineraryError, ContentCardError ๋“ฑ ํ”„๋กœ์ ํŠธ ๋‚ด ๋‹ค๋ฅธ ์—๋Ÿฌ ํƒ€์ž…๋“ค์ฒ˜๋Ÿผ ์„œ๋ฒ„ ์—๋Ÿฌ(COMM-08-001) ์ผ€์ด์Šค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  API ์—”๋“œํฌ์ธํŠธ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ํ‘œ์ค€ ์„œ๋ฒ„ ์—๋Ÿฌ ์ฝ”๋“œ์ด๋ฏ€๋กœ, ์ผ๊ด€์„ฑ ์žˆ๋Š” ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด serverError(message: String) ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ๋งคํ•‘ ํŒŒ์ผ์„ ํ•จ๊ป˜ ์ˆ˜์ •ํ•˜์„ธ์š”.

๐Ÿค– Prompt for AI Agents
In `@Projects/Domain/Sources/Model/Follow/Error/PlaceDetailError.swift` around
lines 12 - 19, Add a serverError case to the PlaceDetailError enum and update
its mapping logic: modify the PlaceDetailError declaration (enum
PlaceDetailError) to include case serverError(message: String) alongside
missingParameter, notFound, and unknown, then update the error-to-API-code
mapping (the file/function that maps response codes to PlaceDetailError
instances) to map the COMM-08-001 server error to
PlaceDetailError.serverError(message:). Ensure any switch statements or
initializers that pattern-match PlaceDetailError handle the new serverError case
to maintain exhaustive handling.

21 changes: 21 additions & 0 deletions Projects/Domain/Sources/Model/Follow/Error/PlacePhotosError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// PlacePhotosError.swift
// Domain
//
// Created by kimnahun on 2026-02-09.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

/// ์žฅ์†Œ ์‚ฌ์ง„ ์กฐํšŒ API ์—๋Ÿฌ
public enum PlacePhotosError: Error, Sendable {
/// ํ•„์ˆ˜ ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Œ (COMM-01-006)
case missingParameter(message: String)
/// ์„œ๋ฒ„ ์—๋Ÿฌ (COMM-08-001)
case serverError(message: String)
/// Google Maps Places API ํ˜ธ์ถœ ์‹คํŒจ (GMAP_PLACE-07-001)
case googleApiError(message: String)
/// ์•Œ ์ˆ˜ ์—†๋Š” ์—๋Ÿฌ
case unknown(code: String, message: String)
}
20 changes: 0 additions & 20 deletions Projects/Domain/Sources/Model/Follow/FollowError.swift

This file was deleted.

64 changes: 64 additions & 0 deletions Projects/Domain/Sources/Model/Follow/PlaceDetail.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// PlaceDetail.swift
// Domain
//
// Created by kimnahun on 2026-02-09.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

/// ์žฅ์†Œ ์ƒ์„ธ ์ •๋ณด
public struct PlaceDetail: Hashable {
public let id: String
public let name: String
public let thumbnail: String?
public let nationalPhoneNumber: String?
public let internationalPhoneNumber: String?
public let formattedAddress: String?
public let location: PlaceLocation
public let userRatingCount: Int?
public let rating: Double?
public let regularOpeningHours: [String]?
public let googleMapsUri: String?
public let websiteUri: String?

public init(
id: String,
name: String,
thumbnail: String? = nil,
nationalPhoneNumber: String? = nil,
internationalPhoneNumber: String? = nil,
formattedAddress: String? = nil,
location: PlaceLocation,
userRatingCount: Int? = nil,
rating: Double? = nil,
regularOpeningHours: [String]? = nil,
googleMapsUri: String? = nil,
websiteUri: String? = nil
) {
self.id = id
self.name = name
self.thumbnail = thumbnail
self.nationalPhoneNumber = nationalPhoneNumber
self.internationalPhoneNumber = internationalPhoneNumber
self.formattedAddress = formattedAddress
self.location = location
self.userRatingCount = userRatingCount
self.rating = rating
self.regularOpeningHours = regularOpeningHours
self.googleMapsUri = googleMapsUri
self.websiteUri = websiteUri
}
}

/// ์žฅ์†Œ ์œ„์น˜ ์ •๋ณด
public struct PlaceLocation: Hashable {
public let latitude: Double
public let longitude: Double

public init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
22 changes: 22 additions & 0 deletions Projects/Domain/Sources/Model/Follow/PlacePhoto.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// PlacePhoto.swift
// Domain
//
// Created by kimnahun on 2026-02-09.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

/// ์žฅ์†Œ ์‚ฌ์ง„ ์ •๋ณด
public struct PlacePhoto: Hashable {
public let photoUri: String
public let widthPx: Int
public let heightPx: Int

public init(photoUri: String, widthPx: Int, heightPx: Int) {
self.photoUri = photoUri
self.widthPx = widthPx
self.heightPx = heightPx
}
}
4 changes: 2 additions & 2 deletions Projects/Domain/Sources/Model/Travel/TravelDetail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,15 @@ public struct YouTubeInfo: Hashable {
public let youtuber: String
public let thumbnail: String?
public let profileImage: String?
public let link: String
public let link: String?
public let summary: String

public init(
title: String,
youtuber: String,
thumbnail: String?,
profileImage: String?,
link: String,
link: String?,
summary: String
) {
self.title = title
Expand Down
15 changes: 13 additions & 2 deletions Projects/Domain/Sources/Model/Travel/TravelPlace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public struct TravelPlace: Hashable {
public let distanceKm: Double?
public let transportation: [Transportation]
public let youtubeTips: [String]
public let planB: [PlaceInfo]
public let planB: [PlanBInfo]
public let estimatedDuration: Int?
public let place: PlaceInfo

Expand All @@ -27,7 +27,7 @@ public struct TravelPlace: Hashable {
distanceKm: Double? = nil,
transportation: [Transportation] = [],
youtubeTips: [String] = [],
planB: [PlaceInfo] = [],
planB: [PlanBInfo] = [],
estimatedDuration: Int? = nil,
place: PlaceInfo
) {
Expand All @@ -43,6 +43,17 @@ public struct TravelPlace: Hashable {
}
}

/// ํ”Œ๋žœB ์ •๋ณด
public struct PlanBInfo: Hashable {
public let name: String
public let feature: String?

public init(name: String, feature: String? = nil) {
self.name = name
self.feature = feature
}
}

/// ๊ตํ†ต์ˆ˜๋‹จ ์ •๋ณด
public struct Transportation: Hashable {
public let mode: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public protocol FollowDetailDependency: Dependency {

// MARK: - FollowDetailComponent

final class FollowDetailComponent: Component<FollowDetailDependency>, TripCalendarDependency {
final class FollowDetailComponent: Component<FollowDetailDependency>, TripCalendarDependency, PlaceDetailDependency {
var followService: FollowServiceProtocol {
dependency.followService
}
Expand Down Expand Up @@ -54,11 +54,13 @@ public final class FollowDetailBuilder: Builder<FollowDetailDependency>, FollowD
interactor.listener = listener

let tripCalendarBuilder = TripCalendarBuilder(dependency: component)
let placeDetailBuilder = PlaceDetailBuilder(dependency: component)

let router = FollowDetailRouter(
interactor: interactor,
viewController: viewController,
tripCalendarBuilder: tripCalendarBuilder
tripCalendarBuilder: tripCalendarBuilder,
placeDetailBuilder: placeDetailBuilder
)

return router
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ protocol FollowDetailPresentable: Presentable {
func hideLoading()
func updateTravelDetail(_ detail: TravelDetail)
func updatePlaces(_ places: [TravelPlace])
func updateBudget(_ budget: Int)
func showPlaceDetail(_ place: TravelPlace)
}

Expand All @@ -38,6 +37,7 @@ protocol FollowDetailPresentableListener: AnyObject {
func didTapAddToTrip()
func didSelectDay(_ day: Int)
func didSelectPlace(_ place: TravelPlace)
func didTapPlaceDetailChevron(_ place: TravelPlace)
}

// MARK: - FollowDetailInteractor
Expand Down Expand Up @@ -105,7 +105,6 @@ final class FollowDetailInteractor: PresentableInteractor<FollowDetailPresentabl
self.placesByDay[1] = places
presenter.updateTravelDetail(detail)
presenter.updatePlaces(places)
updateBudgetForDay(1)
presenter.hideLoading()
}
}
Expand All @@ -114,7 +113,6 @@ final class FollowDetailInteractor: PresentableInteractor<FollowDetailPresentabl
private func loadPlaces(for day: Int) {
if let cachedPlaces = placesByDay[day] {
presenter.updatePlaces(cachedPlaces)
updateBudgetForDay(day)
return
}

Expand All @@ -129,17 +127,10 @@ final class FollowDetailInteractor: PresentableInteractor<FollowDetailPresentabl
await MainActor.run {
self.placesByDay[day] = places
presenter.updatePlaces(places)
updateBudgetForDay(day)
presenter.hideLoading()
}
}
}

private func updateBudgetForDay(_ day: Int) {
guard let detail = travelDetail else { return }
let dailyBudget = detail.budgetPerPerson / detail.days
presenter.updateBudget(dailyBudget)
}
}

// MARK: - FollowDetailPresentableListener
Expand All @@ -163,6 +154,19 @@ extension FollowDetailInteractor: FollowDetailPresentableListener {
func didSelectPlace(_ place: TravelPlace) {
presenter.showPlaceDetail(place)
}

func didTapPlaceDetailChevron(_ place: TravelPlace) {
let youtuberName = travelDetail?.youtube.youtuber ?? ""
router?.routeToPlaceDetail(travelPlace: place, youtuberName: youtuberName)
}
}

// MARK: - PlaceDetailListener

extension FollowDetailInteractor: PlaceDetailListener {
func placeDetailDidTapBack() {
router?.detachPlaceDetail()
}
}

// MARK: - TripCalendarListener
Expand Down
Loading