diff --git a/DDANZI_iOS/DDANZI_iOS.xcodeproj/project.pbxproj b/DDANZI_iOS/DDANZI_iOS.xcodeproj/project.pbxproj index 72d014a..4a48cfe 100644 --- a/DDANZI_iOS/DDANZI_iOS.xcodeproj/project.pbxproj +++ b/DDANZI_iOS/DDANZI_iOS.xcodeproj/project.pbxproj @@ -72,6 +72,13 @@ 363F1A102C9B10BA007527E2 /* LogoutDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363F1A0F2C9B10BA007527E2 /* LogoutDTO.swift */; }; 363F1A122C9B1638007527E2 /* UserAccountDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363F1A112C9B1638007527E2 /* UserAccountDTO.swift */; }; 363F1A182C9CA04F007527E2 /* Amplitude in Frameworks */ = {isa = PBXBuildFile; productRef = 363F1A172C9CA04F007527E2 /* Amplitude */; }; + 363F1A1A2C9EE9EB007527E2 /* AccountAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363F1A192C9EE9EB007527E2 /* AccountAddViewController.swift */; }; + 363F1A1C2C9EF029007527E2 /* AccountAddCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363F1A1B2C9EF029007527E2 /* AccountAddCell.swift */; }; + 363F1A1E2C9F072F007527E2 /* UserAccountRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363F1A1D2C9F072F007527E2 /* UserAccountRequestDTO.swift */; }; + 363F1A232C9F15EB007527E2 /* RxGesture in Frameworks */ = {isa = PBXBuildFile; productRef = 363F1A222C9F15EB007527E2 /* RxGesture */; }; + 363F1A252C9F1BE2007527E2 /* BankList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363F1A242C9F1BE2007527E2 /* BankList.swift */; }; + 363F1A272C9F57D4007527E2 /* CustomAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363F1A262C9F57D4007527E2 /* CustomAlertView.swift */; }; + 363F1A292CA067C7007527E2 /* RefreshTokenRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 363F1A282CA067C7007527E2 /* RefreshTokenRequestDTO.swift */; }; 3648954E2C6281BB00AAA8E2 /* HomeItemsResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3648954D2C6281BB00AAA8E2 /* HomeItemsResponseDTO.swift */; }; 364895502C62822200AAA8E2 /* ProductDetailResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3648954F2C62822200AAA8E2 /* ProductDetailResponseDTO.swift */; }; 364895522C62826200AAA8E2 /* SearchItemsResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 364895512C62826200AAA8E2 /* SearchItemsResponseDTO.swift */; }; @@ -306,6 +313,12 @@ 363F1A0D2C9B10AF007527E2 /* WithDrawDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithDrawDTO.swift; sourceTree = ""; }; 363F1A0F2C9B10BA007527E2 /* LogoutDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutDTO.swift; sourceTree = ""; }; 363F1A112C9B1638007527E2 /* UserAccountDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAccountDTO.swift; sourceTree = ""; }; + 363F1A192C9EE9EB007527E2 /* AccountAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountAddViewController.swift; sourceTree = ""; }; + 363F1A1B2C9EF029007527E2 /* AccountAddCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountAddCell.swift; sourceTree = ""; }; + 363F1A1D2C9F072F007527E2 /* UserAccountRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAccountRequestDTO.swift; sourceTree = ""; }; + 363F1A242C9F1BE2007527E2 /* BankList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankList.swift; sourceTree = ""; }; + 363F1A262C9F57D4007527E2 /* CustomAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAlertView.swift; sourceTree = ""; }; + 363F1A282CA067C7007527E2 /* RefreshTokenRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshTokenRequestDTO.swift; sourceTree = ""; }; 3648954D2C6281BB00AAA8E2 /* HomeItemsResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeItemsResponseDTO.swift; sourceTree = ""; }; 3648954F2C62822200AAA8E2 /* ProductDetailResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDetailResponseDTO.swift; sourceTree = ""; }; 364895512C62826200AAA8E2 /* SearchItemsResponseDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchItemsResponseDTO.swift; sourceTree = ""; }; @@ -464,6 +477,7 @@ 36B744F92C6C50D3002892C4 /* RxKakaoSDKCommon in Frameworks */, 36B744F22C6C4CD2002892C4 /* KakaoSDKCommon in Frameworks */, 3664CFDA2C6E2179007FB5DF /* Lottie in Frameworks */, + 363F1A232C9F15EB007527E2 /* RxGesture in Frameworks */, 368E36A82C4A3BBE003FA911 /* RxDataSources in Frameworks */, 368E36992C4A3B55003FA911 /* Moya in Frameworks */, 363C243C2C6619950097FCB7 /* Kingfisher in Frameworks */, @@ -528,6 +542,7 @@ 363152802C35EAF400DF689E /* DdanziButton.swift */, 363C24282C6520DD0097FCB7 /* DdanziChipButton.swift */, 36A5947D2C87F90C0097DE70 /* DdanziLoadingView.swift */, + 363F1A262C9F57D4007527E2 /* CustomAlertView.swift */, ); path = ReusableView; sourceTree = ""; @@ -538,6 +553,7 @@ 3631528A2C3D120400DF689E /* PurchaseSectionModel.swift */, 363C24202C6512E60097FCB7 /* UserInfoModel.swift */, 363C242C2C6529500097FCB7 /* UserAddressModel.swift */, + 363F1A242C9F1BE2007527E2 /* BankList.swift */, ); path = Model; sourceTree = ""; @@ -653,6 +669,7 @@ 3648955D2C6284D000AAA8E2 /* UserSaleResponseDTO.swift */, 3648955F2C62850200AAA8E2 /* UserInterestResponesDTO.swift */, 363F1A112C9B1638007527E2 /* UserAccountDTO.swift */, + 363F1A1D2C9F072F007527E2 /* UserAccountRequestDTO.swift */, ); path = Mypage; sourceTree = ""; @@ -764,6 +781,8 @@ 36542E802C308ED000D5BFEB /* BankAccountViewController.swift */, 363152F22C44F8BE00DF689E /* AddressSettingViewController.swift */, 36BE65B02C4510FE00CD4511 /* AccountViewController.swift */, + 363F1A192C9EE9EB007527E2 /* AccountAddViewController.swift */, + 363F1A1B2C9EF029007527E2 /* AccountAddCell.swift */, 363C242E2C6537840097FCB7 /* KakaoPostCodeViewController.swift */, ); path = Info; @@ -1179,6 +1198,7 @@ 363F1A0B2C9ADD22007527E2 /* RefreshTokenDTO.swift */, 363F1A0D2C9B10AF007527E2 /* WithDrawDTO.swift */, 363F1A0F2C9B10BA007527E2 /* LogoutDTO.swift */, + 363F1A282CA067C7007527E2 /* RefreshTokenRequestDTO.swift */, ); path = Auth; sourceTree = ""; @@ -1368,6 +1388,7 @@ 3664CFD92C6E2179007FB5DF /* Lottie */, 36130D032C92B0F90028CF3A /* FirebaseMessaging */, 363F1A172C9CA04F007527E2 /* Amplitude */, + 363F1A222C9F15EB007527E2 /* RxGesture */, ); productName = DDANZI_iOS; productReference = 369C63872C1A7A240021E2E0 /* DDANZI_iOS.app */; @@ -1455,6 +1476,7 @@ 3664CFD82C6E2179007FB5DF /* XCRemoteSwiftPackageReference "lottie-ios" */, 36130D022C92B0F90028CF3A /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, 363F1A162C9CA04F007527E2 /* XCRemoteSwiftPackageReference "Amplitude-iOS" */, + 363F1A212C9F15EB007527E2 /* XCRemoteSwiftPackageReference "RxGesture" */, ); productRefGroup = 369C63882C1A7A240021E2E0 /* Products */; projectDirPath = ""; @@ -1543,6 +1565,7 @@ 368E36842C4936AB003FA911 /* APIConstants.swift in Sources */, 363C24332C6607080097FCB7 /* HomeRepository.swift in Sources */, 369C63E52C1A95280021E2E0 /* UIButton+.swift in Sources */, + 363F1A1C2C9EF029007527E2 /* AccountAddCell.swift in Sources */, 3648954E2C6281BB00AAA8E2 /* HomeItemsResponseDTO.swift in Sources */, 36BE65C62C4591F500CD4511 /* PurchaseHeaderView.swift in Sources */, 362864D72C8EBEAB00029452 /* ItemEndpoint.swift in Sources */, @@ -1554,6 +1577,7 @@ 363152F12C44F80900DF689E /* SalesDetailViewController.swift in Sources */, 363C242F2C6537840097FCB7 /* KakaoPostCodeViewController.swift in Sources */, 363C242D2C6529500097FCB7 /* UserAddressModel.swift in Sources */, + 363F1A1E2C9F072F007527E2 /* UserAccountRequestDTO.swift in Sources */, 36BE65C22C458FA600CD4511 /* TermsTableViewCell.swift in Sources */, 363152AB2C3DC57800DF689E /* PurchaseModel.swift in Sources */, 36B745052C6C5AC2002892C4 /* UserDefaltManager.swift in Sources */, @@ -1592,6 +1616,7 @@ 364895542C62829200AAA8E2 /* SearchResultResponseDTO.swift in Sources */, 363C24252C651E620097FCB7 /* AddressFormViewController.swift in Sources */, 364895662C6285C900AAA8E2 /* UserAddressRequestDTO.swift in Sources */, + 363F1A272C9F57D4007527E2 /* CustomAlertView.swift in Sources */, 3648DB802C93E134003EA6BE /* PushViewController.swift in Sources */, 36B833EC2C5CDCE4009C2C3E /* AuthEndpoint.swift in Sources */, 362864CE2C8EBC2C00029452 /* PickerManager.swift in Sources */, @@ -1643,15 +1668,18 @@ 36EB5FA82C1ED6B600FAF995 /* ProductModel.swift in Sources */, 363152A52C3DBE9500DF689E /* TotalPriceFooterView.swift in Sources */, 368833AA2C20A9B7000F3A45 /* String.swift in Sources */, + 363F1A252C9F1BE2007527E2 /* BankList.swift in Sources */, 363152902C3DB37000DF689E /* PurchaseDetailViewController.swift in Sources */, 36774A682C466E4000F32637 /* CompleteCollectionViewCell.swift in Sources */, 36B833EE2C5CDCFB009C2C3E /* HomeEndpoint.swift in Sources */, 36542E732C2E88F500D5BFEB /* SellListViewController.swift in Sources */, 362864E72C8EDF0000029452 /* SellDetailDTO.swift in Sources */, 362864CC2C8EA7B700029452 /* CheckItemViewController.swift in Sources */, + 363F1A292CA067C7007527E2 /* RefreshTokenRequestDTO.swift in Sources */, 36EB5FA52C1ECDED00FAF995 /* String+.swift in Sources */, 362864CA2C8E985400029452 /* LandingViewController.swift in Sources */, 36542E792C2E892500D5BFEB /* CsCenterViewController.swift in Sources */, + 363F1A1A2C9EE9EB007527E2 /* AccountAddViewController.swift in Sources */, 363152A12C3DBA0B00DF689E /* InfoCollectionViewCell.swift in Sources */, 363C24292C6520DD0097FCB7 /* DdanziChipButton.swift in Sources */, 362864E52C8EDE6700029452 /* RegisteItemDTO.swift in Sources */, @@ -1850,7 +1878,7 @@ 369C63B22C1A7A270021E2E0 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon_dev; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = DDANZI_iOS/DDANZI_iOS.entitlements; @@ -1859,7 +1887,7 @@ DEVELOPMENT_TEAM = 65NSM7Z327; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = DDANZI_iOS/Application/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "딴지"; + INFOPLIST_KEY_CFBundleDisplayName = "딴지(DEV)"; INFOPLIST_KEY_LSApplicationCategoryType = ""; INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "상품 캡처본을 위해 접근이 필요합니다. "; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -1872,7 +1900,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.orangeCo.DDANZI-iOS"; + PRODUCT_BUNDLE_IDENTIFIER = "com.orangeCo.DDANZI-iOS.dev"; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -2063,6 +2091,14 @@ kind = branch; }; }; + 363F1A212C9F15EB007527E2 /* XCRemoteSwiftPackageReference "RxGesture" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/RxSwiftCommunity/RxGesture"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 4.0.4; + }; + }; 3664CFD82C6E2179007FB5DF /* XCRemoteSwiftPackageReference "lottie-ios" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/airbnb/lottie-ios"; @@ -2161,6 +2197,11 @@ package = 363F1A162C9CA04F007527E2 /* XCRemoteSwiftPackageReference "Amplitude-iOS" */; productName = Amplitude; }; + 363F1A222C9F15EB007527E2 /* RxGesture */ = { + isa = XCSwiftPackageProductDependency; + package = 363F1A212C9F15EB007527E2 /* XCRemoteSwiftPackageReference "RxGesture" */; + productName = RxGesture; + }; 3664CFD92C6E2179007FB5DF /* Lottie */ = { isa = XCSwiftPackageProductDependency; package = 3664CFD82C6E2179007FB5DF /* XCRemoteSwiftPackageReference "lottie-ios" */; diff --git a/DDANZI_iOS/DDANZI_iOS/Common/LocalStorage/Keychainwrapper.swift b/DDANZI_iOS/DDANZI_iOS/Common/LocalStorage/Keychainwrapper.swift index 415ace2..401094e 100644 --- a/DDANZI_iOS/DDANZI_iOS/Common/LocalStorage/Keychainwrapper.swift +++ b/DDANZI_iOS/DDANZI_iOS/Common/LocalStorage/Keychainwrapper.swift @@ -131,7 +131,7 @@ public final class KeychainWrapper { return status == errSecSuccess } - private func deleteAccessToken() -> Bool { + func deleteAccessToken() -> Bool { let query: [CFString: Any] = [ kSecClass: kSecClassGenericPassword, kSecAttrService: userKey diff --git a/DDANZI_iOS/DDANZI_iOS/Common/Resource/Assets.xcassets/AppIcon_dev.appiconset/Contents.json b/DDANZI_iOS/DDANZI_iOS/Common/Resource/Assets.xcassets/AppIcon_dev.appiconset/Contents.json new file mode 100644 index 0000000..3193f63 --- /dev/null +++ b/DDANZI_iOS/DDANZI_iOS/Common/Resource/Assets.xcassets/AppIcon_dev.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "appicon.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DDANZI_iOS/DDANZI_iOS/Common/Resource/Assets.xcassets/AppIcon_dev.appiconset/appicon.png b/DDANZI_iOS/DDANZI_iOS/Common/Resource/Assets.xcassets/AppIcon_dev.appiconset/appicon.png new file mode 100644 index 0000000..61ed614 Binary files /dev/null and b/DDANZI_iOS/DDANZI_iOS/Common/Resource/Assets.xcassets/AppIcon_dev.appiconset/appicon.png differ diff --git a/DDANZI_iOS/DDANZI_iOS/Data/DTO/Auth/RefreshTokenRequestDTO.swift b/DDANZI_iOS/DDANZI_iOS/Data/DTO/Auth/RefreshTokenRequestDTO.swift new file mode 100644 index 0000000..66dc826 --- /dev/null +++ b/DDANZI_iOS/DDANZI_iOS/Data/DTO/Auth/RefreshTokenRequestDTO.swift @@ -0,0 +1,12 @@ +// +// RefreshTokenRequestDTO.swift +// DDANZI_iOS +// +// Created by 이지희 on 9/22/24. +// + +import Foundation + +struct RefreshTokenRequestDTO: Codable { + let refreshtoken : String +} diff --git a/DDANZI_iOS/DDANZI_iOS/Data/DTO/Item/itemConfirmedDTO.swift b/DDANZI_iOS/DDANZI_iOS/Data/DTO/Item/itemConfirmedDTO.swift index 68f1897..9874a11 100644 --- a/DDANZI_iOS/DDANZI_iOS/Data/DTO/Item/itemConfirmedDTO.swift +++ b/DDANZI_iOS/DDANZI_iOS/Data/DTO/Item/itemConfirmedDTO.swift @@ -11,7 +11,7 @@ struct itemConformedDTO: Codable { let productID, productName: String let imgURL: String let originPrice, salePrice: Int - let isAccountExist: Bool + var isAccountExist: Bool enum CodingKeys: String, CodingKey { case productID = "productId" diff --git a/DDANZI_iOS/DDANZI_iOS/Data/DTO/Mypage/UserAccountRequestDTO.swift b/DDANZI_iOS/DDANZI_iOS/Data/DTO/Mypage/UserAccountRequestDTO.swift new file mode 100644 index 0000000..5cc1b9f --- /dev/null +++ b/DDANZI_iOS/DDANZI_iOS/Data/DTO/Mypage/UserAccountRequestDTO.swift @@ -0,0 +1,14 @@ +// +// UserAccountRequestDTO.swift +// DDANZI_iOS +// +// Created by 이지희 on 9/21/24. +// + +import Foundation + +struct UserAccountRequestDTO: Codable { + let accountName: String + let bank: String + let accountNumber: String +} diff --git a/DDANZI_iOS/DDANZI_iOS/Data/DTO/Setting/UserAddressResponseDTO.swift b/DDANZI_iOS/DDANZI_iOS/Data/DTO/Setting/UserAddressResponseDTO.swift index 44fcd77..fbac4ce 100644 --- a/DDANZI_iOS/DDANZI_iOS/Data/DTO/Setting/UserAddressResponseDTO.swift +++ b/DDANZI_iOS/DDANZI_iOS/Data/DTO/Setting/UserAddressResponseDTO.swift @@ -11,9 +11,9 @@ import Foundation struct UserAddressResponseDTO: Codable { let addressID: Int? let recipient: String? - let zipCode: String - let type: AddressType - let address, detailAddress, recipientPhone: String + let zipCode: String? + let type: AddressType? + let address, detailAddress, recipientPhone: String? enum CodingKeys: String, CodingKey { case addressID = "addressId" diff --git a/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/AuthEndpoint.swift b/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/AuthEndpoint.swift index 532cb93..d6a0e4d 100644 --- a/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/AuthEndpoint.swift +++ b/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/AuthEndpoint.swift @@ -14,7 +14,7 @@ enum AuthEndpoint { case certification(VerificationRequestDTO) case revoke case logout - case refreshToken + case refreshToken(RefreshTokenRequestDTO) } extension AuthEndpoint: BaseTargetType { @@ -73,8 +73,8 @@ extension AuthEndpoint: BaseTargetType { return .requestPlain case .logout: return .requestPlain - case .refreshToken: - return .requestPlain + case let .refreshToken(dto): + return .requestJSONEncodable(dto) } } diff --git a/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/MypageEndpoint.swift b/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/MypageEndpoint.swift index 11b2181..818c1b5 100644 --- a/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/MypageEndpoint.swift +++ b/DDANZI_iOS/DDANZI_iOS/Data/Endpoint/MypageEndpoint.swift @@ -20,12 +20,14 @@ enum MypageEndpoint { /// 주소 관련 API case fetchUserAddress case addUserAddress(UserAddressRequestDTO) - case editUserAddress(Int) + case editUserAddress(Int,UserAddressRequestDTO) case deleteUserAddress(Int) /// 계좌 관련 API case fetchUserAccount - case addUserAccount + case addUserAccount(UserAccountRequestDTO) + case editUserAccount(Int,UserAccountRequestDTO) + case deleteUserAccount(Int) case settingUserNoti } @@ -51,7 +53,7 @@ extension MypageEndpoint: BaseTargetType { return "/api/v1/mypage/setting/address" case .addUserAddress: return "/api/v1/mypage/setting/address" - case let .editUserAddress(id): + case let .editUserAddress(id, _): return "/api/v1/mypage/setting/address/\(id)" case let .deleteUserAddress(id): return "/api/v1/mypage/setting/address/\(id)" @@ -61,6 +63,10 @@ extension MypageEndpoint: BaseTargetType { return "/api/v1/mypage/setting/account" case .addUserAccount: return "/api/v1/mypage/setting/account" + case let .editUserAccount(id, _): + return "/api/v1/mypage/setting/account/\(id)" + case let .deleteUserAccount(id): + return "/api/v1/mypage/setting/account/\(id)" } } @@ -90,6 +96,10 @@ extension MypageEndpoint: BaseTargetType { return .get case .addUserAccount: return .post + case .editUserAccount: + return .put + case .deleteUserAccount: + return .delete } } @@ -109,7 +119,7 @@ extension MypageEndpoint: BaseTargetType { return .requestPlain case let .addUserAddress(body): return .requestJSONEncodable(body) - case .editUserAddress: + case let .editUserAddress(_, body): return .requestPlain case .deleteUserAddress: return .requestPlain @@ -117,7 +127,11 @@ extension MypageEndpoint: BaseTargetType { return .requestPlain case .fetchUserAccount: return .requestPlain - case .addUserAccount: + case .addUserAccount(let body): + return .requestJSONEncodable(body) + case let .editUserAccount(_, body): + return .requestJSONEncodable(body) + case .deleteUserAccount(_): return .requestPlain } } diff --git a/DDANZI_iOS/DDANZI_iOS/Data/Network/Foundation/Interceptor.swift b/DDANZI_iOS/DDANZI_iOS/Data/Network/Foundation/Interceptor.swift index 9c92afb..f7a4de7 100644 --- a/DDANZI_iOS/DDANZI_iOS/Data/Network/Foundation/Interceptor.swift +++ b/DDANZI_iOS/DDANZI_iOS/Data/Network/Foundation/Interceptor.swift @@ -22,38 +22,65 @@ final class AuthInterceptor: RequestInterceptor { func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { print("-------🔧retry 시작🔧-------") - guard (request.response?.statusCode) != nil - else { + guard let statusCode = request.response?.statusCode else { print("🚨status code 오류") return completion(.doNotRetry) } - if request.retryCount < retryLimit { - if let statusCode = request.response?.statusCode, - request.retryCount < retryLimit { - if statusCode == 401 { - refreshToken() - } else { - completion(.doNotRetryWithError(error)) - return + if request.retryCount < retryLimit { + if statusCode == 401 { + refreshToken { success in + if success { + // Retry the request if token refresh was successful + completion(.retry) + } else { + // Token refresh failed; do not retry + completion(.doNotRetry) + } } + } else { + completion(.doNotRetryWithError(error)) } + } else { + completion(.doNotRetry) } } + } extension AuthInterceptor { - private func refreshToken() { - Providers.AuthProvider.request(target: .refreshToken, instance: BaseResponse.self) { response in - guard let data = response.data else { return } + private func refreshToken(completion: @escaping (Bool) -> Void) { + let body = RefreshTokenRequestDTO(refreshtoken: UserDefaults.standard.string(forKey: .refreshToken) ?? "") + Providers.AuthProvider.request(target: .refreshToken(body), instance: BaseResponse.self) { response in + guard let data = response.data else { + // Token refresh failed + self.handleTokenRefreshFailure() + completion(false) // Indicate failure + return + } + if let accessToken = data.accesstoken { KeychainWrapper.shared.setAccessToken(accessToken) UserDefaults.standard.set(data.refreshtoken, forKey: .refreshToken) } - if response.status == 401 { + if response.status == 401 { UserDefaults.standard.set(data.refreshtoken, forKey: .refreshToken) } + + completion(true) // Indicate success } } + + + private func handleTokenRefreshFailure() { + // Clear tokens + KeychainWrapper.shared.deleteAccessToken() + UserDefaults.standard.removeObject(forKey: .refreshToken) + + // Set isLogin to false + UserDefaults.standard.set(false, forKey: "isLogin") + + print("🚨 Token refresh failed. Tokens cleared and isLogin set to false.") + } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Common/NavigationBar/CustomNavigationBarView.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/NavigationBar/CustomNavigationBarView.swift index 5fd51d6..898ac1f 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Common/NavigationBar/CustomNavigationBarView.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/NavigationBar/CustomNavigationBarView.swift @@ -49,7 +49,7 @@ final class CustomNavigationBarView: UIView { // MARK: - componenets private var leftView = UIView(frame: .init(x: 0, y: 0, width: 25, height: 25)) - private var subView = UIView(frame: .init(x: 0, y: 0, width: 25, height: 25)) + private var subView = UIView(frame: .init(x: 0, y: 0, width: 25, height: 25)) private var rightView = UIView(frame: .init(x: 0, y: 0, width: 25, height: 25)) private let titleLabel = UILabel().then { diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Common/ReusableView/CustomAlertView.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/ReusableView/CustomAlertView.swift new file mode 100644 index 0000000..0021028 --- /dev/null +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/ReusableView/CustomAlertView.swift @@ -0,0 +1,131 @@ +// +// CustomAlertView.swift +// DDANZI_iOS +// +// Created by 이지희 on 9/22/24. +// + +import UIKit + +import Then +import SnapKit +import RxSwift + +final class CustomAlertViewController: UIViewController { + + var primaryButtonTap = PublishSubject() + private let disposeBag = DisposeBag() + + private let dimView = UIView().then { + $0.backgroundColor = .black.withAlphaComponent(0.3) + } + + private let alertView = UIView().then { + $0.backgroundColor = .white + $0.makeCornerRound(radius: 10) + } + + private let innerStackView = UIStackView().then { + $0.axis = .vertical + $0.alignment = .center + $0.spacing = 20.adjusted + } + + private let titleLabel = UILabel().then { + $0.font = .title4Sb24 + $0.textAlignment = .center + $0.textColor = .black + } + + private let contentLabel = UILabel().then { + $0.font = .body4R16 + $0.textAlignment = .center + $0.numberOfLines = 3 + $0.textColor = .black + } + + private let conformButton = UIButton().then { + $0.titleLabel?.font = .body3Sb16 + $0.backgroundColor = .black + $0.setTitleColor(.white, for: .normal) + $0.makeCornerRound(radius: 10) + } + + private let subButton = UIButton().then { + $0.setUnderline() + $0.titleLabel?.font = .buttonText + $0.setTitleColor(.gray3, for: .normal) + $0.isHidden = true + } + + init(title: String, content: String, buttonText: String, subButton: String?){ + self.titleLabel.text = title + self.contentLabel.text = content + self.conformButton.setTitle(buttonText, for: .normal) + if let subButton { + self.subButton.setTitle(subButton, for: .normal) + self.subButton.isHidden = false + } + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setUI() + bindActions() + } + + private func setUI() { + setHierarchy() + setConstraints() + // Add tap gesture recognizer to dimView + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissAlert)) + dimView.addGestureRecognizer(tapGesture) + } + + @objc private func dismissAlert() { + dismiss(animated: true, completion: nil) + } + + private func setHierarchy() { + view.addSubview(dimView) + dimView.addSubview(alertView) + alertView.addSubviews(innerStackView) + innerStackView.addArrangedSubviews(titleLabel, contentLabel, conformButton, subButton) + } + + private func setConstraints() { + dimView.snp.makeConstraints { + $0.edges.equalToSuperview() + } + + alertView.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.trailing.equalToSuperview().inset(24) + $0.height.equalTo(240.adjusted) + } + + innerStackView.snp.makeConstraints { + $0.center.equalToSuperview() + $0.leading.trailing.equalToSuperview().inset(15) + } + + conformButton.snp.makeConstraints { + $0.height.equalTo(52.adjusted) + $0.leading.trailing.equalToSuperview().inset(10) + } + } + + private func bindActions() { + conformButton.rx.tap + .do(onNext: { [weak self] in + self?.dismiss(animated: false, completion: nil) + }) + .bind(to: primaryButtonTap) + .disposed(by: disposeBag) + } +} diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Common/ReusableView/DdanziButton.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/ReusableView/DdanziButton.swift index 1f990a4..15dd9e1 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Common/ReusableView/DdanziButton.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Common/ReusableView/DdanziButton.swift @@ -17,9 +17,9 @@ final class DdanziButton: UIButton { fatalError("init(coder:) has not been implemented") } - func setEnable() { - self.backgroundColor = .black - self.isEnabled = true + func setEnable(isEnable: Bool = true) { + self.backgroundColor = isEnable ? .black : .gray2 + self.isEnabled = isEnable } private func configureButton(title: String) { diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Home/ViewController/HomeViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Home/ViewController/HomeViewController.swift index 2eff150..d0a9ca7 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Home/ViewController/HomeViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Home/ViewController/HomeViewController.swift @@ -232,7 +232,12 @@ final class HomeViewController: UIViewController, UIScrollViewDelegate { navigationBarView.alarmButtonTap .bind(with: self) { owner, _ in - self.navigationController?.pushViewController(PushViewController(), animated: true) + if UserDefaults.standard.bool(forKey: .isLogin) { + self.navigationController?.pushViewController(PushViewController(), animated: true) + } else { + owner.navigationController?.pushViewController(LoginViewController(signUpFrom: "sell"), animated: true) + owner.view.showToast(message: "로그인이 필요한 서비스 입니다.", at: 50) + } } .disposed(by: disposeBag) } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/Model/BankList.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/Model/BankList.swift new file mode 100644 index 0000000..3b76dd9 --- /dev/null +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/Model/BankList.swift @@ -0,0 +1,80 @@ +// +// BankList.swift +// DDANZI_iOS +// +// Created by 이지희 on 9/22/24. +// + +import Foundation +struct Bank { + let code: String + let name: String +} + +struct BankList { + static let banks: [Bank] = [ + Bank(code: "001", name: "한국은행"), + Bank(code: "002", name: "산업은행"), + Bank(code: "003", name: "기업은행"), + Bank(code: "004", name: "국민은행"), + Bank(code: "005", name: "외환은행"), + Bank(code: "007", name: "수협중앙회"), + Bank(code: "008", name: "수출입은행"), + Bank(code: "011", name: "농협은행"), + Bank(code: "012", name: "농협회원조합"), + Bank(code: "020", name: "우리은행"), + Bank(code: "023", name: "SC제일은행"), + Bank(code: "027", name: "한국씨티은행"), + Bank(code: "031", name: "대구은행"), + Bank(code: "032", name: "부산은행"), + Bank(code: "034", name: "광주은행"), + Bank(code: "035", name: "제주은행"), + Bank(code: "037", name: "전북은행"), + Bank(code: "039", name: "경남은행"), + Bank(code: "045", name: "새마을금고연합회"), + Bank(code: "048", name: "신협중앙회"), + Bank(code: "050", name: "상호저축은행"), + Bank(code: "052", name: "모건스탠리은행"), + Bank(code: "054", name: "HSBC은행"), + Bank(code: "055", name: "도이치은행"), + Bank(code: "056", name: "에이비엔암로은행"), + Bank(code: "057", name: "제이피모간체이스은행"), + Bank(code: "058", name: "미즈호코퍼레이트은행"), + Bank(code: "059", name: "미쓰비시도쿄UFJ은행"), + Bank(code: "060", name: "BOA"), + Bank(code: "071", name: "정보통신부 우체국"), + Bank(code: "076", name: "신용보증기금"), + Bank(code: "077", name: "기술신용보증기금"), + Bank(code: "081", name: "하나은행"), + Bank(code: "088", name: "신한은행"), + Bank(code: "093", name: "한국주택금융공사"), + Bank(code: "094", name: "서울보증보험"), + Bank(code: "095", name: "경찰청"), + Bank(code: "099", name: "금융결제원"), + Bank(code: "209", name: "동양종합금융증권"), + Bank(code: "218", name: "현대증권"), + Bank(code: "230", name: "미래에셋증권"), + Bank(code: "238", name: "대우증권"), + Bank(code: "240", name: "삼성증권"), + Bank(code: "243", name: "한국투자증권"), + Bank(code: "247", name: "우리투자증권"), + Bank(code: "261", name: "교보증권"), + Bank(code: "262", name: "하이투자증권"), + Bank(code: "263", name: "에이치엠씨투자증권"), + Bank(code: "264", name: "키움증권"), + Bank(code: "265", name: "이트레이드증권"), + Bank(code: "266", name: "에스케이증권"), + Bank(code: "267", name: "대신증권"), + Bank(code: "268", name: "솔로몬투자증권"), + Bank(code: "269", name: "한화투자증권"), + Bank(code: "270", name: "하나대투증권"), + Bank(code: "278", name: "굿모닝신한증권"), + Bank(code: "279", name: "동부증권"), + Bank(code: "280", name: "유진투자증권"), + Bank(code: "287", name: "메리츠증권"), + Bank(code: "289", name: "엔에이치투자증권"), + Bank(code: "290", name: "부국증권"), + Bank(code: "291", name: "신영증권"), + Bank(code: "292", name: "엘아이지투자증권") + ] +} diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountAddCell.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountAddCell.swift new file mode 100644 index 0000000..fa2a4e0 --- /dev/null +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountAddCell.swift @@ -0,0 +1,77 @@ +// +// AccountAddCell.swift +// DDANZI_iOS +// +// Created by 이지희 on 9/21/24. +// + +import UIKit + +import Then +import SnapKit +import RxSwift + +final class AccountAddCell: UITableViewCell { + + let disposeBag = DisposeBag() + + private let titleLabel = UILabel().then { + $0.font = .body2Sb18 + $0.textColor = .black + } + let textField = UITextField().then { + $0.font = .body4R16 + } + private let lineView = UIView().then { + $0.backgroundColor = .black + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func prepareForReuse() { + super.prepareForReuse() + textField.text = "" + textField.isUserInteractionEnabled = true + } + + func configureCell(title: String, placeHolder: String, isEditable: Bool) { + titleLabel.text = title + textField.placeholder = placeHolder + textField.isUserInteractionEnabled = isEditable + } + + private func setUI() { + self.selectionStyle = .none + setHierarchy() + setConstraints() + } + + private func setHierarchy() { + addSubviews(titleLabel, textField, lineView) + } + + private func setConstraints() { + titleLabel.snp.makeConstraints { + $0.leading.equalToSuperview().offset(20) + $0.top.equalToSuperview().offset(7) + } + + textField.snp.makeConstraints { + $0.top.equalTo(titleLabel.snp.bottom).offset(10) + $0.leading.trailing.equalToSuperview().inset(20) + } + + lineView.snp.makeConstraints { + $0.height.equalTo(1) + $0.leading.trailing.equalToSuperview().inset(20) + $0.bottom.equalToSuperview().inset(7) + } + } +} diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountAddViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountAddViewController.swift new file mode 100644 index 0000000..88f0d76 --- /dev/null +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountAddViewController.swift @@ -0,0 +1,240 @@ +// +// AccountAddViewController.swift +// DDANZI_iOS +// +// Created by 이지희 on 9/21/24. +// + +import UIKit + +import Then +import SnapKit + +import RxSwift +import RxCocoa +import RxDataSources +import RxGesture + +final class AccountAddViewController: UIViewController { + + // MARK: Property + private var bankAccountId: Int? + private let titles = ["이름", "은행", "계좌번호"] + private var userName = UserDefaults.standard.string(forKey: "name") ?? "사용자" + + private let disposeBag = DisposeBag() + // 계좌 등록 완료 상태를 전달할 Relay + var accountRegisteredRelay = PublishRelay() + + private var selectedBankCode: String? + private lazy var nameSubject = BehaviorSubject(value: userName) + private let bankSubject = BehaviorSubject(value: "") + private let accountNumberSubject = BehaviorSubject(value: "") + + // MARK: UI + private let navigationView = CustomNavigationBarView(navigationBarType: .normal) + private let headerView = MyPageSectionHeaderView().then { + $0.setTitleLabel(title: "계좌 등록") + } + private let tableView = UITableView(frame: .zero, style: .plain).then { + $0.rowHeight = 75 + $0.register(AccountAddCell.self, forCellReuseIdentifier: AccountAddCell.className) + $0.separatorStyle = .none + $0.isScrollEnabled = false + } + private let conformButton = DdanziButton(title: "입력 완료", enable: false) + + init(bankAccountId: Int?) { + self.bankAccountId = bankAccountId + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: LifeCycle + override func viewDidLoad() { + super.viewDidLoad() + userName = maskMiddleCharacters(of: userName) // 이름 중간 글자 마스킹 처리 + setUI() + bind() + setupDismissKeyboardGesture() + } + + private func setUI() { + self.view.backgroundColor = .white + setHierarchy() + setConstraints() + } + + private func setHierarchy() { + view.addSubviews(navigationView, headerView, tableView, conformButton) + } + + private func setConstraints() { + navigationView.snp.makeConstraints { + $0.top.equalTo(view.safeAreaLayoutGuide) + $0.leading.trailing.equalToSuperview() + } + + headerView.snp.makeConstraints { + $0.top.equalTo(navigationView.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(48) + } + + tableView.snp.makeConstraints { + $0.top.equalTo(headerView.snp.bottom) + $0.leading.trailing.bottom.equalToSuperview() + } + + conformButton.snp.makeConstraints { + $0.height.equalTo(52.adjusted) + $0.leading.trailing.equalToSuperview().inset(20.adjusted) + $0.bottom.equalToSuperview().inset(58.adjusted) + } + } + + private func bind() { + tableView.delegate = self + tableView.dataSource = self + + navigationView.backButtonTap + .subscribe(with: self) { owner, _ in + owner.navigationController?.popViewController(animated: true) + } + .disposed(by: disposeBag) + + // 모든 필드가 채워졌을 때 버튼 활성화 + Observable.combineLatest(nameSubject, bankSubject, accountNumberSubject) + .map { !$0.0.isEmpty && !$0.1.isEmpty && !$0.2.isEmpty } + .bind(with: self, onNext: { owner, isEnable in + if isEnable { owner.conformButton.setEnable() } + }) + .disposed(by: disposeBag) + + conformButton.rx.tap + .subscribe(with: self) { owner, _ in + // 등록 버튼 눌렀을 때의 처리 + let accountName = UserDefaults.standard.string(forKey: .name) + let bank = self.selectedBankCode + let accountNumber = try? owner.accountNumberSubject.value() + if let bankAccountId = self.bankAccountId { + owner.editAccount(accountId: bankAccountId, accountName: accountName ?? "", bank: bank ?? "", accountNumber: accountNumber ?? "") + } else { + owner.conformAccount(accountName: accountName ?? "", bank: bank ?? "", accountNumber: accountNumber ?? "") + } + } + .disposed(by: disposeBag) + } + + + func showBankSelection() { + let alert = UIAlertController(title: "은행 선택", message: nil, preferredStyle: .actionSheet) + + for bank in BankList.banks { + let action = UIAlertAction(title: bank.name, style: .default) { _ in + // 은행 선택 시 코드 저장 + self.selectedBankCode = bank.code + self.bankSubject.onNext(bank.name) + } + alert.addAction(action) + } + + let cancel = UIAlertAction(title: "취소", style: .cancel, handler: nil) + alert.addAction(cancel) + + present(alert, animated: true, completion: nil) + } + + private func maskMiddleCharacters(of name: String) -> String { + guard name.count >= 2 else { return name } + let middleIndex = name.index(name.startIndex, offsetBy: name.count / 2) + return name.replacingCharacters(in: middleIndex...middleIndex, with: "*") + } + + private func editAccount(accountId: Int, accountName: String, bank: String, accountNumber: String) { + let body = UserAccountRequestDTO(accountName: accountName, bank: bank, accountNumber: accountNumber) + Providers.MypageProvider.request(target: .editUserAccount(accountId, body), instance: BaseResponse.self) { response in + guard let data = response.data else { return } + if response.status != 200 || response.status != 201 { + self.view.showToast(message: "계좌 등록 오류 입니다. 잠시 후 다시 시도해주세요", at: 100) + } else { + self.navigationController?.popViewController(animated: true) + } + } + } + + private func conformAccount(accountName: String, bank: String, accountNumber: String) { + let body = UserAccountRequestDTO(accountName: accountName, bank: bank, accountNumber: accountNumber) + Providers.MypageProvider.request(target: .addUserAccount(body), instance: BaseResponse.self) { response in + guard let data = response.data else { return } + if response.status != 200 || response.status != 201 { + self.view.showToast(message: "계좌 등록 오류 입니다. 잠시 후 다시 시도해주세요", at: 100) + } else { + self.accountRegisteredRelay.accept(true) + self.navigationController?.popViewController(animated: true) + } + } + } + + private func setupDismissKeyboardGesture() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) + tapGesture.cancelsTouchesInView = false // 터치 이벤트가 다른 뷰로 전파되도록 설정 + view.addGestureRecognizer(tapGesture) + } + + @objc private func dismissKeyboard() { + view.endEditing(true) + } +} + +extension AccountAddViewController: UITableViewDataSource { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 3 + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: AccountAddCell.className, for: indexPath) as! AccountAddCell + switch indexPath.row { + case 0: + cell.configureCell(title: self.titles[0], placeHolder: self.userName, isEditable: false) + cell.textField.text = self.userName + case 1: + cell.configureCell(title: self.titles[1], placeHolder: "은행을 선택해주세요", isEditable: false) + self.bankSubject + .bind(to: cell.textField.rx.text) + .disposed(by: cell.disposeBag) + case 2: + cell.configureCell(title: self.titles[2], placeHolder: "-를 제외한 계좌번호를 작성해주세요", isEditable: true) + // 계좌번호 입력 값을 accountNumberSubject에 바인딩 + cell.textField.rx.text.orEmpty + .bind(to: self.accountNumberSubject) + .disposed(by: cell.disposeBag) + + cell.textField.delegate = self + default: + break + } + return cell + } +} + +extension AccountAddViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.row == 1 { + self.showBankSelection() + } else if indexPath.row == 2 { + let cell = tableView.cellForRow(at: indexPath) as? AccountAddCell + cell?.textField.becomeFirstResponder() // 포커스 설정 + } + } +} + +extension AccountAddViewController: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } +} diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountViewController.swift index 6ab35b9..e5f0499 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AccountViewController.swift @@ -100,13 +100,13 @@ extension AccountViewController: UITableViewDataSource { UserApi.shared.logout {(error) in if let error = error { print(error) - + KeychainWrapper.shared.deleteAccessToken() UserDefaults.standard.set(false, forKey: .isLogin) self.navigationController?.popToRootViewController(animated: true) } else { UserDefaults.standard.clearAll() - + KeychainWrapper.shared.deleteAccessToken() UserDefaults.standard.set(false, forKey: .isLogin) self.navigationController?.popToRootViewController(animated: true) print("logout() success.") @@ -125,7 +125,7 @@ extension AccountViewController: UITableViewDataSource { } else { UserDefaults.standard.clearAll() - KeychainWrapper.shared.setAccessToken("") + KeychainWrapper.shared.deleteAccessToken() UserDefaults.standard.set(false, forKey: .isLogin) self.navigationController?.popToRootViewController(animated: true) } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressFormViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressFormViewController.swift index 4e533e2..7ce0cf5 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressFormViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressFormViewController.swift @@ -32,7 +32,7 @@ final class AddressFormViewController: UIViewController { $0.rowHeight = 75 $0.separatorStyle = .none $0.scrollsToTop = false - $0.contentInset = .init(top: 30, left: 0, bottom: 0, right: 0) + $0.contentInset = .init(top: 10, left: 0, bottom: 0, right: 0) } private let headerView = MyPageSectionHeaderView().then { $0.setTitleLabel(title: "배송지 등록") diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressSettingViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressSettingViewController.swift index 7434a0b..b9816e0 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressSettingViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/AddressSettingViewController.swift @@ -145,15 +145,21 @@ class AddressSettingViewController: UIViewController { Providers.MypageProvider.request(target: .fetchUserAddress, instance: BaseResponse.self) { result in guard let data = result.data else { return } - let newAddress = Address(name: data.recipient ?? "", - address: "(\(data.zipCode) \(data.address), \(data.detailAddress))", - phone: data.recipientPhone) - - var currentList = (try? self.addressListSubject.value()) ?? [] - currentList.append(newAddress) - self.addressId = data.addressID ?? 0 - - self.addressListSubject.onNext(currentList) + if let name = data.recipient, + let zipcode = data.zipCode, + let address = data.address, + let detailAddress = data.detailAddress, + let phone = data.recipientPhone { + let newAddress = Address(name: name, + address: "(\(zipcode) \(address), \(detailAddress))", + phone: phone) + + var currentList = (try? self.addressListSubject.value()) ?? [] + currentList.append(newAddress) + + self.addressId = data.addressID ?? 0 + self.addressListSubject.onNext(currentList) + } } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/BankAccountViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/BankAccountViewController.swift index 985efb0..df543c6 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/BankAccountViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/Info/BankAccountViewController.swift @@ -14,13 +14,26 @@ import RxSwift class BankAccountViewController: UIViewController { private let disposeBag = DisposeBag() + private var bankAccountId: Int? private let navigationBarView = CustomNavigationBarView(navigationBarType: .normal) private let headerView = MyPageSectionHeaderView() + + private let registerButton = UIButton().then { + $0.backgroundColor = .white + $0.makeBorder(width: 1, color: .gray2) + $0.setTitleColor(.black, for: .normal) + $0.setTitle("+ 계좌 등록", for: .normal) + $0.titleLabel?.font = .body4R16 + $0.makeCornerRound(radius: 10) + $0.isHidden = true // 초기 상태는 숨김 + } + private let accountButton = UIButton().then { $0.backgroundColor = .white $0.makeCornerRound(radius: 10) $0.makeBorder(width: 1, color: .gray2) + $0.isHidden = true // 초기 상태는 숨김 } private let stackView = UIStackView().then { @@ -70,10 +83,11 @@ class BankAccountViewController: UIViewController { private func setHierarchy() { view.addSubviews(navigationBarView, headerView, - accountButton) + accountButton, + registerButton) accountButton.addSubviews(stackView, - detailButton) + detailButton) stackView.addArrangedSubviews(bankNameLabel, innerStackView) @@ -99,6 +113,12 @@ class BankAccountViewController: UIViewController { $0.height.equalTo(100) } + registerButton.snp.makeConstraints { + $0.top.equalTo(headerView.snp.bottom).offset(19) + $0.leading.trailing.equalToSuperview().inset(20) + $0.height.equalTo(100) + } + stackView.snp.makeConstraints { $0.centerY.equalToSuperview() $0.leading.equalToSuperview().offset(18) @@ -114,10 +134,20 @@ class BankAccountViewController: UIViewController { func fetchAccountInfo() { Providers.MypageProvider.request(target: .fetchUserAccount, instance: BaseResponse.self) { response in guard let data = response.data else { return } - - self.bankNameLabel.text = data.bank ?? "" - self.nameLabel.text = data.name - self.accountNumberLabel.text = data.accountNumber ?? "" + if let bank = data.bank, + let accountNumber = data.accountNumber, + let accountId = data.accountId { + self.bankNameLabel.text = bank + self.nameLabel.text = data.name + self.accountNumberLabel.text = accountNumber + self.accountButton.isHidden = false + self.registerButton.isHidden = true // 계좌 정보가 있으면 등록 버튼을 숨김 + self.bankAccountId = accountId + } else { + // 계좌 정보가 없으면 등록 버튼을 보이고 계좌 정보 버튼을 숨김 + self.accountButton.isHidden = true + self.registerButton.isHidden = false + } } } @@ -127,5 +157,19 @@ class BankAccountViewController: UIViewController { self?.navigationController?.popViewController(animated: true) }) .disposed(by: disposeBag) + + registerButton.rx.tap + .subscribe(onNext: { [weak self] in + // 계좌 등록 화면으로 이동하는 로직 추가 + self?.navigateToAccountRegistration() + }) + .disposed(by: disposeBag) + } + + // 계좌 등록 화면으로 이동하는 함수 + private func navigateToAccountRegistration() { + // 계좌 등록 화면 이동 로직을 구현 + let accountRegistrationVC = AccountAddViewController(bankAccountId: bankAccountId) + self.navigationController?.pushViewController(accountRegistrationVC, animated: true) } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/HelperView/MyPageSectionHeaderView.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/HelperView/MyPageSectionHeaderView.swift index 1f8e505..bfee0dc 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/HelperView/MyPageSectionHeaderView.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/HelperView/MyPageSectionHeaderView.swift @@ -11,50 +11,50 @@ import SnapKit import Then final class MyPageSectionHeaderView: UIView { - - private let titleLabel = UILabel().then { - $0.textColor = .black - $0.font = .body2Sb20 - } - - private let lineView = UIView().then { - $0.backgroundColor = .black - } - - init() { - super.init(frame: .zero) - setUI() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setUI() { - self.backgroundColor = .white - setHierarchy() - setConstraints() - } - - private func setHierarchy() { - self.addSubviews(titleLabel, lineView) - } - - private func setConstraints() { - titleLabel.snp.makeConstraints { - $0.centerY.equalToSuperview() - $0.leading.equalToSuperview().offset(20) - } - - lineView.snp.makeConstraints { - $0.height.equalTo(1) - $0.top.equalTo(titleLabel.snp.bottom).offset(5) - $0.leading.trailing.equalToSuperview().inset(20) - } - } - - func setTitleLabel(title: String){ - titleLabel.text = title - } + + private let titleLabel = UILabel().then { + $0.textColor = .black + $0.font = .body2Sb20 + } + + private let lineView = UIView().then { + $0.backgroundColor = .black + } + + init() { + super.init(frame: .zero) + setUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUI() { + self.backgroundColor = .white + setHierarchy() + setConstraints() + } + + private func setHierarchy() { + self.addSubviews(titleLabel, lineView) + } + + private func setConstraints() { + titleLabel.snp.makeConstraints { + $0.centerY.equalToSuperview() + $0.leading.equalToSuperview().offset(20) + } + + lineView.snp.makeConstraints { + $0.height.equalTo(1) + $0.top.equalTo(titleLabel.snp.bottom).offset(5) + $0.leading.trailing.equalToSuperview().inset(20) + } + } + + func setTitleLabel(title: String){ + titleLabel.text = title + } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/MyPageViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/MyPageViewController.swift index 344f0dd..dd2d1db 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/MyPageViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Mypage/View/MyPage/MyPageViewController.swift @@ -109,7 +109,7 @@ final class MyPageViewController: UIViewController { private func fetchUser() { Providers.MypageProvider.request(target: .fetchUser, instance: BaseResponse.self) { result in - if result.status == 403 { + if result.status != 200 { self.isLogin = false self.tableView.reloadData() } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/OptionSelect/OptionSelectViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/OptionSelect/OptionSelectViewController.swift index fcc1137..f130962 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/OptionSelect/OptionSelectViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/OptionSelect/OptionSelectViewController.swift @@ -115,7 +115,8 @@ final class OptionSelectViewController: UIViewController { bottomButton.button.rx.tap .bind { self.dismiss(animated: true) { - self.delegate?.optionViewControllerDidFinish(self) + let unwrappedOptions = self.selectedOptions.compactMap { $0 } + self.delegate?.optionViewControllerDidFinish(self, optionList: unwrappedOptions) Amplitude.instance().logEvent("click_option_next") } } @@ -154,26 +155,30 @@ final class OptionSelectViewController: UIViewController { optionCollectionView.rx.itemSelected .bind(with: self) { owner, indexPath in - if let cell = owner.optionCollectionView.cellForItem(at: indexPath) as? OptionCollectionViewCell { - cell.isSelectedRelay.accept(true) - owner.selectedOptions[indexPath.section] = self.option[indexPath.section].optionDetailList[indexPath.row].optionDetailID - owner.updateButtonState() + // 섹션 내 이전 선택 해제 + if let selectedIndex = owner.selectedOptions[indexPath.section] { + let deselectedIndexPath = IndexPath(row: selectedIndex, section: indexPath.section) + if let cell = owner.optionCollectionView.cellForItem(at: deselectedIndexPath) as? OptionCollectionViewCell { + cell.isSelectedRelay.accept(false) // 이전 선택 해제 + } } - } - .disposed(by: disposeBag) - - optionCollectionView.rx.itemDeselected - .bind(with: self) { owner, indexPath in + + // 선택한 옵션을 섹션별로 저장 + let selectedOptionId = owner.option[indexPath.section].optionDetailList[indexPath.row].optionDetailID + owner.selectedOptions[indexPath.section] = selectedOptionId // optionDetailId 저장 + if let cell = owner.optionCollectionView.cellForItem(at: indexPath) as? OptionCollectionViewCell { - cell.isSelectedRelay.accept(false) - owner.selectedOptions[indexPath.section] = nil - owner.updateButtonState() + cell.isSelectedRelay.accept(true) // 새로운 선택 적용 } + + owner.updateButtonState() // 버튼 상태 업데이트 } .disposed(by: disposeBag) + } private func updateButtonState() { + print(selectedOptions) let allSectionsSelected = selectedOptions.allSatisfy { $0 != nil } bottomButton.button.isEnabled = allSectionsSelected bottomButton.button.backgroundColor = allSectionsSelected ? .black : .gray2 diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/ProductDetailViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/ProductDetailViewController.swift index 9920a0c..4fbbb0b 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/ProductDetailViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/ProductDetail/ViewControllers/ProductDetailViewController.swift @@ -16,7 +16,7 @@ import Amplitude // MARK: - OptionDelegate protocol OptionViewControllerDelegate: AnyObject { - func optionViewControllerDidFinish(_ viewController: OptionSelectViewController) + func optionViewControllerDidFinish(_ viewController: OptionSelectViewController, optionList: [Int]) } final class ProductDetailViewController: UIViewController { @@ -324,9 +324,9 @@ final class ProductDetailViewController: UIViewController { } extension ProductDetailViewController: OptionViewControllerDelegate { - func optionViewControllerDidFinish(_ viewController: OptionSelectViewController) { + func optionViewControllerDidFinish(_ viewController: OptionSelectViewController, optionList: [Int]) { let purchaseVC = PurchaseViewController() - purchaseVC.orderModel = .init(productId: productId, optionList: optionList.map({ $0.optionID })) + purchaseVC.orderModel = .init(productId: productId, optionList: optionList) self.navigationController?.pushViewController(purchaseVC, animated: true) } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/TableViewCell/TermsTableViewCell.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/TableViewCell/TermsTableViewCell.swift index ba50c18..2d7838e 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/TableViewCell/TermsTableViewCell.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Purchase/TableViewCell/TermsTableViewCell.swift @@ -51,7 +51,7 @@ final class TermsTableViewCell: UITableViewCell { private func setConstraints() { containerView.snp.makeConstraints { $0.height.equalTo(38) - $0.horizontalEdges.equalToSuperview().inset(20) + $0.horizontalEdges.equalToSuperview() $0.verticalEdges.equalToSuperview().inset(5) } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemCell.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemCell.swift index ee0507a..5502985 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemCell.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemCell.swift @@ -19,6 +19,15 @@ final class RegisteItemCell: UICollectionViewCell { var selectedTerms = BehaviorRelay<[Bool]>(value: [false, false]) var itemInfo = PublishRelay() var dateString = BehaviorRelay(value: "") + + var isReadyToRegister: Observable { + return Observable.combineLatest(selectedTerms.asObservable(), dateString.asObservable()) + .map { termsSelected, date in + let allSelected = termsSelected.allSatisfy { $0 } + return allSelected && !date.isEmpty + } + } + private let disposeBag = DisposeBag() private let imageView = UIImageView() @@ -200,7 +209,7 @@ final class RegisteItemCell: UICollectionViewCell { } termsTableView.snp.makeConstraints { - $0.top.equalTo(fullAgreementButton.snp.bottom).offset(35.adjusted) + $0.top.equalTo(fullAgreementButton.snp.bottom).offset(18.adjusted) $0.leading.trailing.bottom.equalToSuperview() } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemViewController.swift index ace6312..852fb16 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Selling/RegisteItemViewController.swift @@ -102,7 +102,20 @@ final class RegisteItemViewController: UIViewController { } let dueDate = cell.dateString.value Amplitude.instance().logEvent("click_sell_next", withEventProperties: ["product_id": owner.info.productID]) - owner.registeItem(due: dueDate) + if !owner.info.isAccountExist { + let alertVC = CustomAlertViewController(title: "잠시만요!", content: "상품 판매금액 정산을 위해 입금 받으실 대표계좌 등록이 필요합니다.", buttonText: "계좌 등록 하러 가기", subButton: nil) + + alertVC.primaryButtonTap + .subscribe(onNext: { _ in + owner.navigateToAccountAdd(dueDate: dueDate) + }) + .disposed(by: owner.disposeBag) + + alertVC.modalPresentationStyle = .overFullScreen + owner.present(alertVC, animated: false, completion: nil) + } else { + owner.registeItem(due: dueDate) + } } .disposed(by: disposeBag) @@ -119,12 +132,29 @@ final class RegisteItemViewController: UIViewController { collectionView.delegate = self } + private func navigateToAccountAdd(dueDate: String) { + let accountAddVC = AccountAddViewController(bankAccountId: nil) + + // AccountAddViewController의 accountRegisteredRelay를 구독 + accountAddVC.accountRegisteredRelay + .subscribe(onNext: { [weak self] isRegistered in + guard let self = self else { return } + if isRegistered { + self.info.isAccountExist = true + self.registeItem(due: dueDate) // 계좌 등록이 완료되면 나머지 플로우 진행 + } + }) + .disposed(by: disposeBag) + self.navigationController?.pushViewController(accountAddVC, animated: true) + } + private func registeItem(due: String) { let body = RegisteItemBody(productId: info.productID, productName: info.productName, receivedDate: due, registeredImage: info.imgURL) Providers.ItemProvider.request(target: .registeItem(body: body), instance: BaseResponse.self) { response in guard let data = response.data else { return } let registeCompleteVC = RegisteCompleteViewController(response: data) let pushVC = PushSettingViewController(isSelling: true, orderId: "", response: data) + PermissionManager.shared.checkPermission(for: .notification) .bind(with: self, onNext: { owner, isAllow in Amplitude.instance().logEvent("complete_sell_adjustment", withEventProperties: ["item_id": data.itemId]) @@ -147,20 +177,12 @@ extension RegisteItemViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: RegisteItemCell.className, for: indexPath) as? RegisteItemCell else { return UICollectionViewCell() } cell.configure(info: self.info) - - // 날짜와 전체 동의 여부 확인 - Observable.combineLatest(cell.selectedTerms, cell.dateString) - .map { termsSelected, date in - // 모든 약관이 동의됐고, 날짜가 선택됐는지 확인 - let allSelected = termsSelected.allSatisfy { $0 } - return allSelected && !date.isEmpty - } - .bind(onNext: { isAllow in - self.registeButton.setEnable() + cell.isReadyToRegister + .subscribe(onNext: { [weak self] isReady in + guard let self = self else { return } + self.registeButton.setEnable(isEnable: isReady) }) .disposed(by: disposeBag) - - return cell } @@ -168,6 +190,6 @@ extension RegisteItemViewController: UICollectionViewDataSource { extension RegisteItemViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return CGSize(width: collectionView.frame.width, height: 850.adjusted) // 원하는 크기 설정 + return CGSize(width: collectionView.frame.width, height: 830.adjusted) } } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Model/PurchaseModel.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Model/PurchaseModel.swift index 4cc2992..f6d4f0f 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Model/PurchaseModel.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/Model/PurchaseModel.swift @@ -15,6 +15,8 @@ enum StatusType { case cancel case notDeposit case onSale + case delayedShipping + case warning var statusString: String { switch self { @@ -24,7 +26,7 @@ enum StatusType { return "주문 완료" case .deposit: return "입금 완료" - case .delivery: + case .delivery,.delayedShipping,.warning: return "배송 중" case .complete: return "거래 완료" @@ -52,6 +54,10 @@ enum StatusType { self = .cancel case "ON_SALE": self = .onSale + case "DELAYED_SHIPPING": + self = .delayedShipping + case "WARNING": + self = .warning default: return nil } diff --git a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/SalesDetailViewController.swift b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/SalesDetailViewController.swift index 8e866ba..2e52d99 100644 --- a/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/SalesDetailViewController.swift +++ b/DDANZI_iOS/DDANZI_iOS/Presentation/Transaction/ViewController/SalesDetailViewController.swift @@ -161,7 +161,7 @@ class SalesDetailViewController: UIViewController { case .deposit: owner.button.titleLabel?.text = "판매 확정하기" owner.button.setEnable() - case .delivery: + case .delivery, .delayedShipping, .warning: owner.button.titleLabel?.text = "배송 중인 상품입니다." case .complete: owner.button.titleLabel?.text = "거래가 완료된 상품입니다."