feat: 포인트 내역 UI 추가
This commit is contained in:
		@@ -157,4 +157,6 @@ enum AppStep {
 | 
				
			|||||||
    case introduceCreatorAll
 | 
					    case introduceCreatorAll
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    case message
 | 
					    case message
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    case pointStatus(refresh: () -> Void)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -242,6 +242,9 @@ struct ContentView: View {
 | 
				
			|||||||
            case .message:
 | 
					            case .message:
 | 
				
			||||||
                MessageView()
 | 
					                MessageView()
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
 | 
					            case .pointStatus(let refresh):
 | 
				
			||||||
 | 
					                PointStatusView(refresh: refresh)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
                EmptyView()
 | 
					                EmptyView()
 | 
				
			||||||
                    .frame(width: 0, height: 0, alignment: .topLeading)
 | 
					                    .frame(width: 0, height: 0, alignment: .topLeading)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,7 @@ struct CanStatusView: View {
 | 
				
			|||||||
                                    .resizable()
 | 
					                                    .resizable()
 | 
				
			||||||
                                    .frame(width: 26.7, height: 26.7)
 | 
					                                    .frame(width: 26.7, height: 26.7)
 | 
				
			||||||
                                
 | 
					                                
 | 
				
			||||||
                                Text("\(viewModel.totalCan) 캔")
 | 
					                                Text("\(viewModel.totalCan)")
 | 
				
			||||||
                                    .font(.custom(Font.bold.rawValue, size: 18.3))
 | 
					                                    .font(.custom(Font.bold.rawValue, size: 18.3))
 | 
				
			||||||
                                    .foregroundColor(Color(hex: "eeeeee"))
 | 
					                                    .foregroundColor(Color(hex: "eeeeee"))
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -124,14 +124,23 @@ struct MyPageView: View {
 | 
				
			|||||||
                                            .frame(width: screenSize().width - 26.7)
 | 
					                                            .frame(width: screenSize().width - 26.7)
 | 
				
			||||||
                                            .padding(.top, 26.7)
 | 
					                                            .padding(.top, 26.7)
 | 
				
			||||||
                                            
 | 
					                                            
 | 
				
			||||||
                                            HStack(spacing: 6.7) {
 | 
					                                            HStack {
 | 
				
			||||||
                                                Text("\(data.point)")
 | 
					                                                HStack(spacing: 6.7) {
 | 
				
			||||||
                                                    .font(.custom(Font.bold.rawValue, size: 18.3))
 | 
					                                                    Text("\(data.point)")
 | 
				
			||||||
                                                    .foregroundColor(.grayee)
 | 
					                                                        .font(.custom(Font.bold.rawValue, size: 18.3))
 | 
				
			||||||
                                                
 | 
					                                                        .foregroundColor(.grayee)
 | 
				
			||||||
                                                Image("ic_point")
 | 
					                                                    
 | 
				
			||||||
                                                    .resizable()
 | 
					                                                    Image("ic_point")
 | 
				
			||||||
                                                    .frame(width: 20, height: 20)
 | 
					                                                        .resizable()
 | 
				
			||||||
 | 
					                                                        .frame(width: 20, height: 20)
 | 
				
			||||||
 | 
					                                                    
 | 
				
			||||||
 | 
					                                                    Image("ic_forward")
 | 
				
			||||||
 | 
					                                                        .resizable()
 | 
				
			||||||
 | 
					                                                        .frame(width: 20, height: 20)
 | 
				
			||||||
 | 
					                                                }
 | 
				
			||||||
 | 
					                                                .onTapGesture {
 | 
				
			||||||
 | 
					                                                    AppState.shared.setAppStep(step: .pointStatus(refresh: { viewModel.getMypage() }))
 | 
				
			||||||
 | 
					                                                }
 | 
				
			||||||
                                                
 | 
					                                                
 | 
				
			||||||
                                                Spacer()
 | 
					                                                Spacer()
 | 
				
			||||||
                                            }
 | 
					                                            }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								SodaLive/Sources/MyPage/Point/GetPointStatusResponse.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								SodaLive/Sources/MyPage/Point/GetPointStatusResponse.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  GetPointStatusResponse.swift
 | 
				
			||||||
 | 
					//  SodaLive
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by klaus on 5/20/25.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct GetPointStatusResponse: Decodable {
 | 
				
			||||||
 | 
					    let point: Int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										55
									
								
								SodaLive/Sources/MyPage/Point/PointStatusApi.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								SodaLive/Sources/MyPage/Point/PointStatusApi.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  PointStatusApi.swift
 | 
				
			||||||
 | 
					//  SodaLive
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by klaus on 5/20/25.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Foundation
 | 
				
			||||||
 | 
					import Moya
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum PointStatusApi {
 | 
				
			||||||
 | 
					    case getPointStatus
 | 
				
			||||||
 | 
					    case getPointRewardStatus
 | 
				
			||||||
 | 
					    case getPointUseStatus
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extension PointStatusApi: TargetType {
 | 
				
			||||||
 | 
					    var baseURL: URL {
 | 
				
			||||||
 | 
					        return URL(string: BASE_URL)!
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    var path: String {
 | 
				
			||||||
 | 
					        switch self {
 | 
				
			||||||
 | 
					        case .getPointStatus:
 | 
				
			||||||
 | 
					            return "/point/status"
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        case .getPointRewardStatus:
 | 
				
			||||||
 | 
					            return "/point/status/reward"
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        case .getPointUseStatus:
 | 
				
			||||||
 | 
					            return "/point/status/use"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    var method: Moya.Method {
 | 
				
			||||||
 | 
					        return .get
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    var task: Moya.Task {
 | 
				
			||||||
 | 
					        switch self {
 | 
				
			||||||
 | 
					        case .getPointStatus:
 | 
				
			||||||
 | 
					            return .requestPlain
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        case .getPointRewardStatus, .getPointUseStatus:
 | 
				
			||||||
 | 
					            return .requestParameters(
 | 
				
			||||||
 | 
					                parameters: ["timezone": TimeZone.current.identifier],
 | 
				
			||||||
 | 
					                encoding: URLEncoding.queryString
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    var headers: [String : String]? {
 | 
				
			||||||
 | 
					        return ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								SodaLive/Sources/MyPage/Point/PointStatusRepository.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								SodaLive/Sources/MyPage/Point/PointStatusRepository.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  PointStatusRepository.swift
 | 
				
			||||||
 | 
					//  SodaLive
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by klaus on 5/20/25.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Foundation
 | 
				
			||||||
 | 
					import CombineMoya
 | 
				
			||||||
 | 
					import Combine
 | 
				
			||||||
 | 
					import Moya
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PointStatusRepository {
 | 
				
			||||||
 | 
					    private let api = MoyaProvider<PointStatusApi>()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    func getPointStatus() -> AnyPublisher<Response, MoyaError> {
 | 
				
			||||||
 | 
					        return api.requestPublisher(.getPointStatus)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    func getPointRewardStatus() -> AnyPublisher<Response, MoyaError> {
 | 
				
			||||||
 | 
					        return api.requestPublisher(.getPointRewardStatus)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    func getPointUseStatus() -> AnyPublisher<Response, MoyaError> {
 | 
				
			||||||
 | 
					        return api.requestPublisher(.getPointUseStatus)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										135
									
								
								SodaLive/Sources/MyPage/Point/PointStatusView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								SodaLive/Sources/MyPage/Point/PointStatusView.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  PointStatusView.swift
 | 
				
			||||||
 | 
					//  SodaLive
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by klaus on 5/20/25.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PointStatusView: View {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    let refresh: () -> Void
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @StateObject var viewModel = PointStatusViewModel()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        BaseView(isLoading: $viewModel.isLoading) {
 | 
				
			||||||
 | 
					            GeometryReader { proxy in
 | 
				
			||||||
 | 
					                VStack(spacing: 0) {
 | 
				
			||||||
 | 
					                    DetailNavigationBar(title: "포인트 내역") {
 | 
				
			||||||
 | 
					                        AppState.shared.setAppStep(step: .main)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    ScrollView(.vertical, showsIndicators: false) {
 | 
				
			||||||
 | 
					                        VStack(spacing: 26.7) {
 | 
				
			||||||
 | 
					                            HStack(spacing: 6.7) {
 | 
				
			||||||
 | 
					                                Image("ic_point")
 | 
				
			||||||
 | 
					                                    .resizable()
 | 
				
			||||||
 | 
					                                    .frame(width: 26.7, height: 26.7)
 | 
				
			||||||
 | 
					                                
 | 
				
			||||||
 | 
					                                Text("\(viewModel.totalCan)")
 | 
				
			||||||
 | 
					                                    .font(.custom(Font.bold.rawValue, size: 18.3))
 | 
				
			||||||
 | 
					                                    .foregroundColor(.grayee)
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        .padding(.vertical, 13.3)
 | 
				
			||||||
 | 
					                        .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					                        .padding(.horizontal, 13.3)
 | 
				
			||||||
 | 
					                        .background(Color.gray22)
 | 
				
			||||||
 | 
					                        .cornerRadius(16.7)
 | 
				
			||||||
 | 
					                        .padding(.top, 13.3)
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        HStack(spacing: 0) {
 | 
				
			||||||
 | 
					                            VStack(spacing: 0) {
 | 
				
			||||||
 | 
					                                Spacer()
 | 
				
			||||||
 | 
					                                Text("받은내역")
 | 
				
			||||||
 | 
					                                    .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
				
			||||||
 | 
					                                    .foregroundColor(viewModel.currentTab == .reward ? .grayee : .gray77)
 | 
				
			||||||
 | 
					                                Spacer()
 | 
				
			||||||
 | 
					                                Rectangle()
 | 
				
			||||||
 | 
					                                    .frame(height: 1)
 | 
				
			||||||
 | 
					                                    .foregroundColor(
 | 
				
			||||||
 | 
					                                        .button
 | 
				
			||||||
 | 
					                                            .opacity(viewModel.currentTab == .reward ? 1 : 0)
 | 
				
			||||||
 | 
					                                    )
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					                            .frame(height: 50)
 | 
				
			||||||
 | 
					                            .onTapGesture {
 | 
				
			||||||
 | 
					                                if viewModel.currentTab != .reward {
 | 
				
			||||||
 | 
					                                    viewModel.currentTab = .reward
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            
 | 
				
			||||||
 | 
					                            VStack(spacing: 0) {
 | 
				
			||||||
 | 
					                                Spacer()
 | 
				
			||||||
 | 
					                                Text("사용내역")
 | 
				
			||||||
 | 
					                                    .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
				
			||||||
 | 
					                                    .foregroundColor(viewModel.currentTab == .use ? .grayee : .gray77)
 | 
				
			||||||
 | 
					                                Spacer()
 | 
				
			||||||
 | 
					                                Rectangle()
 | 
				
			||||||
 | 
					                                    .frame(height: 1)
 | 
				
			||||||
 | 
					                                    .foregroundColor(
 | 
				
			||||||
 | 
					                                        .button
 | 
				
			||||||
 | 
					                                            .opacity(viewModel.currentTab == .use ? 1 : 0)
 | 
				
			||||||
 | 
					                                    )
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					                            .frame(height: 50)
 | 
				
			||||||
 | 
					                            .onTapGesture {
 | 
				
			||||||
 | 
					                                if viewModel.currentTab != .use {
 | 
				
			||||||
 | 
					                                    viewModel.currentTab = .use
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        .padding(.top, 13.3)
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        switch viewModel.currentTab {
 | 
				
			||||||
 | 
					                        case .reward:
 | 
				
			||||||
 | 
					                            PointRewardStatusView()
 | 
				
			||||||
 | 
					                            
 | 
				
			||||||
 | 
					                        case .use:
 | 
				
			||||||
 | 
					                            PointUseStatusView()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    Spacer()
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if proxy.safeAreaInsets.bottom > 0 {
 | 
				
			||||||
 | 
					                        Rectangle()
 | 
				
			||||||
 | 
					                            .foregroundColor(.black)
 | 
				
			||||||
 | 
					                            .frame(width: proxy.size.width, height: 15.3)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .edgesIgnoringSafeArea(.bottom)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
 | 
				
			||||||
 | 
					            GeometryReader { geo in
 | 
				
			||||||
 | 
					                HStack {
 | 
				
			||||||
 | 
					                    Spacer()
 | 
				
			||||||
 | 
					                    Text(viewModel.errorMessage)
 | 
				
			||||||
 | 
					                        .padding(.vertical, 13.3)
 | 
				
			||||||
 | 
					                        .padding(.horizontal, 6.7)
 | 
				
			||||||
 | 
					                        .frame(width: geo.size.width - 66.7, alignment: .center)
 | 
				
			||||||
 | 
					                        .font(.custom(Font.medium.rawValue, size: 12))
 | 
				
			||||||
 | 
					                        .background(Color.button)
 | 
				
			||||||
 | 
					                        .foregroundColor(Color.white)
 | 
				
			||||||
 | 
					                        .multilineTextAlignment(.leading)
 | 
				
			||||||
 | 
					                        .fixedSize(horizontal: false, vertical: true)
 | 
				
			||||||
 | 
					                        .cornerRadius(20)
 | 
				
			||||||
 | 
					                        .padding(.top, 66.7)
 | 
				
			||||||
 | 
					                    Spacer()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .onAppear {
 | 
				
			||||||
 | 
					            viewModel.getPointStatus()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#Preview {
 | 
				
			||||||
 | 
					    PointStatusView {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										143
									
								
								SodaLive/Sources/MyPage/Point/PointStatusViewModel.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								SodaLive/Sources/MyPage/Point/PointStatusViewModel.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  PointStatusViewModel.swift
 | 
				
			||||||
 | 
					//  SodaLive
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by klaus on 5/20/25.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Foundation
 | 
				
			||||||
 | 
					import Combine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final class PointStatusViewModel: ObservableObject {
 | 
				
			||||||
 | 
					    private let repository = PointStatusRepository()
 | 
				
			||||||
 | 
					    private var subscription = Set<AnyCancellable>()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @Published var currentTab: CurrentTab = .reward
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @Published var errorMessage = ""
 | 
				
			||||||
 | 
					    @Published var isShowPopup = false
 | 
				
			||||||
 | 
					    @Published var isLoading = false
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @Published var totalCan: Int = 0
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @Published var useStatusItems: [GetPointUseStatusResponse] = []
 | 
				
			||||||
 | 
					    @Published var rewardStatusItems: [GetPointRewardStatusResponse] = []
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    func getPointStatus() {
 | 
				
			||||||
 | 
					        isLoading = true
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        repository.getPointStatus()
 | 
				
			||||||
 | 
					            .sink { result in
 | 
				
			||||||
 | 
					                switch result {
 | 
				
			||||||
 | 
					                case .finished:
 | 
				
			||||||
 | 
					                    DEBUG_LOG("finish")
 | 
				
			||||||
 | 
					                case .failure(let error):
 | 
				
			||||||
 | 
					                    ERROR_LOG(error.localizedDescription)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } receiveValue: { [unowned self] response in
 | 
				
			||||||
 | 
					                self.isLoading = false
 | 
				
			||||||
 | 
					                let responseData = response.data
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                do {
 | 
				
			||||||
 | 
					                    let jsonDecoder = JSONDecoder()
 | 
				
			||||||
 | 
					                    let decoded = try jsonDecoder.decode(ApiResponse<GetPointStatusResponse>.self, from: responseData)
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if let data = decoded.data, decoded.success {
 | 
				
			||||||
 | 
					                        self.totalCan = data.point
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        if let message = decoded.message {
 | 
				
			||||||
 | 
					                            self.errorMessage = message
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        self.isShowPopup = true
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } catch {
 | 
				
			||||||
 | 
					                    self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
				
			||||||
 | 
					                    self.isShowPopup = true
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .store(in: &subscription)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    func getPointRewardStatus() {
 | 
				
			||||||
 | 
					        isLoading = true
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        repository.getPointRewardStatus()
 | 
				
			||||||
 | 
					            .sink { result in
 | 
				
			||||||
 | 
					                switch result {
 | 
				
			||||||
 | 
					                case .finished:
 | 
				
			||||||
 | 
					                    DEBUG_LOG("finish")
 | 
				
			||||||
 | 
					                case .failure(let error):
 | 
				
			||||||
 | 
					                    ERROR_LOG(error.localizedDescription)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } receiveValue: { [unowned self] response in
 | 
				
			||||||
 | 
					                self.isLoading = false
 | 
				
			||||||
 | 
					                let responseData = response.data
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                do {
 | 
				
			||||||
 | 
					                    let jsonDecoder = JSONDecoder()
 | 
				
			||||||
 | 
					                    let decoded = try jsonDecoder.decode(ApiResponse<[GetPointRewardStatusResponse]>.self, from: responseData)
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if let data = decoded.data, decoded.success {
 | 
				
			||||||
 | 
					                        self.rewardStatusItems.append(contentsOf: data)
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        if let message = decoded.message {
 | 
				
			||||||
 | 
					                            self.errorMessage = message
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        self.isShowPopup = true
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } catch {
 | 
				
			||||||
 | 
					                    self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
				
			||||||
 | 
					                    self.isShowPopup = true
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .store(in: &subscription)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    func getPointUseStatus() {
 | 
				
			||||||
 | 
					        isLoading = true
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        repository.getPointUseStatus()
 | 
				
			||||||
 | 
					            .sink { result in
 | 
				
			||||||
 | 
					                switch result {
 | 
				
			||||||
 | 
					                case .finished:
 | 
				
			||||||
 | 
					                    DEBUG_LOG("finish")
 | 
				
			||||||
 | 
					                case .failure(let error):
 | 
				
			||||||
 | 
					                    ERROR_LOG(error.localizedDescription)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } receiveValue: { [unowned self] response in
 | 
				
			||||||
 | 
					                self.isLoading = false
 | 
				
			||||||
 | 
					                let responseData = response.data
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                do {
 | 
				
			||||||
 | 
					                    let jsonDecoder = JSONDecoder()
 | 
				
			||||||
 | 
					                    let decoded = try jsonDecoder.decode(ApiResponse<[GetPointUseStatusResponse]>.self, from: responseData)
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if let data = decoded.data, decoded.success {
 | 
				
			||||||
 | 
					                        self.useStatusItems.append(contentsOf: data)
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        if let message = decoded.message {
 | 
				
			||||||
 | 
					                            self.errorMessage = message
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        self.isShowPopup = true
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } catch {
 | 
				
			||||||
 | 
					                    self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
				
			||||||
 | 
					                    self.isShowPopup = true
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .store(in: &subscription)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    enum CurrentTab: String {
 | 
				
			||||||
 | 
					        case reward, use
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  GetPointRewardStatusResponse.swift
 | 
				
			||||||
 | 
					//  SodaLive
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by klaus on 5/20/25.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct GetPointRewardStatusResponse: Decodable, Hashable {
 | 
				
			||||||
 | 
					    let rewardPoint: String
 | 
				
			||||||
 | 
					    let date: String
 | 
				
			||||||
 | 
					    let method: String
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,66 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  PointRewardStatusView.swift
 | 
				
			||||||
 | 
					//  SodaLive
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by klaus on 5/20/25.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PointRewardStatusView: View {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @StateObject var viewModel = PointStatusViewModel()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        ZStack {
 | 
				
			||||||
 | 
					            VStack(spacing: 13.3) {
 | 
				
			||||||
 | 
					                ForEach(viewModel.rewardStatusItems, id: \.self) { item in
 | 
				
			||||||
 | 
					                    PointRewardStatusItemView(item: item)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .padding(.top, 13.3)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if viewModel.isLoading {
 | 
				
			||||||
 | 
					                LoadingView()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .onAppear {
 | 
				
			||||||
 | 
					            viewModel.getPointRewardStatus()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PointRewardStatusItemView: View {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    let item: GetPointRewardStatusResponse
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        HStack(spacing: 0) {
 | 
				
			||||||
 | 
					            VStack(alignment: .leading, spacing: 6.7) {
 | 
				
			||||||
 | 
					                Text(item.rewardPoint)
 | 
				
			||||||
 | 
					                    .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
				
			||||||
 | 
					                    .foregroundColor(.grayee)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                Text(item.date)
 | 
				
			||||||
 | 
					                    .font(.custom(Font.medium.rawValue, size: 12))
 | 
				
			||||||
 | 
					                    .foregroundColor(.gray77)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Spacer()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Text(item.method)
 | 
				
			||||||
 | 
					                .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
				
			||||||
 | 
					                .foregroundColor(.grayee)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .padding(.horizontal, 13.3)
 | 
				
			||||||
 | 
					        .padding(.vertical, 16)
 | 
				
			||||||
 | 
					        .background(Color.gray11)
 | 
				
			||||||
 | 
					        .cornerRadius(16.7)
 | 
				
			||||||
 | 
					        .padding(.horizontal, 13.3)
 | 
				
			||||||
 | 
					        .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#Preview {
 | 
				
			||||||
 | 
					    PointRewardStatusView()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  GetPointUseStatusResponse.swift
 | 
				
			||||||
 | 
					//  SodaLive
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by klaus on 5/20/25.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct GetPointUseStatusResponse: Decodable, Hashable {
 | 
				
			||||||
 | 
					    let title: String
 | 
				
			||||||
 | 
					    let date: String
 | 
				
			||||||
 | 
					    let point: Int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										71
									
								
								SodaLive/Sources/MyPage/Point/Use/PointUseStatusView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								SodaLive/Sources/MyPage/Point/Use/PointUseStatusView.swift
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					//  PointUseStatusView.swift
 | 
				
			||||||
 | 
					//  SodaLive
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//  Created by klaus on 5/20/25.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PointUseStatusView: View {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    @StateObject var viewModel = PointStatusViewModel()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        ZStack {
 | 
				
			||||||
 | 
					            VStack(spacing: 13.3) {
 | 
				
			||||||
 | 
					                ForEach(viewModel.useStatusItems, id: \.self) { item in
 | 
				
			||||||
 | 
					                    PointUseStatusItemView(item: item)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .padding(.top, 13.3)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if viewModel.isLoading {
 | 
				
			||||||
 | 
					                LoadingView()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .onAppear {
 | 
				
			||||||
 | 
					            viewModel.getPointUseStatus()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PointUseStatusItemView: View {
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    let item: GetPointUseStatusResponse
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        HStack(spacing: 0) {
 | 
				
			||||||
 | 
					            VStack(alignment: .leading, spacing: 6.7) {
 | 
				
			||||||
 | 
					                Text(item.title)
 | 
				
			||||||
 | 
					                    .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
				
			||||||
 | 
					                    .foregroundColor(.grayee)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                Text(item.date)
 | 
				
			||||||
 | 
					                    .font(.custom(Font.medium.rawValue, size: 12))
 | 
				
			||||||
 | 
					                    .foregroundColor(.gray77)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Spacer()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Text("\(item.point)")
 | 
				
			||||||
 | 
					                .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
				
			||||||
 | 
					                .foregroundColor(.grayee)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            Image("ic_point")
 | 
				
			||||||
 | 
					                .resizable()
 | 
				
			||||||
 | 
					                .frame(width: 26.7, height: 26.7)
 | 
				
			||||||
 | 
					                .padding(.leading, 6.7)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .padding(.horizontal, 13.3)
 | 
				
			||||||
 | 
					        .padding(.vertical, 16)
 | 
				
			||||||
 | 
					        .background(Color.gray11)
 | 
				
			||||||
 | 
					        .cornerRadius(16.7)
 | 
				
			||||||
 | 
					        .padding(.horizontal, 13.3)
 | 
				
			||||||
 | 
					        .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#Preview {
 | 
				
			||||||
 | 
					    PointUseStatusView()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user