Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e2322f5
feat: #10 - ๋”ฐ๋ผ๊ฐ€๊ธฐ ์ปจํ…์ธ  ํ™”๋ฉด RIBs ์—ฐ๊ฒฐ ๋ฐ ์˜์กด์„ฑ ์ถ”๊ฐ€
KimNahun Jan 23, 2026
dcf520d
design: #10 - ๋”ฐ๋ผ๊ฐ€๊ธฐ ์ปจํ…์ธ  ์ดˆ์•ˆ UI ์ถ”๊ฐ€ ๊ตฌํ˜„
KimNahun Jan 23, 2026
997dfb2
feat: #10 - follow ์‹ค์ œ api ํ˜ธ์ถœ๋กœ ๋Œ€์ฒด
KimNahun Jan 23, 2026
c8db0e3
design: ๋”ฐ๋ผ๊ฐ€๊ธฐ ์ปจํ…์ธ  Headerview ๋””์ž์ธ ์ˆ˜์ •
KimNahun Jan 24, 2026
1c3d8bb
design: #10 - detailviewcontroller sstickeyheader๊ตฌํ˜„, ui ์ถ”๊ฐ€ ๋Œ€์‘
KimNahun Jan 24, 2026
a7dc7ec
design: #10: ์ปฌ๋ ‰์…˜๋ทฐ์˜ ๋‚ด์šฉ dto์— ๋งž๊ฒŒ ์ˆ˜์ •
KimNahun Jan 24, 2026
19633d9
design: #10 - ์ƒˆ๋กœ์šด ์—ฌํ–‰ ๋งŒ๋“ค๊ธฐ ํ™”๋ฉด RIBs ์บ˜๋ฆฐ๋” ๊ตฌํ˜„
KimNahun Jan 24, 2026
06d398f
design: #10 - navigationbar backbutton ๋™์ž‘ native conponent๋กœ ๋ณ€๊ฒฝ
KimNahun Jan 24, 2026
6c0c432
design: #10 - ์ปค์Šคํ…€ ๋ฐ”ํ…€์‹œํŠธ ๊ตฌํ˜„
KimNahun Jan 24, 2026
6a978af
fix: #10 - ๋ ˆ์ด์•„์›ƒ ์ถฉ๋Œ ํ•ด๊ฒฐ
KimNahun Jan 24, 2026
4f7722f
feat: #10 - ์—ฌํ–‰์„ ๋‹ด์œผ๋ฉด ํŽ˜์ด์ง€ ์ด๋™๋˜๋„๋ก ๊ตฌํ˜„
KimNahun Jan 24, 2026
1838e01
fix: #10 - ํƒญ๋ฐ” ์ž”์ƒ์ด ๋ณด์ด๋˜ ๋ฒ„๊ทธ ์ˆ˜์ •
KimNahun Jan 28, 2026
708cade
refactor: #10 - ์ฝ”๋“œ ์Šคํƒ€์ผ ๋ฆฌํŒฉํ† ๋ง
KimNahun Jan 28, 2026
c9706d4
refactor: #10 - ํ”„๋กœ์ ํŠธ์— ๋‚จ์€ ๊ฒฝ๊ณ ๋ฌธ ์ œ๊ฑฐ
KimNahun Jan 28, 2026
2abae85
del: #10 - ์ฝ”๋“œ์˜ ์ฃผ์„ ์ œ๊ฑฐ
KimNahun Jan 28, 2026
d446b0a
refactor: #10 - ๋ถˆํ•„์š”ํ•œ guard๋ฌธ ์ œ๊ฑฐ
KimNahun Jan 28, 2026
ac38850
fix: #10 - api ํ˜ธ์ถœ mainactor ์ˆ˜์ • ๋ฐ ์ž ์žฌ์  race condition ์ œ๊ฑฐ
KimNahun Jan 29, 2026
165fb33
chore: #10 - UIColor ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„ hexcode๋กœ ๋ณ€๊ฒฝ. (๋””์ž์ธ ์‹œ์Šคํ…œ ๋ณ€๊ฒฝ ๋Œ€์‘)
KimNahun Jan 29, 2026
d244c07
Merge branch 'develop' into design/#10-Travel_Follow_Contents
KimNahun Jan 29, 2026
df1d09a
chore: #10 - DSKitAsset.Colors ์ƒ‰์ƒ์„ ์“ฐ๋Š”๊ณณ hexcode๋กœ ๋ณ€๊ฒฝ
KimNahun Jan 29, 2026
c0d66d1
rename: #10: tabbaritemtype ๋„ค์ด๋ฐ ๋ณ€๊ฒฝ
KimNahun Jan 29, 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 @@ -11,6 +11,8 @@ public extension TargetDependency {
struct Features {
public struct Home {}
public struct TabBar {}
public struct Follow {}
public struct Travel {}
}

struct Modules {}
Expand Down Expand Up @@ -49,3 +51,15 @@ public extension TargetDependency.Features.TabBar {

static let feature = TargetDependency.Features.project(name: "Feature", group: group)
}

public extension TargetDependency.Features.Follow {
static let group = "Follow"

static let feature = TargetDependency.Features.project(name: "Feature", group: group)
}

public extension TargetDependency.Features.Travel {
static let group = "Travel"

static let feature = TargetDependency.Features.project(name: "Feature", group: group)
}
19 changes: 19 additions & 0 deletions Projects/Data/Sources/DI/FollowRepositoryFactory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// FollowRepositoryFactory.swift
// Data
//
// Created by kimnahun on 2026-01-23.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Domain
import Foundation
import Networks

public func makeFollowService() -> FollowServiceProtocol {
FollowService()
}

public func makeFollowRepository(service: FollowServiceProtocol) -> FollowRepositoryProtocol {
FollowRepository(service: service)
}
41 changes: 41 additions & 0 deletions Projects/Data/Sources/Repository/Follow/FollowRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// FollowRepository.swift
// Data
//
// Created by kimnahun on 2026-01-23.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Domain
import Foundation
import Networks

public final class FollowRepository: FollowRepositoryProtocol, @unchecked Sendable {
private let service: FollowServiceProtocol

public init(service: FollowServiceProtocol) {
self.service = service
}

public func fetchTravelDetail(id: Int) async -> TravelDetail? {
let result = await service.getContentCard(id: id)

switch result {
case .success(let response):
return response.toDomain()
case .failure, .networkFailure:
return nil
}
}

public func fetchPlaces(travelId: Int, day: Int) async -> [TravelPlace] {
let result = await service.getItinerary(id: travelId, day: day)

switch result {
case .success(let response):
return response.toDomain()
case .failure, .networkFailure:
return []
Comment on lines +20 to +38
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:

# Search for FollowRepositoryProtocol definition
rg -n "protocol FollowRepositoryProtocol" -C5 --type swift

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

Length of output: 1183


๐Ÿ Script executed:

# Search for callers of fetchTravelDetail and fetchPlaces
rg -n "fetchTravelDetail|fetchPlaces" -C3 --type swift

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

Length of output: 5172


๐Ÿ Script executed:

# Look at the complete FollowRepository file to understand context
fd "FollowRepository.swift" --type f -x head -60 {}

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

Length of output: 1119


๐Ÿ Script executed:

# Check for other repository patterns in the codebase
rg -n "public func fetch.*async.*->" --type swift -C2 | head -100

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

Length of output: 1029


ํ”„๋กœํ† ์ฝœ ์ •์˜ ์ž์ฒด๊ฐ€ ์˜ค๋ฅ˜ ์ƒํƒœ๋ฅผ ๊ตฌ๋ถ„ํ•˜์ง€ ๋ชปํ•˜๋„๋ก ์„ค๊ณ„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

FollowRepositoryProtocol์ด TravelDetail?๊ณผ [TravelPlace]๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ •์˜๋˜์–ด ์žˆ์–ด์„œ, ๊ตฌํ˜„์ฒด์—์„œ ๋„คํŠธ์›Œํฌ ์‹คํŒจ์™€ ์‹ค์ œ ๋นˆ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ nil/๋นˆ ๋ฐฐ์—ด๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋ฐ–์— ์—†์Šต๋‹ˆ๋‹ค. ํ˜ธ์ถœ๋ถ€(FollowDetailInteractor)์—์„œ๋„ ์ด๋ฅผ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์—†์–ด์„œ, fetchTravelDetail ์‹คํŒจ ์‹œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์—†์ด ์กฐ์šฉํžˆ ๋Œ์•„๊ฐ€๊ณ , fetchPlaces ์‹คํŒจ๋„ ๋นˆ ๋ฐฐ์—ด์ฒ˜๋Ÿผ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

ํ”„๋กœํ† ์ฝœ์„ Result<TravelDetail, Error>๋‚˜ throws๋กœ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜, ๋ณ„๋„์˜ ์˜ค๋ฅ˜ ์ƒํƒœ ์ฝœ๋ฐฑ์„ ์ถ”๊ฐ€ํ•ด์•ผ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜๋ฅผ ์•Œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿค– Prompt for AI Agents
In `@Projects/Data/Sources/Repository/Follow/FollowRepository.swift` around lines
20 - 38, The protocol currently returns TravelDetail? and [TravelPlace], which
conflates "empty data" and error states; update the FollowRepositoryProtocol to
surface failures (either change fetchTravelDetail to return Result<TravelDetail,
Error> or make it async throws, and similarly return Result<[TravelPlace],
Error> or throws for fetchPlaces), then update the implementing methods in
FollowRepository (fetchTravelDetail and fetchPlaces) to propagate the service
errors instead of mapping all failures to nil/empty, and adjust the caller
FollowDetailInteractor to handle the new error-returning signatures and present
network errors to the user.

}
}
}
8 changes: 0 additions & 8 deletions Projects/Data/Sources/Repository/RepoEmpty.swift

This file was deleted.

74 changes: 74 additions & 0 deletions Projects/Data/Sources/Transform/Follow/FollowTransform.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// FollowTransform.swift
// Data
//
// Created by kimnahun on 2026-01-23.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Domain
import Foundation
import Networks

// MARK: - ContentCard Response to TravelDetail

extension FollowContentCardResponse {
func toDomain() -> TravelDetail {
TravelDetail(
travelId: travelId,
country: country,
city: city,
budgetPerPerson: budgetPerPerson,
nights: nights,
days: days,
youtube: youtube.toDomain()
)
}
}

extension YouTubeResponse {
func toDomain() -> YouTubeInfo {
YouTubeInfo(
title: title,
youtuber: name,
thumbnail: thumbnail,
profileImage: profileImage,
link: link,
summary: summary
)
}
}

// MARK: - Itinerary Response to TravelPlace

extension FollowItineraryResponse {
func toDomain() -> [TravelPlace] {
itineraries.map { $0.toDomain() }
}
}

extension FollowPlaceResponse {
func toDomain() -> TravelPlace {
TravelPlace(
id: id,
day: day,
sequence: sequence,
travelerTip: travelerTip ?? "",
estimatedDuration: estimatedDuration,
place: place.toDomain()
)
}
}

extension PlaceResponse {
func toDomain() -> PlaceInfo {
PlaceInfo(
googlePlaceId: googlePlaceId,
thumbnail: thumbnail,
latitude: latitude,
longitude: longitude,
name: name,
regularOpeningHours: regularOpeningHours
)
}
}
8 changes: 0 additions & 8 deletions Projects/Data/Sources/Transform/TransEmpty.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// FollowRepositoryProtocol.swift
// Domain
//
// Created by kimnahun on 2026-01-23.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

public protocol FollowRepositoryProtocol {
/// ์—ฌํ–‰ ์ƒ์„ธ ์ •๋ณด ์กฐํšŒ
func fetchTravelDetail(id: Int) async -> TravelDetail?

/// ์ผ์ฐจ๋ณ„ ์žฅ์†Œ ๋ชฉ๋ก ์กฐํšŒ
func fetchPlaces(travelId: Int, day: Int) async -> [TravelPlace]
}
20 changes: 20 additions & 0 deletions Projects/Domain/Sources/Model/Follow/FollowError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// FollowError.swift
// Domain
//
// Created by kimnahun on 2026-01-23.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

public enum FollowError: Error, Sendable {
/// ์—ฌํ–‰ ํ…œํ”Œ๋ฆฟ์„ ์ฐพ์„ ์ˆ˜ ์—†์Œ
case notFound(message: String)
/// ์„œ๋ฒ„ ์—๋Ÿฌ
case serverError(message: String)
/// ๋„คํŠธ์›Œํฌ ์—๋Ÿฌ
case networkError(message: String)
/// ์•Œ ์ˆ˜ ์—†๋Š” ์—๋Ÿฌ
case unknown(code: String, message: String)
}
2 changes: 1 addition & 1 deletion Projects/Domain/Sources/Model/Travel/PopularTrip.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public struct PopularTrip: Hashable {

public enum TripCategory: String, CaseIterable, Hashable {
case all = "์ „์ฒด"
case vietnam = "๋ฒ ๋‹ˆํŠธ๋‚จ"
case vietnam = "๋ฒ ํŠธ๋‚จ"
case europe = "์œ ๋Ÿฝ"
case hongkong = "ํ™์ฝฉ/๋งˆ์นด์˜ค"
case singapore = "์‹ฑ๊ฐ€ํฌ๋ฅด"
Expand Down
64 changes: 64 additions & 0 deletions Projects/Domain/Sources/Model/Travel/TravelDetail.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// TravelDetail.swift
// Domain
//
// Created by kimnahun on 2026-01-23.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

/// ์—ฌํ–‰ ์ƒ์„ธ ์ •๋ณด
public struct TravelDetail: Hashable {
public let travelId: String
public let country: String
public let city: String
public let budgetPerPerson: Int
public let nights: Int
public let days: Int
public let youtube: YouTubeInfo

public init(
travelId: String,
country: String,
city: String,
budgetPerPerson: Int,
nights: Int,
days: Int,
youtube: YouTubeInfo
) {
self.travelId = travelId
self.country = country
self.city = city
self.budgetPerPerson = budgetPerPerson
self.nights = nights
self.days = days
self.youtube = youtube
}
}

/// ์œ ํŠœ๋ธŒ ์ •๋ณด
public struct YouTubeInfo: Hashable {
public let title: String
public let youtuber: String
public let thumbnail: String?
public let profileImage: String?
public let link: String
public let summary: String

public init(
title: String,
youtuber: String,
thumbnail: String?,
profileImage: String?,
link: String,
summary: String
) {
self.title = title
self.youtuber = youtuber
self.thumbnail = thumbnail
self.profileImage = profileImage
self.link = link
self.summary = summary
}
}
61 changes: 61 additions & 0 deletions Projects/Domain/Sources/Model/Travel/TravelPlace.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// TravelPlace.swift
// Domain
//
// Created by kimnahun on 2026-01-23.
// Copyright ยฉ 2026 NDGL-iOS. All rights reserved.
//

import Foundation

/// ์—ฌํ–‰ ์žฅ์†Œ ์ •๋ณด
public struct TravelPlace: Hashable {
public let id: Int
public let day: Int
public let sequence: Int
public let travelerTip: String
public let estimatedDuration: Int
public let place: PlaceInfo

public init(
id: Int,
day: Int,
sequence: Int,
travelerTip: String,
estimatedDuration: Int,
place: PlaceInfo
) {
self.id = id
self.day = day
self.sequence = sequence
self.travelerTip = travelerTip
self.estimatedDuration = estimatedDuration
self.place = place
}
}

/// ์žฅ์†Œ ์ƒ์„ธ ์ •๋ณด
public struct PlaceInfo: Hashable {
public let googlePlaceId: String
public let thumbnail: String?
public let latitude: Double
public let longitude: Double
public let name: String
public let regularOpeningHours: String?

public init(
googlePlaceId: String,
thumbnail: String? = nil,
latitude: Double,
longitude: Double,
name: String,
regularOpeningHours: String?
) {
self.googlePlaceId = googlePlaceId
self.thumbnail = thumbnail
self.latitude = latitude
self.longitude = longitude
self.name = name
self.regularOpeningHours = regularOpeningHours
}
}
25 changes: 25 additions & 0 deletions Projects/Features/FollowFeature/Project.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// Project.swift
// FollowFeature
//
// Created by kimnahun on 2026-01-23.
//

import ProjectDescription
import ProjectDescriptionHelpers
import DependencyPlugin

let project = Project.makeModule(
name: "FollowFeature",
targets: [
.makeFrameworkTarget(
name: "FollowFeature",
dependencies: [
.Features.baseFeatureDependency
],
scripts: [.swiftLint],
isStatic: true,
hasResources: false
)
]
)
Loading