From b13dbaf8c19b9fe17ed2ab46b26dbb871d0f37e1 Mon Sep 17 00:00:00 2001 From: GeonH0 Date: Sat, 7 Sep 2024 18:26:14 +0900 Subject: [PATCH 1/5] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=EC=97=90=20=ED=95=84=EC=9A=94=ED=95=9C=20UI=20?= =?UTF-8?q?=EC=9A=94=EC=86=8C=20=EC=84=A0=EC=96=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HomeCafeRecipes.xcodeproj/project.pbxproj | 60 +++++ .../Login/SginUp/SignUpView.swift | 218 ++++++++++++++++++ .../Login/SginUp/SignUpViewController.swift | 71 ++++++ .../Login/SginUp/SignUpViewModel.swift | 15 ++ 4 files changed, 364 insertions(+) create mode 100644 HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpView.swift create mode 100644 HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewController.swift create mode 100644 HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewModel.swift diff --git a/HomeCafeRecipes/HomeCafeRecipes.xcodeproj/project.pbxproj b/HomeCafeRecipes/HomeCafeRecipes.xcodeproj/project.pbxproj index 2ede86d..0ceeffe 100644 --- a/HomeCafeRecipes/HomeCafeRecipes.xcodeproj/project.pbxproj +++ b/HomeCafeRecipes/HomeCafeRecipes.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 1D1283CA2C16D9C600C5A870 /* RecipeFetchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1283C92C16D9C600C5A870 /* RecipeFetchService.swift */; }; 1D166D0D2C4AD54E00A50963 /* AddRecipeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D0C2C4AD54D00A50963 /* AddRecipeViewModel.swift */; }; 1D166D0E2C4AD54E00A50963 /* AddRecipeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D166D0C2C4AD54D00A50963 /* AddRecipeViewModel.swift */; }; + 1D2398B22C8DC07800626F0C /* SignUpUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2398B12C8DC07800626F0C /* SignUpUseCase.swift */; }; + 1D2398B42C8DC23500626F0C /* SignUpError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2398B32C8DC23500626F0C /* SignUpError.swift */; }; 1D2C16E62BE532B700C04508 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C16E52BE532B700C04508 /* AppDelegate.swift */; }; 1D2C16EA2BE532B700C04508 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C16E92BE532B700C04508 /* ViewController.swift */; }; 1D2C16FD2BE532B800C04508 /* HomeCafeRecipesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D2C16FC2BE532B800C04508 /* HomeCafeRecipesTests.swift */; }; @@ -74,6 +76,7 @@ 1D6958E12C3D5E44008604B3 /* RecipeDetailDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */; }; 1D6958E22C3D5E99008604B3 /* RecipeImageDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CC2C1B4F8D009381CE /* RecipeImageDTO.swift */; }; 1D6958E42C3D5EA6008604B3 /* NetworkResponseDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D4741CF2C1B4F8D009381CE /* NetworkResponseDTO.swift */; }; + 1D6F99492C9291B700430FD8 /* UIViewController+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6F99482C9291B700430FD8 /* UIViewController+Alert.swift */; }; 1D73686E2C305757000EF904 /* RecipeDetailDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */; }; 1D7368702C32BFBB000EF904 /* AddRecipeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D73686F2C32BFBB000EF904 /* AddRecipeInteractor.swift */; }; 1D7368742C32CF09000EF904 /* AddRecipeRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D7368732C32CF09000EF904 /* AddRecipeRepository.swift */; }; @@ -87,8 +90,14 @@ 1D8474592C6CCF6900323001 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8474582C6CCF6900323001 /* TestUtils.swift */; }; 1D95A0A62C37C79500F09077 /* RecipeDetailError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D95A0A52C37C79500F09077 /* RecipeDetailError.swift */; }; 1D96FDAA2C7F55E600EFC657 /* LoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D96FDA92C7F55E600EFC657 /* LoginInteractor.swift */; }; + 1DBB55062C8418490009E033 /* LoginRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBB55052C8418490009E033 /* LoginRouter.swift */; }; + 1DBB55082C8421890009E033 /* SignUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBB55072C8421890009E033 /* SignUpView.swift */; }; + 1DBB550A2C8421920009E033 /* SignUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBB55092C8421920009E033 /* SignUpViewController.swift */; }; + 1DBB550D2C842AE70009E033 /* SignUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBB550C2C842AE70009E033 /* SignUpViewModel.swift */; }; 1DBC63662C47D23000DA00C2 /* AddRecipeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBC63652C47D23000DA00C2 /* AddRecipeError.swift */; }; 1DBC63672C47D23000DA00C2 /* AddRecipeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBC63652C47D23000DA00C2 /* AddRecipeError.swift */; }; + 1DBD90B92C91BDAC00184F67 /* SignUpService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBD90B82C91BDAC00184F67 /* SignUpService.swift */; }; + 1DBD90BB2C91DE1600184F67 /* EmptyResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBD90BA2C91DE1600184F67 /* EmptyResponse.swift */; }; 1DC7CC322C283C0200796889 /* RecipeUploadImgaeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC7CC312C283C0200796889 /* RecipeUploadImgaeCell.swift */; }; 1DC7CC342C294F9200796889 /* SelectImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC7CC332C294F9200796889 /* SelectImageCell.swift */; }; 1DD4F70A2C80C947003E9D9D /* LoginError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DD4F7092C80C947003E9D9D /* LoginError.swift */; }; @@ -113,9 +122,13 @@ 1DF0D1A92C7DF9AA00E2C94C /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF0D1A82C7DF9AA00E2C94C /* LoginViewModel.swift */; }; 1DF0D1AB2C7DF9B500E2C94C /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF0D1AA2C7DF9B500E2C94C /* LoginViewController.swift */; }; 1DF0D1AD2C7DF9BB00E2C94C /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF0D1AC2C7DF9BB00E2C94C /* LoginView.swift */; }; + 1DF6E1432C8C561E005E8875 /* SignUpInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF6E1422C8C561E005E8875 /* SignUpInteractor.swift */; }; 1DF829B42C2A7A7D00C337FC /* Fonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF829B32C2A7A7D00C337FC /* Fonts.swift */; }; 1DF829B72C2A7CDC00C337FC /* UIImageViewImageLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF829B62C2A7CDC00C337FC /* UIImageViewImageLoading.swift */; }; 1DF829B92C2A818D00C337FC /* String+Validation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DF829B82C2A818D00C337FC /* String+Validation.swift */; }; + 1DFC961C2C90809D006C3309 /* SignUpRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFC961B2C90809D006C3309 /* SignUpRepository.swift */; }; + 1DFC961E2C908723006C3309 /* CheckEmailRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFC961D2C908723006C3309 /* CheckEmailRepository.swift */; }; + 1DFC96202C908739006C3309 /* CheckEmailUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFC961F2C908739006C3309 /* CheckEmailUsecase.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -142,6 +155,8 @@ 1D1283AB2C15EBE600C5A870 /* FetchFeedListUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchFeedListUseCase.swift; sourceTree = ""; }; 1D1283C92C16D9C600C5A870 /* RecipeFetchService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeFetchService.swift; sourceTree = ""; }; 1D166D0C2C4AD54D00A50963 /* AddRecipeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipeViewModel.swift; sourceTree = ""; }; + 1D2398B12C8DC07800626F0C /* SignUpUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpUseCase.swift; sourceTree = ""; }; + 1D2398B32C8DC23500626F0C /* SignUpError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpError.swift; sourceTree = ""; }; 1D2C16E22BE532B700C04508 /* HomeCafeRecipes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HomeCafeRecipes.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1D2C16E52BE532B700C04508 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 1D2C16E92BE532B700C04508 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -177,6 +192,7 @@ 1D6958D12C3D0553008604B3 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; 1D6958D32C3D059E008604B3 /* RecipeListRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeListRouter.swift; sourceTree = ""; }; 1D6958D72C3D5A80008604B3 /* RecipeDeatilInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDeatilInteractorTests.swift; sourceTree = ""; }; + 1D6F99482C9291B700430FD8 /* UIViewController+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Alert.swift"; sourceTree = ""; }; 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailDTO.swift; sourceTree = ""; }; 1D73686F2C32BFBB000EF904 /* AddRecipeInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecipeInteractor.swift; sourceTree = ""; }; 1D7368732C32CF09000EF904 /* AddRecipeRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddRecipeRepository.swift; sourceTree = ""; }; @@ -190,7 +206,13 @@ 1D8474582C6CCF6900323001 /* TestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = ""; }; 1D95A0A52C37C79500F09077 /* RecipeDetailError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeDetailError.swift; sourceTree = ""; }; 1D96FDA92C7F55E600EFC657 /* LoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInteractor.swift; sourceTree = ""; }; + 1DBB55052C8418490009E033 /* LoginRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRouter.swift; sourceTree = ""; }; + 1DBB55072C8421890009E033 /* SignUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpView.swift; sourceTree = ""; }; + 1DBB55092C8421920009E033 /* SignUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewController.swift; sourceTree = ""; }; + 1DBB550C2C842AE70009E033 /* SignUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewModel.swift; sourceTree = ""; }; 1DBC63652C47D23000DA00C2 /* AddRecipeError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddRecipeError.swift; sourceTree = ""; }; + 1DBD90B82C91BDAC00184F67 /* SignUpService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpService.swift; sourceTree = ""; }; + 1DBD90BA2C91DE1600184F67 /* EmptyResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyResponse.swift; sourceTree = ""; }; 1DC7CC312C283C0200796889 /* RecipeUploadImgaeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipeUploadImgaeCell.swift; sourceTree = ""; }; 1DC7CC332C294F9200796889 /* SelectImageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectImageCell.swift; sourceTree = ""; }; 1DD4F7092C80C947003E9D9D /* LoginError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginError.swift; sourceTree = ""; }; @@ -211,9 +233,13 @@ 1DF0D1A82C7DF9AA00E2C94C /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; 1DF0D1AA2C7DF9B500E2C94C /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 1DF0D1AC2C7DF9BB00E2C94C /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; + 1DF6E1422C8C561E005E8875 /* SignUpInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpInteractor.swift; sourceTree = ""; }; 1DF829B32C2A7A7D00C337FC /* Fonts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fonts.swift; sourceTree = ""; }; 1DF829B62C2A7CDC00C337FC /* UIImageViewImageLoading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageViewImageLoading.swift; sourceTree = ""; }; 1DF829B82C2A818D00C337FC /* String+Validation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Validation.swift"; sourceTree = ""; }; + 1DFC961B2C90809D006C3309 /* SignUpRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpRepository.swift; sourceTree = ""; }; + 1DFC961D2C908723006C3309 /* CheckEmailRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckEmailRepository.swift; sourceTree = ""; }; + 1DFC961F2C908739006C3309 /* CheckEmailUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckEmailUsecase.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -255,6 +281,7 @@ 1D95A0A52C37C79500F09077 /* RecipeDetailError.swift */, 1DBC63652C47D23000DA00C2 /* AddRecipeError.swift */, 1DD4F7092C80C947003E9D9D /* LoginError.swift */, + 1D2398B32C8DC23500626F0C /* SignUpError.swift */, ); path = Entities; sourceTree = ""; @@ -267,6 +294,8 @@ 1D1283AB2C15EBE600C5A870 /* FetchFeedListUseCase.swift */, 1D439E9B2C2C58DD008530A5 /* FetchRecipeDetailUseCase.swift */, 1D7641442C81BE90002AC68F /* LoginUseCase.swift */, + 1D2398B12C8DC07800626F0C /* SignUpUseCase.swift */, + 1DFC961F2C908739006C3309 /* CheckEmailUsecase.swift */, ); path = UseCases; sourceTree = ""; @@ -297,6 +326,7 @@ 1D60CC3C2C3E4F1600D08FA3 /* APIConfig.swift */, 1D39726B2C458CE100495014 /* MultipartFormDataRequest.swift */, 1D7641482C831295002AC68F /* LoginService.swift */, + 1DBD90B82C91BDAC00184F67 /* SignUpService.swift */, ); path = Network; sourceTree = ""; @@ -391,6 +421,7 @@ children = ( 1D6958D12C3D0553008604B3 /* Router.swift */, 1D6958D32C3D059E008604B3 /* RecipeListRouter.swift */, + 1DBB55052C8418490009E033 /* LoginRouter.swift */, ); path = Router; sourceTree = ""; @@ -406,6 +437,7 @@ 1D73686D2C305757000EF904 /* RecipeDetailDTO.swift */, 1D7368792C32EB18000EF904 /* RecipeUploadDTO.swift */, 1D7368852C33D7BE000EF904 /* RecipeUploadResponseDTO.swift */, + 1DBD90BA2C91DE1600184F67 /* EmptyResponse.swift */, ); path = DTO; sourceTree = ""; @@ -428,6 +460,16 @@ path = TestUtilities; sourceTree = ""; }; + 1DBB550B2C842ACF0009E033 /* SginUp */ = { + isa = PBXGroup; + children = ( + 1DBB55072C8421890009E033 /* SignUpView.swift */, + 1DBB55092C8421920009E033 /* SignUpViewController.swift */, + 1DBB550C2C842AE70009E033 /* SignUpViewModel.swift */, + ); + path = SginUp; + sourceTree = ""; + }; 1DDFFD822C1C09AB0083B077 /* Mapper */ = { isa = PBXGroup; children = ( @@ -443,6 +485,7 @@ 1D439EA12C2C6997008530A5 /* RecipeDetailInteractor.swift */, 1D73686F2C32BFBB000EF904 /* AddRecipeInteractor.swift */, 1D96FDA92C7F55E600EFC657 /* LoginInteractor.swift */, + 1DF6E1422C8C561E005E8875 /* SignUpInteractor.swift */, ); path = Interactor; sourceTree = ""; @@ -455,6 +498,8 @@ 1D439E9D2C2C598A008530A5 /* RecipeDetailRepository.swift */, 1D7368732C32CF09000EF904 /* AddRecipeRepository.swift */, 1D7641462C831192002AC68F /* LoginRepository.swift */, + 1DFC961B2C90809D006C3309 /* SignUpRepository.swift */, + 1DFC961D2C908723006C3309 /* CheckEmailRepository.swift */, ); path = Repositories; sourceTree = ""; @@ -515,6 +560,7 @@ isa = PBXGroup; children = ( 1DF0D1972C7B92C500E2C94C /* DateFormatter+Extensions.swift */, + 1D6F99482C9291B700430FD8 /* UIViewController+Alert.swift */, ); path = Utilities; sourceTree = ""; @@ -522,6 +568,7 @@ 1DF0D1A72C7DF99F00E2C94C /* Login */ = { isa = PBXGroup; children = ( + 1DBB550B2C842ACF0009E033 /* SginUp */, 1DF0D1A82C7DF9AA00E2C94C /* LoginViewModel.swift */, 1DF0D1AA2C7DF9B500E2C94C /* LoginViewController.swift */, 1DF0D1AC2C7DF9BB00E2C94C /* LoginView.swift */, @@ -688,6 +735,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1DBB550A2C8421920009E033 /* SignUpViewController.swift in Sources */, 1D439E9E2C2C598A008530A5 /* RecipeDetailRepository.swift in Sources */, 1D2C6F6C2C27051D004BB54E /* CustomNavigationBar.swift in Sources */, 1DD4F70A2C80C947003E9D9D /* LoginError.swift in Sources */, @@ -707,8 +755,10 @@ 1DE19EB12C1B42200031804A /* NetworkService.swift in Sources */, 1DC7CC322C283C0200796889 /* RecipeUploadImgaeCell.swift in Sources */, 1D7368782C32E7FE000EF904 /* RecipePostService.swift in Sources */, + 1D2398B42C8DC23500626F0C /* SignUpError.swift in Sources */, 1DBC63662C47D23000DA00C2 /* AddRecipeError.swift in Sources */, 1D95A0A62C37C79500F09077 /* RecipeDetailError.swift in Sources */, + 1DBB55082C8421890009E033 /* SignUpView.swift in Sources */, 1D5AEE552C592F2600BBD5F0 /* AddRecipeUseCase.swift in Sources */, 1D1283AC2C15EBE600C5A870 /* FetchFeedListUseCase.swift in Sources */, 1DF0D1A92C7DF9AA00E2C94C /* LoginViewModel.swift in Sources */, @@ -716,33 +766,43 @@ 1D73687A2C32EB18000EF904 /* RecipeUploadDTO.swift in Sources */, 1D2C6F6A2C26AF9F004BB54E /* AddRecipeView.swift in Sources */, 1D2C6F682C246998004BB54E /* AddRecipeViewController.swift in Sources */, + 1DFC961C2C90809D006C3309 /* SignUpRepository.swift in Sources */, 1DF829B72C2A7CDC00C337FC /* UIImageViewImageLoading.swift in Sources */, 1D5AEE532C592A9900BBD5F0 /* RecipeListItemViewModel.swift in Sources */, 1D60CC3D2C3E4F1600D08FA3 /* APIConfig.swift in Sources */, + 1D6F99492C9291B700430FD8 /* UIViewController+Alert.swift in Sources */, 1D1283A42C15EA8100C5A870 /* RecipeType.swift in Sources */, 1D7641472C831192002AC68F /* LoginRepository.swift in Sources */, 1DF829B42C2A7A7D00C337FC /* Fonts.swift in Sources */, 1DF0D1AD2C7DF9BB00E2C94C /* LoginView.swift in Sources */, 1D4741D22C1B4F8D009381CE /* RecipeDTO.swift in Sources */, + 1DFC96202C908739006C3309 /* CheckEmailUsecase.swift in Sources */, 1DE19EC02C1B422F0031804A /* RecipeDetailView.swift in Sources */, 1DF0D1982C7B92C500E2C94C /* DateFormatter+Extensions.swift in Sources */, + 1DBB55062C8418490009E033 /* LoginRouter.swift in Sources */, + 1DFC961E2C908723006C3309 /* CheckEmailRepository.swift in Sources */, 1D1283AA2C15EBCF00C5A870 /* SearchFeedUseCase.swift in Sources */, 1DE19EA82C1B420A0031804A /* SearchFeedListRepository.swift in Sources */, 1DE19EC32C1B422F0031804A /* SearchBar.swift in Sources */, 1D439EA22C2C6997008530A5 /* RecipeDetailInteractor.swift in Sources */, 1D73686E2C305757000EF904 /* RecipeDetailDTO.swift in Sources */, 1D4741D72C1B4FF4009381CE /* RecipeListInteractor.swift in Sources */, + 1D2398B22C8DC07800626F0C /* SignUpUseCase.swift in Sources */, 1DC7CC342C294F9200796889 /* SelectImageCell.swift in Sources */, 1DE19E9D2C1B3DC10031804A /* SceneDelegate.swift in Sources */, 1D4741D12C1B4F8D009381CE /* RecipeImageDTO.swift in Sources */, + 1DF6E1432C8C561E005E8875 /* SignUpInteractor.swift in Sources */, 1DF0D1AB2C7DF9B500E2C94C /* LoginViewController.swift in Sources */, 1D7368742C32CF09000EF904 /* AddRecipeRepository.swift in Sources */, 1DE19EA72C1B420A0031804A /* FeedListRepository.swift in Sources */, 1DF0D19B2C7B92D600E2C94C /* UserDTO.swift in Sources */, 1DE19EC62C1B422F0031804A /* RecipeListCell.swift in Sources */, + 1DBB550D2C842AE70009E033 /* SignUpViewModel.swift in Sources */, 1D166D0D2C4AD54E00A50963 /* AddRecipeViewModel.swift in Sources */, + 1DBD90BB2C91DE1600184F67 /* EmptyResponse.swift in Sources */, 1D7641452C81BE90002AC68F /* LoginUseCase.swift in Sources */, 1DF829B92C2A818D00C337FC /* String+Validation.swift in Sources */, + 1DBD90B92C91BDAC00184F67 /* SignUpService.swift in Sources */, 1D7368862C33D7BE000EF904 /* RecipeUploadResponseDTO.swift in Sources */, 1DE19EC42C1B422F0031804A /* RecipeListViewController.swift in Sources */, 1DE19EBF2C1B422F0031804A /* RecipeDetailViewModel.swift in Sources */, diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpView.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpView.swift new file mode 100644 index 0000000..c007235 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpView.swift @@ -0,0 +1,218 @@ +// +// SignUpView.swift +// HomeCafeRecipes +// +// Created by 김건호 on 9/1/24. +// + +import UIKit + +protocol SignupviewDelegate: AnyObject { + func didTapBackButton() + func didTapSignupButton() + func didUpdateTextFields() +} + +final class SignUpView: UIView { + private let nicknameLabel: UILabel = { + let nicknameLabel = UILabel() + nicknameLabel.text = "닉네임" + nicknameLabel.font = Fonts.detailBodyFont + return nicknameLabel + }() + + private lazy var nicknameField: UITextField = { [weak self] in + let nicknameTextField = UITextField() + nicknameTextField.placeholder = "Enter your nickname" + nicknameTextField.borderStyle = .roundedRect + nicknameTextField.addTarget(self, action: #selector(self?.handleTextFieldEditing), for: .editingChanged) + return nicknameTextField + }() + + private let IDLabel: UILabel = { + let IDLabel = UILabel() + IDLabel.text = "아이디" + IDLabel.font = Fonts.detailBodyFont + return IDLabel + }() + + private lazy var IDField: UITextField = { [weak self] in + let IDTextField = UITextField() + IDTextField.placeholder = "Enter your ID" + IDTextField.borderStyle = .roundedRect + IDTextField.addTarget(self, action: #selector(self?.handleTextFieldEditing), for: .editingChanged) + return IDTextField + }() + + private let passwordLabel: UILabel = { + let passwordLabel = UILabel() + passwordLabel.text = "비밀번호" + passwordLabel.font = Fonts.detailBodyFont + return passwordLabel + }() + + private lazy var passwordField: UITextField = { [weak self] in + let passwordField = UITextField() + passwordField.placeholder = "Enter your password" + passwordField.borderStyle = .roundedRect + passwordField.isSecureTextEntry = true + passwordField.addTarget(self, action: #selector(self?.handleTextFieldEditing), for: .editingChanged) + return passwordField + }() + + private let passwordCheckLabel: UILabel = { + let passwordLabel = UILabel() + passwordLabel.text = "비밀번호 확인" + passwordLabel.font = Fonts.detailBodyFont + return passwordLabel + }() + + private lazy var passwordCheckField: UITextField = { [weak self] in + let passwordCheckField = UITextField() + passwordCheckField.placeholder = "Enter your password" + passwordCheckField.borderStyle = .roundedRect + passwordCheckField.isSecureTextEntry = true + passwordCheckField.addTarget(self, action: #selector(self?.handleTextFieldEditing), for: .editingChanged) + return passwordCheckField + }() + + lazy var signUpButton: UIButton = { + let signUpButton = UIButton() + signUpButton.setTitle("회원가입", for: .normal) + signUpButton.backgroundColor = .lightGray + signUpButton.titleLabel?.font = Fonts.detailBodyFont + signUpButton.setTitleColor(.white, for: .normal) + signUpButton.layer.cornerRadius = 8 + signUpButton.isEnabled = false + signUpButton.addAction( + UIAction( + handler: { [weak self] _ in + self?.delegate?.didTapSignupButton() + } + ), + for: .touchUpInside + ) + return signUpButton + }() + + let customNavigationBar = CustomNavigationBar() + + weak var delegate: SignupviewDelegate? + + override init(frame: CGRect) { + super.init(frame: frame) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + setupNavigationBar() + addsubviews() + setupConstraints() + } + + private func setupNavigationBar() { + addSubview(customNavigationBar) + customNavigationBar.translatesAutoresizingMaskIntoConstraints = false + customNavigationBar.setTitle("회원가입") + customNavigationBar.backButton.addAction( + UIAction( + handler: { [weak self] _ in + self?.delegate?.didTapBackButton() + } + ), + for: .touchUpInside + ) + } + + private func addsubviews() { + addSubview(nicknameLabel) + addSubview(nicknameField) + addSubview(IDLabel) + addSubview(IDField) + addSubview(passwordLabel) + addSubview(passwordField) + addSubview(passwordCheckLabel) + addSubview(passwordCheckField) + addSubview(signUpButton) + } + + private func setupConstraints() { + nicknameLabel.translatesAutoresizingMaskIntoConstraints = false + nicknameField.translatesAutoresizingMaskIntoConstraints = false + IDLabel.translatesAutoresizingMaskIntoConstraints = false + IDField.translatesAutoresizingMaskIntoConstraints = false + passwordLabel.translatesAutoresizingMaskIntoConstraints = false + passwordField.translatesAutoresizingMaskIntoConstraints = false + passwordCheckLabel.translatesAutoresizingMaskIntoConstraints = false + passwordCheckField.translatesAutoresizingMaskIntoConstraints = false + signUpButton.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + customNavigationBar.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor), + customNavigationBar.leadingAnchor.constraint(equalTo: leadingAnchor), + customNavigationBar.trailingAnchor.constraint(equalTo: trailingAnchor), + customNavigationBar.heightAnchor.constraint(equalToConstant: 44), + + nicknameLabel.topAnchor.constraint(equalTo: customNavigationBar.bottomAnchor, constant: 150), + nicknameLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + nicknameLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + + nicknameField.topAnchor.constraint(equalTo: nicknameLabel.bottomAnchor, constant: 10), + nicknameField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + nicknameField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + + IDLabel.topAnchor.constraint(equalTo: nicknameField.bottomAnchor, constant: 20), + IDLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + IDLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + + IDField.topAnchor.constraint(equalTo: IDLabel.bottomAnchor, constant: 10), + IDField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + IDField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + + passwordLabel.topAnchor.constraint(equalTo: IDField.bottomAnchor, constant: 20), + passwordLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + passwordLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + + passwordField.topAnchor.constraint(equalTo: passwordLabel.bottomAnchor, constant: 10), + passwordField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + passwordField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + + passwordCheckLabel.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 20), + passwordCheckLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + passwordCheckLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + + passwordCheckField.topAnchor.constraint(equalTo: passwordCheckLabel.bottomAnchor, constant: 10), + passwordCheckField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + passwordCheckField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + + signUpButton.topAnchor.constraint(equalTo: passwordCheckField.bottomAnchor, constant: 30), + signUpButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + signUpButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), + signUpButton.heightAnchor.constraint(equalToConstant: 50) + ]) + } + + @objc private func handleTextFieldEditing(_ textField: UITextField) { + delegate?.didUpdateTextFields() + } + + var nickname: String { + return nicknameField.text ?? "" + } + + var ID: String { + return IDField.text ?? "" + } + + var password: String { + return passwordField.text ?? "" + } + + var passwordCheck: String { + return passwordCheckField.text ?? "" + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewController.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewController.swift new file mode 100644 index 0000000..e90b2a0 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewController.swift @@ -0,0 +1,71 @@ +// +// SignUpViewController.swift +// HomeCafeRecipes +// +// Created by 김건호 on 9/1/24. +// + +import UIKit + +final class SignUpViewController: UIViewController { + private let contentView = SignUpView() + + init() { + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = contentView + } + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + contentView.delegate = self + } + + private func setupUI() { + view.backgroundColor = .white + NSLayoutConstraint.activate([ + contentView.topAnchor.constraint(equalTo: view.topAnchor), + contentView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + contentView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor) + ]) + } +} + +// MARK: SignupviewDelegate + +extension SignUpViewController: SignupviewDelegate { + func didUpdateTextFields() { + let isNicknameValid = !contentView.nickname.isEmpty + let isIDValid = !contentView.ID.isEmpty + let isPasswordValid = !contentView.password.isEmpty + let isPasswordCheckValid = !contentView.passwordCheck.isEmpty + let isFormValid = isNicknameValid && isIDValid && isPasswordValid && isPasswordCheckValid + + contentView.signUpButton.isEnabled = isFormValid + contentView.signUpButton.backgroundColor = isFormValid ? .systemBlue : .lightGray + } + + func didTapBackButton() { + navigationController?.popViewController(animated: true) + } + + func didTapSignupButton() { + return + } +} + +// MARK: Drawable + +extension SignUpViewController: Drawable { + var viewController: UIViewController? { + return self + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewModel.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewModel.swift new file mode 100644 index 0000000..bd988f0 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewModel.swift @@ -0,0 +1,15 @@ +// +// SignUpViewModel.swift +// HomeCafeRecipes +// +// Created by 김건호 on 9/1/24. +// + +import Foundation + +struct SignUpViewModel { + var nickName: String + var ID: String + var password: String + var checkpassword: String +} From 418eda941009bc34c68915cd651ffba6d4d0d483 Mon Sep 17 00:00:00 2001 From: GeonH0 Date: Sat, 7 Sep 2024 18:31:51 +0900 Subject: [PATCH 2/5] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=B2=84=ED=8A=BC=20=EC=83=9D=EC=84=B1=ED=9B=84=20?= =?UTF-8?q?Navigation=20=EB=A1=9C=EC=A7=81=20Router=EC=97=90=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Presentation/Login/LoginView.swift | 29 +++++++++++++++-- .../Login/LoginViewController.swift | 8 ++++- .../HomeCafeRecipes/Router/LoginRouter.swift | 31 +++++++++++++++++++ .../HomeCafeRecipes/Router/Router.swift | 23 ++++++++++++++ 4 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 HomeCafeRecipes/HomeCafeRecipes/Router/LoginRouter.swift diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/LoginView.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/LoginView.swift index b21005f..bf26aab 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/LoginView.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/LoginView.swift @@ -9,6 +9,7 @@ import UIKit protocol LoginViewDelegate : AnyObject { func didtapLoginButton() + func didtapSignUpButton() } final class LoginView: UIView { @@ -53,12 +54,28 @@ final class LoginView: UIView { UIAction( handler:{ [weak self] _ in self?.delegate?.didtapLoginButton() - }), + } + ), for: .touchUpInside ) return loginButton }() + private lazy var signUpButton: UIButton = { + let signUpButton = UIButton(type: .system) + signUpButton.setTitle("회원가입", for: .normal) + signUpButton.titleLabel?.font = Fonts.detailBodyFont + signUpButton.addAction( + UIAction( + handler: { [weak self] _ in + self?.delegate?.didtapSignUpButton() + } + ), + for: .touchUpInside + ) + return signUpButton + }() + override init(frame: CGRect) { super.init(frame: frame) setupUI() @@ -79,6 +96,7 @@ final class LoginView: UIView { addSubview(passwordLabel) addSubview(passwordField) addSubview(loginButton) + addSubview(signUpButton) } private func setupConstraints() { @@ -87,25 +105,32 @@ final class LoginView: UIView { passwordLabel.translatesAutoresizingMaskIntoConstraints = false passwordField.translatesAutoresizingMaskIntoConstraints = false loginButton.translatesAutoresizingMaskIntoConstraints = false + signUpButton.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ loginLabel.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor,constant: 230), loginLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + loginField.topAnchor.constraint(equalTo: loginLabel.bottomAnchor, constant: 10), loginField.leadingAnchor.constraint(equalTo: leadingAnchor,constant: 20), loginField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), loginField.heightAnchor.constraint(equalToConstant: 40), + passwordLabel.topAnchor.constraint(equalTo: loginField.bottomAnchor,constant: 10), passwordLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + passwordField.topAnchor.constraint(equalTo: passwordLabel.bottomAnchor, constant: 10), passwordField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), passwordField.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), passwordField.heightAnchor.constraint(equalToConstant: 40), + + signUpButton.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 10), + signUpButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), + loginButton.topAnchor.constraint(equalTo: passwordField.bottomAnchor, constant: 200), loginButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20), loginButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20), loginButton.heightAnchor.constraint(equalToConstant: 50) - ]) } diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/LoginViewController.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/LoginViewController.swift index 2a54f04..27689c2 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/LoginViewController.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/LoginViewController.swift @@ -13,11 +13,13 @@ final class LoginViewController: UIViewController { private let contentView = LoginView() private let loginInteractor: LoginInteractor + private let router: LoginRouter private let disposeBag = DisposeBag() private var loginViewModel: LoginViewModel? - init(loginInteractor: LoginInteractor) { + init(loginInteractor: LoginInteractor,router: LoginRouter) { self.loginInteractor = loginInteractor + self.router = router super.init(nibName: nil, bundle: nil) } @@ -89,4 +91,8 @@ extension LoginViewController: LoginViewDelegate { loginInteractor.didEndEditing(password: password) login(ID: ID, password: password) } + + func didtapSignUpButton() { + router.navigateToSignUP(from: self) + } } diff --git a/HomeCafeRecipes/HomeCafeRecipes/Router/LoginRouter.swift b/HomeCafeRecipes/HomeCafeRecipes/Router/LoginRouter.swift new file mode 100644 index 0000000..6d78ff9 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Router/LoginRouter.swift @@ -0,0 +1,31 @@ +// +// LoginRouter.swift +// HomeCafeRecipes +// +// Created by 김건호 on 9/1/24. +// + +import UIKit + +protocol LoginRouter { + func navigateToSignUP(from viewController: UIViewController) +} + +final class LoginRouterImpl: LoginRouter { + private let router: Router + + init(router: Router) { + self.router = router + } + + func navigateToSignUP(from viewController: UIViewController) { + let SignUpViewController = router.makeSignUpViewController() + + router.push( + SignUpViewController, + from: viewController, + isAnimated: true, + onNavigateBack: nil + ) + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Router/Router.swift b/HomeCafeRecipes/HomeCafeRecipes/Router/Router.swift index 570dbf0..ac39708 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Router/Router.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Router/Router.swift @@ -105,4 +105,27 @@ extension Router { detailInteractor.delegate = detailVC return detailVC } + + func makeLoginViewController() -> LoginViewController { + let loginInteractor = LoginInteractorImpl( + loginUseCase: LoginUseCaseImpl( + repository: LoginRepositoryImpl( + loginService: LoginServiceImpl( + networkService: BaseNetworkService() + ) + ) + ) + ) + let loginRouter = LoginRouterImpl(router: self) + let LoginViewController = LoginViewController( + loginInteractor: loginInteractor, + router: loginRouter + ) + return LoginViewController + } + + func makeSignUpViewController() -> SignUpViewController { + let signUpViewcontroller = SignUpViewController() + return signUpViewcontroller + } } From af3201f223a7092f26881a5c05091efe6dad16c5 Mon Sep 17 00:00:00 2001 From: GeonH0 Date: Thu, 12 Sep 2024 22:44:14 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=B2=84=ED=8A=BC=20=EB=88=84=EB=A5=BC=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=8B=9C?= =?UTF-8?q?=EB=8F=84=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Entities/SignUpError.swift | 33 ++++++++ .../Domain/Interactor/SignUpInteractor.swift | 76 +++++++++++++++++++ .../Domain/UseCases/SignUpUseCase.swift | 51 +++++++++++++ .../Login/SginUp/SignUpViewController.swift | 37 ++++++++- 4 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 HomeCafeRecipes/HomeCafeRecipes/Domain/Entities/SignUpError.swift create mode 100644 HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/SignUpInteractor.swift create mode 100644 HomeCafeRecipes/HomeCafeRecipes/Domain/UseCases/SignUpUseCase.swift diff --git a/HomeCafeRecipes/HomeCafeRecipes/Domain/Entities/SignUpError.swift b/HomeCafeRecipes/HomeCafeRecipes/Domain/Entities/SignUpError.swift new file mode 100644 index 0000000..2410b06 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Domain/Entities/SignUpError.swift @@ -0,0 +1,33 @@ +// +// SignUpError.swift +// HomeCafeRecipes +// +// Created by 김건호 on 9/8/24. +// + +import Foundation + +enum SignUpError: Error { + case passwordMismatch + case genericError(Error) +} + +extension SignUpError: LocalizedError { + var title: String { + switch self { + case .passwordMismatch: + return "비밀번호 불일치" + case .genericError: + return "회원가입 실패" + } + } + + var errorDescription: String? { + switch self { + case .passwordMismatch: + return "비밀번호를 확인해 주세요" + case .genericError(let error): + return error.localizedDescription + } + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/SignUpInteractor.swift b/HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/SignUpInteractor.swift new file mode 100644 index 0000000..2bd1e6f --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Domain/Interactor/SignUpInteractor.swift @@ -0,0 +1,76 @@ +// +// SignUpInteractor.swift +// HomeCafeRecipes +// +// Created by 김건호 on 9/7/24. +// + +import UIKit + +import RxSwift + +protocol SignUpViewControllerDelegate: AnyObject { + func loadnewUser(viewModel: SignUpViewModel) +} + +protocol SignUpInteractor { + func loadNewUser() + func signUp() -> Single + func didEndEditing(userNickName: String) + func didEndEditing(userID: String) + func didEndEditing(password: String) + func didEndEditing(checkpassword: String) + +} + +final class SignUpInteractorImpl: SignUpInteractor { + + private var userNickName: String = "" + private var userID: String = "" + private var password: String = "" + private var checkpassword: String = "" + + weak var delegate: SignUpViewControllerDelegate? + + private let usecase: SignUpUseCase + + + init(usecase: SignUpUseCase){ + self.usecase = usecase + } + + func loadNewUser() { + let viewmodel = SignUpViewModel( + nickName: userNickName, + ID: userID, + password: password, + checkpassword: checkpassword + ) + delegate?.loadnewUser(viewModel: viewmodel) + } + + func didEndEditing(userNickName: String) { + self.userNickName = userNickName + } + + func didEndEditing(userID: String) { + self.userID = userID + } + + func didEndEditing(password: String) { + self.password = password + } + + func didEndEditing(checkpassword: String) { + self.checkpassword = checkpassword + } + + func signUp() -> Single { + return usecase.execute( + userNickName: userNickName, + userID: userID, + password: password, + checkpassword: checkpassword + ) + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Domain/UseCases/SignUpUseCase.swift b/HomeCafeRecipes/HomeCafeRecipes/Domain/UseCases/SignUpUseCase.swift new file mode 100644 index 0000000..3b357c5 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Domain/UseCases/SignUpUseCase.swift @@ -0,0 +1,51 @@ +// +// SignUpUseCase.swift +// HomeCafeRecipes +// +// Created by 김건호 on 9/8/24. +// + +import Foundation + +import RxSwift + +protocol SignUpUseCase { + func execute( + userNickName: String, + userID: String, + password: String, + checkpassword: String + ) -> Single +} + +final class SignUpUseCaseImpl: SignUpUseCase { + private let repository: SignUpRepository + + init(repository: SignUpRepository) { + self.repository = repository + } + + func execute( + userNickName: String, + userID: String, + password: String, + checkpassword: String + ) -> Single { + + guard password.isCheck(equalTo: checkpassword) else { + return .just(.passwordMismatch) + } + + return repository.signUp( + userNickName: userNickName, + userID: userID, + password: password + ) + .map { _ in + return nil + } + .catch { error in + return .just(.genericError(error)) + } + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewController.swift b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewController.swift index e90b2a0..c757212 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewController.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Presentation/Login/SginUp/SignUpViewController.swift @@ -7,10 +7,19 @@ import UIKit +import RxSwift + final class SignUpViewController: UIViewController { private let contentView = SignUpView() + private let signUpInteractor: SignUpInteractor + private let disposeBag = DisposeBag() + private let router: LoginRouter + private var signUpViewModel: SignUpViewModel? + - init() { + init(signUpInteractor: SignUpInteractor, router: LoginRouter){ + self.signUpInteractor = signUpInteractor + self.router = router super.init(nibName: nil, bundle: nil) } @@ -26,6 +35,7 @@ final class SignUpViewController: UIViewController { super.viewDidLoad() setupUI() contentView.delegate = self + signUpInteractor.loadNewUser() } private func setupUI() { @@ -37,6 +47,27 @@ final class SignUpViewController: UIViewController { contentView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } + + private func signUp() { + signUpInteractor.didEndEditing(userNickName: contentView.nickname) + signUpInteractor.didEndEditing(userID: contentView.ID) + signUpInteractor.didEndEditing(password: contentView.password) + signUpInteractor.didEndEditing(checkpassword: contentView.passwordCheck) + + signUpInteractor.signUp() + .subscribe(onSuccess: { [weak self] error in + if let error = error { + DispatchQueue.main.async { + self?.showCompletedAlert(title: error.title, message: "\(error.errorDescription!)", success: false) + } + } else { + DispatchQueue.main.async { + self?.showCompletedAlert(title: "회원가입 성공", message: "회원가입에 성공했습니다.", success: true) + } + } + }) + .disposed(by: disposeBag) + } } // MARK: SignupviewDelegate @@ -58,7 +89,9 @@ extension SignUpViewController: SignupviewDelegate { } func didTapSignupButton() { - return + signUp() + } + } } From a01a1303a41c757f463f74401f048dde113bb61c Mon Sep 17 00:00:00 2001 From: GeonH0 Date: Thu, 12 Sep 2024 22:45:38 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=84=A4=ED=8A=B8=EC=9B=8C=ED=81=AC=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/DTO/EmptyResponse.swift | 10 +++ .../Data/Network/NetworkService.swift | 49 +++++++++++++++ .../Data/Network/SignUpService.swift | 63 +++++++++++++++++++ .../Data/Repositories/SignUpRepository.swift | 31 +++++++++ 4 files changed, 153 insertions(+) create mode 100644 HomeCafeRecipes/HomeCafeRecipes/Data/Network/DTO/EmptyResponse.swift create mode 100644 HomeCafeRecipes/HomeCafeRecipes/Data/Network/SignUpService.swift create mode 100644 HomeCafeRecipes/HomeCafeRecipes/Data/Repositories/SignUpRepository.swift diff --git a/HomeCafeRecipes/HomeCafeRecipes/Data/Network/DTO/EmptyResponse.swift b/HomeCafeRecipes/HomeCafeRecipes/Data/Network/DTO/EmptyResponse.swift new file mode 100644 index 0000000..ed18218 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Data/Network/DTO/EmptyResponse.swift @@ -0,0 +1,10 @@ +// +// EmptyResponse.swift +// HomeCafeRecipes +// +// Created by 김건호 on 9/11/24. +// + +import Foundation + +struct EmptyResponse: Decodable {} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Data/Network/NetworkService.swift b/HomeCafeRecipes/HomeCafeRecipes/Data/Network/NetworkService.swift index 1baf3dc..47a0f82 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Data/Network/NetworkService.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Data/Network/NetworkService.swift @@ -13,6 +13,7 @@ import RxSwift protocol NetworkService { func getRequest(url: URL, responseType: T.Type) -> Single func postRequest(url: URL, parameters: [String: Any], imageDatas: [Data], responseType: T.Type) -> Single + func postJsonRequest(url: URL, parameters:[String: Any], responseType: T.Type) -> Single } class BaseNetworkService: NetworkService { @@ -98,4 +99,52 @@ class BaseNetworkService: NetworkService { } } } + + func postJsonRequest( + url: URL, + parameters: [String: Any], + responseType: T.Type + ) -> Single { + return Single.create { single in + // JSON으로 변환 + guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else { + single(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "JSON 인코딩 실패"]))) + return Disposables.create() + } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = httpBody + + let task = URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + single(.failure(error)) + } else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { + let statusCode = httpResponse.statusCode + let responseString = data.flatMap { String(data: $0, encoding: .utf8) } ?? "No response data" + let error = NSError( + domain: "", + code: statusCode, + userInfo: [NSLocalizedDescriptionKey: "HTTP \(statusCode): \(responseString)"] + ) + single(.failure(error)) + } else if let data = data { + do { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + let responseObject = try decoder.decode(T.self, from: data) + single(.success(responseObject)) + } catch let decodingError { + single(.failure(decodingError)) + } + } + } + task.resume() + + return Disposables.create { + task.cancel() + } + } + } } diff --git a/HomeCafeRecipes/HomeCafeRecipes/Data/Network/SignUpService.swift b/HomeCafeRecipes/HomeCafeRecipes/Data/Network/SignUpService.swift new file mode 100644 index 0000000..f8056b4 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Data/Network/SignUpService.swift @@ -0,0 +1,63 @@ +// +// SignUpService.swift +// HomeCafeRecipes +// +// Created by 김건호 on 9/11/24. +// + +import Foundation + +import RxSwift + +protocol SignUpService { + func signUp( + userNickName: String, + userID: String, + password: String + ) -> Single +} + +final class SignUpServiceImpl: SignUpService { + private let networkService: NetworkService + + init(networkService: NetworkService) { + self.networkService = networkService + } + + private func makeURL(endpoint: String) -> URL { + return APIConfig().baseURL.appendingPathComponent(endpoint) + } + + func signUp( + userNickName: String, + userID: String, + password: String + ) -> Single { + let url = makeURL(endpoint: "auth/register") + + let parameters: [String: Any] = [ + "email": userID, + "password": password, + "nickname": userNickName + ] + + return networkService.postJsonRequest( + url: url, + parameters: parameters, + responseType: NetworkResponseDTO.self + ) + .flatMap { response in + if response.statusCode == 200 { + // 성공 시 data가 null이더라도 성공 처리 + return .just(()) + } else { + // statusCode가 200이 아니면 에러 처리 + return .error(NSError(domain: "SignUpError", code: response.statusCode, userInfo: [NSLocalizedDescriptionKey: response.message])) + } + } + .catch { error in + // 네트워크 또는 서버에서 에러가 발생했을 경우 처리 + return .error(error) + } + } +} diff --git a/HomeCafeRecipes/HomeCafeRecipes/Data/Repositories/SignUpRepository.swift b/HomeCafeRecipes/HomeCafeRecipes/Data/Repositories/SignUpRepository.swift new file mode 100644 index 0000000..a3c0925 --- /dev/null +++ b/HomeCafeRecipes/HomeCafeRecipes/Data/Repositories/SignUpRepository.swift @@ -0,0 +1,31 @@ +// +// SignUpRepository.swift +// HomeCafeRecipes +// +// Created by 김건호 on 9/10/24. +// + +import UIKit + +import RxSwift + +protocol SignUpRepository { + func signUp(userNickName: String, userID: String, password: String) -> Single +} + +final class SignUpRepositoryImpl: SignUpRepository { + + private let SignUpService: SignUpService + + init(SignUpService: SignUpService) { + self.SignUpService = SignUpService + } + + func signUp(userNickName: String, userID: String, password: String) -> Single { + return SignUpService.signUp( + userNickName: userNickName, + userID: userID, + password: password + ) + } +} From 7363e25a5761ab718e87faef2f59811db76159fb Mon Sep 17 00:00:00 2001 From: GeonH0 Date: Thu, 12 Sep 2024 23:06:11 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Fix:=20NetworkService=EC=9D=98=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=EC=9C=BC=EB=A1=9C=20=EC=82=AC=EC=9A=A9=EB=90=98?= =?UTF-8?q?=EB=8A=94=20URLSession=20=EC=9E=91=EC=97=85=EA=B3=BC=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=B2=98=EB=A6=AC=EB=A5=BC=20=EB=B3=84?= =?UTF-8?q?=EB=8F=84=EC=9D=98=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Data/Network/NetworkService.swift | 135 ++++++------------ 1 file changed, 43 insertions(+), 92 deletions(-) diff --git a/HomeCafeRecipes/HomeCafeRecipes/Data/Network/NetworkService.swift b/HomeCafeRecipes/HomeCafeRecipes/Data/Network/NetworkService.swift index 47a0f82..3c233e8 100644 --- a/HomeCafeRecipes/HomeCafeRecipes/Data/Network/NetworkService.swift +++ b/HomeCafeRecipes/HomeCafeRecipes/Data/Network/NetworkService.swift @@ -16,59 +16,9 @@ protocol NetworkService { func postJsonRequest(url: URL, parameters:[String: Any], responseType: T.Type) -> Single } -class BaseNetworkService: NetworkService { - - func getRequest(url: URL, responseType: T.Type) -> Single { - var request = URLRequest(url: url) - request.httpMethod = "GET" - return Single.create { single in - let task = URLSession.shared.dataTask(with: request) { data, response, error in - if let error = error { - single(.failure(error)) - } else if let data = data { - do { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - let responseObject = try decoder.decode(T.self, from: data) - single(.success(responseObject)) - } catch let decodingError { - single(.failure(decodingError)) - } - } - } - task.resume() - - return Disposables.create { - task.cancel() - } - } - } - - func postRequest( - url: URL, parameters: [String: Any], - imageDatas: [Data], - responseType: T.Type - ) -> Single { +final class BaseNetworkService: NetworkService { + private func createRequest(with request: URLRequest, responseType: T.Type) -> Single { return Single.create { single in - var formDataRequest = MultipartFormDataRequest(url: url) - - for (key, value) in parameters { - formDataRequest.addTextField(named: key, value: String(describing: value)) - } - - for (index, imageData) in imageDatas.enumerated() { - let filename = "image\(index).jpg" - formDataRequest.addDataField( - named: "recipeImgUrls", - data: imageData, - filename: filename, - mimeType: "image/jpeg" - ) - } - - formDataRequest.finalize() - let request = formDataRequest.asURLRequest() - let task = URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { single(.failure(error)) @@ -99,52 +49,53 @@ class BaseNetworkService: NetworkService { } } } + + func getRequest(url: URL, responseType: T.Type) -> Single { + var request = URLRequest(url: url) + request.httpMethod = "GET" + return createRequest(with: request, responseType: responseType) + } + + func postRequest( + url: URL, parameters: [String: Any], + imageDatas: [Data], + responseType: T.Type + ) -> Single { + var formDataRequest = MultipartFormDataRequest(url: url) + + for (key, value) in parameters { + formDataRequest.addTextField(named: key, value: String(describing: value)) + } + + for (index, imageData) in imageDatas.enumerated() { + let filename = "image\(index).jpg" + formDataRequest.addDataField( + named: "recipeImgUrls", + data: imageData, + filename: filename, + mimeType: "image/jpeg" + ) + } + + formDataRequest.finalize() + let request = formDataRequest.asURLRequest() + return createRequest(with: request, responseType: responseType) + } func postJsonRequest( url: URL, parameters: [String: Any], responseType: T.Type ) -> Single { - return Single.create { single in - // JSON으로 변환 - guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else { - single(.failure(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "JSON 인코딩 실패"]))) - return Disposables.create() - } - - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = httpBody - - let task = URLSession.shared.dataTask(with: request) { data, response, error in - if let error = error { - single(.failure(error)) - } else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 { - let statusCode = httpResponse.statusCode - let responseString = data.flatMap { String(data: $0, encoding: .utf8) } ?? "No response data" - let error = NSError( - domain: "", - code: statusCode, - userInfo: [NSLocalizedDescriptionKey: "HTTP \(statusCode): \(responseString)"] - ) - single(.failure(error)) - } else if let data = data { - do { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - let responseObject = try decoder.decode(T.self, from: data) - single(.success(responseObject)) - } catch let decodingError { - single(.failure(decodingError)) - } - } - } - task.resume() - - return Disposables.create { - task.cancel() - } + guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: []) else { + return .error(NSError(domain: "", code: -1, userInfo: [NSLocalizedDescriptionKey: "JSON 인코딩 실패"])) } + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = httpBody + + return createRequest(with: request, responseType: responseType) } }