import Combine import Foundation final class GiftRecipientAddressViewModel: ObservableObject { @Published var recipientName: String = "" @Published var recipientPhoneNumber: String = "" @Published var recipientZipCode: String = "" @Published var recipientAddress: String = "" @Published var recipientAddressDetail: String = "" @Published var recipientTermsAgreed: Bool = false @Published var recipientPrivacyAgreed: Bool = false @Published private(set) var isSubmitting = false @Published var errorMessage = "" @Published var isShowPopup = false @Published var isShowSuccessToast = false let successMessage = "배송지 입력 완료" private let applicationNo: String private let onSubmitted: () -> Void private let repository = GiftRepository() private var mutationSubscription: AnyCancellable? @Published private(set) var hasSubmitted = false private var hasNotifiedSubmission = false init(applicationNo: String, onSubmitted: @escaping () -> Void) { self.applicationNo = applicationNo self.onSubmitted = onSubmitted } var canSubmit: Bool { !isSubmitting && !hasSubmitted && recipientTermsAgreed && recipientPrivacyAgreed && GiftContactInputRule.name.acceptsSubmission(recipientName) && GiftContactInputRule.phoneNumber.acceptsSubmission(recipientPhoneNumber) && [recipientZipCode, recipientAddress].allSatisfy { !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } } func submit() { guard canSubmit else { return } isSubmitting = true errorMessage = "" isShowPopup = false let request = SubmitGiftRecipientAddressRequest( recipientName: recipientName.trimmingCharacters(in: .whitespacesAndNewlines), recipientPhoneNumber: recipientPhoneNumber.trimmingCharacters(in: .whitespacesAndNewlines), recipientZipCode: recipientZipCode.trimmingCharacters(in: .whitespacesAndNewlines), recipientAddress: recipientAddress.trimmingCharacters(in: .whitespacesAndNewlines), recipientAddressDetail: recipientAddressDetail.trimmingCharacters(in: .whitespacesAndNewlines), recipientTermsAgreed: recipientTermsAgreed, recipientPrivacyAgreed: recipientPrivacyAgreed ) mutationSubscription = repository.submitRecipientAddress(applicationNo: applicationNo, request: request) .receive(on: DispatchQueue.main) .sink { [weak self] result in guard let self, !self.hasSubmitted else { return } if case .failure(let error) = result { self.finishFailure(data: error.response?.data) } } receiveValue: { [weak self] response in guard let self, !self.hasSubmitted else { return } do { let decoded = try JSONDecoder().decode(ApiResponseWithoutData.self, from: response.data) guard decoded.success else { self.finishFailure(data: response.data) return } self.hasSubmitted = true self.isSubmitting = false self.isShowSuccessToast = true } catch { self.finishFailure(data: response.data) } } } func successToastDidDismiss() { guard hasSubmitted, !isShowSuccessToast, !hasNotifiedSubmission else { return } hasNotifiedSubmission = true onSubmitted() } private func finishFailure(data: Data?) { struct ErrorResponse: Decodable { let message: String? } let decoded = data.flatMap { try? JSONDecoder().decode(ErrorResponse.self, from: $0) } let message = decoded?.message?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" isSubmitting = false errorMessage = message.isEmpty ? I18n.Common.commonError : message isShowPopup = true } }