diff --git a/KNU_CSE/KNU_CSE.xcodeproj/project.pbxproj b/KNU_CSE/KNU_CSE.xcodeproj/project.pbxproj index 5e5d524..ec0895d 100644 --- a/KNU_CSE/KNU_CSE.xcodeproj/project.pbxproj +++ b/KNU_CSE/KNU_CSE.xcodeproj/project.pbxproj @@ -49,6 +49,8 @@ 681D382626ADC61D000D9FE8 /* BoardWriteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 681D382526ADC61D000D9FE8 /* BoardWriteView.swift */; }; 686889C626C55F6800486985 /* CommentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 686889C526C55F6800486985 /* CommentView.swift */; }; 686889C826C5977D00486985 /* BoardWithPaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 686889C726C5977D00486985 /* BoardWithPaging.swift */; }; + 686889CB26D2A2A000486985 /* DeleteAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 686889CA26D2A29F00486985 /* DeleteAccountView.swift */; }; + 686889CD26D2A2B400486985 /* DeleteAccountViewModel+Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 686889CC26D2A2B400486985 /* DeleteAccountViewModel+Model.swift */; }; 68C7629126B96EFA00792498 /* CategoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C7629026B96EFA00792498 /* CategoryCell.swift */; }; 68C7629326B99F7F00792498 /* ResponseBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C7629226B99F7F00792498 /* ResponseBody.swift */; }; 68C7629626BC0CAE00792498 /* BaseViewModelChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68C7629526BC0CAE00792498 /* BaseViewModelChange.swift */; }; @@ -169,6 +171,8 @@ 681D382526ADC61D000D9FE8 /* BoardWriteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardWriteView.swift; sourceTree = ""; }; 686889C526C55F6800486985 /* CommentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentView.swift; sourceTree = ""; }; 686889C726C5977D00486985 /* BoardWithPaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardWithPaging.swift; sourceTree = ""; }; + 686889CA26D2A29F00486985 /* DeleteAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccountView.swift; sourceTree = ""; }; + 686889CC26D2A2B400486985 /* DeleteAccountViewModel+Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeleteAccountViewModel+Model.swift"; sourceTree = ""; }; 68C7629026B96EFA00792498 /* CategoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryCell.swift; sourceTree = ""; }; 68C7629226B99F7F00792498 /* ResponseBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseBody.swift; sourceTree = ""; }; 68C7629526BC0CAE00792498 /* BaseViewModelChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewModelChange.swift; sourceTree = ""; }; @@ -291,6 +295,7 @@ children = ( 68F861C626B1B18B00934BD2 /* UserInfrom */, 68F861C726B1B19D00934BD2 /* EditPw */, + 686889C926D2A28200486985 /* DeleteAccount */, 68F861C826B1B1B800934BD2 /* ReservationList */, 68F861C926B1B1C100934BD2 /* WrittenBoardList */, 68F861CA26B1B1CA00934BD2 /* AppSetting */, @@ -434,6 +439,15 @@ path = View; sourceTree = ""; }; + 686889C926D2A28200486985 /* DeleteAccount */ = { + isa = PBXGroup; + children = ( + 686889CA26D2A29F00486985 /* DeleteAccountView.swift */, + 686889CC26D2A2B400486985 /* DeleteAccountViewModel+Model.swift */, + ); + path = DeleteAccount; + sourceTree = ""; + }; 68C7629426BC0CA200792498 /* Base */ = { isa = PBXGroup; children = ( @@ -997,6 +1011,7 @@ 68F861EB26B471D600934BD2 /* UserInformViewModel+Model.swift in Sources */, 68C7629326B99F7F00792498 /* ResponseBody.swift in Sources */, 681D37E526A6AD36000D9FE8 /* ViewProtocol.swift in Sources */, + 686889CB26D2A2A000486985 /* DeleteAccountView.swift in Sources */, 681D37C426A45442000D9FE8 /* ClassRoomCell.swift in Sources */, 681D382026ADAE85000D9FE8 /* ReplyViewModel.swift in Sources */, 68F861E326B2EE8A00934BD2 /* FindPwView.swift in Sources */, @@ -1029,6 +1044,7 @@ 681D37F426A737AE000D9FE8 /* SignInNavigationView.swift in Sources */, 68F1A5EE26A008FF007B4B86 /* BaseObject.swift in Sources */, 681D37B926A42782000D9FE8 /* ClassRoomView.swift in Sources */, + 686889CD26D2A2B400486985 /* DeleteAccountViewModel+Model.swift in Sources */, 681D37F926A73F71000D9FE8 /* BoardTitleCollectionCell.swift in Sources */, 68C762A626C40FEC00792498 /* Extension+StackView.swift in Sources */, 68F861DF26B2D31D00934BD2 /* ProfileTableCell.swift in Sources */, diff --git a/KNU_CSE/KNU_CSE/Base.lproj/Main.storyboard b/KNU_CSE/KNU_CSE/Base.lproj/Main.storyboard index 4866860..a8d1523 100644 --- a/KNU_CSE/KNU_CSE/Base.lproj/Main.storyboard +++ b/KNU_CSE/KNU_CSE/Base.lproj/Main.storyboard @@ -381,6 +381,21 @@ + + + + + + + + + + + + + + + diff --git a/KNU_CSE/KNU_CSE/Network Service/Base/BaseApiRequest.swift b/KNU_CSE/KNU_CSE/Network Service/Base/BaseApiRequest.swift index 58ad5d7..c525471 100644 --- a/KNU_CSE/KNU_CSE/Network Service/Base/BaseApiRequest.swift +++ b/KNU_CSE/KNU_CSE/Network Service/Base/BaseApiRequest.swift @@ -49,34 +49,26 @@ extension BaseApiRequest{ request.method = .get case .post: request.method = .post - if(requestBodyObject != nil){ - let jsonEncoder = JSONEncoder() - do { - if let data = try? jsonEncoder.encode(requestBodyObject.self){ - let jsonString = String(decoding: data, as: UTF8.self) - request.httpBody = data - print(jsonString) - } - } - } case .put: request.method = .put - if(requestBodyObject != nil){ - let jsonEncoder = JSONEncoder() - do { - if let data = try? jsonEncoder.encode(requestBodyObject.self){ - let jsonString = String(decoding: data, as: UTF8.self) - request.httpBody = data - print(jsonString) - } - } - } case .delete: request.method = .delete default: request.httpMethod = "GET" break } + + if(requestBodyObject != nil){ + let jsonEncoder = JSONEncoder() + do { + if let data = try? jsonEncoder.encode(requestBodyObject.self){ + let jsonString = String(decoding: data, as: UTF8.self) + request.httpBody = data + print(jsonString) + } + } + } + return request } @@ -86,6 +78,8 @@ extension BaseApiRequest{ return getAddress(domain: "user/signIn") case .SignUp: return getAddress(domain: "user/signUp") + case .deleteAccount: + return getAddress(domain: "user/deleteMember") case .codeForPw(let email): return getAddress(domain: "user/findPassword/\(email)") case .codeConfirmForPw: @@ -100,9 +94,9 @@ extension BaseApiRequest{ return getAddress(domain: "user/profileimage") case .changePassword: return getAddress(domain: "user/changePassword") - case .CodeRequest(let email): - return "\(getAddress(domain: "user/verify"))/\(email)" - case .CodeConfirm: + case .codeRequest(let email): + return (getAddress(domain: "user/verify/\(email)")) + case .codeConfirm: return getAddress(domain: "user/verify") case .BoardWrite: return getAddress(domain: "board/write") @@ -156,6 +150,7 @@ enum RequestHttpMethod{ enum Environment{ case SignIn case SignUp + case deleteAccount case codeForPw(String) case codeConfirmForPw case changePasswordForFindPw @@ -163,8 +158,8 @@ enum Environment{ case setInform case resetImage case changePassword - case CodeRequest(String) - case CodeConfirm + case codeRequest(String) + case codeConfirm case BoardWrite case getBoardPaging(String,Int,Int) case getBoard(Int) diff --git a/KNU_CSE/KNU_CSE/View/Main/MyPage/DeleteAccount/DeleteAccountView.swift b/KNU_CSE/KNU_CSE/View/Main/MyPage/DeleteAccount/DeleteAccountView.swift new file mode 100644 index 0000000..826b471 --- /dev/null +++ b/KNU_CSE/KNU_CSE/View/Main/MyPage/DeleteAccount/DeleteAccountView.swift @@ -0,0 +1,196 @@ +// +// DeleteAccount.swift +// KNU_CSE +// +// Created by junseok on 2021/08/23. +// + +import UIKit + +class DeleteAccountView:UIViewController,ViewProtocol{ + + var indicator : IndicatorView! + + var deleteAccountViewModel : DeleteAccountViewModel = DeleteAccountViewModel() + + let containerView = UIView() + var pwTextField: BindingTextField! { + didSet { + self.pwTextField.isSecureTextEntry = true + self.pwTextField.delegate = self + self.pwTextField.textContentType = .password + self.pwTextField.draw() + self.pwTextField.setUpImage(imageName: "eye.fill", on: .right, color: UIColor.darkGray, width:40, height: 40) + self.pwTextField.bind { [weak self] password in + self?.deleteAccountViewModel.model.password = password + self?.checkChangeValue() + } + } + } + + var pw2TextField: BindingTextField! { + didSet { + self.pw2TextField.isSecureTextEntry = true + self.pw2TextField.delegate = self + self.pw2TextField.textContentType = .password + self.pw2TextField.draw() + self.pw2TextField.setUpImage(imageName: "eye.fill", on: .right, color: UIColor.darkGray, width:40, height: 40) + self.pw2TextField.bind { [weak self] password2 in + self?.deleteAccountViewModel.model.password2 = password2 + self?.checkChangeValue() + } + } + } + + var registerBtn : UIButton! { + didSet{ + self.registerBtn.backgroundColor = UIColor.lightGray + self.registerBtn.setTitle("회원 탈퇴", for: .normal) + self.registerBtn.setTitleColor(UIColor.init(white: 1, alpha: 0.3), for: .highlighted) + self.registerBtn.layer.cornerRadius = 5 + } + } + + let pwTitle : SignUpUILabel = SignUpUILabel(text: "현재 비밀번호") + let pw2Title : SignUpUILabel = SignUpUILabel(text: "현재 비밀번호 확인") + + override func viewWillAppear(_ animated: Bool) { + self.setNavigationTitle(title:"회원탈퇴") + self.hideBackTitle() + } + + override func viewDidLoad() { + super.viewDidLoad() + self.BindingEditPw() + + self.initUI() + self.addView() + self.setUpConstraints() + } + + func initUI(){ + self.pwTextField = BindingTextField() + self.pw2TextField = BindingTextField() + self.registerBtn = UIButton() + self.indicator = IndicatorView(viewController: self) + } + + func addView(){ + self.view.addSubview(containerView) + _ = [self.pwTextField,self.pw2TextField,self.pwTitle,self.pw2Title,self.registerBtn].map{ + self.containerView.addSubview($0) + } + } + + func setUpConstraints(){ + let title_height:CGFloat = self.view.frame.width * 0.05 + let height:CGFloat = self.view.frame.height * 0.05 + let top_padding:CGFloat = 15 + let title_To_textField_margin:CGFloat = 3 + let left_margin:CGFloat = 30 + let right_margin:CGFloat = -30 + + self.containerView.snp.makeConstraints{ make in + make.top.equalTo(self.view.safeAreaLayoutGuide).offset(10) + make.left.right.equalToSuperview() + make.bottom.equalTo(self.view.safeAreaLayoutGuide) + } + + // MARK: - 비밀번호 + self.pwTitle.snp.makeConstraints{ make in + make.left.equalTo(left_margin) + make.width.equalTo(100) + make.top.equalTo(self.containerView.snp.top).offset(top_padding+10) + make.height.equalTo(title_height) + } + + self.pwTextField.snp.makeConstraints{ make in + make.left.equalTo(left_margin) + make.right.equalTo(right_margin) + make.top.equalTo(self.pwTitle.snp.bottom).offset(title_To_textField_margin) + make.height.equalTo(height) + } + + // MARK: - 비밀번호 확인 + self.pw2Title.snp.makeConstraints{ make in + make.left.equalTo(left_margin) + make.right.equalTo(right_margin) + make.top.equalTo(self.pwTextField.snp.bottom).offset(top_padding) + make.height.equalTo(title_height) + } + + self.pw2TextField.snp.makeConstraints{ make in + make.left.equalTo(left_margin) + make.right.equalTo(right_margin) + make.top.equalTo(self.pw2Title.snp.bottom).offset(title_To_textField_margin) + make.height.equalTo(height) + } + + // MARK: - 회원가입 버튼 + self.registerBtn.snp.makeConstraints{ make in + make.left.equalTo(left_margin) + make.right.equalTo(right_margin) + make.top.equalTo(self.pw2TextField.snp.bottom).offset(top_padding) + make.height.equalTo(height) + } + } +} + + +extension DeleteAccountView: UITextFieldDelegate{ + + //touch any space then keyboard shut down + open override func touchesBegan(_ touches: Set, with event: UIEvent?){ + self.view.endEditing(true) + } + + //if keyboard show up and press return button then keyboard shutdown + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + + func checkChangeValue(){ + if self.deleteAccountViewModel.PwCheck(){ + self.addBtnAction() + }else{ + self.removeBtnAction() + } + } + + func addBtnAction(){ + self.registerBtn.backgroundColor = Color.mainColor + self.registerBtn.addAction{ [weak self] in + Alert(title: "회원탈퇴", message: "정말로 계정을 탈퇴하시겠습니까?", viewController: self!).popUpNormalAlert{ [weak self] _ in + self?.deleteAccountViewModel.deleteAccount() + } + } + } + + func removeBtnAction(){ + self.registerBtn.backgroundColor = UIColor.lightGray + self.registerBtn.removeTarget(nil, action: nil, for: .allEvents) + } +} + +extension DeleteAccountView{ + func BindingEditPw(){ + self.deleteAccountViewModel.deleteAccountAction.binding(successHandler: { response in + if response.success{ + Alert(title: "회원 탈퇴", message: "회원님의 계정이 정상적으로 탈퇴처리 되었습니다.\n이용해주셔서 감사합니다.", viewController: self).popUpDefaultAlert(action:{ action in + self.popTwiceView() + self.logOut() + }) + }else{ + Alert(title: "회원 탈퇴 실패", message: response.error!.message, viewController: self).popUpDefaultAlert(action:nil) + } + }, failHandler: { Error in + Alert(title: "실패", message: "네트워크 상태를 확인하세요", viewController: self).popUpDefaultAlert(action: nil) + }, asyncHandler: { + + }, endHandler: { + + }) + } +} + diff --git a/KNU_CSE/KNU_CSE/View/Main/MyPage/DeleteAccount/DeleteAccountViewModel+Model.swift b/KNU_CSE/KNU_CSE/View/Main/MyPage/DeleteAccount/DeleteAccountViewModel+Model.swift new file mode 100644 index 0000000..c23c2d3 --- /dev/null +++ b/KNU_CSE/KNU_CSE/View/Main/MyPage/DeleteAccount/DeleteAccountViewModel+Model.swift @@ -0,0 +1,59 @@ +// +// DeleteAccountViewModel + Model.swift +// KNU_CSE +// +// Created by junseok on 2021/08/23. +// + +import Foundation + +class DeleteAccountViewModel { + var deleteAccountAction: BaseAction = BaseAction() + var model: DeleteAccountModel = DeleteAccountModel(password: "", password2: "") + + init(){ + + } + + func PwCheck()-> Bool{ + return model.Check() + } + + public func deleteAccount(){ + let request = Request(requestBodyObject: self.model, requestMethod: .delete, enviroment: .deleteAccount) + request.sendRequest(request: request, responseType: String.self, errorType: errorHandler.self, action:self.deleteAccountAction) + } +} + +class DeleteAccountModel: BaseObject{ + var password:String + var password2:String + + init(password : String, password2 : String){ + self.password = password + self.password2 = password2 + super.init() + } + + required init(from decoder: Decoder) throws { + fatalError("init(from:) has not been implemented") + } + + func Check() -> Bool{ + if self.password != "" && self.password2 != "" && self.password == self.password2{ + return true + }else{ + return false + } + } + + override func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(password, forKey: .password) + try super.encode(to: encoder) + } + + enum CodingKeys: CodingKey { + case password + } +} diff --git a/KNU_CSE/KNU_CSE/View/Main/MyPage/MyPageView.swift b/KNU_CSE/KNU_CSE/View/Main/MyPage/MyPageView.swift index 81774b1..4e348db 100644 --- a/KNU_CSE/KNU_CSE/View/Main/MyPage/MyPageView.swift +++ b/KNU_CSE/KNU_CSE/View/Main/MyPage/MyPageView.swift @@ -11,7 +11,7 @@ import SnapKit class MyPageView : UIViewController{ let sectionHeader = ["계정관리", "강의실", "게시판", "앱 관리"] - let accountSection = ["회원정보", "비밀번호 변경"] + let accountSection = ["회원정보", "비밀번호 변경", "회원탈퇴"] let classRoomSection = ["예약내역"] let boardRoomSection = ["내가 쓴 글"] let appSettingSection = ["환경설정", "로그아웃"] @@ -109,6 +109,8 @@ extension MyPageView:UITableViewDelegate{ pushView(identifier: "UserInformView", typeOfVC: UserInformView.self) }else if indexPath.row == 1{ pushView(identifier: "EditPwView", typeOfVC: EditPwView.self) + }else if indexPath.row == 2{ + pushView(identifier: "DeleteAccountView", typeOfVC: DeleteAccountView.self) } case 1: if indexPath.row == 0 { diff --git a/KNU_CSE/KNU_CSE/View/SignUp/SignUpView.swift b/KNU_CSE/KNU_CSE/View/SignUp/SignUpView.swift index 4109913..290faf6 100644 --- a/KNU_CSE/KNU_CSE/View/SignUp/SignUpView.swift +++ b/KNU_CSE/KNU_CSE/View/SignUp/SignUpView.swift @@ -34,7 +34,6 @@ class SignUpView: UIViewController,ViewProtocol{ requestCodeBtn.titleLabel?.font = UIFont.systemFont(ofSize: 12, weight: .light) requestCodeBtn.tintColor = .white requestCodeBtn.setTitleColor(UIColor.init(white: 1, alpha: 0.3), for: .highlighted) - self.BindingRequestCodeBtn() requestCodeBtn.addAction{ [weak self] in self?.signUpViewModel.CodeRequest() } @@ -61,7 +60,6 @@ class SignUpView: UIViewController,ViewProtocol{ confirmCodeBtn.titleLabel?.font = UIFont.systemFont(ofSize: 12, weight: .light) confirmCodeBtn.tintColor = .white confirmCodeBtn.setTitleColor(UIColor.init(white: 1, alpha: 0.3), for: .highlighted) - self.BindingConfirmCodeBtn() confirmCodeBtn.addAction{ [weak self] in self?.signUpViewModel.CodeConfirm() } @@ -223,7 +221,6 @@ class SignUpView: UIViewController,ViewProtocol{ registerBtn.setTitle("회원가입", for: .normal) registerBtn.setTitleColor(UIColor.init(white: 1, alpha: 0.3), for: .highlighted) registerBtn.layer.cornerRadius = 5 - self.BindingSignUp() registerBtn.addAction{ [weak self] in guard let check = self?.signUpViewModel.SignUpCheck() else{ return @@ -269,6 +266,8 @@ class SignUpView: UIViewController,ViewProtocol{ self.initUI() self.addView() self.setUpConstraints() + + self.Binding() } func initUI(){ @@ -533,6 +532,12 @@ extension SignUpView: UITextFieldDelegate{ //About Action Binding extension SignUpView{ + func Binding(){ + self.BindingRequestCodeBtn() + self.BindingConfirmCodeBtn() + self.BindingSignUp() + } + func BindingRequestCodeBtn(){ self.signUpViewModel.codeRequestListner.binding(successHandler: { response in if response.success{ @@ -550,9 +555,13 @@ extension SignUpView{ func BindingConfirmCodeBtn(){ self.signUpViewModel.codeConfirmListner.binding(successHandler: { response in - if response.success{ - let alert = Alert(title: "인증 성공", message: "이메일 인증에 성공했습니다.", viewController: self) - alert.popUpDefaultAlert(action: nil) + if response.success, let permissionCode = response.response{ + self.signUpViewModel.account.permissionCode = permissionCode + Alert(title: "인증 성공", message: "이메일 인증에 성공했습니다.", viewController: self).popUpDefaultAlert(action: nil) + }else{ + if let message = response.error?.message{ + Alert(title: "인증 실패", message: message, viewController: self).popUpDefaultAlert(action: nil) + } } }, failHandler: { Error in print(Error) @@ -571,8 +580,9 @@ extension SignUpView{ self.navigationController?.popViewController(animated: true) }) } else{ - let alert = Alert(title: "회원가입실패", message: "\((response.error?.message)!)", viewController: self) - alert.popUpDefaultAlert(action: nil) + if let message = response.error?.message{ + Alert(title: "회원가입실패", message: "\((response.error?.message)!)", viewController: self).popUpDefaultAlert(action: nil) + } } }, failHandler: { Error in print("Fail(\(Error)") diff --git a/KNU_CSE/KNU_CSE/View/SignUp/SignUpViewModel.swift b/KNU_CSE/KNU_CSE/View/SignUp/SignUpViewModel.swift index b0881e4..61fc200 100644 --- a/KNU_CSE/KNU_CSE/View/SignUp/SignUpViewModel.swift +++ b/KNU_CSE/KNU_CSE/View/SignUp/SignUpViewModel.swift @@ -26,12 +26,12 @@ struct SignUpViewModel{ extension SignUpViewModel{ func CodeRequest() { - let request = Request(requestBodyObject:nil, requestMethod: .get, enviroment: .CodeRequest(self.account.email)) + let request = Request(requestBodyObject:nil, requestMethod: .get, enviroment: .codeRequest(self.account.email)) request.sendRequest(request: request, responseType: String.self, errorType: errorHandler.self, action:self.codeRequestListner) } func CodeConfirm() { - let request = Request(requestBodyObject:account.getEmailRequest(), requestMethod: .post, enviroment: .CodeConfirm) + let request = Request(requestBodyObject:account.getEmailRequest(), requestMethod: .post, enviroment: .codeConfirm) request.sendRequest(request: request, responseType: String.self, errorType: errorHandler.self, action:self.codeConfirmListner) }