쿠폰등록 페이지 추가
This commit is contained in:
		
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_coupon.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_coupon.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | { | ||||||
|  |   "images" : [ | ||||||
|  |     { | ||||||
|  |       "idiom" : "universal", | ||||||
|  |       "scale" : "1x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "idiom" : "universal", | ||||||
|  |       "scale" : "2x" | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "filename" : "ic_coupon.png", | ||||||
|  |       "idiom" : "universal", | ||||||
|  |       "scale" : "3x" | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "info" : { | ||||||
|  |     "author" : "xcode", | ||||||
|  |     "version" : 1 | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_coupon.imageset/ic_coupon.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_coupon.imageset/ic_coupon.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 873 B | 
| @@ -117,4 +117,6 @@ enum AppStep { | |||||||
|     case creatorCommunityWrite(onSuccess: () -> Void) |     case creatorCommunityWrite(onSuccess: () -> Void) | ||||||
|      |      | ||||||
|     case creatorCommunityModify(postId: Int, onSuccess: () -> Void) |     case creatorCommunityModify(postId: Int, onSuccess: () -> Void) | ||||||
|  |      | ||||||
|  |     case canCoupon | ||||||
| } | } | ||||||
|   | |||||||
| @@ -172,6 +172,9 @@ struct ContentView: View { | |||||||
|             case .creatorCommunityModify(let postId, let onSuccess): |             case .creatorCommunityModify(let postId, let onSuccess): | ||||||
|                 CreatorCommunityModifyView(postId: postId, onSuccess: onSuccess) |                 CreatorCommunityModifyView(postId: postId, onSuccess: onSuccess) | ||||||
|                  |                  | ||||||
|  |             case .canCoupon: | ||||||
|  |                 CanCouponView() | ||||||
|  |                  | ||||||
|             default: |             default: | ||||||
|                 EmptyView() |                 EmptyView() | ||||||
|                     .frame(width: 0, height: 0, alignment: .topLeading) |                     .frame(width: 0, height: 0, alignment: .topLeading) | ||||||
|   | |||||||
| @@ -19,6 +19,8 @@ enum CanApi { | |||||||
|      |      | ||||||
|     case pgChargeCan(request: PgChargeRequest) |     case pgChargeCan(request: PgChargeRequest) | ||||||
|     case pgVerify(request: PgVerifyRequest) |     case pgVerify(request: PgVerifyRequest) | ||||||
|  |      | ||||||
|  |     case useCanCoupon(request: UseCanCouponRequest) | ||||||
| } | } | ||||||
|  |  | ||||||
| extension CanApi: TargetType { | extension CanApi: TargetType { | ||||||
| @@ -51,6 +53,9 @@ extension CanApi: TargetType { | |||||||
|              |              | ||||||
|         case .pgVerify: |         case .pgVerify: | ||||||
|             return "/charge/verify" |             return "/charge/verify" | ||||||
|  |              | ||||||
|  |         case .useCanCoupon: | ||||||
|  |             return "/can/coupon/use" | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |      | ||||||
| @@ -59,7 +64,7 @@ extension CanApi: TargetType { | |||||||
|         case .getCanStatus, .getCanChargeStatus, .getCanUseStatus, .getCans: |         case .getCanStatus, .getCanChargeStatus, .getCanUseStatus, .getCans: | ||||||
|             return .get |             return .get | ||||||
|              |              | ||||||
|         case .chargeCan, .verify, .pgChargeCan, .pgVerify: |         case .chargeCan, .verify, .pgChargeCan, .pgVerify, .useCanCoupon: | ||||||
|             return .post |             return .post | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -97,6 +102,9 @@ extension CanApi: TargetType { | |||||||
|              |              | ||||||
|         case .pgVerify(let request): |         case .pgVerify(let request): | ||||||
|             return .requestJSONEncodable(request) |             return .requestJSONEncodable(request) | ||||||
|  |              | ||||||
|  |         case .useCanCoupon(let request): | ||||||
|  |             return .requestJSONEncodable(request) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|   | |||||||
| @@ -44,5 +44,10 @@ final class CanRepository { | |||||||
|     func getCans() -> AnyPublisher<Response, MoyaError> { |     func getCans() -> AnyPublisher<Response, MoyaError> { | ||||||
|         return api.requestPublisher(.getCans) |         return api.requestPublisher(.getCans) | ||||||
|     } |     } | ||||||
|  |      | ||||||
|  |     func useCanCoupon(couponNumber: String) -> AnyPublisher<Response, MoyaError> { | ||||||
|  |         let request = UseCanCouponRequest(couponNumber: couponNumber) | ||||||
|  |         return api.requestPublisher(.useCanCoupon(request: request)) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,34 @@ | |||||||
|  | // | ||||||
|  | //  CanChargeCouponButtonView.swift | ||||||
|  | //  SodaLive | ||||||
|  | // | ||||||
|  | //  Created by klaus on 2024/01/03. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | import SwiftUI | ||||||
|  |  | ||||||
|  | struct CanChargeCouponButtonView: View { | ||||||
|  |     var body: some View { | ||||||
|  |         HStack(spacing: 5.3) { | ||||||
|  |             Text("쿠폰 등록") | ||||||
|  |                 .font(.custom(Font.bold.rawValue, size: 16)) | ||||||
|  |                 .foregroundColor(Color(hex: "eeeeee")) | ||||||
|  |              | ||||||
|  |             Image("ic_coupon") | ||||||
|  |              | ||||||
|  |             Spacer() | ||||||
|  |              | ||||||
|  |             Image("ic_forward") | ||||||
|  |         } | ||||||
|  |         .padding(.horizontal, 13.3) | ||||||
|  |         .padding(.vertical, 20) | ||||||
|  |         .background(Color(hex: "13181b")) | ||||||
|  |         .cornerRadius(6.7) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | struct CanChargeCouponButtonView_Previews: PreviewProvider { | ||||||
|  |     static var previews: some View { | ||||||
|  |         CanChargeCouponButtonView() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,34 @@ | |||||||
|  | // | ||||||
|  | //  CanCouponNoticeItemView.swift | ||||||
|  | //  SodaLive | ||||||
|  | // | ||||||
|  | //  Created by klaus on 2024/01/03. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | import SwiftUI | ||||||
|  |  | ||||||
|  | struct CanCouponNoticeItemView: View { | ||||||
|  |      | ||||||
|  |     let notice: String | ||||||
|  |      | ||||||
|  |     var body: some View { | ||||||
|  |         HStack(alignment: .top, spacing: 4) { | ||||||
|  |             Text("-") | ||||||
|  |                 .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||||
|  |                 .foregroundColor(Color(hex: "eeeeee")) | ||||||
|  |              | ||||||
|  |             Text(notice) | ||||||
|  |                 .multilineTextAlignment(.leading) | ||||||
|  |                 .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||||
|  |                 .foregroundColor(Color(hex: "eeeeee")) | ||||||
|  |                 .fixedSize(horizontal: false, vertical: true) | ||||||
|  |         } | ||||||
|  |         .padding(.horizontal, 10) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | struct CanCouponNoticeItemView_Previews: PreviewProvider { | ||||||
|  |     static var previews: some View { | ||||||
|  |         CanCouponNoticeItemView(notice: "본인인증한 계정 1회에 한해 이벤트 쿠폰등록이 적용 됩니다.") | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										132
									
								
								SodaLive/Sources/MyPage/Can/Coupon/CanCouponView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								SodaLive/Sources/MyPage/Can/Coupon/CanCouponView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | |||||||
|  | // | ||||||
|  | //  CanCouponView.swift | ||||||
|  | //  SodaLive | ||||||
|  | // | ||||||
|  | //  Created by klaus on 2024/01/03. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | import SwiftUI | ||||||
|  |  | ||||||
|  | struct CanCouponView: View { | ||||||
|  |      | ||||||
|  |     @StateObject var viewModel = CanCouponViewModel() | ||||||
|  |      | ||||||
|  |     var body: some View { | ||||||
|  |         BaseView(isLoading: $viewModel.isLoading) { | ||||||
|  |             GeometryReader { proxy in | ||||||
|  |                 VStack(spacing: 0) { | ||||||
|  |                     DetailNavigationBar(title: "쿠폰등록") | ||||||
|  |                      | ||||||
|  |                     ScrollView(.vertical, showsIndicators: false) { | ||||||
|  |                         VStack(spacing: 0) { | ||||||
|  |                             Text("쿠폰번호 입력") | ||||||
|  |                                 .font(.custom(Font.bold.rawValue, size: 16.7)) | ||||||
|  |                                 .foregroundColor(Color(hex: "eeeeee")) | ||||||
|  |                                 .frame(maxWidth: .infinity, alignment: .leading) | ||||||
|  |                              | ||||||
|  |                             TextField("쿠폰번호를 입력하세요", text: $viewModel.couponNumber) | ||||||
|  |                                 .autocapitalization(.allCharacters) // Force uppercase keyboard | ||||||
|  |                                 .textContentType(.none) | ||||||
|  |                                 .disableAutocorrection(true) | ||||||
|  |                                 .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||||
|  |                                 .foregroundColor(Color(hex: "eeeeee")) | ||||||
|  |                                 .padding(.vertical, 16.7) | ||||||
|  |                                 .padding(.horizontal, 13.3) | ||||||
|  |                                 .background(Color(hex: "222222")) | ||||||
|  |                                 .cornerRadius(6.7) | ||||||
|  |                                 .keyboardType(.default) | ||||||
|  |                                 .padding(.top, 13.3) | ||||||
|  |                                 .onChange(of: viewModel.couponNumber) { newString in | ||||||
|  |                                     let filtered = newString.filter { $0.isUppercase || $0.isNumber } | ||||||
|  |                                     if filtered != newString { | ||||||
|  |                                         viewModel.couponNumber = filtered | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |                              | ||||||
|  |                             Text("등록하기") | ||||||
|  |                                 .font(.custom(Font.bold.rawValue, size: 18.3)) | ||||||
|  |                                 .foregroundColor(.white) | ||||||
|  |                                 .padding(.vertical, 16) | ||||||
|  |                                 .frame(maxWidth: .infinity) | ||||||
|  |                                 .background(Color(hex: "3bb9f1")) | ||||||
|  |                                 .cornerRadius(10) | ||||||
|  |                                 .padding(.top, 21.3) | ||||||
|  |                                 .onTapGesture { | ||||||
|  |                                     viewModel.useCoupon() | ||||||
|  |                                 } | ||||||
|  |                              | ||||||
|  |                             VStack(alignment: .leading, spacing: 0) { | ||||||
|  |                                 Text("등록안내") | ||||||
|  |                                     .font(.custom(Font.bold.rawValue, size: 13.3)) | ||||||
|  |                                     .foregroundColor(Color(hex: "eeeeee")) | ||||||
|  |                                  | ||||||
|  |                                 CanCouponNoticeItemView(notice: "공백없이 쿠폰번호 16자리를 입력해주세요.") | ||||||
|  |                                     .padding(.top, 8) | ||||||
|  |                                  | ||||||
|  |                                 CanCouponNoticeItemView(notice: "입력한 쿠폰은 소멸시까지 취소처리가 되지 않습니다.") | ||||||
|  |                                     .padding(.top, 4) | ||||||
|  |                                  | ||||||
|  |                                 CanCouponNoticeItemView(notice: "충전된 캔은 캔 충전현황에서 확인할 수 있습니다.") | ||||||
|  |                                     .padding(.top, 4) | ||||||
|  |                                  | ||||||
|  |                                 Rectangle() | ||||||
|  |                                   .foregroundColor(.clear) | ||||||
|  |                                   .frame(maxWidth: .infinity, minHeight: 1, maxHeight: 1) | ||||||
|  |                                   .background(Color(hex: "555555")) | ||||||
|  |                                   .padding(.vertical, 26.7) | ||||||
|  |                                  | ||||||
|  |                                 Text("주의사항") | ||||||
|  |                                     .font(.custom(Font.bold.rawValue, size: 13.3)) | ||||||
|  |                                     .foregroundColor(Color(hex: "eeeeee")) | ||||||
|  |                                  | ||||||
|  |                                 CanCouponNoticeItemView(notice: "이벤트 쿠폰을 통해 충전한 캔은 환불되지 않습니다.") | ||||||
|  |                                     .padding(.top, 8) | ||||||
|  |                                  | ||||||
|  |                                 CanCouponNoticeItemView(notice: "쿠폰은 상업적 용도로 사용하거나 매매할 수 없습니다.") | ||||||
|  |                                     .padding(.top, 4) | ||||||
|  |                                  | ||||||
|  |                                 CanCouponNoticeItemView(notice: "한번 등록한 쿠폰은 재사용이 불가합니다.") | ||||||
|  |                                     .padding(.top, 4) | ||||||
|  |                                  | ||||||
|  |                                 CanCouponNoticeItemView(notice: "연령 제한 정책에 따라 쿠폰이용은 본인인증한 회원만 이용 가능합니다.") | ||||||
|  |                                     .padding(.top, 4) | ||||||
|  |                             } | ||||||
|  |                             .padding(.horizontal, 13.3) | ||||||
|  |                             .padding(.vertical, 26.7) | ||||||
|  |                             .frame(maxWidth: .infinity, alignment: .leading) | ||||||
|  |                             .background(Color(hex: "13181b")) | ||||||
|  |                             .cornerRadius(6.7) | ||||||
|  |                             .padding(.top, 21.3) | ||||||
|  |                         } | ||||||
|  |                         .padding(13.3) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 .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(hex: "9970ff")) | ||||||
|  |                                 .foregroundColor(Color.white) | ||||||
|  |                                 .multilineTextAlignment(.leading) | ||||||
|  |                                 .fixedSize(horizontal: false, vertical: true) | ||||||
|  |                                 .cornerRadius(20) | ||||||
|  |                                 .padding(.top, 66.7) | ||||||
|  |                             Spacer() | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | struct CanCouponView_Previews: PreviewProvider { | ||||||
|  |     static var previews: some View { | ||||||
|  |         CanCouponView() | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										65
									
								
								SodaLive/Sources/MyPage/Can/Coupon/CanCouponViewModel.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								SodaLive/Sources/MyPage/Can/Coupon/CanCouponViewModel.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | // | ||||||
|  | //  CanCouponViewModel.swift | ||||||
|  | //  SodaLive | ||||||
|  | // | ||||||
|  | //  Created by klaus on 2024/01/03. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | import Foundation | ||||||
|  | import Combine | ||||||
|  |  | ||||||
|  | final class CanCouponViewModel: ObservableObject { | ||||||
|  |      | ||||||
|  |     private let repository = CanRepository() | ||||||
|  |     private var subscription = Set<AnyCancellable>() | ||||||
|  |      | ||||||
|  |     @Published var errorMessage = "" | ||||||
|  |     @Published var isShowPopup = false | ||||||
|  |     @Published var isLoading = false | ||||||
|  |      | ||||||
|  |     @Published var couponNumber = "" | ||||||
|  |      | ||||||
|  |     func useCoupon() { | ||||||
|  |         if !isLoading { | ||||||
|  |             isLoading = true | ||||||
|  |             repository.useCanCoupon(couponNumber: couponNumber) | ||||||
|  |                 .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(ApiResponseWithoutData.self, from: responseData) | ||||||
|  |                          | ||||||
|  |                         if decoded.success { | ||||||
|  |                             self.errorMessage = "해당 쿠폰의 캔이 충전되었습니다." | ||||||
|  |                              | ||||||
|  |                             DispatchQueue.main.asyncAfter(deadline: .now() + 1) { | ||||||
|  |                                 AppState.shared.setAppStep(step: .canStatus(refresh: {})) | ||||||
|  |                             } | ||||||
|  |                         } 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) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								SodaLive/Sources/MyPage/Can/Coupon/UseCanCouponRequest.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								SodaLive/Sources/MyPage/Can/Coupon/UseCanCouponRequest.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | // | ||||||
|  | //  UseCanCouponRequest.swift | ||||||
|  | //  SodaLive | ||||||
|  | // | ||||||
|  | //  Created by klaus on 2024/01/03. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | struct UseCanCouponRequest: Encodable { | ||||||
|  |     let couponNumber: String | ||||||
|  | } | ||||||
| @@ -52,9 +52,9 @@ struct CanCardView: View { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         .padding(.horizontal, 13.3) |         .padding(.horizontal, 13.3) | ||||||
|         .padding(.vertical, 16.7) |         .padding(.vertical, 10) | ||||||
|         .background(Color(hex: "222222")) |         .background(Color(hex: "222222")) | ||||||
|         .cornerRadius(16.7) |         .cornerRadius(6.7) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -87,6 +87,15 @@ struct MyPageView: View { | |||||||
|                                     .frame(width: screenSize().width - 26.7) |                                     .frame(width: screenSize().width - 26.7) | ||||||
|                                     .padding(.top, 26.7) |                                     .padding(.top, 26.7) | ||||||
|                                      |                                      | ||||||
|  |                                     if data.isAuth { | ||||||
|  |                                         CanChargeCouponButtonView() | ||||||
|  |                                             .frame(width: screenSize().width - 26.7) | ||||||
|  |                                             .padding(.top, 13.3) | ||||||
|  |                                             .onTapGesture { | ||||||
|  |                                                 AppState.shared.setAppStep(step: .canCoupon) | ||||||
|  |                                             } | ||||||
|  |                                     } | ||||||
|  |                                      | ||||||
|                                     ReservationStatusView(data: data) |                                     ReservationStatusView(data: data) | ||||||
|                                         .padding(.top, 33.3) |                                         .padding(.top, 33.3) | ||||||
|                                      |                                      | ||||||
|   | |||||||
| @@ -25,8 +25,8 @@ struct ReservationStatusView: View { | |||||||
|                         .frame(width: 20, height: 20) |                         .frame(width: 20, height: 20) | ||||||
|                      |                      | ||||||
|                     Text("라이브") |                     Text("라이브") | ||||||
|                         .font(.custom(Font.medium.rawValue, size: 14.7)) |                         .font(.custom(Font.bold.rawValue, size: 14.7)) | ||||||
|                         .foregroundColor(Color(hex: "eeeeee")) |                         .foregroundColor(Color(hex: "3bb9f1")) | ||||||
|                      |                      | ||||||
|                     Text("\(data.liveReservationCount)") |                     Text("\(data.liveReservationCount)") | ||||||
|                         .font(.custom(Font.medium.rawValue, size: 14.7)) |                         .font(.custom(Font.medium.rawValue, size: 14.7)) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung