diff --git a/SodaLive/Sources/App/AppStep.swift b/SodaLive/Sources/App/AppStep.swift index 444c9a6..54fc4b7 100644 --- a/SodaLive/Sources/App/AppStep.swift +++ b/SodaLive/Sources/App/AppStep.swift @@ -37,4 +37,6 @@ enum AppStep { case privacy case notificationSettings + + case signOut } diff --git a/SodaLive/Sources/ContentView.swift b/SodaLive/Sources/ContentView.swift index 2487e34..579ca1f 100644 --- a/SodaLive/Sources/ContentView.swift +++ b/SodaLive/Sources/ContentView.swift @@ -59,6 +59,9 @@ struct ContentView: View { case .notificationSettings: NotificationSettingsView() + case .signOut: + SignOutView() + default: EmptyView() .frame(width: 0, height: 0, alignment: .topLeading) diff --git a/SodaLive/Sources/Settings/SettingsView.swift b/SodaLive/Sources/Settings/SettingsView.swift index b07826d..9d4c3a0 100644 --- a/SodaLive/Sources/Settings/SettingsView.swift +++ b/SodaLive/Sources/Settings/SettingsView.swift @@ -181,6 +181,7 @@ struct SettingsView: View { .underline() .padding(.vertical, 26.7) .onTapGesture { + AppState.shared.setAppStep(step: .signOut) } } } diff --git a/SodaLive/Sources/Settings/SignOut/SignOutRequest.swift b/SodaLive/Sources/Settings/SignOut/SignOutRequest.swift new file mode 100644 index 0000000..ad16113 --- /dev/null +++ b/SodaLive/Sources/Settings/SignOut/SignOutRequest.swift @@ -0,0 +1,13 @@ +// +// SignOutRequest.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import Foundation + +struct SignOutRequest: Encodable { + let reason: String + let password: String +} diff --git a/SodaLive/Sources/Settings/SignOut/SignOutView.swift b/SodaLive/Sources/Settings/SignOut/SignOutView.swift new file mode 100644 index 0000000..9282536 --- /dev/null +++ b/SodaLive/Sources/Settings/SignOut/SignOutView.swift @@ -0,0 +1,137 @@ +// +// SignOutView.swift +// SodaLive +// +// Created by klaus on 2023/08/10. +// + +import SwiftUI + +struct SignOutView: View { + + @StateObject var viewModel = SignOutViewModel() + + var body: some View { + BaseView(isLoading: $viewModel.isLoading) { + VStack(spacing: 0) { + DetailNavigationBar(title: "회원탈퇴") + + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 0) { + VStack(spacing: 13.3) { + Text("정말로 탈퇴하실 거에요?\n한 번 더 생각해보지 않으실래요?") + .font(.custom(Font.bold.rawValue, size: 20)) + .foregroundColor(Color(hex: "a285eb")) + .frame(width: screenSize().width - 26.7, alignment: .leading) + + Text("계정을 삭제하려는 이유를 선택해주세요.\n서비스 개선에 중요한 자료로 활용하겠습니다.") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color(hex: "eeeeee")) + .frame(width: screenSize().width - 26.7, alignment: .leading) + + VStack(alignment: .leading, spacing: 16.7) { + ForEach(0..() + + let reasons = [ + "닉네임을 변경하고 싶어서", + "다른 사용자와의 다툼이 있어서", + "이용이 불편하고 장애가 많아서", + "서비스 운영이 마음에 들지 않아서", + "다른 서비스가 더 좋아서", + "삭제하고 싶은 내용이 있어서", + "이용빈도가 낮아서", + "원하는 콘텐츠나 크리에이터가 없어서", + "이용요금이 비싸서", + "기타" + ] + + @Published var reasonSelectedIndex = -1 + @Published var reason = "" + @Published var password = "" + + @Published var errorMessage = "" + @Published var isShowPopup = false + @Published var isLoading = false + + func signOut() { + if validate() { + isLoading = true + let reason = reasonSelectedIndex == reasons.count - 1 ? self.reason : reasons[reasonSelectedIndex] + repository.signOut(reason: reason, password: password) + .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 { + UserDefaults.reset() + AppState.shared.setAppStep(step: .splash) + } 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) + } + } + + private func validate() -> Bool { + if reasonSelectedIndex < 0 || (reasonSelectedIndex == reasons.count - 1 && self.reason.trimmingCharacters(in: .whitespaces).isEmpty) { + errorMessage = "계정을 삭제하려는 이유를 선택해 주세요." + isShowPopup = true + return false + } + + if password.isEmpty { + errorMessage = "비밀번호를 입력해 주세요." + isShowPopup = true + return false + } + + return true + } +} diff --git a/SodaLive/Sources/User/UserApi.swift b/SodaLive/Sources/User/UserApi.swift index 71aa16e..19da034 100644 --- a/SodaLive/Sources/User/UserApi.swift +++ b/SodaLive/Sources/User/UserApi.swift @@ -18,6 +18,7 @@ enum UserApi { case notification(request: UpdateNotificationSettingRequest) case logout case logoutAllDevice + case signOut(request: SignOutRequest) } extension UserApi: TargetType { @@ -53,12 +54,15 @@ extension UserApi: TargetType { case .logoutAllDevice: return "/member/logout/all" + + case .signOut: + return "/member/sign_out" } } var method: Moya.Method { switch self { - case .login, .signUp, .findPassword, .notification, .logout, .logoutAllDevice: + case .login, .signUp, .findPassword, .notification, .logout, .logoutAllDevice, .signOut: return .post case .searchUser, .getMypage, .getMemberInfo: @@ -88,6 +92,9 @@ extension UserApi: TargetType { case .notification(let request): return .requestJSONEncodable(request) + + case .signOut(let request): + return .requestJSONEncodable(request) } } diff --git a/SodaLive/Sources/User/UserRepository.swift b/SodaLive/Sources/User/UserRepository.swift index af5cb71..4c997a4 100644 --- a/SodaLive/Sources/User/UserRepository.swift +++ b/SodaLive/Sources/User/UserRepository.swift @@ -56,4 +56,8 @@ final class UserRepository { func logoutAllDevice() -> AnyPublisher { return api.requestPublisher(.logoutAllDevice) } + + func signOut(reason: String, password: String) -> AnyPublisher { + return api.requestPublisher(.signOut(request: SignOutRequest(reason: reason, password: password))) + } }