From 856901bd1af028517f80798b96a9f33ad41dad7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=9D=B4=EA=B0=95=ED=98=B8?= <ibcylon@naver.com>
Date: Thu, 13 Jun 2024 14:26:14 +0900
Subject: [PATCH] Feature/auth (#81)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Add SignUpFlows

* Feat: Signup Flow - 자기소개까지

* Mod: ResizableTextView

* Add LocationField

* Add: Like

* Feat LocationInput

* Feat: Auth

* Add: Kakao Service

* Feat: SignUp, Auth

* Update: Dim 색상 값 변경

* [#74]Feat: 경고창 클래스 커스텀

- ReportAction enum 커스텀
- title, message, contentView, 왼쪽, 오른쪽, dim action 추가 함수 구현
- UIViewController에서 경고창 표시하는 함수 구현
- Color -> Image로 변환 함수 구현 => button에 State에 따른 색상을 넣기 위함
- addAction Custom -> button 혹은 view에서 동작 구현을 위함

* [#74]Update: 디자인 변경 사항 반영

* [#74]Feat: TimerActiveAction 추가 -> 멈추는 이벤트에 따라 케이스를 나눔

- 프로필 더블 클릭, 다른 화면으로 이동할 때는 pauseView를 표시해야하고
- report 버튼을 클릭할 때는 pauseView를 표시하지 않아야 해서
- TimerActiveAction 케이스를 기준으로 Timer를 재개, 멈춤 구현
- info button tap, report button tap 합성 -> infoView를 hidden 시키는 두 가지 액션 처리

* [#74]Feat: report 버튼 클릭 시, 타이머를 멈추고 info view를 hidden하고 alert를 표시

- 현재는 차단하기를 했을 떄, 데이터 또는 dataSource에서 item을 삭제하지 않고, 0.5초 후에 scroll하는 방식으로 구현
- dimview를 클릭하면 타이머가 다시 시작되도록 이벤트 전달
- reject 버튼 이벤트는 애니메이션 1초여서 1초후에 다음 유저로 넘어가도록 구현

* Fix: Add MyPageRepository

* [#74]Feat: 신고하기 팝업 커스텀

- ContentView 자체를 커스텀해서 content view를 파라미터로 받아서 팝업에 표시하도록 구현
- left, right -> top, bottom으로 수정
- withSeprator 플래그를 만들어서 구분선 유뮤 구현
- bottom button(마지막 버튼)은 무조건 separator 있음

* [#74]Feat: Toast 메시지 구현

- Toast-Swift 참고
- 디폴트 값 설정

* Update: 컴포넌트의 접근 지정 연산자 수정 및 이름 수정

* Update: 테스트 이미지로 임시 변경

- 서버 연동 전까지는 해당 이미지로 테스트하고자 함

* [#74]Feat: 차단, 신고 시, .none 상태로 업데이트 후, 다음 셀로 스크롤 구현

- user info box 팝업에서 report(느낌표) 버튼 클릭 시, 멈춤 화면 디자인 반영
- 차단 or 신고 팝업 표시 시, 멈춤 화면에서 이미지, 텍스트 히든
- 중지했을 때의 동작을 enum으로 나눠서 처리할 필요 없는 거 같아서 bool로 변경
- 차단 or 신고 시, 멈춤 화면 없애고 dim view 표시 후, none 상태로 설정

* Update: 그레디언트 색상 추가

* [#79]Feat: 좋아요 버튼 클릭 시, 원형 timer, progress 그레디언트 적용

* [#74]Feat: 마지막 더미 유저 Footer View 추가

- footer와 section의 제약조건 설정

* [#74]Feat: 좋아요 버튼 액션 구현

- TimeState에 none 케이스 생성
- 좋아요, 싫어요, 신고, 차단 시, 타이머, 프로그래스를 초기화하기 위함
- 13초에서 15초로 디자인 반영
- vc에서는 scroll만 처리하고 나머지는 cell에서 처리할 수 있도록 구현

* Mod: self capturing in closure

* Add: Fastlane (#78)

* Add: Fastlane

fastlane

* [#74]Feat: 신고하기 팝업 커스텀

- ContentView 자체를 커스텀해서 content view를 파라미터로 받아서 팝업에 표시하도록 구현
- left, right -> top, bottom으로 수정
- withSeprator 플래그를 만들어서 구분선 유뮤 구현
- bottom button(마지막 버튼)은 무조건 separator 있음

* [#74]Feat: Toast 메시지 구현

- Toast-Swift 참고
- 디폴트 값 설정

* Update: 컴포넌트의 접근 지정 연산자 수정 및 이름 수정

* Update: 테스트 이미지로 임시 변경

- 서버 연동 전까지는 해당 이미지로 테스트하고자 함

* [#74]Feat: 차단, 신고 시, .none 상태로 업데이트 후, 다음 셀로 스크롤 구현

- user info box 팝업에서 report(느낌표) 버튼 클릭 시, 멈춤 화면 디자인 반영
- 차단 or 신고 팝업 표시 시, 멈춤 화면에서 이미지, 텍스트 히든
- 중지했을 때의 동작을 enum으로 나눠서 처리할 필요 없는 거 같아서 bool로 변경
- 차단 or 신고 시, 멈춤 화면 없애고 dim view 표시 후, none 상태로 설정

* Update: 그레디언트 색상 추가

* [#79]Feat: 좋아요 버튼 클릭 시, 원형 timer, progress 그레디언트 적용

* [#74]Feat: 마지막 더미 유저 Footer View 추가

- footer와 section의 제약조건 설정

* [#74]Feat: 좋아요 버튼 액션 구현

- TimeState에 none 케이스 생성
- 좋아요, 싫어요, 신고, 차단 시, 타이머, 프로그래스를 초기화하기 위함
- 13초에서 15초로 디자인 반영
- vc에서는 scroll만 처리하고 나머지는 cell에서 처리할 수 있도록 구현

* CI_CD: add pkg extension to .gitignore and fastlane 인증 순서 변경

* CICD: TestFlight 수출 규정 암호화 규정 스킵

info plist에 추가 ITSAppUsesNonExemptEncryption

---------

Co-authored-by: LeeSeungmin <dltmdals0608@naver.com>

* Update: layoutSubview() 호출될 때 layer의 frame 설정 및 mask path 설정

- borderWidth 삭제
- cgcolor로 설정

* Refactor: 좋아요, 싫어요 중복 클릭 방지

- 최근 클릭 후, 5초 딜레이
- 필요없는 코드 삭제

* Add: Interceptor

* Fix: AuthRepository Init

---------

Co-authored-by: LeeSeungmin <dltmdals0608@naver.com>
---
 .gitignore                                    |   2 +-
 .../ProjectDescriptionHelpers/InfoPlist.swift |  71 +++--
 Projects/App/Src/SceneDelegate+Register.swift |  25 +-
 .../BaseCoordinator/LaunchCoordinator.swift   |   8 +-
 .../Core/Src/BaseType/ViewControllable.swift  |  46 +++
 Projects/Core/Src/Util/AppData.swift          |  32 ++
 .../Core/Src/Util/DateFormatter+Util.swift    |  37 ++-
 Projects/Core/Src/Util/JSONStorage.swift      |  57 ++++
 Projects/Core/Src/Util/Storage.swift          |  28 ++
 Projects/Data/Src/Base/OAuthCredential.swift  |  29 ++
 Projects/Data/Src/Base/TFIntercepter.swift    |  69 +++++
 .../Src/Model/Request/Auth/LoginReq.swift     |  13 +
 .../Response/Kakao/KakaoCoordinate2dRes.swift |  71 +++++
 .../Model/Response/Kakao/KakaoSearchRes.swift |  71 +++++
 .../Src/Repository/Auth/AuthRepository.swift  |  68 +++++
 .../Data/Src/Repository/Auth/AuthTarget.swift |  44 +++
 .../Src/Repository/Auth/TokenProvider.swift   |  44 +++
 .../Repository/Auth/TokenProviderTarget.swift |  52 ++++
 .../Like/LikeTarget+SampleData.swift          |  12 +-
 .../Local/UserDefaultTokenStore.swift         |  34 +++
 .../Local/UserInfoRepositoory.swift           | 101 ++++++
 .../Repository/MyPage/MyPageRepository.swift  |   7 +-
 .../Src/Repository/MyPage/MyPageTarget.swift  |  15 +-
 .../Repository/SignUp/SignUpRepository.swift  |  64 ++++
 .../SignUp/SignUpTarget+SampleData.swift      |  78 +++++
 .../Src/Repository/SignUp/SignUpTarget.swift  |  70 +++++
 .../Data/Src/Service/ContactService.swift     |  58 ++++
 .../Data/Src/Service/KakaoAPIService.swift    |  40 +++
 .../Data/Src/Service/KakaoAPITarget.swift     |  62 ++++
 .../Data/Src/Service/LocationService.swift    |  76 +++++
 .../Src/Model/User/UserProfilePhoto.swift     |   2 +-
 .../Auth/Demo/Src/AppDelegate+Register.swift  |  57 ++++
 .../Features/Auth/Demo/Src/AppDelegate.swift  |  27 ++
 .../Demo/Src/Coordinator/AppBuilder.swift     |  51 ++++
 .../Demo/Src/Coordinator/AppCoordinator.swift |  98 ++++++
 .../Auth/Demo/Src/SceneDelegate.swift         |  40 +++
 .../Interface/Src/AuthUseCaseInterface.swift  |  18 ++
 .../Src/Coordinator/AuthBuildable.swift       |  14 +
 .../Coordinator/AuthCoordinating+Action.swift |  14 +
 .../Src/Coordinator/AuthCoordinating.swift    |  26 ++
 .../Coordinator/AuthLaunchCoordinating.swift  |  24 ++
 .../Src/Coordinator/LaunchBuildable.swift     |  14 +
 .../Src/DTO/Request/UserSNSLoginRequest.swift |  22 ++
 .../Src/DTO/Response/UserSignUpInfoRes.swift  |  13 +
 .../Features/Auth/Interface/Src/File.swift    |   8 -
 .../Auth/Interface/Src/Model/AuthError.swift  |  12 +
 .../Auth/Interface/Src/Model/SNSType.swift    |  16 +
 .../Auth/Interface/Src/Model/Token.swift      |  18 ++
 .../AuthRepositoryInterface.swift             |  19 ++
 .../RepositoryInterface/TokenProvider.swift   |  16 +
 .../Src/RepositoryInterface/TokenStore.swift  |  16 +
 Projects/Features/Auth/Project.swift          |  11 +-
 .../PhoneCertificationViewController.swift    |  25 +-
 .../PhoneCertificationViewModel.swift         | 224 ++++++++++++++
 .../SubView/SuccessCertificationView.swift    |  67 ++++
 .../Root/AuthRootViewController.swift}        |  49 ++-
 .../Src/AuthRoot/Root/AuthRootViewModel.swift |  38 +++
 .../Root/SubView/TFLogginButton.swift         |  89 ++++++
 .../Auth/Src/Coordinator/AuthBuilder.swift    |  27 ++
 .../Src/Coordinator/AuthCoordinator.swift     | 123 ++++++++
 .../Auth/Src/Coordinator/LaunchBuilder.swift  |  20 ++
 .../Src/Coordinator/LaunchCoordinator.swift   |  39 +++
 Projects/Features/Auth/Src/File.swift         |   8 -
 .../Auth/Src/Launcher/LauncherViewModel.swift |  75 +++++
 .../TFAuthLauncherViewController.swift        |  33 ++
 .../Auth/Src/SubView/TFTextButton.swift       |  45 +++
 .../Auth/Src/UseCase/AuthUseCase.swift        |  55 ++++
 .../Features/Auth/Src/Util/Regex+Util.swift   |  23 ++
 .../Src/Home/FallingHomeViewController.swift  |   1 -
 .../Src/Coordinator/LikeCoordinating.swift    |   8 +-
 .../Like/Src/Coordinator/LikeBuilder.swift    |   3 +-
 .../Src/Coordinator/LikeCoordinator.swift     |  88 ++++--
 .../Src/Coordinator/MockChatCoordinator.swift |  62 ++++
 .../Src/Coordinator/MockChatRoomBuilder.swift |  23 ++
 .../MockChatRoomViewController.swift          |  47 +++
 .../Src/Home/LikeHomeViewController.swift     |  14 -
 .../Like/Src/Home/LikeHomeViewModel.swift     | 121 +++++---
 .../Src/Profile/LikeProfileViewModel.swift    |  26 +-
 .../MyPageRepositoryInterface.swift           |   4 +-
 Projects/Features/MyPage/Project.swift        |   1 +
 .../Demo/Src/AppDelegate+Register.swift       |  43 +++
 .../SignUp/Demo/Src/AppDelegate.swift         |   4 +-
 .../Demo/Src/Coordinator/AppCoordinator.swift |  13 +-
 .../Coordinator/BottomSheet/BottomSheet.swift |  33 ++
 .../Src/Coordinator/SignUpCoordinating.swift  |   6 +-
 .../Src/DTO/Request/LocationReq.swift         |  26 ++
 .../Interface/Src/DTO/Request/SignUpReq.swift |  99 ++++++
 .../DTO/Request/UserFriendContactReq.swift    |  16 +
 .../Src/DTO/Response/AddressRes.swift         |  42 +++
 .../Src/DTO/Response/EmojiResponse.swift      |   8 +
 .../Response/PhoneValidationRes.swift}        |   2 +-
 .../DTO/Response/UserFriendContactRes.swift   |  12 +
 .../DTO/Response/UserNicknameValidRes.swift   |  16 +
 .../SignUp/Interface/Src/Model/Agrement.swift |  18 ++
 .../Interface/Src/Model/ContactType.swift     |  18 ++
 .../Interface/Src/Model/EmojiType.swift       |  25 ++
 .../Interface/Src/Model/Frequency.swift       |  14 +
 .../SignUp/Interface/Src/Model/Gender.swift   |  27 ++
 .../SignUp/Interface/Src/Model/Religion.swift |  17 ++
 .../SignUp/Interface/Src/Model/UserInfo.swift |  49 +++
 .../SignUpRepositoryInterface.swift           |  20 ++
 .../UserInfoRepositoryInterface.swift         |  19 ++
 .../Src/UseCase/ContactServiceType.swift      |  14 +
 .../Src/UseCase/KakaoAPIServiceType.swift     |  15 +
 .../Src/UseCase/LocationServiceType.swift     |  21 ++
 .../Interface/Src/UseCase/SignUpError.swift   |  14 +
 .../Src/UseCase/SignUpUseCaseInterface.swift  |  23 ++
 .../UseCase/UserInfoUseCaseInterface.swift    |  19 ++
 Projects/Features/SignUp/Project.swift        |   4 +-
 .../SignUpCoordinator+BottomSheet.swift       |  30 ++
 .../SignUpCoordinator+Contacts.swift          |  84 +++++
 .../SignUpCoordinator+PHPicker.swift          |  51 ++++
 .../Src/Coordinator/SignUpCoordinator.swift   | 211 ++++++++++---
 .../Coordinator/SignUpCoordinatorAction.swift |  43 +++
 .../SignUp/Src/Coordinator/SignUpStore.swift  | 106 +++++++
 .../AlcoholTobaccoInputViewController.swift   |  65 ++++
 .../AlcoholTobaccoInputViewModel.swift        |  89 ++++++
 .../AlcoholTobaccoPickerView.swift            | 160 ++++++++++
 .../Email/EmailInputViewController.swift      |  55 +---
 .../Email/EmailInputViewModel.swift           |  59 +++-
 .../GenderPick/GenderPickerView.swift         | 110 +++++++
 .../GenderPickerViewController.swift          |  80 +++++
 .../GenderPick/GenderPickerViewModel.swift    |  96 ++++++
 .../HeightPicker/HeightPickerView.swift       | 103 +++++++
 .../HeightPicker/HeightVIewController.swift   |  62 ++++
 .../HeightPicker/HeightVIewModel.swift        |  94 ++++++
 .../IdealTypePickerViewController.swift       |  59 ++++
 .../IdealTypePickerViewModel.swift            | 122 ++++++++
 .../InterestPickerViewController.swift        |  59 ++++
 .../InterestPicker/TagPickerViewModel.swift   | 123 ++++++++
 .../IntroduceInput/IntroduceInputView.swift   |  80 +++++
 .../IntroduceInputViewController.swift        |  50 +++
 .../IntroduceInputViewModel.swift             |  73 +++++
 .../Location/LocationInputView.swift          |  80 +++++
 .../LocationInputViewController.swift         |  55 ++++
 .../Location/LocationInputViewModel.swift     | 127 ++++++++
 .../Location/PostCodeWebViewController.swift  | 124 ++++++++
 .../Location/SubView/LocationInputField.swift | 146 +++++++++
 .../NicknamInputeViewController.swift         | 114 ++-----
 .../SignUpRoot/Nickname/NicknameView.swift    |  93 ++----
 .../Nickname/NicknameViewModel.swift          |  95 +++++-
 .../PhoneCertificationViewModel.swift         | 161 ----------
 .../Photo/PhotoCell/PhotoCell.swift           |  63 ++++
 .../Photo/PhotoCell/PhotoCellViewModel.swift  |  25 ++
 .../Src/SignUpRoot/Photo/PhotoInputView.swift | 113 +++++++
 .../Photo/PhotoInputViewController+Diff.swift |  44 +++
 .../Photo/PhotoInputViewController.swift      |  91 ++++++
 .../Photo/PhotoInputViewModel.swift           | 192 ++++++++++++
 .../Src/SignUpRoot/Photo/PhotoPicker.swift    | 221 ++++++++++++++
 .../Src/SignUpRoot/Photo/PhotoService.swift   |  42 +++
 .../Policy/PolicyAgreementView.swift          |  76 +++--
 .../PolicyAgreementViewController.swift       |  56 ++--
 .../Policy/PolicyAgreementViewModel.swift     | 239 +++++++++------
 .../PreferGenderPickerView.swift              | 121 ++++++++
 .../PreferGenderPickerViewController.swift    |  59 ++++
 .../PreferGenderPickerViewModel.swift         |  82 +++++
 .../Religion/ReligionPickerView.swift         | 111 +++++++
 .../ReligionPickerViewController.swift        |  59 ++++
 .../Religion/ReligionViewModel.swift          | 127 ++++++++
 .../SignUpComplete/SignUpCompleteView.swift   | 128 ++++++++
 .../SignUpCompleteViewController.swift        |  55 ++++
 .../SignUpCompleteViewModel.swift             | 130 ++++++++
 .../Src/SignUpRoot/SignUpRootViewModel.swift  |  66 ----
 .../UserContact/UserContactView.swift         |  86 ++++++
 .../UserContactViewController.swift           |  54 ++++
 .../UserContact/UserContactViewModel.swift    |  90 ++++++
 .../PickerBottomSheet/PickerAdapter.swift     |  51 ++++
 .../PickerBottomSheet/PickerBottomSheet.swift |  60 ++++
 .../PickerBottomSheetView.swift               |  88 ++++++
 .../PickerBottomSheetViewModel.swift          | 133 ++++++++
 .../SinglePickerBottomSheet.swift             |  67 ++++
 .../SinglePickerBottomSheetViewModel.swift    |  65 ++++
 .../SinglePickerViewController.swift          |  92 ++++++
 .../Src/SubView/ButtonPickerView+Rx.swift     |  43 +++
 .../SignUp/Src/SubView/ButtonPickerView.swift | 129 ++++++++
 .../SignUp/Src/SubView/CTAButton.swift        |   2 +-
 .../Cell/InputTagCollectionViewCell.swift     | 121 ++++++++
 .../Src/SubView/Cell/ReligionPickerCell.swift |  57 ++++
 .../SignUp/Src/SubView/DatePickerView.swift   |  69 +++++
 .../Src/SubView/ResizableTextView.swift       |  20 ++
 .../Src/SubView/ServiceAgreementRowView.swift | 160 +++++-----
 .../Src/SubView/TFButtonPickerView.swift      | 110 +++++++
 .../SignUp/Src/SubView/TFCheckButton.swift    |  54 ++++
 .../Src/SubView/TFGenderPickerView.swift      |   8 +
 .../Src/SubView/TFResizableTextView.swift     | 287 ++++++++++++++++++
 .../SignUp/Src/SubView/TFTextField.swift      | 234 ++++++++++++++
 .../SignUp/Src/SubView/TFTextView.swift       | 261 ++++++++++++++++
 .../SignUp/Src/SubView/TagPickerView.swift    | 103 +++++++
 .../SignUp/Src/UseCase/SignUpUseCase.swift    | 111 +++++++
 .../SignUp/Src/UseCase/UserInfoUseCase.swift  |  47 +++
 .../Src/Util/CollectionViewLayout+Util.swift  |  43 +++
 .../Features/SignUp/Src/Util/Date+Util.swift  |  24 ++
 .../SignUp/Src/Util/UIButton+Util.swift       |  27 ++
 .../SignUp/Src/Util/UILabel+Util.swift        |  45 +++
 .../Coordinator/AppCoordinator.swift          |  61 ++--
 .../Coordinator/AppRootBuilder.swift          |  19 +-
 .../THTOrange400.colorset/Contents.json       |  20 ++
 .../TFBaseViewController.swift                |  32 +-
 .../TFLaunchVIewController.swift              |  35 ++-
 .../Cell/TagCollectionViewCell.swift          |  28 +-
 .../Src/UIComponent/TagCollectionView.swift   |  18 +-
 .../DesignSystem/Src/Util/Emoji+Util.swift    |   2 +-
 .../Src/Util/Preview+UIView.swift             |   2 -
 .../Src/Util/UICollectionView+Utils.swift     |  10 +
 .../Src/Util/UITableView+Utils.swift          |  10 +
 .../Src/WebView/TFWebViewController.swift     |  87 ++++++
 .../Network/Src/Network/BaseTargetType.swift  |  10 +-
 .../Src/Network/ProviderProtocol.swift        |  30 +-
 .../Target+Templates.swift                    |   2 +-
 209 files changed, 11130 insertions(+), 1031 deletions(-)
 create mode 100644 Projects/Core/Src/Util/AppData.swift
 create mode 100644 Projects/Core/Src/Util/JSONStorage.swift
 create mode 100644 Projects/Core/Src/Util/Storage.swift
 create mode 100644 Projects/Data/Src/Base/OAuthCredential.swift
 create mode 100644 Projects/Data/Src/Base/TFIntercepter.swift
 create mode 100644 Projects/Data/Src/Model/Request/Auth/LoginReq.swift
 create mode 100644 Projects/Data/Src/Model/Response/Kakao/KakaoCoordinate2dRes.swift
 create mode 100644 Projects/Data/Src/Model/Response/Kakao/KakaoSearchRes.swift
 create mode 100644 Projects/Data/Src/Repository/Auth/AuthRepository.swift
 create mode 100644 Projects/Data/Src/Repository/Auth/AuthTarget.swift
 create mode 100644 Projects/Data/Src/Repository/Auth/TokenProvider.swift
 create mode 100644 Projects/Data/Src/Repository/Auth/TokenProviderTarget.swift
 create mode 100644 Projects/Data/Src/Repository/Local/UserDefaultTokenStore.swift
 create mode 100644 Projects/Data/Src/Repository/Local/UserInfoRepositoory.swift
 create mode 100644 Projects/Data/Src/Repository/SignUp/SignUpRepository.swift
 create mode 100644 Projects/Data/Src/Repository/SignUp/SignUpTarget+SampleData.swift
 create mode 100644 Projects/Data/Src/Repository/SignUp/SignUpTarget.swift
 create mode 100644 Projects/Data/Src/Service/ContactService.swift
 create mode 100644 Projects/Data/Src/Service/KakaoAPIService.swift
 create mode 100644 Projects/Data/Src/Service/KakaoAPITarget.swift
 create mode 100644 Projects/Data/Src/Service/LocationService.swift
 create mode 100644 Projects/Features/Auth/Demo/Src/AppDelegate+Register.swift
 create mode 100644 Projects/Features/Auth/Demo/Src/AppDelegate.swift
 create mode 100644 Projects/Features/Auth/Demo/Src/Coordinator/AppBuilder.swift
 create mode 100644 Projects/Features/Auth/Demo/Src/Coordinator/AppCoordinator.swift
 create mode 100644 Projects/Features/Auth/Demo/Src/SceneDelegate.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/AuthUseCaseInterface.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/Coordinator/AuthBuildable.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/Coordinator/AuthCoordinating+Action.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/Coordinator/AuthCoordinating.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/Coordinator/AuthLaunchCoordinating.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/Coordinator/LaunchBuildable.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/DTO/Request/UserSNSLoginRequest.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/DTO/Response/UserSignUpInfoRes.swift
 delete mode 100644 Projects/Features/Auth/Interface/Src/File.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/Model/AuthError.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/Model/SNSType.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/Model/Token.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/RepositoryInterface/AuthRepositoryInterface.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/RepositoryInterface/TokenProvider.swift
 create mode 100644 Projects/Features/Auth/Interface/Src/RepositoryInterface/TokenStore.swift
 rename Projects/Features/{SignUp/Src/SignUpRoot/PhoneNumber => Auth/Src/AuthRoot/Certificate}/PhoneCertificationViewController.swift (95%)
 create mode 100644 Projects/Features/Auth/Src/AuthRoot/Certificate/PhoneCertificationViewModel.swift
 create mode 100644 Projects/Features/Auth/Src/AuthRoot/Certificate/SubView/SuccessCertificationView.swift
 rename Projects/Features/{SignUp/Src/SignUpRoot/SignUpRootViewController.swift => Auth/Src/AuthRoot/Root/AuthRootViewController.swift} (55%)
 create mode 100644 Projects/Features/Auth/Src/AuthRoot/Root/AuthRootViewModel.swift
 create mode 100644 Projects/Features/Auth/Src/AuthRoot/Root/SubView/TFLogginButton.swift
 create mode 100644 Projects/Features/Auth/Src/Coordinator/AuthBuilder.swift
 create mode 100644 Projects/Features/Auth/Src/Coordinator/AuthCoordinator.swift
 create mode 100644 Projects/Features/Auth/Src/Coordinator/LaunchBuilder.swift
 create mode 100644 Projects/Features/Auth/Src/Coordinator/LaunchCoordinator.swift
 delete mode 100644 Projects/Features/Auth/Src/File.swift
 create mode 100644 Projects/Features/Auth/Src/Launcher/LauncherViewModel.swift
 create mode 100644 Projects/Features/Auth/Src/Launcher/TFAuthLauncherViewController.swift
 create mode 100644 Projects/Features/Auth/Src/SubView/TFTextButton.swift
 create mode 100644 Projects/Features/Auth/Src/UseCase/AuthUseCase.swift
 create mode 100644 Projects/Features/Auth/Src/Util/Regex+Util.swift
 create mode 100644 Projects/Features/Like/Src/Coordinator/MockChatCoordinator.swift
 create mode 100644 Projects/Features/Like/Src/Coordinator/MockChatRoomBuilder.swift
 create mode 100644 Projects/Features/Like/Src/Coordinator/MockChatRoomViewController.swift
 create mode 100644 Projects/Features/SignUp/Demo/Src/AppDelegate+Register.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/Coordinator/BottomSheet/BottomSheet.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/DTO/Request/LocationReq.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/DTO/Request/SignUpReq.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/DTO/Request/UserFriendContactReq.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/DTO/Response/AddressRes.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/DTO/Response/EmojiResponse.swift
 rename Projects/Features/SignUp/Interface/Src/{Model/PhoneValidationResponse.swift => DTO/Response/PhoneValidationRes.swift} (86%)
 create mode 100644 Projects/Features/SignUp/Interface/Src/DTO/Response/UserFriendContactRes.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/DTO/Response/UserNicknameValidRes.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/Model/Agrement.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/Model/ContactType.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/Model/EmojiType.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/Model/Frequency.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/Model/Gender.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/Model/Religion.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/Model/UserInfo.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/RepositoryInterface/SignUpRepositoryInterface.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/RepositoryInterface/UserInfoRepositoryInterface.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/UseCase/ContactServiceType.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/UseCase/KakaoAPIServiceType.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/UseCase/LocationServiceType.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/UseCase/SignUpError.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/UseCase/SignUpUseCaseInterface.swift
 create mode 100644 Projects/Features/SignUp/Interface/Src/UseCase/UserInfoUseCaseInterface.swift
 create mode 100644 Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+BottomSheet.swift
 create mode 100644 Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+Contacts.swift
 create mode 100644 Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+PHPicker.swift
 create mode 100644 Projects/Features/SignUp/Src/Coordinator/SignUpCoordinatorAction.swift
 create mode 100644 Projects/Features/SignUp/Src/Coordinator/SignUpStore.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoInputViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoInputViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoPickerView.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerView.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightPickerView.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightVIewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightVIewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/IdealTypePicker/IdealTypePickerViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/IdealTypePicker/IdealTypePickerViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/InterestPicker/InterestPickerViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/InterestPicker/TagPickerViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputView.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputView.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Location/PostCodeWebViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Location/SubView/LocationInputField.swift
 delete mode 100644 Projects/Features/SignUp/Src/SignUpRoot/PhoneNumber/PhoneCertificationViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoCell/PhotoCell.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoCell/PhotoCellViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputView.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewController+Diff.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoPicker.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoService.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerView.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionPickerView.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionPickerViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteView.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteViewModel.swift
 delete mode 100644 Projects/Features/SignUp/Src/SignUpRoot/SignUpRootViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactView.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerAdapter.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheet.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheetView.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheetViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerBottomSheet.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerBottomSheetViewModel.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerViewController.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/ButtonPickerView+Rx.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/ButtonPickerView.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/Cell/InputTagCollectionViewCell.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/Cell/ReligionPickerCell.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/DatePickerView.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/ResizableTextView.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/TFButtonPickerView.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/TFCheckButton.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/TFGenderPickerView.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/TFResizableTextView.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/TFTextField.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/TFTextView.swift
 create mode 100644 Projects/Features/SignUp/Src/SubView/TagPickerView.swift
 create mode 100644 Projects/Features/SignUp/Src/UseCase/SignUpUseCase.swift
 create mode 100644 Projects/Features/SignUp/Src/UseCase/UserInfoUseCase.swift
 create mode 100644 Projects/Features/SignUp/Src/Util/CollectionViewLayout+Util.swift
 create mode 100644 Projects/Features/SignUp/Src/Util/Date+Util.swift
 create mode 100644 Projects/Features/SignUp/Src/Util/UIButton+Util.swift
 create mode 100644 Projects/Features/SignUp/Src/Util/UILabel+Util.swift
 create mode 100644 Projects/Modules/DesignSystem/Resources/Color.xcassets/THTOrange400.colorset/Contents.json
 create mode 100644 Projects/Modules/DesignSystem/Src/WebView/TFWebViewController.swift

diff --git a/.gitignore b/.gitignore
index 1b3f42ee..02ba8c34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,4 +80,4 @@ fastlane/*.env
 # App Packaging
 *.ipa
 *.dSYM.zip
-*.dSYM
\ No newline at end of file
+*.dSYM
diff --git a/Plugins/THTIOSHappyNewYear/ProjectDescriptionHelpers/InfoPlist.swift b/Plugins/THTIOSHappyNewYear/ProjectDescriptionHelpers/InfoPlist.swift
index 507567ae..9ec2feb3 100644
--- a/Plugins/THTIOSHappyNewYear/ProjectDescriptionHelpers/InfoPlist.swift
+++ b/Plugins/THTIOSHappyNewYear/ProjectDescriptionHelpers/InfoPlist.swift
@@ -12,6 +12,36 @@ public let infoPlistExtension: [String: InfoPlist.Value] = [
   "CFBundleVersion": "1",
   "UILaunchStoryboardName": "LaunchScreen",
   "CFBundleName": "THT",
+  "UIApplicationSceneManifest": [
+    "UIApplicationSupportsMultipleScenes": false,
+    "UISceneConfigurations": [
+      "UIWindowSceneSessionRoleApplication": [
+        [
+          "UISceneConfigurationName": "Default Configuration",
+          "UISceneDelegateClassName": "$(PRODUCT_MODULE_NAME).SceneDelegate"
+        ],
+      ]
+    ]
+  ],
+
+  // MARK: Privacy
+
+  "UIAppFonts": [
+    "Item 0": "Pretendard-Medium.otf",
+    "Item 1": "Pretendard-Regular.otf",
+    "Item 2": "Pretendard-SemiBold.otf",
+    "Item 3": "Pretendard-Bold.otf",
+    "Item 4": "Pretendard-ExtraBold.otf"
+  ],
+  "UIUserInterfaceStyle": "Dark"
+]
+
+public func infoPlistExtension(name: String) -> [String: InfoPlist.Value] {
+  [
+    "CFBundleShortVersionString": "1.0",
+    "CFBundleVersion": "1",
+    "UILaunchStoryboardName": "LaunchScreen",
+    "CFBundleName": "\(name)",
     "UIApplicationSceneManifest": [
       "UIApplicationSupportsMultipleScenes": false,
       "UISceneConfigurations": [
@@ -23,8 +53,15 @@ public let infoPlistExtension: [String: InfoPlist.Value] = [
         ]
       ]
     ],
-    "App Transport Security Settings": ["Allow Arbitrary Loads": true],
-    "Privacy - Photo Library Additions Usage Description": "프로필에 사용됨",
+    "NSAppTransportSecurity": ["NSAllowsArbitraryLoads": true],
+
+    // MARK: Privacy
+    "NSContactsUsageDescription": "연락처 사용",
+    "NSLocationWhenInUseUsageDescription": "위치 정보 사용",
+
+    // MARK: 수출 규청 알고리즘 통과
+    "ITSAppUsesNonExemptEncryption": false,
+
     "UIAppFonts": [
       "Item 0": "Pretendard-Medium.otf",
       "Item 1": "Pretendard-Regular.otf",
@@ -33,35 +70,5 @@ public let infoPlistExtension: [String: InfoPlist.Value] = [
       "Item 4": "Pretendard-ExtraBold.otf"
     ],
     "UIUserInterfaceStyle": "Dark"
-]
-
-public func infoPlistExtension(name: String) -> [String: InfoPlist.Value] {
-  [
-    "CFBundleShortVersionString": "1.0",
-    "CFBundleVersion": "1",
-    "UILaunchStoryboardName": "LaunchScreen",
-    "CFBundleName": "\(name)",
-      "UIApplicationSceneManifest": [
-        "UIApplicationSupportsMultipleScenes": false,
-        "UISceneConfigurations": [
-          "UIWindowSceneSessionRoleApplication": [
-            [
-              "UISceneConfigurationName": "Default Configuration",
-              "UISceneDelegateClassName": "$(PRODUCT_MODULE_NAME).SceneDelegate"
-            ],
-          ]
-        ]
-      ],
-      "App Transport Security Settings": ["Allow Arbitrary Loads": true],
-      "Privacy - Photo Library Additions Usage Description": "프로필에 사용됨",
-      "UIAppFonts": [
-        "Item 0": "Pretendard-Medium.otf",
-        "Item 1": "Pretendard-Regular.otf",
-        "Item 2": "Pretendard-SemiBold.otf",
-        "Item 3": "Pretendard-Bold.otf",
-        "Item 4": "Pretendard-ExtraBold.otf"
-      ],
-    "UIUserInterfaceStyle": "Dark",
-    "ITSAppUsesNonExemptEncryption": false // 수출 규정 누락 문제
   ]
 }
diff --git a/Projects/App/Src/SceneDelegate+Register.swift b/Projects/App/Src/SceneDelegate+Register.swift
index 64a40fa7..9003c88c 100644
--- a/Projects/App/Src/SceneDelegate+Register.swift
+++ b/Projects/App/Src/SceneDelegate+Register.swift
@@ -12,10 +12,6 @@ import Data
 
 import Feature
 import Networks
-import FallingInterface
-import LikeInterface
-import ChatInterface
-import MyPageInterface
 
 extension AppDelegate {
   var container: DIContainer {
@@ -23,6 +19,27 @@ extension AppDelegate {
   }
 
   func registerDependencies() {
+    let tokenStore = UserDefaultTokenStore()
+    let tokenProvider = DefaultTokenProvider()
+
+    container.register(
+      interface: UserInfoUseCaseInterface.self,
+      implement: { UserInfoUseCase(repository: UserDefaultUserInfoRepository()) })
+
+    container.register(
+      interface: AuthUseCaseInterface.self,
+      implement: { AuthUseCase(authRepository: AuthRepository(tokenStore: tokenStore, tokenProvider: tokenProvider),
+                               tokenStore: tokenStore) })
+    container.register(
+      interface: SignUpUseCaseInterface.self,
+      implement: { SignUpUseCase(
+        repository: SignUpRepository(),
+        locationService: LocationService(),
+        kakaoAPIService: KakaoAPIService(),
+        contactService: ContactService(),
+        tokenStore: tokenStore)
+      })
+    
     container.register(
       interface: FallingUseCaseInterface.self,
       implement: {
diff --git a/Projects/Core/Src/BaseCoordinator/LaunchCoordinator.swift b/Projects/Core/Src/BaseCoordinator/LaunchCoordinator.swift
index 1b51bf0e..1d7571b8 100644
--- a/Projects/Core/Src/BaseCoordinator/LaunchCoordinator.swift
+++ b/Projects/Core/Src/BaseCoordinator/LaunchCoordinator.swift
@@ -7,7 +7,7 @@
 
 import UIKit
 
-public protocol LaunchCoordinating {
+public protocol LaunchCoordinating: Coordinator {
   func launch(window: UIWindow)
 }
 
@@ -21,10 +21,6 @@ open class LaunchCoordinator: BaseCoordinator, LaunchCoordinating {
     window.rootViewController = self.viewControllable.uiController
     window.makeKeyAndVisible()
 
-    TFLogger.domain.debug("AppCoordinator 1초 async")
-    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-      self.start()
-    }
+    self.start()
   }
 }
-
diff --git a/Projects/Core/Src/BaseType/ViewControllable.swift b/Projects/Core/Src/BaseType/ViewControllable.swift
index d39bc61f..84e96f99 100644
--- a/Projects/Core/Src/BaseType/ViewControllable.swift
+++ b/Projects/Core/Src/BaseType/ViewControllable.swift
@@ -39,4 +39,50 @@ public extension ViewControllable {
       self.uiController.navigationController?.popViewController(animated: animated)
     }
   }
+
+  func present(_ viewControllable: ViewControllable, animated: Bool) {
+    if let nav = self.uiController as? UINavigationController {
+      nav.present(viewControllable.uiController, animated: animated)
+    } else {
+      self.uiController.navigationController?.present(viewControllable.uiController, animated: animated)
+    }
+  }
+
+  func dismiss() {
+    if let presented = self.uiController.presentedViewController {
+      presented.dismiss(animated: true)
+    } else {
+      self.uiController.dismiss(animated: true)
+    }
+  }
+
+  func presentBottomSheet(_ viewControllable: ViewControllable, animated: Bool) {
+    let navigation = NavigationViewControllable(rootViewControllable: viewControllable)
+    if let sheet = navigation.uiController.sheetPresentationController {
+      sheet.prefersGrabberVisible = false
+      sheet.preferredCornerRadius = 12
+      sheet.detents = [
+        .small()
+      ]
+    }
+
+    if let nav = self.uiController as? UINavigationController {
+      nav.present(navigation.uiController, animated: animated)
+    } else {
+      self.uiController.navigationController?.present(navigation.uiController, animated: animated)
+    }
+  }
+}
+
+extension UISheetPresentationController.Detent {
+  static func small(
+      identifier: UISheetPresentationController.Detent.Identifier? = nil,
+      resolvedValue:  CGFloat = 300
+  ) -> UISheetPresentationController.Detent {
+    return .custom { context in
+      resolvedValue
+    }
+  }
+
+//  static let small = UISheetPresentationController.Detent.Identifier("small")
 }
diff --git a/Projects/Core/Src/Util/AppData.swift b/Projects/Core/Src/Util/AppData.swift
new file mode 100644
index 00000000..84358d9a
--- /dev/null
+++ b/Projects/Core/Src/Util/AppData.swift
@@ -0,0 +1,32 @@
+//
+//  AppData.swift
+//  Core
+//
+//  Created by Kanghos on 2024/03/02.
+//
+
+import Foundation
+
+public struct AppData {
+  private enum Key: String {
+    case accessToken
+    case phoneNumber
+    case accessTokenExpiredIn
+  }
+  public struct Auth {
+    @Storage<String>(key: Key.accessToken.rawValue, defaultValue: "")
+    public static var accessToken
+
+    @Storage<Int>(key: Key.accessTokenExpiredIn.rawValue, defaultValue: 0)
+    public static var accessTokenExpiredIn
+
+    public static var needAuth: Bool {
+      accessToken.isEmpty
+    }
+  }
+
+  public struct User {
+    @Storage<String>(key: Key.phoneNumber.rawValue, defaultValue: "")
+    public static var phoneNumber
+  }
+}
diff --git a/Projects/Core/Src/Util/DateFormatter+Util.swift b/Projects/Core/Src/Util/DateFormatter+Util.swift
index d0d86228..a23398ef 100644
--- a/Projects/Core/Src/Util/DateFormatter+Util.swift
+++ b/Projects/Core/Src/Util/DateFormatter+Util.swift
@@ -25,11 +25,19 @@ extension DateFormatter {
 //    formatter.locale = Locale(identifier: "ko-KR")
     return formatter
   }
+
+  static var normalDateFormatter: DateFormatter {
+    let formatter = DateFormatter()
+    formatter.dateFormat = "yyyy.MM.dd"
+    formatter.locale = Locale(identifier: "ko-KR")
+
+    return formatter
+  }
 }
 
 public extension String {
   func toDate() -> Date {
-    DateFormatter.unixDateFormatter.date(from: self) ?? Date()
+    DateFormatter.normalDateFormatter.date(from: self) ?? Date()
   }
 }
 
@@ -37,4 +45,31 @@ public extension Date {
   func toTimeString() -> String {
     DateFormatter.timeFormatter.string(from: self)
   }
+  func toDateString() -> String {
+    DateFormatter.unixDateFormatter.string(from: self)
+  }
+  func toYMDDotDateString() -> String {
+    DateFormatter.normalDateFormatter.string(from: self)
+  }
+
+  // From GPT
+  static func currentAdultDateOrNil() -> Date? {
+         // 성년이 되는 나이
+         let adulthoodAge = 21
+
+         // 현재 달력
+         var calendar = Calendar.current
+
+         // 지역을 한국으로 설정 (옵션)
+         calendar.locale = Locale(identifier: "ko_KR")
+
+         // 날짜 구성 요소를 추출
+         var dateComponents = calendar.dateComponents([.year, .month, .day], from: Date())
+
+         // 성년 나이를 더함
+         dateComponents.year = (dateComponents.year ?? 0) - adulthoodAge
+
+         // 새로운 날짜를 생성하여 반환
+         return calendar.date(from: dateComponents)
+     }
 }
diff --git a/Projects/Core/Src/Util/JSONStorage.swift b/Projects/Core/Src/Util/JSONStorage.swift
new file mode 100644
index 00000000..91c70d5c
--- /dev/null
+++ b/Projects/Core/Src/Util/JSONStorage.swift
@@ -0,0 +1,57 @@
+//
+//  JSONStorage.swift
+//  Core
+//
+//  Created by Kanghos on 5/14/24.
+//
+
+import Foundation
+
+extension UserDefaults {
+
+    public func setCodableObject<Object>(
+        _ object: Object, forKey: String
+    ) throws where Object: Encodable {
+
+        let data = try JSONEncoder().encode(object)
+        self.set(data, forKey: forKey)
+    }
+
+    public func getCodableObject<Object>(
+      forKey: String,
+      as type: Object.Type
+    ) throws -> Object where Object: Decodable {
+
+        guard let data = self.data(forKey: forKey) else {
+            throw NSError(domain: "UserDefaults", code: 0, userInfo: nil)
+        }
+        return try JSONDecoder().decode(type, from: data)
+    }
+}
+
+@propertyWrapper
+public struct CodableStorage<T: Codable> {
+  private let key: String
+  private let defaultValue: T?
+
+  public init(key: String, defaultValue: T? = nil) {
+    self.key = key
+    self.defaultValue = defaultValue
+  }
+
+  public var wrappedValue: T? {
+    get {
+      guard let object = try? UserDefaults.standard.getCodableObject(forKey: key, as: T.self) else {
+        return defaultValue
+      }
+      return object
+    }
+    set {
+      if newValue == nil {
+        UserDefaults.standard.removeObject(forKey: key)
+      }
+      try? UserDefaults.standard.setCodableObject(newValue, forKey: key)
+    }
+  }
+}
+
diff --git a/Projects/Core/Src/Util/Storage.swift b/Projects/Core/Src/Util/Storage.swift
new file mode 100644
index 00000000..d7e00267
--- /dev/null
+++ b/Projects/Core/Src/Util/Storage.swift
@@ -0,0 +1,28 @@
+//
+//  Storage.swift
+//  Core
+//
+//  Created by Kanghos on 2024/03/02.
+//
+
+import Foundation
+
+@propertyWrapper
+public struct Storage<T> {
+  private let key: String
+  private let defaultValue: T
+
+  public init(key: String, defaultValue: T) {
+    self.key = key
+    self.defaultValue = defaultValue
+  }
+
+  public var wrappedValue: T {
+    get {
+      return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
+    }
+    set {
+      UserDefaults.standard.setValue(newValue, forKey: key)
+    }
+  }
+}
diff --git a/Projects/Data/Src/Base/OAuthCredential.swift b/Projects/Data/Src/Base/OAuthCredential.swift
new file mode 100644
index 00000000..2c246236
--- /dev/null
+++ b/Projects/Data/Src/Base/OAuthCredential.swift
@@ -0,0 +1,29 @@
+//
+//  OAuthCredential.swift
+//  Data
+//
+//  Created by Kanghos on 6/6/24.
+//
+
+import Foundation
+import AuthInterface
+
+import Alamofire
+
+struct OAuthCredential: AuthenticationCredential {
+  let accessToken: String
+  let accessTokenExpiresIn: Double
+  var requiresRefresh: Bool { Date().timeIntervalSince1970 - 60 * 5 > accessTokenExpiresIn }
+}
+
+extension Token {
+  func toAuthOCredential() -> OAuthCredential {
+    OAuthCredential(accessToken: accessToken, accessTokenExpiresIn: accessTokenExpiresIn)
+  }
+}
+
+extension OAuthCredential {
+  func toToken() -> Token {
+    Token(accessToken: accessToken, accessTokenExpiresIn: accessTokenExpiresIn)
+  }
+}
diff --git a/Projects/Data/Src/Base/TFIntercepter.swift b/Projects/Data/Src/Base/TFIntercepter.swift
new file mode 100644
index 00000000..e104c1b1
--- /dev/null
+++ b/Projects/Data/Src/Base/TFIntercepter.swift
@@ -0,0 +1,69 @@
+//
+//  TFIntercepter.swift
+//  Data
+//
+//  Created by Kanghos on 6/5/24.
+//
+
+import Foundation
+import Moya
+import Alamofire
+import RxSwift
+import AuthInterface
+
+final class OAuthAuthenticator: Authenticator {
+  private let tokenProvider: TokenProvider
+
+  init(tokenProvider: TokenProvider) {
+    self.tokenProvider = tokenProvider
+  }
+
+  func apply(_ credential: OAuthCredential, to urlRequest: inout URLRequest) {
+
+    // SignUp 관련 API는 토큰 없이 호출해야함
+    if let url = urlRequest.url, url.path().contains("users/join") == false {
+      urlRequest.headers.add(.authorization(bearerToken: credential.accessToken))
+    }
+  }
+
+  func refresh(_ credential: OAuthCredential,
+               for session: Session,
+               completion: @escaping (Result<OAuthCredential, Error>) -> Void) {
+
+
+    // Refresh the credential using the refresh token...then call completion with the new credential.
+    //
+    // The new credential will automatically be stored within the `AuthenticationInterceptor`. Future requests will
+    // be authenticated using the `apply(_:to:)` method using the new credential.
+    
+    tokenProvider.refreshToken(token: credential.toToken()) { result in
+      switch result {
+      case .success(let token):
+        completion(.success(token.toAuthOCredential()))
+      case .failure(let error):
+        completion(.failure(error))
+      }
+    }
+  }
+
+  func didRequest(_ urlRequest: URLRequest,
+                  with response: HTTPURLResponse,
+                  failDueToAuthenticationError error: Error) -> Bool {
+    // If authentication server CANNOT invalidate credentials, return `false`
+
+    // If authentication server CAN invalidate credentials, then inspect the response matching against what the
+    // authentication server returns as an authentication failure. This is generally a 401 along with a custom
+    // header value.
+    // return response.statusCode == 401
+    return response.statusCode == 401
+  }
+
+  func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: OAuthCredential) -> Bool {
+    // If authentication server CANNOT invalidate credentials, return `true`
+
+    // If authentication server CAN invalidate credentials, then compare the "Authorization" header value in the
+    // `URLRequest` against the Bearer token generated with the access token of the `Credential`.
+    let bearerToken = HTTPHeader.authorization(bearerToken: credential.accessToken).value
+    return urlRequest.headers["Authorization"] == bearerToken
+  }
+}
diff --git a/Projects/Data/Src/Model/Request/Auth/LoginReq.swift b/Projects/Data/Src/Model/Request/Auth/LoginReq.swift
new file mode 100644
index 00000000..4e7951a8
--- /dev/null
+++ b/Projects/Data/Src/Model/Request/Auth/LoginReq.swift
@@ -0,0 +1,13 @@
+//
+//  LoginReq.swift
+//  Data
+//
+//  Created by Kanghos on 6/3/24.
+//
+
+import Foundation
+
+struct LoginReq: Codable {
+  let phoneNumber: String
+  let deviceKey: String
+}
diff --git a/Projects/Data/Src/Model/Response/Kakao/KakaoCoordinate2dRes.swift b/Projects/Data/Src/Model/Response/Kakao/KakaoCoordinate2dRes.swift
new file mode 100644
index 00000000..229d6e0f
--- /dev/null
+++ b/Projects/Data/Src/Model/Response/Kakao/KakaoCoordinate2dRes.swift
@@ -0,0 +1,71 @@
+//
+//  KakaoCoordinate2dRes.swift
+//  Data
+//
+//  Created by Kanghos on 5/12/24.
+//
+
+import Foundation
+import SignUpInterface
+
+struct KakaoCoordinateRes: Codable {
+  let documents: [Document]
+
+  struct Document: Codable {
+    let addressName: String
+    let region1depthName, region2depthName, region3depthName: String
+    let regionType: String
+    let code: String
+    let x, y: Double
+
+    private enum CodingKeys: String, CodingKey {
+      case addressName = "address_name"
+      case region1depthName = "region_1depth_name"
+      case region2depthName = "region_2depth_name"
+      case region3depthName = "region_3depth_name"
+      case regionType = "region_type"
+      case code
+      case x, y
+    }
+
+    enum RegionType: String, Codable {
+      case admin = "H"
+      case law = "B"
+
+      private enum CodingKeys: String, CodingKey {
+        case admin = "H"
+        case law = "B"
+      }
+    }
+  }
+}
+
+extension KakaoCoordinateRes {
+  func toDomain() -> LocationReq? {
+    let lawAddress = documents.first { docmuent in
+      docmuent.regionType == "B"
+    }
+
+    guard let document = lawAddress else { return nil }
+
+
+    var cityName = document.region1depthName
+    if cityName.hasSuffix("특별시") {
+      cityName = cityName.replacingOccurrences(of: "특별시", with: "")
+    } else if cityName.hasSuffix("광역시") {
+      cityName = cityName.replacingOccurrences(of: "광역시", with: "")
+    }
+
+    var dongName = document.region3depthName
+    while !dongName.isEmpty {
+      if dongName.hasSuffix("동") {
+        break
+      }
+      dongName.removeLast()
+    }
+
+    let addressName = [cityName, document.region2depthName, dongName].joined(separator: " ")
+
+    return .init(address: addressName, regionCode: Int(document.code) ?? 0, lat: document.y, lon: document.x)
+  }
+}
diff --git a/Projects/Data/Src/Model/Response/Kakao/KakaoSearchRes.swift b/Projects/Data/Src/Model/Response/Kakao/KakaoSearchRes.swift
new file mode 100644
index 00000000..ff42e3fe
--- /dev/null
+++ b/Projects/Data/Src/Model/Response/Kakao/KakaoSearchRes.swift
@@ -0,0 +1,71 @@
+//
+//  KakaoSearchRes.swift
+//  Data
+//
+//  Created by Kanghos on 5/12/24.
+//
+
+import Foundation
+
+import SignUpInterface
+
+struct KakaoSearchRes: Codable {
+  let documents: [Document]
+
+  struct Document: Codable {
+    let address: Address?
+    let roadAddress: RoadAddress?
+    let x, y: String
+
+    private enum CodingKeys: String, CodingKey {
+      case address, x, y
+      case roadAddress = "road_address"
+    }
+  }
+
+  struct Address: Codable {
+    let addressName: String
+    let region1depthName, region2depthName, region3depthName: String
+    let lawCode: String
+    let x, y: String
+
+    private enum CodingKeys: String, CodingKey {
+      case addressName = "address_name"
+      case region1depthName = "region_1depth_name"
+      case region2depthName = "region_2depth_name"
+      case region3depthName = "region_3depth_name"
+      case lawCode = "b_code"
+      case x, y
+    }
+  }
+
+  struct RoadAddress: Codable {
+    let addressName: String
+    let region1depthName, region2depthName, region3depthName: String
+    let x,y: String
+
+    private enum CodingKeys: String, CodingKey {
+      case addressName = "address_name"
+      case region1depthName = "region_1depth_name"
+      case region2depthName = "region_2depth_name"
+      case region3depthName = "region_3depth_name"
+      case x, y
+    }
+  }
+}
+
+extension KakaoSearchRes {
+  func toDomain() -> LocationReq? {
+    guard let document = documents.first,
+          let address = document.address,
+          let longitude = Double(document.x),
+          let latitude = Double(document.y) else {
+      return nil
+    }
+    
+    let addressName = [address.region1depthName, address.region2depthName, address.region3depthName].joined(separator: " ")
+
+    let code = Int(address.lawCode) ?? 0
+    return .init(address: addressName, regionCode: code, lat: latitude, lon: longitude)
+  }
+}
diff --git a/Projects/Data/Src/Repository/Auth/AuthRepository.swift b/Projects/Data/Src/Repository/Auth/AuthRepository.swift
new file mode 100644
index 00000000..8af393aa
--- /dev/null
+++ b/Projects/Data/Src/Repository/Auth/AuthRepository.swift
@@ -0,0 +1,68 @@
+//
+//  AuthRepository.swift
+//  Data
+//
+//  Created by Kanghos on 6/3/24.
+//
+
+import Foundation
+
+import AuthInterface
+import SignUpInterface
+import Networks
+
+import RxSwift
+import RxMoya
+import Moya
+import Alamofire
+
+public final class AuthRepository: ProviderProtocol {
+
+  public typealias Target = AuthTarget
+  public var provider: MoyaProvider<Target>
+  private let tokenStore: TokenStore
+  private let tokenProvider: TokenProvider
+
+  public init(tokenStore: TokenStore, tokenProvider: TokenProvider) {
+    self.tokenStore = tokenStore
+    self.tokenProvider = tokenProvider
+
+    let token = (try? tokenStore.getToken()) ?? Token(accessToken: "", accessTokenExpiresIn: 0)
+    let credential = token.toAuthOCredential()
+
+    let authenticator = OAuthAuthenticator(tokenProvider: tokenProvider)
+    let intercepter = AuthenticationInterceptor(authenticator: authenticator, credential: credential)
+    let session = Session(interceptor: intercepter)
+
+    self.provider = MoyaProvider(session: session)
+  }
+}
+
+extension AuthRepository: AuthRepositoryInterface {
+  public func checkUserExist(phoneNumber: String) -> RxSwift.Single<AuthInterface.UserSignUpInfoRes> {
+    request(type: UserSignUpInfoRes.self, target: .checkExistence(phoneNumber: phoneNumber))
+  }
+  
+  public func refresh(_ token: Token, completion: @escaping (Result<Token, Error>) -> Void) {
+    tokenProvider.refreshToken(token: token, completion: completion)
+  }
+
+  public func refresh(_ token: Token) -> Single<Token> {
+    tokenProvider.refresh(token: token)
+  }
+
+  public func certificate(phoneNumber: String) -> Single<Int> {
+    Single.just(PhoneValidationResponse(phoneNumber: "01012345678", authNumber: 123456))
+      .map { $0.authNumber }
+    //    request(type: PhoneValidationResponse.self, target: .certificate(phoneNumber: phoneNumber))
+  }
+
+  public func login(phoneNumber: String, deviceKey: String) -> Single<AuthInterface.Token> {
+    tokenProvider.login(phoneNumber: phoneNumber, deviceKey: deviceKey)
+  }
+
+  public func loginSNS(_ userSNSLoginRequest: AuthInterface.UserSNSLoginRequest) -> Single<AuthInterface.Token> {
+    tokenProvider.loginSNS(userSNSLoginRequest)
+  }
+}
+
diff --git a/Projects/Data/Src/Repository/Auth/AuthTarget.swift b/Projects/Data/Src/Repository/Auth/AuthTarget.swift
new file mode 100644
index 00000000..c563fe83
--- /dev/null
+++ b/Projects/Data/Src/Repository/Auth/AuthTarget.swift
@@ -0,0 +1,44 @@
+//
+//  AuthTarget.swift
+//  Data
+//
+//  Created by Kanghos on 6/3/24.
+//
+
+import Foundation
+
+import Networks
+
+import Moya
+import AuthInterface
+
+public enum AuthTarget {
+  case certificate(phoneNumber: String)
+  case checkExistence(phoneNumber: String)
+}
+
+extension AuthTarget: BaseTargetType {
+
+  public var path: String {
+    switch self {
+    case .certificate(let phoneNumber):
+      return "users/join/certification/phone-number/\(phoneNumber)"
+    case .checkExistence(let phoneNumber):
+      return "users/join/exist/user-info/\(phoneNumber)"
+    }
+  }
+
+  public var method: Moya.Method {
+    switch self {
+    case .certificate, .checkExistence: return .get
+    }
+  }
+
+  // Request의 파라미터를 결정한다.
+  public var task: Task {
+    switch self {
+    default:
+      return .requestPlain
+    }
+  }
+}
diff --git a/Projects/Data/Src/Repository/Auth/TokenProvider.swift b/Projects/Data/Src/Repository/Auth/TokenProvider.swift
new file mode 100644
index 00000000..b36f65ef
--- /dev/null
+++ b/Projects/Data/Src/Repository/Auth/TokenProvider.swift
@@ -0,0 +1,44 @@
+//
+//  TokenProvider.swift
+//  Data
+//
+//  Created by Kanghos on 6/5/24.
+//
+
+import Foundation
+
+import RxMoya
+import Moya
+import RxSwift
+
+import Networks
+
+import AuthInterface
+
+public final class DefaultTokenProvider: ProviderProtocol {
+  public typealias Target = TokenProviderTarget
+
+  public var provider: MoyaProvider<Target>
+
+  public init() {
+    provider = MoyaProvider()
+  }
+}
+
+extension DefaultTokenProvider: TokenProvider {
+  public func refresh(token: AuthInterface.Token) -> RxSwift.Single<AuthInterface.Token> {
+    request(type: Token.self, target: .refresh(token))
+  }
+  
+  public func refreshToken(token: Token, completion: @escaping (Result<Token, Error>) -> Void) {
+    request(target: .refresh(token), completion: completion)
+  }
+
+  public func login(phoneNumber: String, deviceKey: String) -> Single<Token> {
+    request(type: Token.self, target: .login(phoneNumber: phoneNumber, deviceKey: deviceKey))
+  }
+
+  public func loginSNS(_ userSNSLoginRequest: UserSNSLoginRequest) -> Single<Token> {
+    request(type: Token.self, target: .loginSNS(request: userSNSLoginRequest))
+  }
+}
diff --git a/Projects/Data/Src/Repository/Auth/TokenProviderTarget.swift b/Projects/Data/Src/Repository/Auth/TokenProviderTarget.swift
new file mode 100644
index 00000000..60f88286
--- /dev/null
+++ b/Projects/Data/Src/Repository/Auth/TokenProviderTarget.swift
@@ -0,0 +1,52 @@
+//
+//  TokenProviderTarget.swift
+//  Data
+//
+//  Created by Kanghos on 6/5/24.
+//
+
+import Foundation
+
+import Networks
+
+import Moya
+import AuthInterface
+
+public enum TokenProviderTarget {
+  case login(phoneNumber: String, deviceKey: String)
+  case loginSNS(request: UserSNSLoginRequest)
+  case refresh(Token)
+}
+
+extension TokenProviderTarget: BaseTargetType {
+
+  public var path: String {
+    switch self {
+    case .login:
+      return "users/login/normal"
+    case .loginSNS:
+      return "users/login/sns"
+    case .refresh:
+      return "users/login/refresh"
+    }
+  }
+
+  public var method: Moya.Method {
+    switch self {
+    default: return .post
+    }
+  }
+
+  // Request의 파라미터를 결정한다.
+  public var task: Task {
+    switch self {
+    case let .login(phoneNumber, deviceKey):
+      let request = LoginReq(phoneNumber: phoneNumber, deviceKey: deviceKey)
+      return .requestParameters(parameters: request.toDictionary(), encoding: JSONEncoding.default)
+    case let .loginSNS(snsDTO):
+      return .requestParameters(parameters: snsDTO.toDictionary(), encoding: JSONEncoding.default)
+    case let .refresh(token):
+      return .requestParameters(parameters: token.toDictionary(), encoding: JSONEncoding.default)
+    }
+  }
+}
diff --git a/Projects/Data/Src/Repository/Like/LikeTarget+SampleData.swift b/Projects/Data/Src/Repository/Like/LikeTarget+SampleData.swift
index fcf9900f..f4282ac3 100644
--- a/Projects/Data/Src/Repository/Like/LikeTarget+SampleData.swift
+++ b/Projects/Data/Src/Repository/Like/LikeTarget+SampleData.swift
@@ -109,7 +109,7 @@ extension LikeTarget {
                     "likeIdx": 1,
                     "topic": "행복",
                     "issue": "무엇을 할때 행복한가요?",
-                    "userUuid": "user-uuid-1",
+                    "userUuid": "user-uuid-5",
                     "username": "유저1",
                     "profileUrl": "profile-url",
                     "age": 24,
@@ -121,7 +121,7 @@ extension LikeTarget {
                     "likeIdx": 3,
                     "topic": "취미",
                     "issue": "침대에 누워있고 싶지 않으세요?",
-                    "userUuid": "user-uuid-2",
+                    "userUuid": "user-uuid-6",
                     "username": "유저2",
                     "profileUrl": "profile-url",
                     "age": 32,
@@ -133,7 +133,7 @@ extension LikeTarget {
                     "likeIdx": 154,
                     "topic": "평화",
                     "issue": "돈많은 백수가 되고싶네요",
-                    "userUuid": "user-uuid-3",
+                    "userUuid": "user-uuid-7",
                     "username": "유저3",
                     "profileUrl": "profile-url",
                     "age": 64,
@@ -145,7 +145,7 @@ extension LikeTarget {
                     "likeIdx": 893,
                     "topic": "취미",
                     "issue": "침대에 누워있고 싶지 않으세요?",
-                    "userUuid": "user-uuid-4",
+                    "userUuid": "user-uuid-8",
                     "username": "유저4",
                     "profileUrl": "profile-url",
                     "age": 27,
@@ -157,7 +157,7 @@ extension LikeTarget {
                     "likeIdx": 891,
                     "topic": "취미",
                     "issue": "침대에 누워있고 싶지 않으세요?",
-                    "userUuid": "user-uuid-4",
+                    "userUuid": "user-uuid-9",
                     "username": "유저5",
                     "profileUrl": "profile-url",
                     "age": 27,
@@ -169,7 +169,7 @@ extension LikeTarget {
                     "likeIdx": 88,
                     "topic": "행복",
                     "issue": "침대에 누워있고 싶지 않으세요?",
-                    "userUuid": "user-uuid-4",
+                    "userUuid": "user-uuid-10",
                     "username": "유저6",
                     "profileUrl": "profile-url",
                     "age": 27,
diff --git a/Projects/Data/Src/Repository/Local/UserDefaultTokenStore.swift b/Projects/Data/Src/Repository/Local/UserDefaultTokenStore.swift
new file mode 100644
index 00000000..75862176
--- /dev/null
+++ b/Projects/Data/Src/Repository/Local/UserDefaultTokenStore.swift
@@ -0,0 +1,34 @@
+//
+//  UserDefaultTokenStore.swift
+//  Data
+//
+//  Created by Kanghos on 6/3/24.
+//
+
+import Foundation
+
+import AuthInterface
+import RxSwift
+
+public final class UserDefaultTokenStore: TokenStore {
+  enum Key {
+    static let token = "Token"
+  }
+  public var cachedToken: Token?
+
+  public init () {}
+
+  public func saveToken(token: AuthInterface.Token) {
+    cachedToken = token
+    try? UserDefaults.standard.setCodableObject(token, forKey: Key.token)
+  }
+
+  public func getToken() throws -> Token {
+    try UserDefaults.standard.getCodableObject(forKey: Key.token, as: AuthInterface.Token.self)
+  }
+
+  public func clearToken() {
+    cachedToken = nil
+    UserDefaults.standard.removeObject(forKey: Key.token)
+  }
+}
diff --git a/Projects/Data/Src/Repository/Local/UserInfoRepositoory.swift b/Projects/Data/Src/Repository/Local/UserInfoRepositoory.swift
new file mode 100644
index 00000000..98fd75e6
--- /dev/null
+++ b/Projects/Data/Src/Repository/Local/UserInfoRepositoory.swift
@@ -0,0 +1,101 @@
+//
+//  UserInfoRepositoory.swift
+//  Data
+//
+//  Created by Kanghos on 5/29/24.
+//
+
+import Foundation
+import RxSwift
+import SignUpInterface
+import Core
+
+public class UserDefaultUserInfoRepository: UserInfoRepositoryInterface {
+  enum Key {
+    static let userInfo = "userInfo"
+    static let phoneNumber = "phoneNumber"
+  }
+
+  public init () {}
+
+  public func savePhoneNumber(_ phoneNumber: String) {
+    UserDefaults.standard.setValue(phoneNumber, forKey: Key.phoneNumber)
+  }
+
+  public func fetchPhoneNumber() -> Single<String> {
+    .create { observer in
+      guard let phoneNumber = UserDefaults.standard.string(forKey: Key.phoneNumber) else {
+        observer(.failure(NSError(domain: "UserDefaultRepository", code: 0)))
+        return Disposables.create()
+      }
+      observer(.success(phoneNumber))
+               
+      return Disposables.create { }
+    }
+  }
+
+  public func fetchUserInfo() -> Single<UserInfo> {
+    do {
+      let userinfo = try UserDefaults.standard.getCodableObject(forKey: Key.userInfo, as: UserInfo.self)
+      return .just(userinfo)
+    } catch {
+      return .error(error)
+    }
+  }
+
+  public func updateUserInfo(userInfo: UserInfo) {
+    try? UserDefaults.standard.setCodableObject(userInfo, forKey: Key.userInfo)
+  }
+
+  public func deleteUserInfo() {
+    UserDefaults.standard.removeObject(forKey: Key.userInfo)
+  }
+
+  public func fetchUserPhotos(key: String, fileNames: [String]) -> Single<[Data]> {
+    Single.create { observer in
+      var datas: [Data] = []
+      let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appending(path: key, directoryHint: .isDirectory)
+
+      for fileName in fileNames {
+        do {
+          let fileURL = path.appending(component: fileName)
+          let data = try Data(contentsOf: fileURL)
+          datas.append(data)
+        } catch {
+          print("Failed: Filed Fetch User Photos")
+          observer(.failure(error))
+          return Disposables.create { }
+        }
+      }
+      observer(.success(datas))
+      return Disposables.create { }
+    }
+  }
+
+  public func saveUserPhotos(key: String, datas: [Data]) -> Single<[String]> {
+    return Single.create { observer in
+      var urls: [String] = []
+
+      let userDomain = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
+      let directory = userDomain.appendingPathComponent(key, isDirectory: true)
+
+      do {
+        if !FileManager.default.fileExists(atPath: directory.path()) {
+          try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)
+        }
+        for (index, data) in datas.enumerated() {
+          let fileName = "\(index).jpeg"
+          let url = directory.appendingPathComponent(fileName)
+          try data.write(to: url)
+          urls.append(fileName)
+        }
+      } catch {
+        print(error.localizedDescription)
+        observer(.failure(error))
+        return Disposables.create { }
+      }
+      observer(.success(urls))
+      return Disposables.create { }
+    }
+  }
+}
diff --git a/Projects/Data/Src/Repository/MyPage/MyPageRepository.swift b/Projects/Data/Src/Repository/MyPage/MyPageRepository.swift
index c4356d58..50725ca5 100644
--- a/Projects/Data/Src/Repository/MyPage/MyPageRepository.swift
+++ b/Projects/Data/Src/Repository/MyPage/MyPageRepository.swift
@@ -8,9 +8,12 @@
 import Foundation
 
 import MyPageInterface
+import SignUpInterface
+
 import Networks
 
 import RxSwift
+import RxMoya
 import Moya
 
 public final class MyPageRepository: ProviderProtocol {
@@ -23,7 +26,5 @@ public final class MyPageRepository: ProviderProtocol {
 
 
 extension MyPageRepository: MyPageRepositoryInterface {
-  public func test() {
-    
-  }
+  
 }
diff --git a/Projects/Data/Src/Repository/MyPage/MyPageTarget.swift b/Projects/Data/Src/Repository/MyPage/MyPageTarget.swift
index 474e0ea5..f69a5b1a 100644
--- a/Projects/Data/Src/Repository/MyPage/MyPageTarget.swift
+++ b/Projects/Data/Src/Repository/MyPage/MyPageTarget.swift
@@ -8,29 +8,32 @@
 import Networks
 
 import Moya
+import SignUpInterface
 
 public enum MyPageTarget {
-  case test
+  case blockUserFriendContact(request: UserFriendContactReq)
 }
 
 extension MyPageTarget: BaseTargetType {
   public var path: String {
     switch self {
-    case .test:
-      return ""
+    case .blockUserFriendContact:
+      return "user/friend-contact-list"
     }
   }
   
   public var method: Moya.Method {
     switch self {
-    case .test:
-      return .get
+    case .blockUserFriendContact:
+      return .post
     }
   }
   
   public var task: Moya.Task {
     switch self {
-    case .test:
+    case let .blockUserFriendContact(request):
+      return .requestParameters(parameters: request.toDictionary(), encoding: JSONEncoding.default)
+    default:
       return .requestPlain
     }
   }
diff --git a/Projects/Data/Src/Repository/SignUp/SignUpRepository.swift b/Projects/Data/Src/Repository/SignUp/SignUpRepository.swift
new file mode 100644
index 00000000..59abc0af
--- /dev/null
+++ b/Projects/Data/Src/Repository/SignUp/SignUpRepository.swift
@@ -0,0 +1,64 @@
+//
+//  SignUpRepository.swift
+//  Data
+//
+//  Created by kangho lee on 5/1/24.
+//
+
+import Foundation
+
+import SignUpInterface
+import AuthInterface
+import Networks
+
+import RxSwift
+import RxMoya
+import Moya
+
+public final class SignUpRepository: ProviderProtocol {
+
+  public typealias Target = SignUpTarget
+  public var provider: MoyaProvider<Target>
+
+  public init(isStub: Bool, sampleStatusCode: Int, customEndpointClosure: ((SignUpTarget) -> Moya.Endpoint)?) {
+    self.provider = Self.consProvider(isStub, sampleStatusCode, customEndpointClosure)
+  }
+
+  public convenience init() {
+    self.init(isStub: false, sampleStatusCode: 200, customEndpointClosure: nil)
+  }
+}
+
+extension SignUpRepository: SignUpRepositoryInterface {
+  public func uploadImage(data: [Data]) -> RxSwift.Single<[String]> {
+    .just(["test.jpg", "test2.jpg"])
+  }
+  
+  public func signUp(_ signUpRequest: SignUpInterface.SignUpReq) -> RxSwift.Single<AuthInterface.Token> {
+    request(type: Token.self, target: .signUp(signUpReq: signUpRequest))
+  }
+  
+  public func certificate(phoneNumber: String) -> RxSwift.Single<Int> {
+    Single.just(PhoneValidationResponse(phoneNumber: "01012345678", authNumber: 123456))
+      .map { $0.authNumber }
+
+    //    request(type: PhoneValidationResponse.self, target: .certificate(phoneNumber: phoneNumber))
+  }
+
+  public func checkNickname(nickname: String) -> RxSwift.Single<Bool> {
+    request(type: UserNicknameValidRes.self, target: .checkNickname(nickname: nickname))
+      .map { $0.isDuplicate }
+  }
+
+  public func idealTypes() -> RxSwift.Single<[EmojiType]> {
+    request(type: [EmojiType].self, target: .idealTypes)
+  }
+
+  public func interests() -> RxSwift.Single<[EmojiType]> {
+    request(type: [EmojiType].self, target: .interests)
+  }
+
+  public func fetchAgreements() -> Single<Agreement> {
+    request(type: Agreement.self, target: .agreement)
+  }
+}
diff --git a/Projects/Data/Src/Repository/SignUp/SignUpTarget+SampleData.swift b/Projects/Data/Src/Repository/SignUp/SignUpTarget+SampleData.swift
new file mode 100644
index 00000000..1e78b32d
--- /dev/null
+++ b/Projects/Data/Src/Repository/SignUp/SignUpTarget+SampleData.swift
@@ -0,0 +1,78 @@
+//
+//  SignUpTarget+SampleData.swift
+//  Data
+//
+//  Created by kangho lee on 5/1/24.
+//
+
+import Foundation
+
+import Networks
+
+import Moya
+
+extension SignUpTarget {
+  public var sampleData: Data {
+    switch self {
+    case .certificate:
+      return Data(
+        """
+    {
+      "phoneNumber": "01012345678",
+      "authNumber": 123456
+    }
+    """.utf8)
+    case .checkNickname:
+      return Data(
+        """
+    {
+      "isDuplicate": true
+    }
+    """.utf8)
+    case .checkExistence:
+      return Data(
+        """
+    {
+      "isSignUp": false,
+      "typeList": [
+        "NORMAL",
+        "KAKAO",
+        "NAVER",
+        "GOOGLE"
+      ]
+    }
+    """.utf8)
+
+    case .agreement:
+      return Data(
+      """
+      [
+        {
+        "name":"serviceUseAgree","subject":"이용약관을 읽고, 이해했으며, 동의합니다.",
+        "isRequired":true,
+        "description":"",
+        "detailLink":"https://www.notion.so/janechoi/526c51e9cb584f29a7c16251914bb3cb?pvs=4"
+        },
+        {
+        "name":"personalPrivacyInfoAgree",
+        "subject":"개인 정보 처리 방침을 읽고, 이해했으며, 동의합니다.",
+        "isRequired":true,
+        "description":"",
+        "detailLink":"https://www.notion.so/janechoi/5923a3c20259459bbacaff41290fc615?pvs=4"
+        },
+        {
+        "name":"marketingAgree","subject":"마케팅 정보 수신 동의",
+        "isRequired":false,
+        "description":"폴링에서 제공하는 이벤트/혜택 등 다양한 정보를 Push 알림으로 받아보실 수 있습니다.",
+        "detailLink":""
+        }
+      ]
+    """.utf8)
+
+    default:
+      return Data(
+    """
+    """.utf8)
+    }
+  }
+}
diff --git a/Projects/Data/Src/Repository/SignUp/SignUpTarget.swift b/Projects/Data/Src/Repository/SignUp/SignUpTarget.swift
new file mode 100644
index 00000000..cf50651d
--- /dev/null
+++ b/Projects/Data/Src/Repository/SignUp/SignUpTarget.swift
@@ -0,0 +1,70 @@
+//
+//  SignUpTarget.swift
+//  Data
+//
+//  Created by kangho lee on 5/1/24.
+//
+
+import Foundation
+
+import Networks
+
+import Moya
+import SignUpInterface
+
+public enum SignUpTarget {
+  case certificate(phoneNumber: String)
+  case checkExistence(phoneNumber: String)
+  case checkNickname(nickname: String)
+  case idealTypes
+  case interests
+  case block(contacts: UserFriendContactReq)
+  case signUp(signUpReq: SignUpReq)
+  case agreement
+}
+
+extension SignUpTarget: BaseTargetType {
+
+  public var path: String {
+    switch self {
+    case .certificate(let phoneNumber):
+      return "users/join/certification/phone-number/\(phoneNumber)"
+    case .checkExistence(let phoneNumber):
+      return "users/join/exist/user-info/\(phoneNumber)"
+    case .checkNickname(let nickname):
+      return "users/join/nick-name/duplicate-check/\(nickname)"
+    case .idealTypes:
+      return "ideal-types"
+    case .interests:
+      return "interests"
+    case .block:
+      return "user/friend-contact-list"
+    case .signUp:
+      return "users/join/signup"
+    case .agreement:
+      return "users/join/agreements/main-category"
+    }
+  }
+
+  public var method: Moya.Method {
+    switch self {
+    case .block:
+      return .post
+    case .signUp:
+      return .post
+    default: return .get
+    }
+  }
+
+  // Request의 파라미터를 결정한다.
+  public var task: Task {
+    switch self {
+    case let .block(contacts):
+      return .requestParameters(parameters: contacts.toDictionary(), encoding: JSONEncoding.default)
+    case let .signUp(dto):
+      return .requestParameters(parameters: dto.toDictionary(), encoding: JSONEncoding.default)
+    default:
+      return .requestPlain
+    }
+  }
+}
diff --git a/Projects/Data/Src/Service/ContactService.swift b/Projects/Data/Src/Service/ContactService.swift
new file mode 100644
index 00000000..a2463b78
--- /dev/null
+++ b/Projects/Data/Src/Service/ContactService.swift
@@ -0,0 +1,58 @@
+//
+//  ContactService.swift
+//  Data
+//
+//  Created by Kanghos on 5/12/24.
+//
+
+import Foundation
+import RxSwift
+import SignUpInterface
+import Contacts
+
+enum ContactError: Error {
+  case fetchError(message: String)
+}
+
+public final class ContactService: ContactServiceType {
+
+  public init() { }
+  public func fetchContact() -> Single<[ContactType]> {
+    return Single.create { [unowned self] single in
+      self.fetchContacts { result in
+        switch result {
+        case .success(let contacts):
+          single(.success(contacts))
+        case .failure(let error):
+          single(.failure(error))
+        }
+      }
+      return Disposables.create()
+    }
+  }
+
+  private func fetchContacts(completion: @escaping (Result<[ContactType], ContactError>) -> Void) {
+    let store = CNContactStore()
+    store.requestAccess(for: .contacts) { granted, error in
+      guard granted == true, error == nil else {
+        return
+      }
+
+      let keys = [CNContactGivenNameKey, CNContactPhoneNumbersKey] as [CNKeyDescriptor]
+      let request = CNContactFetchRequest(keysToFetch: keys)
+      var contacts: [ContactType] = []
+
+      do {
+        try store.enumerateContacts(with: request) { contact, _ in
+          let name = contact.givenName
+          let phoneNumber = contact.phoneNumbers.first?.value.stringValue ?? ""
+          let contact = ContactType(name: name, phoneNumber: phoneNumber)
+          contacts.append(contact)
+        }
+        completion(.success(contacts))
+      } catch {
+        completion(.failure(.fetchError(message: error.localizedDescription)))
+      }
+    }
+  }
+}
diff --git a/Projects/Data/Src/Service/KakaoAPIService.swift b/Projects/Data/Src/Service/KakaoAPIService.swift
new file mode 100644
index 00000000..15f5ad2d
--- /dev/null
+++ b/Projects/Data/Src/Service/KakaoAPIService.swift
@@ -0,0 +1,40 @@
+//
+//  KakaoAPIService.swift
+//  Data
+//
+//  Created by Kanghos on 5/12/24.
+//
+
+import Foundation
+
+import SignUpInterface
+import Networks
+
+import Moya
+import RxSwift
+
+public final class KakaoAPIService: ProviderProtocol {
+  public typealias Target = KakaoAPITarget
+  public var provider: MoyaProvider<Target>
+
+  public init(isStub: Bool, sampleStatusCode: Int, customEndpointClosure: ((Target) -> Endpoint)?) {
+    self.provider = Self.consProvider(isStub, sampleStatusCode, customEndpointClosure)
+  }
+
+  public convenience init() {
+    self.init(isStub: false, sampleStatusCode: 200, customEndpointClosure: nil)
+  }
+}
+
+
+extension KakaoAPIService: KakaoAPIServiceType {  
+  public func fetchLocationByAddress(address: String) -> Single<LocationReq?> {
+    request(type: KakaoSearchRes.self, target: .searchAddress(query: address))
+      .map { $0.toDomain() }
+  }
+
+  public func fetchLocationByCoordinate2d(longitude: Double, latitude: Double) -> Single<LocationReq?> {
+    request(type: KakaoCoordinateRes.self, target: .searchCoordinate(longitude: longitude, latitude: latitude))
+      .map { $0.toDomain() }
+  }
+}
diff --git a/Projects/Data/Src/Service/KakaoAPITarget.swift b/Projects/Data/Src/Service/KakaoAPITarget.swift
new file mode 100644
index 00000000..6f30ea9a
--- /dev/null
+++ b/Projects/Data/Src/Service/KakaoAPITarget.swift
@@ -0,0 +1,62 @@
+//
+//  KakaoAPITarget.swift
+//  Data
+//
+//  Created by Kanghos on 5/12/24.
+//
+
+import Foundation
+
+import Networks
+
+import Moya
+
+public enum KakaoAPITarget {
+  case searchAddress(query: String)
+  case searchCoordinate(longitude: Double, latitude: Double)
+}
+
+extension KakaoAPITarget: BaseTargetType {
+
+  public var baseURL: URL {
+    URL(string: "https://dapi.kakao.com")!
+  }
+
+  public var path: String {
+    switch self {
+    case .searchAddress:
+      return "/v2/local/search/address.json"
+    case .searchCoordinate:
+      return "/v2/local/geo/coord2regioncode.json"
+    }
+  }
+
+  public var headers: [String : String]? {
+    ["Authorization": "KakaoAK fd26fcdea335b93122163284d3fd4047"]
+  }
+
+  public var method: Moya.Method {
+    switch self {
+    default: return .get
+    }
+  }
+
+  var parameters: [String: Any] {
+    switch self {
+    case .searchAddress(let query):
+      return ["query": query]
+    case .searchCoordinate(let longitude, let latitude):
+      return ["x": longitude, "y": latitude]
+    }
+  }
+
+  // Request의 파라미터를 결정한다.
+  public var task: Task {
+    switch self {
+    case.searchAddress:
+      return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
+    case .searchCoordinate:
+      return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
+    }
+  }
+}
diff --git a/Projects/Data/Src/Service/LocationService.swift b/Projects/Data/Src/Service/LocationService.swift
new file mode 100644
index 00000000..16ef8ae4
--- /dev/null
+++ b/Projects/Data/Src/Service/LocationService.swift
@@ -0,0 +1,76 @@
+//
+//  LocationService.swift
+//  Data
+//
+//  Created by Kanghos on 5/12/24.
+//
+
+import Foundation
+import CoreLocation
+import SignUpInterface
+
+import RxSwift
+
+public final class LocationService: NSObject, LocationServiceType {
+  private let manager = CLLocationManager()
+  private let geoCoder = CLGeocoder()
+  public let publisher = PublishSubject<LocationReq>()
+  private let location = PublishSubject<CLLocation>()
+
+  private var disposeBag = DisposeBag()
+
+  public override init() {
+    super.init()
+
+    bind()
+  }
+
+  private func bind() {
+    manager.delegate = self
+
+    location
+      .map { location in
+        LocationReq(address: "", regionCode: 0, lat: location.coordinate.latitude, lon: location.coordinate.longitude)
+      }.bind(to: publisher)
+      .disposed(by: disposeBag)
+  }
+
+  public func requestAuthorization() {
+    manager.requestWhenInUseAuthorization()
+  }
+
+  public func handleAuthorization(granted: @escaping (Bool) -> Void) {
+    switch manager.authorizationStatus {
+    case .notDetermined:
+      manager.requestWhenInUseAuthorization()
+      granted(false)
+    case .restricted, .denied:
+      granted(false)
+      location.onError(LocationError.denied)
+    case .authorizedAlways, .authorizedWhenInUse:
+      granted(true)
+    @unknown default:
+      granted(false)
+    }
+  }
+
+  public func requestLocation() {
+    manager.requestLocation()
+  }
+}
+
+extension LocationService: CLLocationManagerDelegate {
+  public func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
+
+  }
+
+  public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
+    if let location = locations.last {
+      self.location.onNext(location)
+    }
+  }
+
+  public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
+    print(error.localizedDescription)
+  }
+}
diff --git a/Projects/Domain/Src/Model/User/UserProfilePhoto.swift b/Projects/Domain/Src/Model/User/UserProfilePhoto.swift
index 1d7c79a3..0b57354b 100644
--- a/Projects/Domain/Src/Model/User/UserProfilePhoto.swift
+++ b/Projects/Domain/Src/Model/User/UserProfilePhoto.swift
@@ -7,7 +7,7 @@
 
 import Foundation
 
-public struct UserProfilePhoto {
+public struct UserProfilePhoto: Codable {
   public let identifier = UUID()
   public let url: String
   public let priority: Int
diff --git a/Projects/Features/Auth/Demo/Src/AppDelegate+Register.swift b/Projects/Features/Auth/Demo/Src/AppDelegate+Register.swift
new file mode 100644
index 00000000..f1679384
--- /dev/null
+++ b/Projects/Features/Auth/Demo/Src/AppDelegate+Register.swift
@@ -0,0 +1,57 @@
+//
+//  AppDelegate+Register.swift
+//  AuthDemo
+//
+//  Created by Kanghos on 6/3/24.
+//
+
+import Foundation
+
+import Core
+
+import SignUp
+import SignUpInterface
+import AuthInterface
+import Auth
+import Data
+
+extension AppDelegate {
+  var container: DIContainer {
+    DIContainer.shared
+  }
+
+  func registerDependencies() {
+    let tokenStore = UserDefaultTokenStore()
+    let tokenProvider = DefaultTokenProvider()
+
+    container.register(
+      interface: SignUpUseCaseInterface.self,
+      implement: {
+        SignUpUseCase(
+          repository: SignUpRepository(),
+          locationService: LocationService(),
+          kakaoAPIService: KakaoAPIService(),
+          contactService: ContactService(),
+          tokenStore: tokenStore
+        )
+      }
+    )
+
+    container.register(
+      interface: AuthUseCaseInterface.self,
+      implement: {
+        AuthUseCase(
+          authRepository: AuthRepository(tokenStore: tokenStore, tokenProvider: tokenProvider),
+          tokenStore: tokenStore
+        )
+      })
+
+    container.register(
+      interface: UserInfoUseCaseInterface.self,
+      implement: {
+        UserInfoUseCase(repository: UserDefaultUserInfoRepository())
+      })
+  }
+}
+
+
diff --git a/Projects/Features/Auth/Demo/Src/AppDelegate.swift b/Projects/Features/Auth/Demo/Src/AppDelegate.swift
new file mode 100644
index 00000000..fe9001cf
--- /dev/null
+++ b/Projects/Features/Auth/Demo/Src/AppDelegate.swift
@@ -0,0 +1,27 @@
+//
+//  AppDelegate.swift
+//  ProjectDescriptionHelpers
+//
+//  Created by Kanghos on 6/3/24.
+//
+
+import UIKit
+//import FirebaseCore
+
+@main
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+
+    registerDependencies()
+
+    return true
+  }
+
+  // MARK: UISceneSession Lifecycle
+  func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
+    // Called when a new scene session is being created.
+    // Use this method to select a configuration to create the new scene with.
+    return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
+  }
+}
diff --git a/Projects/Features/Auth/Demo/Src/Coordinator/AppBuilder.swift b/Projects/Features/Auth/Demo/Src/Coordinator/AppBuilder.swift
new file mode 100644
index 00000000..9e5d15bd
--- /dev/null
+++ b/Projects/Features/Auth/Demo/Src/Coordinator/AppBuilder.swift
@@ -0,0 +1,51 @@
+//
+//  AppBuilder.swift
+//  AuthDemo
+//
+//  Created by Kanghos on 6/3/24.
+//
+
+import UIKit
+
+import DSKit
+
+import Auth
+import AuthInterface
+import SignUp
+import SignUpInterface
+
+public protocol AppRootBuildable {
+  func build() -> LaunchCoordinating
+}
+
+public final class AppRootBuilder: AppRootBuildable {
+  public init() { }
+
+  private lazy var signUpBuildable: SignUpBuildable = {
+    SignUpBuilder()
+  }()
+
+  private lazy var authBuildable: AuthBuildable = {
+    AuthBuilder(signUpBuilable: signUpBuildable)
+  }()
+
+  private lazy var launchBuildable: LaunchBuildable = {
+    LaunchBuilder()
+  }()
+
+  public func build() -> LaunchCoordinating {
+
+    // MARK: Launcher
+
+    let viewController =  NavigationViewControllable()
+
+    let coordinator = AppCoordinator(
+      viewControllable: viewController,
+      authBuildable: self.authBuildable, 
+      launchBuildable: launchBuildable
+    )
+
+    return coordinator
+  }
+}
+
diff --git a/Projects/Features/Auth/Demo/Src/Coordinator/AppCoordinator.swift b/Projects/Features/Auth/Demo/Src/Coordinator/AppCoordinator.swift
new file mode 100644
index 00000000..7105a3f0
--- /dev/null
+++ b/Projects/Features/Auth/Demo/Src/Coordinator/AppCoordinator.swift
@@ -0,0 +1,98 @@
+//
+//  AppCoordinator.swift
+//  AuthDemo
+//
+//  Created by Kanghos on 6/3/24.
+//
+
+import UIKit
+import SignUpInterface
+import AuthInterface
+import Core
+import DSKit
+
+protocol AppCoordinating {
+  func authFlow()
+}
+
+final class AppCoordinator: LaunchCoordinator, AppCoordinating {
+
+  private let authBuildable: AuthBuildable
+  private let launchBuildable: LaunchBuildable
+
+  init(
+    viewControllable: ViewControllable,
+    authBuildable: AuthBuildable,
+    launchBuildable: LaunchBuildable
+  ) {
+    self.authBuildable = authBuildable
+    self.launchBuildable = launchBuildable
+    super.init(viewControllable: viewControllable)
+  }
+
+  public override func start() {
+    launchFlow()
+  }
+
+  func launchFlow() {
+    let coordinator = self.launchBuildable.build(rootViewControllable: self.viewControllable)
+    attachChild(coordinator)
+    coordinator.delegate = self
+    coordinator.start()
+  }
+
+  // MARK: - public
+  func authFlow() {
+    let authCoordinator = self.authBuildable.build()
+
+    attachChild(authCoordinator)
+    authCoordinator.delegate = self
+    authCoordinator.start()
+  }
+
+  class MainViewController: TFBaseViewController {
+    override func makeUI() {
+      let button = UIButton()
+      button.setTitle("가입내역 지우기", for: .normal)
+      button.backgroundColor = DSKitAsset.Color.primary500.color
+      self.view.addSubview(button)
+      button.addAction(UIAction {_ in 
+        UserDefaults.standard.removeObject(forKey: "phoneNumber")
+      }, for: .touchUpInside)
+      button.layer.cornerRadius = 16
+      button.clipsToBounds = true
+
+      button.snp.makeConstraints {
+        $0.center.equalToSuperview()
+        $0.height.equalTo(60)
+        $0.width.equalToSuperview().inset(80)
+      }
+    }
+  }
+
+  func mainFlow() {
+    replaceWindowRootViewController(rootViewController: viewControllable)
+    let vc = MainViewController()
+
+    self.viewControllable.setViewControllers([vc])
+  }
+}
+
+extension AppCoordinator: AuthCoordinatingDelegate {
+  func detachAuth(_ coordinator: Core.Coordinator) {
+    detachChild(coordinator)
+    mainFlow()
+  }
+}
+
+extension AppCoordinator: LaunchCoordinatingDelegate {
+  func finishFlow(_ coordinator: Coordinator, _ action: LaunchAction) {
+    detachChild(coordinator)
+    switch action {
+    case .needAuth:
+      authFlow()
+    case .toMain:
+      mainFlow()
+    }
+  }
+}
diff --git a/Projects/Features/Auth/Demo/Src/SceneDelegate.swift b/Projects/Features/Auth/Demo/Src/SceneDelegate.swift
new file mode 100644
index 00000000..4af0672e
--- /dev/null
+++ b/Projects/Features/Auth/Demo/Src/SceneDelegate.swift
@@ -0,0 +1,40 @@
+//
+//  SceneDelegate.swift
+//  AuthDemo
+//
+//  Created by Kanghos on 6/3/24.
+//
+
+import UIKit
+
+import Core
+import DSKit
+
+class SceneDelegate: UIResponder, UIWindowSceneDelegate {
+
+  var window: UIWindow?
+  var launcher: LaunchCoordinating?
+
+  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
+    guard let windowScene = (scene as? UIWindowScene) else { return }
+
+    let window = UIWindow(windowScene: windowScene)
+
+    let appCoordinator = AppRootBuilder().build()
+
+    self.launcher = appCoordinator
+    self.launcher?.launch(window: window)
+
+    self.window = window
+  }
+
+  func sceneDidDisconnect(_ scene: UIScene) { }
+
+  func sceneDidBecomeActive(_ scene: UIScene) { }
+
+  func sceneWillResignActive(_ scene: UIScene) { }
+
+  func sceneWillEnterForeground(_ scene: UIScene) { }
+
+  func sceneDidEnterBackground(_ scene: UIScene) { }
+}
diff --git a/Projects/Features/Auth/Interface/Src/AuthUseCaseInterface.swift b/Projects/Features/Auth/Interface/Src/AuthUseCaseInterface.swift
new file mode 100644
index 00000000..53d33269
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/AuthUseCaseInterface.swift
@@ -0,0 +1,18 @@
+//
+//  AuthUseCaseInterface.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+
+import RxSwift
+
+public protocol AuthUseCaseInterface {
+  func certificate(phoneNumber: String) -> Single<Int>
+  func checkUserExists(phoneNumber: String) -> Single<UserSignUpInfoRes>
+  func login(phoneNumber: String, deviceKey: String) -> Single<Void>
+  func loginSNS(_ request: UserSNSLoginRequest) -> Single<Void>
+  func refresh() -> Single<Void>
+}
diff --git a/Projects/Features/Auth/Interface/Src/Coordinator/AuthBuildable.swift b/Projects/Features/Auth/Interface/Src/Coordinator/AuthBuildable.swift
new file mode 100644
index 00000000..0359b750
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/Coordinator/AuthBuildable.swift
@@ -0,0 +1,14 @@
+//
+//  AuthBuildable.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+
+import Core
+
+public protocol AuthBuildable {
+  func build() -> AuthCoordinating
+}
diff --git a/Projects/Features/Auth/Interface/Src/Coordinator/AuthCoordinating+Action.swift b/Projects/Features/Auth/Interface/Src/Coordinator/AuthCoordinating+Action.swift
new file mode 100644
index 00000000..771b04d0
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/Coordinator/AuthCoordinating+Action.swift
@@ -0,0 +1,14 @@
+//
+//  AuthCoordinating+Action.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+
+public enum AuthCoordinatingAction {
+  case tologinType(_ type: SNSType)
+  case toSignUp(phoneNumber: String)
+  case toMain
+}
diff --git a/Projects/Features/Auth/Interface/Src/Coordinator/AuthCoordinating.swift b/Projects/Features/Auth/Interface/Src/Coordinator/AuthCoordinating.swift
new file mode 100644
index 00000000..aa2b2b37
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/Coordinator/AuthCoordinating.swift
@@ -0,0 +1,26 @@
+//
+//  AuthCoordinating.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+
+import Core
+
+public protocol AuthCoordinatingDelegate: AnyObject {
+  func detachAuth(_ coordinator: Coordinator)
+}
+
+public protocol AuthCoordinating: Coordinator {
+  var delegate: AuthCoordinatingDelegate? { get set }
+
+  func launchFlow()
+
+  func rootFlow()
+
+  func phoneNumberFlow()
+
+  func snsFlow(type: SNSType)
+}
diff --git a/Projects/Features/Auth/Interface/Src/Coordinator/AuthLaunchCoordinating.swift b/Projects/Features/Auth/Interface/Src/Coordinator/AuthLaunchCoordinating.swift
new file mode 100644
index 00000000..dafb5f08
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/Coordinator/AuthLaunchCoordinating.swift
@@ -0,0 +1,24 @@
+//
+//  AuthLaunchCoordinating.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 6/4/24.
+//
+
+import Foundation
+import Core
+
+public protocol LaunchCoordinatingDelegate: AnyObject {
+  func finishFlow(_ coordinator: Coordinator, _ action: LaunchAction)
+}
+
+public enum LaunchAction {
+  case needAuth
+  case toMain
+}
+
+public protocol AuthLaunchCoordinating: Coordinator {
+  var delegate: LaunchCoordinatingDelegate? { get set }
+  
+  func launchFlow()
+}
diff --git a/Projects/Features/Auth/Interface/Src/Coordinator/LaunchBuildable.swift b/Projects/Features/Auth/Interface/Src/Coordinator/LaunchBuildable.swift
new file mode 100644
index 00000000..88ba7493
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/Coordinator/LaunchBuildable.swift
@@ -0,0 +1,14 @@
+//
+//  LaunchBuildable.swift
+//  Auth
+//
+//  Created by Kanghos on 6/4/24.
+//
+
+import Foundation
+
+import Core
+
+public protocol LaunchBuildable {
+  func build(rootViewControllable: ViewControllable) -> AuthLaunchCoordinating
+}
diff --git a/Projects/Features/Auth/Interface/Src/DTO/Request/UserSNSLoginRequest.swift b/Projects/Features/Auth/Interface/Src/DTO/Request/UserSNSLoginRequest.swift
new file mode 100644
index 00000000..7b6d14b2
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/DTO/Request/UserSNSLoginRequest.swift
@@ -0,0 +1,22 @@
+//
+//  UserSNSLoginRequest.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+
+public struct UserSNSLoginRequest: Codable {
+  public let email: String
+  public let snsType: SNSType
+  public let snsUniqueId: String
+  public let deviceKey: String
+
+  public init(email: String, snsType: SNSType, snsUniqueId: String, deviceKey: String) {
+    self.email = email
+    self.snsType = snsType
+    self.snsUniqueId = snsUniqueId
+    self.deviceKey = deviceKey
+  }
+}
diff --git a/Projects/Features/Auth/Interface/Src/DTO/Response/UserSignUpInfoRes.swift b/Projects/Features/Auth/Interface/Src/DTO/Response/UserSignUpInfoRes.swift
new file mode 100644
index 00000000..684583cf
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/DTO/Response/UserSignUpInfoRes.swift
@@ -0,0 +1,13 @@
+//
+//  UserSignUpInfoRes.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+
+public struct UserSignUpInfoRes: Codable {
+  public let isSignUp: Bool
+  public let typeList: [SNSType]
+}
diff --git a/Projects/Features/Auth/Interface/Src/File.swift b/Projects/Features/Auth/Interface/Src/File.swift
deleted file mode 100644
index 436c7e7d..00000000
--- a/Projects/Features/Auth/Interface/Src/File.swift
+++ /dev/null
@@ -1,8 +0,0 @@
-//
-//  File.swift
-//  AuthInterface
-//
-//  Created by Hoo's MacBookPro on 12/3/23.
-//
-
-import Foundation
diff --git a/Projects/Features/Auth/Interface/Src/Model/AuthError.swift b/Projects/Features/Auth/Interface/Src/Model/AuthError.swift
new file mode 100644
index 00000000..7f9b9d86
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/Model/AuthError.swift
@@ -0,0 +1,12 @@
+//
+//  AuthError.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 6/6/24.
+//
+
+import Foundation
+
+public enum AuthError: Error {
+  case invalidToken
+}
diff --git a/Projects/Features/Auth/Interface/Src/Model/SNSType.swift b/Projects/Features/Auth/Interface/Src/Model/SNSType.swift
new file mode 100644
index 00000000..47c25fd7
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/Model/SNSType.swift
@@ -0,0 +1,16 @@
+//
+//  SNSType.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import Foundation
+
+public enum SNSType: String, Codable {
+  case kakao = "KAKAO"
+  case naver = "NAVER"
+  case google = "GOOGLE"
+  case apple = "APPLE"
+  case normal = "NORMAL"
+}
diff --git a/Projects/Features/Auth/Interface/Src/Model/Token.swift b/Projects/Features/Auth/Interface/Src/Model/Token.swift
new file mode 100644
index 00000000..c1b292d4
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/Model/Token.swift
@@ -0,0 +1,18 @@
+//
+//  Token.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+
+public struct Token: Codable {
+  public let accessToken: String
+  public let accessTokenExpiresIn: Double
+
+  public init(accessToken: String, accessTokenExpiresIn: Double) {
+    self.accessToken = accessToken
+    self.accessTokenExpiresIn = accessTokenExpiresIn
+  }
+}
diff --git a/Projects/Features/Auth/Interface/Src/RepositoryInterface/AuthRepositoryInterface.swift b/Projects/Features/Auth/Interface/Src/RepositoryInterface/AuthRepositoryInterface.swift
new file mode 100644
index 00000000..fe4a0398
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/RepositoryInterface/AuthRepositoryInterface.swift
@@ -0,0 +1,19 @@
+//
+//  AuthRepositoryInterface.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+
+import RxSwift
+
+public protocol AuthRepositoryInterface {
+  func certificate(phoneNumber: String) -> Single<Int>
+  func checkUserExist(phoneNumber: String) -> Single<UserSignUpInfoRes>
+  func login(phoneNumber: String, deviceKey: String) -> Single<Token>
+  func loginSNS(_ userSNSLoginRequest: UserSNSLoginRequest) -> Single<Token>
+  func refresh(_ token: Token, completion: @escaping (Result<Token, Error>) -> Void)
+  func refresh(_ token: Token) -> Single<Token>
+}
diff --git a/Projects/Features/Auth/Interface/Src/RepositoryInterface/TokenProvider.swift b/Projects/Features/Auth/Interface/Src/RepositoryInterface/TokenProvider.swift
new file mode 100644
index 00000000..c8d5fda2
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/RepositoryInterface/TokenProvider.swift
@@ -0,0 +1,16 @@
+//
+//  TokenProvider.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+import RxSwift
+
+public protocol TokenProvider {
+  func refreshToken(token: Token, completion: @escaping (Result<Token, Error>) -> Void)
+  func refresh(token: Token) -> Single<Token>
+  func login(phoneNumber: String, deviceKey: String) -> Single<Token>
+  func loginSNS(_ userSNSLoginRequest: UserSNSLoginRequest) -> Single<Token>
+}
diff --git a/Projects/Features/Auth/Interface/Src/RepositoryInterface/TokenStore.swift b/Projects/Features/Auth/Interface/Src/RepositoryInterface/TokenStore.swift
new file mode 100644
index 00000000..34ad36a8
--- /dev/null
+++ b/Projects/Features/Auth/Interface/Src/RepositoryInterface/TokenStore.swift
@@ -0,0 +1,16 @@
+//
+//  TokenStore.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+
+import RxSwift
+public protocol TokenStore {
+  var cachedToken: Token? { get set }
+  func saveToken(token: Token)
+  func getToken() throws -> Token
+  func clearToken()
+}
diff --git a/Projects/Features/Auth/Project.swift b/Projects/Features/Auth/Project.swift
index 76eb61eb..2ea1e2f0 100644
--- a/Projects/Features/Auth/Project.swift
+++ b/Projects/Features/Auth/Project.swift
@@ -22,9 +22,18 @@ let project = Project(
 			implementation: .Auth,
 			dependencies: [
 				.feature(interface: .Auth),
+        .feature(interface: .SignUp),
         .dsKit
 			]
-		)
+		),
+    .feature(
+      demo: .Auth,
+      dependencies: [
+        .feature(implementation: .SignUp),
+        .feature(implementation: .Auth),
+        .data
+      ]
+    )
 	]
 )
 
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/PhoneNumber/PhoneCertificationViewController.swift b/Projects/Features/Auth/Src/AuthRoot/Certificate/PhoneCertificationViewController.swift
similarity index 95%
rename from Projects/Features/SignUp/Src/SignUpRoot/PhoneNumber/PhoneCertificationViewController.swift
rename to Projects/Features/Auth/Src/AuthRoot/Certificate/PhoneCertificationViewController.swift
index 2d206d01..3930de4f 100644
--- a/Projects/Features/SignUp/Src/SignUpRoot/PhoneNumber/PhoneCertificationViewController.swift
+++ b/Projects/Features/Auth/Src/AuthRoot/Certificate/PhoneCertificationViewController.swift
@@ -226,11 +226,10 @@ final class PhoneCertificationViewController: TFBaseViewController {
   override func viewDidLoad() {
     super.viewDidLoad()
     keyBoardSetting()
-    setupAccessibilityIdentifier()
   }
 
-  override func viewDidAppear(_ animated: Bool) {
-    super.viewDidAppear(animated)
+  override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
     phoneNumTextField.becomeFirstResponder()
   }
 
@@ -280,10 +279,10 @@ final class PhoneCertificationViewController: TFBaseViewController {
       .disposed(by: disposeBag)
 
     output.error
-      .asSignal()
-      .emit {
-        print($0)
-      }.disposed(by: disposeBag)
+      .drive(with: self, onNext: { owner, error in
+        print(error.localizedDescription)
+      })
+      .disposed(by: disposeBag)
 
     output.clearButtonTapped
       .drive(phoneNumTextField.rx.text)
@@ -346,10 +345,6 @@ final class PhoneCertificationViewController: TFBaseViewController {
       .map { return $0.color }
       .drive(timerLabel.rx.textColor)
       .disposed(by: disposeBag)
-
-    output.navigatorDisposble
-      .drive()
-      .disposed(by: disposeBag)
   }
 
   func keyBoardSetting() {
@@ -378,11 +373,3 @@ final class PhoneCertificationViewController: TFBaseViewController {
       .disposed(by: disposeBag)
   }
 }
-
-extension PhoneCertificationViewController {
-
-  private func setupAccessibilityIdentifier() {
-    phoneNumTextField.accessibilityIdentifier = AccessibilityIdentifier.phoneNumberTextField
-    verifyBtn.accessibilityIdentifier = AccessibilityIdentifier.verifyBtn
-  }
-}
diff --git a/Projects/Features/Auth/Src/AuthRoot/Certificate/PhoneCertificationViewModel.swift b/Projects/Features/Auth/Src/AuthRoot/Certificate/PhoneCertificationViewModel.swift
new file mode 100644
index 00000000..7d3c051a
--- /dev/null
+++ b/Projects/Features/Auth/Src/AuthRoot/Certificate/PhoneCertificationViewModel.swift
@@ -0,0 +1,224 @@
+//
+//  PhoneCertificationViewModel.swift
+//  DSKit
+//
+//  Created by Hoo's MacBookPro on 2023/08/03.
+//
+
+import Foundation
+
+import AuthInterface
+import SignUpInterface
+import DSKit
+
+final class PhoneCertificationViewModel: ViewModelType {
+
+  struct Input {
+    let viewWillAppear: Driver<Void>
+    let phoneNum: Driver<String>
+    let clearBtn: Driver<Void>
+    let verifyBtn: Driver<String>
+    let codeInput: Driver<String>
+    let finishAnimationTrigger: Driver<Void>
+  }
+
+  struct Output {
+    let phoneNum: Driver<String>
+    let validate: Driver<Bool>
+    let error: Driver<Error>
+    let clearButtonTapped: Driver<String>
+    let viewStatus: Driver<ViewType>
+    let certificateSuccess: Driver<Bool>
+    let certificateFailuer: Driver<Bool>
+    let timeStampLabel: Driver<String>
+    let timeLabelTextColor: Driver<DSKitColors>
+  }
+
+  weak var delegate: AuthCoordinatingActionDelegate?
+  private let useCase: AuthUseCaseInterface
+  private let userInfoUseCase: UserInfoUseCaseInterface
+
+  private var disposeBag = DisposeBag()
+
+  init(useCase: AuthUseCaseInterface, userInfoUseCase: UserInfoUseCaseInterface) {
+    self.useCase = useCase
+    self.userInfoUseCase = userInfoUseCase
+  }
+
+  func transform(input: Input) -> Output {
+
+    let errorTracker = PublishRelay<Error>()
+    
+    let phoneNum = input.phoneNum
+      .debounce(.milliseconds(300))
+
+    let validate = phoneNum
+      .map { $0.phoneNumValidation() }
+
+    let clearButtonTapped = input.clearBtn
+      .map { "" }.asDriver()
+
+    let response = input.verifyBtn
+      .throttle(.milliseconds(500), latest: false)
+      .asObservable()
+      .withUnretained(self)
+      .flatMapLatest { owner, phoneNum in
+        owner.useCase.certificate(phoneNumber: phoneNum)
+          .catch { certificateError in
+            errorTracker.accept(certificateError)
+            return .error(certificateError)
+          }
+      }.asDriver(onErrorDriveWith: .empty())
+
+    let authNumber = response
+      .map { AuthCodeWithTimeStamp(authCode: $0) }
+
+    let viewStatus = authNumber.map { _ in ViewType.authCode }
+      .startWith(.phoneNumber)
+
+    let certificateResult = input.codeInput
+      .distinctUntilChanged()
+      .filter { $0.count == 6 }
+      .withLatestFrom(authNumber) { inputCode, authNumber -> Bool in
+        guard authNumber.isAvailableCode() else {
+          return false
+        }
+        return inputCode == "\(authNumber.authCode)"
+      }
+      .asDriver(onErrorJustReturn: false)
+
+    let certificateSuccess = certificateResult.filter { $0 == true }
+
+    let checkUserExists = certificateSuccess
+      .withLatestFrom(phoneNum)
+      .asObservable()
+      .withUnretained(self)
+      .flatMapLatest { owner, phoneNum in
+        owner.useCase.checkUserExists(phoneNumber: phoneNum)
+          .asObservable()
+          .catch { networkError in
+            errorTracker.accept(networkError)
+            return .empty()
+          }
+      }.asDriverOnErrorJustEmpty()
+
+    let userInfo = certificateSuccess
+      .withLatestFrom(phoneNum)
+      .asObservable()
+      .withUnretained(self)
+      .flatMapLatest({ owner, phoneNum in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: phoneNum))
+      })
+      .asDriverOnErrorJustEmpty()
+
+    checkUserExists
+      .withLatestFrom(Driver<UserInfo>.combineLatest(userInfo, phoneNum) { userinfo, phoneNum in
+        var mutable = userinfo
+        mutable.phoneNumber = phoneNum
+        return mutable
+      })
+      .drive(with: self) { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+      }.disposed(by: disposeBag)
+
+
+    let timer = authNumber
+      .flatMap { authNumber in
+        return Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
+          .filter { _ in authNumber.isAvailableCode() }
+          .take(until: certificateResult.filter{ $0 == true }
+            .asObservable()
+          )
+          .asDriver(onErrorDriveWith: Driver<Int>.empty())
+      }
+
+    let timeLabelStr = timer
+      .withLatestFrom(authNumber) { _, authNumber in
+        authNumber.timeString
+      }
+
+    let timerLabelColor = timer
+      .withLatestFrom(authNumber) { _, authNumber in
+        if authNumber.isAvailableCode() {
+          return DSKitAsset.Color.neutral50
+        } else {
+          return DSKitAsset.Color.error
+        }
+      }
+
+    let isSignUp = Driver.zip(input.finishAnimationTrigger, checkUserExists) { $1 }
+
+    isSignUp.filter { $0.isSignUp == true }
+      .withLatestFrom(phoneNum) { _, phoneNum in phoneNum }
+      .drive(with: self) { owner, phoneNum in
+        owner.userInfoUseCase.savePhoneNumber(phoneNum)
+      }
+      .disposed(by: disposeBag)
+
+    isSignUp.filter { $0.isSignUp == true }
+      .withLatestFrom(phoneNum) { _, phoneNum in phoneNum }
+      .asObservable()
+      .withUnretained(self)
+      .flatMapLatest { owner, phoneNum in
+        owner.useCase.login(phoneNumber: phoneNum, deviceKey: "device_key")
+          .asObservable()
+          .catch { error in
+            errorTracker.accept(error)
+            return .empty()
+          }
+      }
+      .asDriverOnErrorJustEmpty()
+      .drive(with: self) { owner, _ in
+        owner.delegate?.invoke(.toMain)
+      }.disposed(by: disposeBag)
+
+    isSignUp.filter { $0.isSignUp == false }
+      .withLatestFrom(phoneNum)
+      .drive(with: self, onNext: { owner, phoneNum in
+        owner.delegate?.invoke(.toSignUp(phoneNumber: phoneNum))
+      })
+      .disposed(by: disposeBag)
+
+    return Output(
+      phoneNum: phoneNum,
+      validate: validate,
+      error: errorTracker.asDriverOnErrorJustEmpty(),
+      clearButtonTapped: clearButtonTapped,
+      viewStatus: viewStatus,
+      certificateSuccess: certificateSuccess,
+      certificateFailuer: certificateResult.filter { $0 == false },
+      timeStampLabel: timeLabelStr,
+      timeLabelTextColor: timerLabelColor
+    )
+  }
+}
+
+// MARK: Test Code
+extension PhoneCertificationViewModel {
+
+  enum ViewType {
+    case phoneNumber
+    case authCode
+  }
+
+  struct AuthCodeWithTimeStamp {
+    let authCode: Int
+    let timeStamp = Date.now
+    var timeString: String {
+      let timeInterval = timeStamp.timeIntervalSinceNow
+      let min = abs(Int(timeInterval.truncatingRemainder(dividingBy: 3600)) / 60)
+      let sec = abs(Int(timeInterval.truncatingRemainder(dividingBy: 60)))
+      return String(format: "%02d:%02d", min, sec)
+    }
+    init(authCode: Int) {
+      self.authCode = authCode
+    }
+
+    func isAvailableCode() -> Bool {
+      let timeInterval = timeStamp.timeIntervalSinceNow
+      let sec = Int(timeInterval)
+      return abs(sec) <= 180
+    }
+  }
+}
diff --git a/Projects/Features/Auth/Src/AuthRoot/Certificate/SubView/SuccessCertificationView.swift b/Projects/Features/Auth/Src/AuthRoot/Certificate/SubView/SuccessCertificationView.swift
new file mode 100644
index 00000000..7ed087bd
--- /dev/null
+++ b/Projects/Features/Auth/Src/AuthRoot/Certificate/SubView/SuccessCertificationView.swift
@@ -0,0 +1,67 @@
+//
+//  SuccessCertificationView.swift
+//  DSKit
+//
+//  Created by Hoo's MacBookPro on 2023/08/15.
+//
+
+import UIKit
+
+import DSKit
+import Lottie
+
+final class SuccessCertificationView: UIView {
+
+  private lazy var backCardView = UIView().then {
+    $0.layer.cornerRadius = 12
+    $0.backgroundColor = DSKitAsset.Color.neutral600.color
+  }
+
+  private lazy var animationView = LottieAnimationView(animation: AnimationAsset.authSuccess.animation)
+
+  private lazy var titleLabel = UILabel().then {
+    $0.font = .thtH2B
+    $0.textColor = DSKitAsset.Color.neutral50.color
+    $0.text = "핸드폰 번호 인증 완료"
+  }
+
+  init() {
+    super.init(frame: .zero)
+    self.makeUI()
+  }
+
+  func makeUI() {
+    self.backgroundColor = DSKitAsset.Color.DimColor.signUpDim.color
+
+    self.addSubview(backCardView)
+
+    [animationView, titleLabel]
+      .forEach { backCardView.addSubview($0) }
+
+    backCardView.snp.makeConstraints {
+      $0.center.equalToSuperview()
+      $0.width.equalToSuperview().multipliedBy(0.795)
+      $0.height.equalToSuperview().multipliedBy(0.536)
+    }
+
+    animationView.snp.makeConstraints {
+      $0.centerX.equalToSuperview()
+      $0.top.equalToSuperview().offset(69)
+      $0.height.equalToSuperview().multipliedBy(0.469)
+      $0.width.equalToSuperview().multipliedBy(0.852)
+    }
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(animationView.snp.bottom).offset(34)
+      $0.centerX.equalToSuperview()
+    }
+  }
+
+  func animationPlay(completion: @escaping () -> Void) {
+    self.animationView.play { _ in completion() }
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/SignUpRootViewController.swift b/Projects/Features/Auth/Src/AuthRoot/Root/AuthRootViewController.swift
similarity index 55%
rename from Projects/Features/SignUp/Src/SignUpRoot/SignUpRootViewController.swift
rename to Projects/Features/Auth/Src/AuthRoot/Root/AuthRootViewController.swift
index d4938445..54ebf9ae 100644
--- a/Projects/Features/SignUp/Src/SignUpRoot/SignUpRootViewController.swift
+++ b/Projects/Features/Auth/Src/AuthRoot/Root/AuthRootViewController.swift
@@ -10,8 +10,10 @@ import UIKit
 
 import DSKit
 
-final class SignUpRootViewController: TFBaseViewController {
-  private lazy var buttonStackView: UIStackView = UIStackView().then {
+import AuthInterface
+
+final class AuthRootViewController: TFBaseViewController {
+  private lazy var buttonStackView = UIStackView().then {
     $0.axis = .vertical
     $0.spacing = 16
   }
@@ -28,14 +30,10 @@ final class SignUpRootViewController: TFBaseViewController {
     $0.contentMode = .scaleAspectFit
   }
 
-  var viewModel: SignUpRootViewModel!
-
-  override func viewDidLoad() {
-    super.viewDidLoad()
-    setupAccessibilityIdentifier()
-  }
+  var viewModel: AuthRootViewModel!
 
   override func makeUI() {
+    view.backgroundColor = DSKitAsset.Color.neutral700.color
     view.addSubview(signitureImageView)
     signitureImageView.snp.makeConstraints {
       $0.centerX.equalToSuperview()
@@ -43,6 +41,11 @@ final class SignUpRootViewController: TFBaseViewController {
         .inset(view.bounds.height * 0.162)
       $0.height.equalTo(180)
     }
+    
+    signitureImageView.transform = CGAffineTransform(translationX: 0, y: 60)
+    UIView.animate(withDuration: 0.1, delay: 0, options: .curveEaseOut) {
+      self.signitureImageView.transform = .identity
+    }
 
     self.view.addSubview(buttonStackView)
     self.buttonStackView.snp.makeConstraints {
@@ -62,29 +65,17 @@ final class SignUpRootViewController: TFBaseViewController {
   }
 
   override func bindViewModel() {
-    let input = SignUpRootViewModel.Input(
-      phoneBtn: startPhoneBtn.rx.tap.asDriver(),
-      kakaoBtn: startKakaoButton.rx.tap.asDriver(),
-      googleBtn: startGoogleBtn.rx.tap.asDriver(),
-      naverBtn: startNaverBtn.rx.tap.asDriver()
+    let buttonTap = Driver<SNSType>.merge(
+      startPhoneBtn.rx.tap.asDriver().map { return SNSType.normal },
+      startKakaoButton.rx.tap.asDriver().map { return SNSType.kakao },
+      startGoogleBtn.rx.tap.asDriver().map { SNSType.google },
+      startNaverBtn.rx.tap.asDriver().map { SNSType.naver }
     )
 
-    let output = viewModel.transform(input: input)
-  }
-}
-
-extension SignUpRootViewController {
+    let input = AuthRootViewModel.Input(
+      buttonTap: buttonTap
+    )
 
-  private func setupAccessibilityIdentifier() {
-    startPhoneBtn.accessibilityIdentifier = AccessibilityIdentifier.phoneBtn
-    startKakaoButton.accessibilityIdentifier = AccessibilityIdentifier.kakoBtn
-    startNaverBtn.accessibilityIdentifier = AccessibilityIdentifier.naverBtn
-    startGoogleBtn.accessibilityIdentifier = AccessibilityIdentifier.googleBtn
+    let output = viewModel.transform(input: input)
   }
 }
-
-//struct SignUpRootViewControllerPreview: PreviewProvider {
-//  static var previews: some View {
-//    SignUpRootViewController(viewModel: SignUpRootViewModel(navigator: SignUpNavigator(controller: UINavigationController()))).toPreView()
-//  }
-//}
diff --git a/Projects/Features/Auth/Src/AuthRoot/Root/AuthRootViewModel.swift b/Projects/Features/Auth/Src/AuthRoot/Root/AuthRootViewModel.swift
new file mode 100644
index 00000000..be2c7309
--- /dev/null
+++ b/Projects/Features/Auth/Src/AuthRoot/Root/AuthRootViewModel.swift
@@ -0,0 +1,38 @@
+//
+//  SignUpViewModel.swift
+//  Falling
+//
+//  Created by Hoo's MacBookPro on 2023/07/22.
+//
+
+import Foundation
+
+import RxSwift
+import RxCocoa
+import AuthInterface
+
+import Core
+
+final class AuthRootViewModel: ViewModelType {
+  struct Input {
+    let buttonTap: Driver<SNSType>
+  }
+
+  struct Output {
+
+  }
+
+  weak var delegate: AuthCoordinatingActionDelegate?
+
+  var disposeBag: DisposeBag = DisposeBag()
+
+  func transform(input: Input) -> Output {
+    input.buttonTap
+      .drive(with: self, onNext: { owner, sns in
+        owner.delegate?.invoke(.tologinType(sns))
+      })
+      .disposed(by: disposeBag)
+
+    return Output()
+  }
+}
diff --git a/Projects/Features/Auth/Src/AuthRoot/Root/SubView/TFLogginButton.swift b/Projects/Features/Auth/Src/AuthRoot/Root/SubView/TFLogginButton.swift
new file mode 100644
index 00000000..1db95313
--- /dev/null
+++ b/Projects/Features/Auth/Src/AuthRoot/Root/SubView/TFLogginButton.swift
@@ -0,0 +1,89 @@
+//
+//  LoginButton.swift
+//  Falling
+//
+//  Created by Hoo's MacBookPro on 2023/07/22.
+//
+
+import UIKit
+
+import DSKit
+
+enum TFLoginButtonType {
+  case phone
+  case kakao
+  case google
+  case naver
+
+  var title: String {
+    switch self {
+    case .phone:
+      return "핸드폰 번호로 시작하기"
+    case .kakao:
+      return "카카오톡으로 시작하기"
+    case .google:
+      return "구글로 시작하기"
+    case .naver:
+      return "네이버로 시작하기"
+    }
+  }
+
+  var backGroundColor: UIColor {
+    switch self {
+    case .phone:
+      return DSKitAsset.Color.neutral900.color
+    case .kakao:
+      return .yellow
+//      return .kakaoPrimary
+    case .google:
+      return DSKitAsset.Color.neutral50.color
+    case .naver:
+      return .blue
+//      return .naverPrimary
+    }
+  }
+
+  var titleColor: UIColor {
+    switch self {
+    case .phone, .naver:
+      return DSKitAsset.Color.neutral50.color
+    case .kakao, .google:
+      return DSKitAsset.Color.neutral900.color
+    }
+  }
+
+//  var icon: UIImage {
+//    switch self {
+//    case .phone:
+//      return Icon.Profile.pin
+//    case .kakao:
+//      return Icon.Profile.pin
+//    case .google:
+//      return Icon.Profile.pin
+//    case .naver:
+//      return Icon.Profile.pin
+//    }
+//  }
+}
+
+final class TFLoginButton: UIButton {
+  let btnType: TFLoginButtonType
+
+  init(btnType: TFLoginButtonType) {
+    self.btnType = btnType
+    super.init(frame: .zero)
+
+    setTitle(btnType.title, for: .normal)
+//    setImage(btnType.icon, for: .normal)
+    setTitleColor(btnType.titleColor, for: .normal)
+    backgroundColor = btnType.backGroundColor
+    titleLabel?.font = .thtSubTitle1Sb
+    imageEdgeInsets.right = 52
+    layer.cornerRadius = 26
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+}
diff --git a/Projects/Features/Auth/Src/Coordinator/AuthBuilder.swift b/Projects/Features/Auth/Src/Coordinator/AuthBuilder.swift
new file mode 100644
index 00000000..083e039b
--- /dev/null
+++ b/Projects/Features/Auth/Src/Coordinator/AuthBuilder.swift
@@ -0,0 +1,27 @@
+//
+//  SignUpBuilder.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+import Core
+
+import SignUpInterface
+import AuthInterface
+
+public final class AuthBuilder: AuthBuildable {
+  private let signUpBuilable: SignUpBuildable
+
+  public init(signUpBuilable: SignUpBuildable) {
+    self.signUpBuilable = signUpBuilable
+  }
+
+  public func build() -> AuthCoordinating {
+    let rootViewController = NavigationViewControllable()
+    let coordinator = AuthCoordinator(signUpBuildable: signUpBuilable, viewControllable: rootViewController)
+
+    return coordinator
+  }
+}
diff --git a/Projects/Features/Auth/Src/Coordinator/AuthCoordinator.swift b/Projects/Features/Auth/Src/Coordinator/AuthCoordinator.swift
new file mode 100644
index 00000000..2b7be3d2
--- /dev/null
+++ b/Projects/Features/Auth/Src/Coordinator/AuthCoordinator.swift
@@ -0,0 +1,123 @@
+//
+//  AuthCoordinator.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+
+import Core
+import AuthInterface
+import SignUpInterface
+
+protocol AuthCoordinatingActionDelegate: AnyObject {
+  func invoke(_ action: AuthCoordinatingAction)
+}
+
+public final class AuthCoordinator: BaseCoordinator, AuthCoordinating {
+  @Injected private var authUseCase: AuthUseCaseInterface
+  @Injected private var userInfoUseCase: UserInfoUseCaseInterface
+  
+  private let signUpBuildable: SignUpBuildable
+  private var signUpCoordinator: SignUpCoordinating?
+  public weak var delegate: AuthCoordinatingDelegate?
+
+  public init(signUpBuildable: SignUpBuildable, viewControllable: ViewControllable) {
+    self.signUpBuildable = signUpBuildable
+    super.init(viewControllable: viewControllable)
+  }
+
+  public override func start() {
+    replaceWindowRootViewController(rootViewController: self.viewControllable)
+
+    launchFlow()
+  }
+
+  
+  // MARK: Launch Screen
+  public func launchFlow() {
+    // TODO: Launch Screen 에서 가입/인증/메인 분기 처리
+
+    var needAuth = true
+    if needAuth {
+      rootFlow()
+    }
+  }
+
+  // MARK: 인증 토큰 재발급 또는 가입 시
+  public func rootFlow() {
+    let viewModel = AuthRootViewModel()
+    viewModel.delegate = self
+
+    let viewController = AuthRootViewController()
+    viewController.viewModel = viewModel
+
+    self.viewControllable.setViewControllers([viewController])
+  }
+
+  public func phoneNumberFlow() {
+    let viewModel = PhoneCertificationViewModel(useCase: authUseCase, userInfoUseCase: self.userInfoUseCase)
+    viewModel.delegate = self
+
+    let viewController = PhoneCertificationViewController(viewModel: viewModel)
+
+    self.viewControllable.pushViewController(viewController, animated: true)
+  }
+
+  public func snsFlow(type: SNSType) {
+    switch type {
+    case .normal:
+      phoneNumberFlow()
+    case .kakao:
+      print(type.rawValue)
+    case .naver:
+      print(type.rawValue)
+    case .google:
+      print(type.rawValue)
+    case .apple:
+      print(type.rawValue)
+    }
+  }
+}
+
+extension AuthCoordinator: AuthCoordinatingActionDelegate {
+  func invoke(_ action: AuthCoordinatingAction) {
+    switch action {
+    case let .tologinType(snsType):
+      snsFlow(type: snsType)
+    case let .toSignUp(phoneNum):
+      attachSignUpCoordiantor()
+    case .toMain:
+      self.delegate?.detachAuth(self)
+    }
+  }
+}
+
+// MARK: SignUpCoordinator
+
+extension AuthCoordinator {
+  func attachSignUpCoordiantor() {
+    if self.signUpCoordinator != nil { return }
+    let coordinator = self.signUpBuildable.build()
+    coordinator.delegate = self
+    self.attachChild(coordinator)
+    self.signUpCoordinator = coordinator
+
+    coordinator.start()
+  }
+  func detachSignUpCoordinator() {
+    guard let coordinator = self.signUpCoordinator else { return }
+    self.detachChild(coordinator)
+    self.signUpCoordinator = nil
+  }
+}
+
+extension AuthCoordinator: SignUpCoordinatorDelegate {
+  public func detachSignUp(_ coordinator: Coordinator) {
+    self.detachSignUpCoordinator()
+
+    self.delegate?.detachAuth(self)
+  }
+}
+
diff --git a/Projects/Features/Auth/Src/Coordinator/LaunchBuilder.swift b/Projects/Features/Auth/Src/Coordinator/LaunchBuilder.swift
new file mode 100644
index 00000000..b793f464
--- /dev/null
+++ b/Projects/Features/Auth/Src/Coordinator/LaunchBuilder.swift
@@ -0,0 +1,20 @@
+//
+//  LaunchBuilder.swift
+//  Auth
+//
+//  Created by Kanghos on 6/4/24.
+//
+
+import Foundation
+import Core
+import AuthInterface
+
+public final class LaunchBuilder: LaunchBuildable {
+
+  public init() { }
+
+  public func build(rootViewControllable: ViewControllable) -> AuthLaunchCoordinating {
+    let coordinator = AuthLaunchCoordinator(viewControllable: rootViewControllable)
+    return coordinator
+  }
+}
diff --git a/Projects/Features/Auth/Src/Coordinator/LaunchCoordinator.swift b/Projects/Features/Auth/Src/Coordinator/LaunchCoordinator.swift
new file mode 100644
index 00000000..0300602b
--- /dev/null
+++ b/Projects/Features/Auth/Src/Coordinator/LaunchCoordinator.swift
@@ -0,0 +1,39 @@
+//
+//  LaunchCoordinator.swift
+//  AuthDemo
+//
+//  Created by Kanghos on 6/4/24.
+//
+
+import UIKit
+import Core
+import DSKit
+import AuthInterface
+import SignUpInterface
+
+public final class AuthLaunchCoordinator: BaseCoordinator, AuthLaunchCoordinating {
+  @Injected private var useCase: AuthUseCaseInterface
+  @Injected private var userInfoUseCase: UserInfoUseCaseInterface
+  public weak var delegate: LaunchCoordinatingDelegate?
+
+  public override func start() {
+    launchFlow()
+  }
+
+  public func launchFlow() {
+    let vm = LauncherViewModel(userInfoUseCase: self.userInfoUseCase, useCase: self.useCase)
+    let vc = TFAuthLauncherViewController(viewModel: vm)
+    vm.delegate = self
+    self.viewControllable.pushViewController(vc, animated: true)
+  }
+}
+
+extension AuthLaunchCoordinator: LauncherDelegate {
+  public func needAuth() {
+    self.delegate?.finishFlow(self, .needAuth)
+  }
+
+  public func toMain() {
+    self.delegate?.finishFlow(self, .toMain)
+  }
+}
diff --git a/Projects/Features/Auth/Src/File.swift b/Projects/Features/Auth/Src/File.swift
deleted file mode 100644
index 436c7e7d..00000000
--- a/Projects/Features/Auth/Src/File.swift
+++ /dev/null
@@ -1,8 +0,0 @@
-//
-//  File.swift
-//  AuthInterface
-//
-//  Created by Hoo's MacBookPro on 12/3/23.
-//
-
-import Foundation
diff --git a/Projects/Features/Auth/Src/Launcher/LauncherViewModel.swift b/Projects/Features/Auth/Src/Launcher/LauncherViewModel.swift
new file mode 100644
index 00000000..5c6b3c7a
--- /dev/null
+++ b/Projects/Features/Auth/Src/Launcher/LauncherViewModel.swift
@@ -0,0 +1,75 @@
+//
+//  LauncherViewModel.swift
+//  AuthDemo
+//
+//  Created by Kanghos on 6/4/24.
+//
+
+import Foundation
+
+import AuthInterface
+import SignUpInterface
+import Core
+
+import RxSwift
+import RxCocoa
+
+protocol LauncherDelegate: AnyObject {
+  func needAuth()
+  func toMain()
+}
+
+public final class LauncherViewModel: ViewModelType {
+  private var disposeBag = DisposeBag()
+  private let userInfoUseCase: UserInfoUseCaseInterface
+  private let useCase: AuthUseCaseInterface
+  weak var delegate: LauncherDelegate?
+
+  public struct Input {
+    let viewDidLoad: Driver<Void>
+  }
+
+  public struct Output {
+    let state: Driver<Void>
+  }
+
+  public init(userInfoUseCase: UserInfoUseCaseInterface, useCase: AuthUseCaseInterface) {
+    self.userInfoUseCase = userInfoUseCase
+    self.useCase = useCase
+  }
+
+  public func transform(input: Input) -> Output {
+
+    let phoneNumber = userInfoUseCase.fetchPhoneNumber()
+      .catchAndReturn("")
+      .asDriver(onErrorJustReturn: "")
+
+    let needAuth = input.viewDidLoad
+      .withLatestFrom(phoneNumber)
+      .map { $0.isEmpty }
+
+      needAuth
+      .filter { $0 }
+      .drive(with: self) { owner, needAuth in
+        owner.delegate?.needAuth()
+      }.disposed(by: disposeBag)
+
+    needAuth
+      .filter { !$0 }
+      .asObservable()
+      .withLatestFrom(phoneNumber)
+      .withUnretained(self)
+      .flatMap { owner, phoneNum in
+        owner.useCase.login(phoneNumber: phoneNum, deviceKey: "device")
+          .asObservable()
+          .catch { error in
+            TFLogger.dataLogger.error("\(error.localizedDescription)")
+            return .empty()
+          }
+      }.subscribe(with: self) { owner, _ in
+        owner.delegate?.toMain()
+      }.disposed(by: disposeBag)
+
+    return Output(state: Driver.just(()))
+  }
+}
diff --git a/Projects/Features/Auth/Src/Launcher/TFAuthLauncherViewController.swift b/Projects/Features/Auth/Src/Launcher/TFAuthLauncherViewController.swift
new file mode 100644
index 00000000..f0abe630
--- /dev/null
+++ b/Projects/Features/Auth/Src/Launcher/TFAuthLauncherViewController.swift
@@ -0,0 +1,33 @@
+//
+//  TFLauncherViewController.swift
+//  AuthDemo
+//
+//  Created by Kanghos on 6/4/24.
+//
+
+import DSKit
+import Foundation
+
+public final class TFAuthLauncherViewController: TFLaunchViewController {
+  private let viewModel: LauncherViewModel
+
+  public init(viewModel: LauncherViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+
+  public required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+  public override func bindViewModel() {
+    let input = LauncherViewModel.Input(
+      viewDidLoad: self.rx.viewDidAppear.asDriver().delay(.seconds(1)).map { _ in }
+    )
+    let output = viewModel.transform(input: input)
+
+    output.state
+      .drive()
+      .disposed(by: disposeBag)
+  }
+}
diff --git a/Projects/Features/Auth/Src/SubView/TFTextButton.swift b/Projects/Features/Auth/Src/SubView/TFTextButton.swift
new file mode 100644
index 00000000..2b0bdd01
--- /dev/null
+++ b/Projects/Features/Auth/Src/SubView/TFTextButton.swift
@@ -0,0 +1,45 @@
+//
+//  TFTextButton.swift
+//  Falling
+//
+//  Created by Hoo's MacBookPro on 2023/08/13.
+//
+
+import UIKit
+
+import DSKit
+
+final class TFTextButton: UIButton {
+  let title: String
+
+  init(title: String) {
+    self.title = title
+    super.init(frame: .zero)
+    makeView()
+  }
+
+  func makeView() {
+    let attributedString = NSMutableAttributedString(string: title)
+    attributedString.addAttribute(.underlineStyle,
+                                  value: NSUnderlineStyle.single.rawValue,
+                                  range: NSRange(location: 0, length: title.count))
+
+    attributedString.addAttribute(.font,
+                                  value: UIFont.thtP2M,
+                                  range: NSRange(location: 0, length: title.count))
+
+    attributedString.addAttribute(.underlineColor,
+                                  value: DSKitAsset.Color.neutral400.color,
+                                  range: NSRange(location: 0, length: title.count))
+
+    attributedString.addAttribute(.foregroundColor,
+                                  value: DSKitAsset.Color.neutral400.color,
+                                  range: NSRange(location: 0, length: title.count))
+
+    setAttributedTitle(attributedString, for: .normal)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+}
diff --git a/Projects/Features/Auth/Src/UseCase/AuthUseCase.swift b/Projects/Features/Auth/Src/UseCase/AuthUseCase.swift
new file mode 100644
index 00000000..cb850a01
--- /dev/null
+++ b/Projects/Features/Auth/Src/UseCase/AuthUseCase.swift
@@ -0,0 +1,55 @@
+//
+//  AuthUseCase.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/27/24.
+//
+
+import Foundation
+import AuthInterface
+import RxSwift
+
+public final class AuthUseCase: AuthUseCaseInterface {
+  private let repository: AuthRepositoryInterface
+  private let tokenStore: TokenStore
+
+  public init(authRepository: AuthRepositoryInterface, tokenStore: TokenStore) {
+    self.repository = authRepository
+    self.tokenStore = tokenStore
+  }
+  public func certificate(phoneNumber: String) -> RxSwift.Single<Int> {
+    repository.certificate(phoneNumber: phoneNumber)
+  }
+
+  public func checkUserExists(phoneNumber: String) -> Single<UserSignUpInfoRes> {
+    return repository.checkUserExist(phoneNumber: phoneNumber)
+  }
+
+  public func login(phoneNumber: String, deviceKey: String) -> Single<Void> {
+    return repository.login(phoneNumber: phoneNumber, deviceKey: deviceKey)
+      .flatMap { [weak self] token in
+        self?.tokenStore.saveToken(token: token)
+        return .just(())
+      }
+  }
+
+  public func loginSNS(_ request: AuthInterface.UserSNSLoginRequest) -> Single<Void> {
+    return repository.loginSNS(request)
+      .flatMap { [weak self] token in
+        self?.tokenStore.saveToken(token: token)
+        return .just(())
+      }
+  }
+
+  // TODO: token을 어디서 집어넣을 지 생각해볼 것 e,g Authenticator
+  public func refresh() -> Single<Void> {
+    guard let token = try? tokenStore.getToken() else {
+      return .error(AuthError.invalidToken)
+    }
+    return repository.refresh(token)
+      .flatMap { [weak self] token in
+        self?.tokenStore.saveToken(token: token)
+        return .just(())
+      }
+  }
+}
diff --git a/Projects/Features/Auth/Src/Util/Regex+Util.swift b/Projects/Features/Auth/Src/Util/Regex+Util.swift
new file mode 100644
index 00000000..37083451
--- /dev/null
+++ b/Projects/Features/Auth/Src/Util/Regex+Util.swift
@@ -0,0 +1,23 @@
+//
+//  Regex+Util.swift
+//  AuthInterface
+//
+//  Created by Kanghos on 5/8/24.
+//
+
+import Foundation
+
+enum Regex: String {
+  case phoneNum = "^01[0-1,7][0-9]{7,8}$"
+  case email = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
+}
+
+extension String {
+  func phoneNumValidation() -> Bool {
+    return (self.range(of: Regex.phoneNum.rawValue, options: .regularExpression) != nil)
+  }
+
+  func emailValidation() -> Bool {
+    return (self.range(of: Regex.email.rawValue, options: .regularExpression) != nil)
+  }
+}
diff --git a/Projects/Features/Falling/Src/Home/FallingHomeViewController.swift b/Projects/Features/Falling/Src/Home/FallingHomeViewController.swift
index 25017041..2b521b3e 100644
--- a/Projects/Features/Falling/Src/Home/FallingHomeViewController.swift
+++ b/Projects/Features/Falling/Src/Home/FallingHomeViewController.swift
@@ -206,7 +206,6 @@ final class FallingHomeViewController: TFBaseViewController {
               dimColor: DSKitAsset.Color.clear.color,
               topActionCompletion: {
                 blockButtonTapTrigger.accept(())
-                
                 owner.homeView.makeToast("차단하기가 완료되었습니다. 해당 사용자와\n서로 차단되며 설정에서 확인 가능합니다.", duration: 3.0, position: .bottom)
               },
               bottomActionCompletion: { timerActiveRelay.accept(true) },
diff --git a/Projects/Features/Like/Interface/Src/Coordinator/LikeCoordinating.swift b/Projects/Features/Like/Interface/Src/Coordinator/LikeCoordinating.swift
index d34e7565..b5109f76 100644
--- a/Projects/Features/Like/Interface/Src/Coordinator/LikeCoordinating.swift
+++ b/Projects/Features/Like/Interface/Src/Coordinator/LikeCoordinating.swift
@@ -13,12 +13,18 @@ public protocol LikeCoordinatorDelegate: AnyObject {
   func test(_ coordinator: Coordinator)
 
 }
+
+public protocol LikeProfileListener: AnyObject {
+  func likeProfileDidTapReject(_ like: Like)
+  func likeProfileDidTapChat(_ like: Like)
+}
+
 public protocol LikeCoordinating: Coordinator {
   var delegate: LikeCoordinatorDelegate? { get set }
 
   func homeFlow()
   func chatRoomFlow()
-  func profileFlow(_ item: Like)
+  func profileFlow(_ item: Like, listener: LikeProfileListener)
 }
 
 public enum LikeCoordinatorAction {
diff --git a/Projects/Features/Like/Src/Coordinator/LikeBuilder.swift b/Projects/Features/Like/Src/Coordinator/LikeBuilder.swift
index 507818c4..fb2e237a 100644
--- a/Projects/Features/Like/Src/Coordinator/LikeBuilder.swift
+++ b/Projects/Features/Like/Src/Coordinator/LikeBuilder.swift
@@ -15,7 +15,8 @@ public final class LikeBuilder: LikeBuildable {
   public init() { }
   public func build(rootViewControllable: ViewControllable) -> LikeCoordinating {
 
-    let coordinator = LikeCoordinator(viewControllable: rootViewControllable)
+    let buildable = MockChatRoomBuilder()
+    let coordinator = LikeCoordinator(chatRoomBuildable: buildable, viewControllable: rootViewControllable)
 
     return coordinator
   }
diff --git a/Projects/Features/Like/Src/Coordinator/LikeCoordinator.swift b/Projects/Features/Like/Src/Coordinator/LikeCoordinator.swift
index 441f4b01..8fe43c49 100644
--- a/Projects/Features/Like/Src/Coordinator/LikeCoordinator.swift
+++ b/Projects/Features/Like/Src/Coordinator/LikeCoordinator.swift
@@ -10,15 +10,54 @@ import Foundation
 import LikeInterface
 import Core
 
+enum LikeCoordinatorAction {
+  case presentProfile(like: Like, listener: LikeProfileListener)
+  case pushChatRoom(id: String)
+  case dismissProfile
+}
+
+protocol LikeCoordinatorActionDelegate: AnyObject {
+  func invoke(_ action: LikeCoordinatorAction)
+}
+
+// MARK: Using Other Feature Coordinator
+// step 1. get buildable when init
+// step 2. build coordinator when need, and assign optional variable -> attachMethod
+// step 3. release when coordinator finish, bc, optimize memory -> detachMethod
+
 public final class LikeCoordinator: BaseCoordinator, LikeCoordinating {
   @Injected var likeUseCase: LikeUseCaseInterface
 
+  private let chatRoomBuildable: ChatRoomBuildable
+  private var chatRoomCoordinator: ChatRoomCoordinating?
+
   public weak var delegate: LikeCoordinatorDelegate?
-  
+
   public override func start() {
     homeFlow()
   }
 
+  init(chatRoomBuildable: ChatRoomBuildable, viewControllable: ViewControllable) {
+    self.chatRoomBuildable = chatRoomBuildable
+    super.init(viewControllable: viewControllable)
+  }
+
+  func attachChatRoomCoordinator() {
+    if self.chatRoomCoordinator != nil { return }
+    let coordinator = chatRoomBuildable.build(rootViewControllable: self.viewControllable, listener: self)
+    coordinator.chatRoomFlow()
+    self.attachChild(coordinator)
+    self.chatRoomCoordinator = coordinator
+  }
+
+  func detachChatRoomCoordinator() {
+    guard let coordinator = self.chatRoomCoordinator else { return }
+    self.viewControllable.popViewController(animated: true)
+
+    self.detachChild(coordinator)
+    self.chatRoomCoordinator = nil
+  }
+
   public func homeFlow() {
     let viewModel = LikeHomeViewModel(likeUseCase: likeUseCase)
     viewModel.delegate = self
@@ -27,41 +66,44 @@ public final class LikeCoordinator: BaseCoordinator, LikeCoordinating {
 
     self.viewControllable.setViewControllers([viewController])
   }
-  
+
   public func chatRoomFlow() {
     TFLogger.dataLogger.info("ChatRoom!")
+    attachChatRoomCoordinator()
   }
-  
-  public func profileFlow(_ item: Like) {
+
+  public func profileFlow(_ item: Like, listener: LikeProfileListener) {
     let viewModel = LikeProfileViewModel(likeUseCase: likeUseCase, likItem: item)
+    viewModel.listener = listener
     viewModel.delegate = self
 
     let viewController = LikeProfileViewController(viewModel: viewModel)
-
-    self.viewControllable.pushViewController(viewController, animated: true)
+    viewController.modalPresentationStyle = .currentContext
+    self.viewControllable.present(viewController, animated: true)
   }
 }
 
-extension LikeCoordinator: LikeHomeDelegate {
-  func toProfile(like: LikeInterface.Like) {
-    profileFlow(like)
+extension LikeCoordinator: LikeCoordinatorActionDelegate {
+  func invoke(_ action: LikeCoordinatorAction) {
+    switch action {
+    case let .presentProfile(like, listener):
+      profileFlow(like, listener: listener)
+    case .pushChatRoom(let id):
+      chatRoomFlow()
+    case .dismissProfile:
+      dismissProfile()
+    }
   }
-  
-  func toChatRoom(userID: String) {
-    chatRoomFlow()
+
+  func dismissProfile() {
+    self.viewControllable.dismiss()
   }
 }
 
-extension LikeCoordinator: LikeProfileDelegate {
-  func selectNextTime(userUUID: String) {
-    viewControllable.popViewController(animated: true)
-  }
-  
-  func selectLike(userUUID: String) {
-    viewControllable.popViewController(animated: true)
-  }
-  
-  func toList() {
-    viewControllable.popViewController(animated: true)
+extension LikeCoordinator: ChatRoomCoordinatorDelegate {
+  func didFinishChatRoomCoordinator(_ coordinator: Coordinator?) {
+    if let coordinator {
+      detachChatRoomCoordinator()
+    }
   }
 }
diff --git a/Projects/Features/Like/Src/Coordinator/MockChatCoordinator.swift b/Projects/Features/Like/Src/Coordinator/MockChatCoordinator.swift
new file mode 100644
index 00000000..4bbdaeac
--- /dev/null
+++ b/Projects/Features/Like/Src/Coordinator/MockChatCoordinator.swift
@@ -0,0 +1,62 @@
+//
+//  MockChatCoordinator.swift
+//  Like
+//
+//  Created by Kanghos on 2024/05/03.
+//
+
+import Foundation
+import Core
+
+protocol ChatRoomCoordinating: Coordinator {
+  var delegate: ChatRoomCoordinatorDelegate? { get set }
+  func chatRoomFlow()
+  func profileFLow()
+  func photoselect()
+  func logout()
+}
+
+// MARK: Communicate Parent Coordinator
+protocol ChatRoomCoordinatorDelegate: AnyObject {
+  func didFinishChatRoomCoordinator(_ coordinator: Coordinator?) // release Coordinator
+}
+
+enum ChatRoomCoordinatorAction {
+  case finish
+}
+
+protocol ChatRoomActionDelegate: AnyObject {
+  func invoke(_ action: ChatRoomCoordinatorAction)
+}
+
+class MockChatCoordinator: BaseCoordinator, ChatRoomCoordinating  {
+  func profileFLow() {
+
+  }
+  
+  func photoselect() {
+
+  }
+  
+  func logout() {
+    
+  }
+  
+  weak var delegate: ChatRoomCoordinatorDelegate?
+  func chatRoomFlow() {
+    let vc = MockChatRoomViewController()
+    vc.delegate = self
+
+    self.viewControllable.pushViewController(vc, animated: true)
+  }
+}
+
+// MARK: process ChatRoom navigation action
+extension MockChatCoordinator: ChatRoomActionDelegate {
+  func invoke(_ action: ChatRoomCoordinatorAction) {
+    switch action {
+    case .finish:
+      self.delegate?.didFinishChatRoomCoordinator(self)
+    }
+  }
+}
diff --git a/Projects/Features/Like/Src/Coordinator/MockChatRoomBuilder.swift b/Projects/Features/Like/Src/Coordinator/MockChatRoomBuilder.swift
new file mode 100644
index 00000000..4d3c210c
--- /dev/null
+++ b/Projects/Features/Like/Src/Coordinator/MockChatRoomBuilder.swift
@@ -0,0 +1,23 @@
+//
+//  MockChatRoomBuilder.swift
+//  Like
+//
+//  Created by Kanghos on 2024/05/03.
+//
+
+import Foundation
+import Core
+
+protocol ChatRoomBuildable {
+  func build(rootViewControllable: ViewControllable, listener: ChatRoomCoordinatorDelegate) -> ChatRoomCoordinating
+}
+
+class MockChatRoomBuilder: ChatRoomBuildable {
+  public init() { }
+  public func build(rootViewControllable: ViewControllable, listener: ChatRoomCoordinatorDelegate) -> ChatRoomCoordinating {
+
+    let coordinator = MockChatCoordinator(viewControllable: rootViewControllable)
+    coordinator.delegate = listener
+    return coordinator
+  }
+}
diff --git a/Projects/Features/Like/Src/Coordinator/MockChatRoomViewController.swift b/Projects/Features/Like/Src/Coordinator/MockChatRoomViewController.swift
new file mode 100644
index 00000000..11bca709
--- /dev/null
+++ b/Projects/Features/Like/Src/Coordinator/MockChatRoomViewController.swift
@@ -0,0 +1,47 @@
+//
+//  MockChatRoomViewController.swift
+//  Like
+//
+//  Created by Kanghos on 2024/05/03.
+//
+
+import UIKit
+
+import DSKit
+
+import RxSwift
+import RxCocoa
+
+final class MockChatRoomViewController: TFBaseViewController {
+
+  weak var delegate: ChatRoomActionDelegate?
+
+  private let label: UILabel = {
+    let label = UILabel()
+    label.text = "Mock Chat Room"
+    label.font = .thtH1B
+    return label
+  }()
+
+  private let backButton: UIBarButtonItem = .backButton
+
+  override func makeUI() {
+    self.view.addSubview(label)
+
+    label.center = self.view.center
+  }
+
+  override func navigationSetting() {
+    super.navigationSetting()
+
+    self.navigationItem.leftBarButtonItem = backButton
+  }
+
+  override func bindViewModel() {
+    backButton.rx.tap
+      .asDriver()
+      .drive(with: self) { owner, _ in
+        owner.delegate?.invoke(.finish)
+      }.disposed(by: disposeBag)
+  }
+}
diff --git a/Projects/Features/Like/Src/Home/LikeHomeViewController.swift b/Projects/Features/Like/Src/Home/LikeHomeViewController.swift
index 508a1f6c..a7d922bd 100644
--- a/Projects/Features/Like/Src/Home/LikeHomeViewController.swift
+++ b/Projects/Features/Like/Src/Home/LikeHomeViewController.swift
@@ -11,12 +11,6 @@ import Core
 import DSKit
 import LikeInterface
 
-enum LikeCellButtonAction {
-  case reject(IndexPath)
-  case chat(IndexPath)
-  case profile(IndexPath)
-}
-
 public final class LikeHomeViewController: TFBaseViewController {
   private lazy var mainView = HeartListView()
   private var dataSource: DataSource!
@@ -70,10 +64,6 @@ public final class LikeHomeViewController: TFBaseViewController {
 
       let output = viewModel.transform(input: input)
 
-      output.chatRoom
-        .drive()
-        .disposed(by: disposeBag)
-
       output.heartList
         .drive(onNext: { [weak self] list in
           self?.mainView.emptyView.isHidden = !list.isEmpty
@@ -90,10 +80,6 @@ public final class LikeHomeViewController: TFBaseViewController {
           self?.pagingDataSource(items)
         }).disposed(by: disposeBag)
 
-      output.profile
-        .drive()
-        .disposed(by: disposeBag)
-
       output.reject
         .drive(onNext: { [weak self] item in
           self?.deleteItems(item)
diff --git a/Projects/Features/Like/Src/Home/LikeHomeViewModel.swift b/Projects/Features/Like/Src/Home/LikeHomeViewModel.swift
index 43787f17..dd173528 100644
--- a/Projects/Features/Like/Src/Home/LikeHomeViewModel.swift
+++ b/Projects/Features/Like/Src/Home/LikeHomeViewModel.swift
@@ -13,17 +13,13 @@ import LikeInterface
 import RxSwift
 import RxCocoa
 
-protocol LikeHomeDelegate: AnyObject {
-  func toProfile(like: Like)
-  func toChatRoom(userID: String)
-}
-
 final class LikeHomeViewModel: ViewModelType {
   private let likeUseCase: LikeUseCaseInterface
-  
-  weak var delegate: LikeHomeDelegate?
 
-  var disposeBag: DisposeBag = DisposeBag()
+  weak var delegate: LikeCoordinatorActionDelegate?
+
+  private var disposeBag: DisposeBag = DisposeBag()
+  private let signal = PublishSubject<Action>()
 
   init(likeUseCase: LikeUseCaseInterface) {
     self.likeUseCase = likeUseCase
@@ -35,7 +31,17 @@ final class LikeHomeViewModel: ViewModelType {
   }
 }
 
+enum LikeCellButtonAction {
+  case reject(IndexPath)
+  case chat(IndexPath)
+  case profile(IndexPath)
+}
+
 extension LikeHomeViewModel {
+  enum Action {
+    case removeItem(Like)
+  }
+
   struct Input {
     let trigger: Driver<Void>
     let cellButtonAction: Driver<LikeCellButtonAction>
@@ -44,8 +50,6 @@ extension LikeHomeViewModel {
 
   struct Output {
     let heartList: Driver<[Like]>
-    let chatRoom: Driver<Void>
-    let profile: Driver<Void>
     let reject: Driver<Like>
     let pagingList: Driver<[Like]>
   }
@@ -59,35 +63,50 @@ extension LikeHomeViewModel {
   func transform(input: Input) -> Output {
     let currentCursor = BehaviorRelay<CursorInfo>(value: CursorInfo(nil, nil))
     let snapshot = BehaviorRelay<[Like]>(value: [])
+    let navigateAction = PublishSubject<LikeCoordinatorAction>()
 
     let refresh = input.trigger
-      .asObservable()
-      .flatMapLatest(weak: self, selector: { owner, _ in
-        owner.likeUseCase.fetchList(size: 100, lastTopicIndex: nil, lastLikeIndex: nil)
-        .map {
-          currentCursor.accept(CursorInfo($0.lastLikeIdx, $0.lastFallingTopicIdx))
-          let initial = $0.likeList
-          snapshot.accept(initial)
-          return initial
-        }
-      })
-      .asDriverOnErrorJustEmpty()
+      .flatMapLatest { [unowned self] _ in
+        self.likeUseCase.fetchList(size: 100, lastTopicIndex: nil, lastLikeIndex: nil)
+          .flatMap { info in
+            currentCursor.accept(CursorInfo(info.lastLikeIdx, info.lastFallingTopicIdx))
+            let initial = info.likeList
+            snapshot.accept(initial)
+            return Observable.just(initial)
+          }
+          .asDriverOnErrorJustEmpty()
+      }
 
     let newPage = input.pagingTrigger
       .asObservable()
       .withLatestFrom(currentCursor)
-      .flatMapLatest(weak: self, selector: { owner, cursorInfo in
+      .withUnretained(self)
+      .flatMapLatest { owner, cursorInfo in
         owner.likeUseCase.fetchList(size: 100, lastTopicIndex: nil, lastLikeIndex: nil)
-        .map {
-          currentCursor.accept(CursorInfo($0.lastLikeIdx, $0.lastFallingTopicIdx))
-          var mutable = snapshot.value
-          mutable.append(contentsOf: $0.likeList )
-          snapshot.accept(mutable)
-          return mutable
-        }
-      })
+          .map {
+            currentCursor.accept(CursorInfo($0.lastLikeIdx, $0.lastFallingTopicIdx))
+            var mutable = snapshot.value
+            mutable.append(contentsOf: $0.likeList )
+            snapshot.accept(mutable)
+            return mutable
+          }
+      }
       .asDriverOnErrorJustEmpty()
 
+    let rejectFromSignal = self.signal
+      .compactMap { action -> Like? in
+        if case let .removeItem(like) = action {
+          return like
+        }
+        return nil
+      }.map { like in
+        var mutable = snapshot.value
+        mutable.removeAll { $0 == like }
+        snapshot.accept(mutable)
+        return like
+      }.asDriverOnErrorJustEmpty()
+
+
     let reject = input.cellButtonAction
       .compactMap { action -> IndexPath? in
         if case let .reject(indexPath) = action {
@@ -105,7 +124,7 @@ extension LikeHomeViewModel {
         return deleted
       }
 
-    let chatRoom = input.cellButtonAction
+    input.cellButtonAction
       .compactMap { action -> IndexPath? in
         if case let .chat(indexPath) = action {
           return indexPath
@@ -114,14 +133,13 @@ extension LikeHomeViewModel {
       }
       .withLatestFrom(snapshot.asDriverOnErrorJustEmpty()) {
         indexPath, dataSource in
-        dataSource[indexPath.item]
+        dataSource[indexPath.item].userUUID
       }
-      .do(onNext: { [weak self] item in
-        self?.delegate?.toChatRoom(userID: item.userUUID)
-      })
-      .map { _ in }
+      .map { .pushChatRoom(id: $0) }
+      .drive(navigateAction)
+      .disposed(by: disposeBag)
 
-    let profile = input.cellButtonAction
+    input.cellButtonAction
       .compactMap { action -> IndexPath? in
         if case let .profile(indexPath) = action {
           return indexPath
@@ -132,16 +150,33 @@ extension LikeHomeViewModel {
         indexPath, dataSource in
         dataSource[indexPath.item]
       }
-      .do(onNext: { [weak self] item in
-        self?.delegate?.toProfile(like: item)
-      }).map { _ in }
+      .drive(with: self, onNext: { owner, like in
+        navigateAction.onNext(.presentProfile(like: like, listener: owner))
+      } )
+      .disposed(by: disposeBag)
+
+    navigateAction
+      .subscribe(onNext: { [weak self] action in
+        self?.delegate?.invoke(action)
+      })
+      .disposed(by: disposeBag)
 
     return Output(
       heartList: refresh,
-      chatRoom: chatRoom,
-      profile: profile,
-      reject: reject,
+      reject: Driver.merge(reject, rejectFromSignal),
       pagingList: newPage
     )
   }
 }
+
+extension LikeHomeViewModel: LikeProfileListener {
+
+  func likeProfileDidTapChat(_ like: Like) {
+    self.delegate?.invoke(.pushChatRoom(id: like.userUUID))
+  }
+
+  func likeProfileDidTapReject(_ like: Like) {
+    self.signal.onNext(.removeItem(like))
+    //    self.delegate?.invoke(.dismissProfile)
+  }
+}
diff --git a/Projects/Features/Like/Src/Profile/LikeProfileViewModel.swift b/Projects/Features/Like/Src/Profile/LikeProfileViewModel.swift
index aee96ad9..6ddc021b 100644
--- a/Projects/Features/Like/Src/Profile/LikeProfileViewModel.swift
+++ b/Projects/Features/Like/Src/Profile/LikeProfileViewModel.swift
@@ -11,20 +11,14 @@ import DSKit
 import LikeInterface
 import Domain
 
-protocol LikeProfileDelegate: AnyObject {
-  func selectNextTime(userUUID: String)
-  func selectLike(userUUID: String)
-  func toList()
-}
-
 final class LikeProfileViewModel: ViewModelType {
 
   private let likeUseCase: LikeUseCaseInterface
   private let likItem: Like
   private var disposeBag = DisposeBag()
 
-  weak var delegate: LikeProfileDelegate?
-
+  weak var listener: LikeProfileListener?
+  weak var delegate: LikeCoordinatorActionDelegate?
 
   init(likeUseCase: LikeUseCaseInterface, likItem: Like) {
     self.likeUseCase = likeUseCase
@@ -65,23 +59,25 @@ final class LikeProfileViewModel: ViewModelType {
 
     input.rejectTrigger
       .withLatestFrom(userIDSubject.asDriverOnErrorJustEmpty())
-      .map { $0.likeIdx }.map(String.init)
-      .drive(with: self, onNext: { owner, uuid in
-        owner.delegate?.selectNextTime(userUUID: uuid)
+      .map { $0 }
+      .drive(with: self, onNext: { owner, like in
+        owner.listener?.likeProfileDidTapReject(like)
+        owner.delegate?.invoke(.dismissProfile)
       })
       .disposed(by: disposeBag)
 
     input.likeTrigger
       .withLatestFrom(userIDSubject.asDriver(onErrorDriveWith: .empty()))
-    .map { $0.likeIdx }.map(String.init)
-    .drive(with: self, onNext: { owner, id in
-      owner.delegate?.selectLike(userUUID: id)
+    .map { $0 }
+    .drive(with: self, onNext: { owner, like in
+      owner.listener?.likeProfileDidTapChat(like)
+      owner.delegate?.invoke(.dismissProfile)
     })
     .disposed(by: disposeBag)
 
     input.closeTrigger
       .drive(with: self, onNext: { owner, _ in
-        owner.delegate?.toList()
+        owner.delegate?.invoke(.dismissProfile)
       })
       .disposed(by: disposeBag)
 
diff --git a/Projects/Features/MyPage/Interface/Src/RepositoryInterface/MyPageRepositoryInterface.swift b/Projects/Features/MyPage/Interface/Src/RepositoryInterface/MyPageRepositoryInterface.swift
index 91146505..a1d0a4aa 100644
--- a/Projects/Features/MyPage/Interface/Src/RepositoryInterface/MyPageRepositoryInterface.swift
+++ b/Projects/Features/MyPage/Interface/Src/RepositoryInterface/MyPageRepositoryInterface.swift
@@ -6,7 +6,9 @@
 //
 
 import Foundation
+import RxSwift
+import RxCocoa
 
 public protocol MyPageRepositoryInterface {
-  func test()
+  
 }
diff --git a/Projects/Features/MyPage/Project.swift b/Projects/Features/MyPage/Project.swift
index f4c42098..1d325469 100644
--- a/Projects/Features/MyPage/Project.swift
+++ b/Projects/Features/MyPage/Project.swift
@@ -23,6 +23,7 @@ let project = Project(
 			dependencies: [
 				.feature(interface: .MyPage),
 				.feature(interface: .Auth),
+        .feature(interface: .SignUp),
         .dsKit
 			]
 		),
diff --git a/Projects/Features/SignUp/Demo/Src/AppDelegate+Register.swift b/Projects/Features/SignUp/Demo/Src/AppDelegate+Register.swift
new file mode 100644
index 00000000..b02c74ea
--- /dev/null
+++ b/Projects/Features/SignUp/Demo/Src/AppDelegate+Register.swift
@@ -0,0 +1,43 @@
+//
+//  AppDelegate+Register.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/1/24.
+//
+
+import Foundation
+
+import Core
+
+import SignUp
+import SignUpInterface
+import AuthInterface
+import Data
+
+extension AppDelegate {
+  var container: DIContainer {
+    DIContainer.shared
+  }
+
+  func registerDependencies() {
+
+    container.register(
+      interface: SignUpUseCaseInterface.self,
+      implement: {
+        SignUpUseCase(
+          repository: SignUpRepository(),
+          locationService: LocationService(),
+          kakaoAPIService: KakaoAPIService(),
+          contactService: ContactService()
+        )
+      }
+    )
+
+    container.register(
+      interface: UserInfoUseCaseInterface.self,
+      implement: {
+        UserInfoUseCase(repository: UserDefaultUserInfoRepository())
+      })
+  }
+}
+
diff --git a/Projects/Features/SignUp/Demo/Src/AppDelegate.swift b/Projects/Features/SignUp/Demo/Src/AppDelegate.swift
index 7af57c99..ed5f183e 100644
--- a/Projects/Features/SignUp/Demo/Src/AppDelegate.swift
+++ b/Projects/Features/SignUp/Demo/Src/AppDelegate.swift
@@ -12,7 +12,9 @@ import UIKit
 class AppDelegate: UIResponder, UIApplicationDelegate {
 
   func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
-
+    
+    registerDependencies()
+    
     return true
   }
 
diff --git a/Projects/Features/SignUp/Demo/Src/Coordinator/AppCoordinator.swift b/Projects/Features/SignUp/Demo/Src/Coordinator/AppCoordinator.swift
index b8bb8836..96b0e56b 100644
--- a/Projects/Features/SignUp/Demo/Src/Coordinator/AppCoordinator.swift
+++ b/Projects/Features/SignUp/Demo/Src/Coordinator/AppCoordinator.swift
@@ -8,6 +8,7 @@
 import UIKit
 import SignUpInterface
 import Core
+import DSKit
 
 protocol AppCoordinating {
   func signUpFlow()
@@ -38,11 +39,21 @@ final class AppCoordinator: LaunchCoordinator, AppCoordinating {
 
     signUpCoordinator.start()
   }
+
+  class MainViewController: TFBaseViewController {
+    override func viewDidLoad() {
+      super.viewDidLoad()
+
+      let label = UILabel()
+      label.center = self.view.center
+      label.text = "Main"
+      self.view.addSubview(label)
+    }
+  }
 }
 
 extension AppCoordinator: SignUpCoordinatorDelegate {
   func detachSignUp(_ coordinator: Core.Coordinator) {
-    self.viewControllable.setViewControllers([])
     detachChild(coordinator)
   }
 }
diff --git a/Projects/Features/SignUp/Interface/Src/Coordinator/BottomSheet/BottomSheet.swift b/Projects/Features/SignUp/Interface/Src/Coordinator/BottomSheet/BottomSheet.swift
new file mode 100644
index 00000000..d8ffdaba
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/Coordinator/BottomSheet/BottomSheet.swift
@@ -0,0 +1,33 @@
+//
+//  File.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/18.
+//
+
+import Foundation
+
+public enum BottomSheetValueType {
+  case date(date: Date)
+  case text(text: String)
+}
+
+public enum BottomSheetViewAction {
+  case onDismiss
+}
+
+public protocol BottomSheetActionDelegate: AnyObject {
+  func sheetInvoke(_ action: BottomSheetViewAction)
+}
+
+public protocol BottomSheetListener: AnyObject {
+  func sendData(item: BottomSheetValueType)
+}
+
+public protocol BottomSheetCoordinator {
+
+}
+
+public protocol PickerBottomSheetCoordinator: BottomSheetCoordinator {
+  func pickerBottomSheetFlow(_ item: BottomSheetValueType, listener: BottomSheetListener)
+}
diff --git a/Projects/Features/SignUp/Interface/Src/Coordinator/SignUpCoordinating.swift b/Projects/Features/SignUp/Interface/Src/Coordinator/SignUpCoordinating.swift
index a60a1283..53788b44 100644
--- a/Projects/Features/SignUp/Interface/Src/Coordinator/SignUpCoordinating.swift
+++ b/Projects/Features/SignUp/Interface/Src/Coordinator/SignUpCoordinating.swift
@@ -8,6 +8,7 @@
 import Foundation
 
 import Core
+import AuthInterface
 
 public protocol SignUpCoordinatorDelegate: AnyObject {
   func detachSignUp(_ coordinator: Coordinator)
@@ -16,10 +17,11 @@ public protocol SignUpCoordinatorDelegate: AnyObject {
 public protocol SignUpCoordinating: Coordinator {
   var delegate: SignUpCoordinatorDelegate? { get set }
 
-  func rootFlow()
   func nicknameFlow()
   func emailFlow()
   func finishFlow()
-  func phoneNumberFlow()
   func policyFlow()
+  func genderPickerFlow()
+  func preferGenderPickerFlow()
+  func photoFlow()
 }
diff --git a/Projects/Features/SignUp/Interface/Src/DTO/Request/LocationReq.swift b/Projects/Features/SignUp/Interface/Src/DTO/Request/LocationReq.swift
new file mode 100644
index 00000000..de50f1d3
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/DTO/Request/LocationReq.swift
@@ -0,0 +1,26 @@
+//
+//  LocaleReq.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import Foundation
+
+// MARK: - LocationRequest
+public struct LocationReq: Codable {
+  public let address: String
+  public let regionCode: Int
+  public let lat, lon: Double
+
+  public init(
+    address: String,
+    regionCode: Int,
+    lat: Double, lon: Double
+  ) {
+    self.address = address
+    self.regionCode = regionCode
+    self.lat = lat
+    self.lon = lon
+  }
+}
diff --git a/Projects/Features/SignUp/Interface/Src/DTO/Request/SignUpReq.swift b/Projects/Features/SignUp/Interface/Src/DTO/Request/SignUpReq.swift
new file mode 100644
index 00000000..b6d0ca03
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/DTO/Request/SignUpReq.swift
@@ -0,0 +1,99 @@
+//
+//  SignUpReq.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import Foundation
+import AuthInterface
+
+public struct SignUpReq: Encodable {
+  public let phoneNumber, username, email, birthDay: String
+  public let gender, preferGender: String
+  public let introduction, deviceKey: String
+  public let agreement: [String: Bool]
+  public let locationRequest: LocationReq
+  public var photoList: [String]
+  public let interestList, idealTypeList: [Int]
+  public let snsType: String
+  public let snsUniqueID: String
+  public let tall: Int
+  public let smoking: String
+  public let drinking: String
+  public let religion: String
+  public let contacts: [ContactType]
+
+  enum CodingKeys: String, CodingKey {
+    case phoneNumber, username, email, birthDay, gender, preferGender, introduction, deviceKey, agreement, locationRequest, photoList, interestList, idealTypeList, snsType
+    case snsUniqueID = "snsUniqueId"
+    case tall, smoking, drinking, religion
+    case contacts
+  }
+
+  public init(phoneNumber: String, username: String, email: String, birthDay: String, gender: String, preferGender: String, introduction: String, deviceKey: String, agreement: [String: Bool], locationRequest: LocationReq, photoList: [String], interestList: [Int], idealTypeList: [Int], snsType: String, snsUniqueID: String, tall: Int, smoking: String, drinking: String, religion: String, contacts: [ContactType]) {
+    self.phoneNumber = phoneNumber
+    self.username = username
+    self.email = email
+    self.birthDay = birthDay
+    self.gender = gender
+    self.preferGender = preferGender
+    self.introduction = introduction
+    self.deviceKey = deviceKey
+    self.agreement = agreement
+    self.locationRequest = locationRequest
+    self.photoList = photoList
+    self.interestList = interestList
+    self.idealTypeList = idealTypeList
+    self.snsType = snsType
+    self.snsUniqueID = snsUniqueID
+    self.tall = tall
+    self.smoking = smoking
+    self.drinking = drinking
+    self.religion = religion
+    self.contacts = contacts
+  }
+}
+
+extension UserInfo {
+  public func toRequest(contacts: [ContactType]) -> SignUpReq? {
+    guard 
+      let username = self.name,
+      let email = self.email,
+      let gender = self.gender?.rawValue,
+      let preferGender = self.preferGender?.rawValue,
+      let birthday = self.birthday,
+      let introduction = self.introduction,
+      let agreement = self.userAgreements,
+      let location = self.address,
+      let tall = self.tall,
+      let drinking = self.drinking?.rawValue,
+      let smoking = self.smoking?.rawValue,
+      let religion = self.religion?.rawValue
+
+    else { return nil }
+
+    return SignUpReq(
+      phoneNumber: self.phoneNumber,
+      username: username,
+      email: email,
+      birthDay: birthday,
+      gender: gender,
+      preferGender: preferGender,
+      introduction: introduction,
+      deviceKey: "device-key",
+      agreement: agreement,
+      locationRequest: location,
+      photoList: photos,
+      interestList: interestsList,
+      idealTypeList: idealTypeList,
+      snsType: SNSType.normal.rawValue,
+      snsUniqueID: "",
+      tall: tall,
+      smoking: smoking,
+      drinking: drinking,
+      religion: religion,
+      contacts: contacts
+    )
+  }
+}
diff --git a/Projects/Features/SignUp/Interface/Src/DTO/Request/UserFriendContactReq.swift b/Projects/Features/SignUp/Interface/Src/DTO/Request/UserFriendContactReq.swift
new file mode 100644
index 00000000..fe130081
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/DTO/Request/UserFriendContactReq.swift
@@ -0,0 +1,16 @@
+//
+//  BlockRes.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/2/24.
+//
+
+import Foundation
+
+public struct UserFriendContactReq: Codable {
+  public let contacts: [ContactType]
+
+  public init(contacts: [ContactType]) {
+    self.contacts = contacts
+  }
+}
diff --git a/Projects/Features/SignUp/Interface/Src/DTO/Response/AddressRes.swift b/Projects/Features/SignUp/Interface/Src/DTO/Response/AddressRes.swift
new file mode 100644
index 00000000..70119e1f
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/DTO/Response/AddressRes.swift
@@ -0,0 +1,42 @@
+//
+//  DocumentFromAddressRes.swift
+//  SignUp
+//
+//  Created by Kanghos on 5/5/24.
+//
+
+import Foundation
+
+// MARK: - AddressResponse
+struct AddressRes: Codable {
+  let documents: [Document]
+
+  // MARK: - Document
+  struct Document: Codable {
+    let address: Address
+    let addressName: String
+    let roadAddress: Address
+    let lon: String
+    let lat: String
+
+    enum CodingKeys: String, CodingKey {
+      case address
+      case addressName = "address_name"
+      case roadAddress = "road_address"
+      case lon = "x"
+      case lat = "y"
+    }
+  }
+
+  // MARK: - Address
+  struct Address: Codable {
+    let addressName, region1DepthName, region2DepthName, region3DepthName: String
+
+    enum CodingKeys: String, CodingKey {
+      case addressName = "address_name"
+      case region1DepthName = "region_1depth_name"
+      case region2DepthName = "region_2depth_name"
+      case region3DepthName = "region_3depth_name"
+    }
+  }
+}
diff --git a/Projects/Features/SignUp/Interface/Src/DTO/Response/EmojiResponse.swift b/Projects/Features/SignUp/Interface/Src/DTO/Response/EmojiResponse.swift
new file mode 100644
index 00000000..ab34dc6c
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/DTO/Response/EmojiResponse.swift
@@ -0,0 +1,8 @@
+//
+//  EmojiResponse.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/1/24.
+//
+
+import Foundation
diff --git a/Projects/Features/SignUp/Interface/Src/Model/PhoneValidationResponse.swift b/Projects/Features/SignUp/Interface/Src/DTO/Response/PhoneValidationRes.swift
similarity index 86%
rename from Projects/Features/SignUp/Interface/Src/Model/PhoneValidationResponse.swift
rename to Projects/Features/SignUp/Interface/Src/DTO/Response/PhoneValidationRes.swift
index 90f26201..10a78ab5 100644
--- a/Projects/Features/SignUp/Interface/Src/Model/PhoneValidationResponse.swift
+++ b/Projects/Features/SignUp/Interface/Src/DTO/Response/PhoneValidationRes.swift
@@ -7,7 +7,7 @@
 
 import Foundation
 
-public struct PhoneValidationResponse {
+public struct PhoneValidationResponse: Decodable {
   public let phoneNumber: String
   public let authNumber: Int
 
diff --git a/Projects/Features/SignUp/Interface/Src/DTO/Response/UserFriendContactRes.swift b/Projects/Features/SignUp/Interface/Src/DTO/Response/UserFriendContactRes.swift
new file mode 100644
index 00000000..6d04228d
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/DTO/Response/UserFriendContactRes.swift
@@ -0,0 +1,12 @@
+//
+//  UserFriendContactResponse.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/2/24.
+//
+
+import Foundation
+
+public struct UserFriendContactRes: Codable {
+  public let count: Int
+}
diff --git a/Projects/Features/SignUp/Interface/Src/DTO/Response/UserNicknameValidRes.swift b/Projects/Features/SignUp/Interface/Src/DTO/Response/UserNicknameValidRes.swift
new file mode 100644
index 00000000..bebcbcad
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/DTO/Response/UserNicknameValidRes.swift
@@ -0,0 +1,16 @@
+//
+//  UserNicknameValidResponse.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/1/24.
+//
+
+import Foundation
+
+public struct UserNicknameValidRes: Codable {
+  public let isDuplicate: Bool
+  
+  public init(isDuplicate: Bool) {
+    self.isDuplicate = isDuplicate
+  }
+}
diff --git a/Projects/Features/SignUp/Interface/Src/Model/Agrement.swift b/Projects/Features/SignUp/Interface/Src/Model/Agrement.swift
new file mode 100644
index 00000000..8d8cf225
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/Model/Agrement.swift
@@ -0,0 +1,18 @@
+//
+//  Agrement.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import Foundation
+
+// MARK: - AgreementElement
+public struct AgreementElement: Codable {
+    public let name, subject: String
+    public let isRequired: Bool
+    public let description: String?
+    public let detailLink: String?
+}
+
+public typealias Agreement = [AgreementElement]
diff --git a/Projects/Features/SignUp/Interface/Src/Model/ContactType.swift b/Projects/Features/SignUp/Interface/Src/Model/ContactType.swift
new file mode 100644
index 00000000..3cb3ee3c
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/Model/ContactType.swift
@@ -0,0 +1,18 @@
+//
+//  ContactType.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 5/12/24.
+//
+
+import Foundation
+
+public struct ContactType: Codable {
+  public let name: String
+  public let phoneNumber: String
+
+  public init(name: String, phoneNumber: String) {
+    self.name = name
+    self.phoneNumber = phoneNumber
+  }
+}
diff --git a/Projects/Features/SignUp/Interface/Src/Model/EmojiType.swift b/Projects/Features/SignUp/Interface/Src/Model/EmojiType.swift
new file mode 100644
index 00000000..46067bfa
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/Model/EmojiType.swift
@@ -0,0 +1,25 @@
+//
+//  EmojiType.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/1/24.
+//
+
+import Foundation
+
+public struct EmojiType: Codable {
+  public let index: Int
+  public let name: String
+  public let emojiCode: String
+  
+  enum CodingKeys: String, CodingKey {
+    case index = "idx"
+    case name, emojiCode
+  }
+  
+  public init(index: Int, name: String, emojiCode: String) {
+    self.index = index
+    self.name = name
+    self.emojiCode = emojiCode
+  }
+}
diff --git a/Projects/Features/SignUp/Interface/Src/Model/Frequency.swift b/Projects/Features/SignUp/Interface/Src/Model/Frequency.swift
new file mode 100644
index 00000000..e7e01bbb
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/Model/Frequency.swift
@@ -0,0 +1,14 @@
+//
+//  Frequency.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import Foundation
+
+public enum Frequency: String, Codable {
+  case sometimes = "SOMETIMES"
+  case frequently = "FREQUENTLY"
+  case none = "NONE"
+}
diff --git a/Projects/Features/SignUp/Interface/Src/Model/Gender.swift b/Projects/Features/SignUp/Interface/Src/Model/Gender.swift
new file mode 100644
index 00000000..2bac960f
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/Model/Gender.swift
@@ -0,0 +1,27 @@
+//
+//  Gender.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import Foundation
+
+public enum Gender: String, Codable {
+  case male = "MALE"
+  case female = "FEMALE"
+  case both = "BOTH"
+
+  public init?(number: Int) {
+    switch number {
+    case 1:
+      self = .male
+    case 0:
+      self = .female
+    case 2:
+      self = .both
+    default:
+      return nil
+    }
+  }
+}
diff --git a/Projects/Features/SignUp/Interface/Src/Model/Religion.swift b/Projects/Features/SignUp/Interface/Src/Model/Religion.swift
new file mode 100644
index 00000000..8f21002b
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/Model/Religion.swift
@@ -0,0 +1,17 @@
+//
+//  Religion.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import Foundation
+
+public enum Religion: String, Codable {
+  case christian = "CHRISTIAN"
+  case catholic = "CATHOLICISM"
+  case buddhism = "BUDDHISM"
+  case wonBuddhism = "WON_BUDDHISM"
+  case none = "NONE"
+  case other = "OTHER"
+}
diff --git a/Projects/Features/SignUp/Interface/Src/Model/UserInfo.swift b/Projects/Features/SignUp/Interface/Src/Model/UserInfo.swift
new file mode 100644
index 00000000..0abd9092
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/Model/UserInfo.swift
@@ -0,0 +1,49 @@
+//
+//  UserInfo.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import Foundation
+import Domain
+
+public struct UserInfo: Codable {
+  public var phoneNumber: String
+  public var name: String?
+  public var userUUID: String?
+  public var birthday: String?
+  public var introduction: String?
+  public var address: LocationReq?
+  public var email: String?
+  public var gender: Gender?
+  public var preferGender: Gender?
+  public var tall: Int?
+  public var smoking: Frequency?
+  public var drinking: Frequency?
+  public var religion: Religion?
+  public var idealTypeList: [Int]
+  public var interestsList: [Int]
+  public var photos: [String]
+  public var userAgreements: [String: Bool]?
+
+  public init(phoneNumber: String, name: String? = nil, userUUID: String? = nil, birthday: String? = nil, introduction: String? = nil, address: LocationReq? = nil, email: String? = nil, gender: Gender? = nil, preferGender: Gender? = nil, tall: Int? = nil, smoking: Frequency? = nil, drinking: Frequency? = nil, religion: Religion? = nil, idealTypeList: [Int] = [], interestsList: [Int] = [], photos: [String] = [], userAgreements: [String: Bool]? = nil) {
+    self.phoneNumber = phoneNumber
+    self.name = name
+    self.userUUID = userUUID
+    self.birthday = birthday
+    self.introduction = introduction
+    self.address = address
+    self.email = email
+    self.gender = gender
+    self.preferGender = preferGender
+    self.tall = tall
+    self.smoking = smoking
+    self.drinking = drinking
+    self.religion = religion
+    self.idealTypeList = idealTypeList
+    self.interestsList = interestsList
+    self.photos = photos
+    self.userAgreements = userAgreements
+  }
+}
diff --git a/Projects/Features/SignUp/Interface/Src/RepositoryInterface/SignUpRepositoryInterface.swift b/Projects/Features/SignUp/Interface/Src/RepositoryInterface/SignUpRepositoryInterface.swift
new file mode 100644
index 00000000..e3635cb8
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/RepositoryInterface/SignUpRepositoryInterface.swift
@@ -0,0 +1,20 @@
+//
+//  SignUpRepositoryInterface.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/1/24.
+//
+
+import Foundation
+
+import RxSwift
+import AuthInterface
+
+public protocol SignUpRepositoryInterface {
+  func checkNickname(nickname: String) -> Single<Bool>
+  func idealTypes() -> Single<[EmojiType]>
+  func interests() -> Single<[EmojiType]>
+  func signUp(_ signUpRequest: SignUpReq) -> Single<Token>
+  func uploadImage(data: [Data]) -> Single<[String]>
+  func fetchAgreements() -> Single<Agreement>
+}
diff --git a/Projects/Features/SignUp/Interface/Src/RepositoryInterface/UserInfoRepositoryInterface.swift b/Projects/Features/SignUp/Interface/Src/RepositoryInterface/UserInfoRepositoryInterface.swift
new file mode 100644
index 00000000..fd5a44a1
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/RepositoryInterface/UserInfoRepositoryInterface.swift
@@ -0,0 +1,19 @@
+//
+//  UserInfoRepository.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 5/29/24.
+//
+
+import Foundation
+import RxSwift
+
+public protocol UserInfoRepositoryInterface {
+  func savePhoneNumber(_ phoneNumber: String)
+  func fetchPhoneNumber() -> Single<String>
+  func fetchUserInfo() -> Single<UserInfo>
+  func updateUserInfo(userInfo: UserInfo)
+  func deleteUserInfo()
+  func fetchUserPhotos(key: String, fileNames: [String]) -> Single<[Data]>
+  func saveUserPhotos(key: String, datas: [Data]) -> Single<[String]>
+}
diff --git a/Projects/Features/SignUp/Interface/Src/UseCase/ContactServiceType.swift b/Projects/Features/SignUp/Interface/Src/UseCase/ContactServiceType.swift
new file mode 100644
index 00000000..a00a6def
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/UseCase/ContactServiceType.swift
@@ -0,0 +1,14 @@
+//
+//  ContactServiceType.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 5/14/24.
+//
+
+import Foundation
+
+import RxSwift
+
+public protocol ContactServiceType {
+  func fetchContact() -> Single<[ContactType]>
+}
diff --git a/Projects/Features/SignUp/Interface/Src/UseCase/KakaoAPIServiceType.swift b/Projects/Features/SignUp/Interface/Src/UseCase/KakaoAPIServiceType.swift
new file mode 100644
index 00000000..480c9ab7
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/UseCase/KakaoAPIServiceType.swift
@@ -0,0 +1,15 @@
+//
+//  KakaoAPIServiceType.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 5/14/24.
+//
+
+import Foundation
+
+import RxSwift
+
+public protocol KakaoAPIServiceType {
+  func fetchLocationByCoordinate2d(longitude: Double, latitude: Double) -> Single<LocationReq?>
+  func fetchLocationByAddress(address: String) -> Single<LocationReq?>
+}
diff --git a/Projects/Features/SignUp/Interface/Src/UseCase/LocationServiceType.swift b/Projects/Features/SignUp/Interface/Src/UseCase/LocationServiceType.swift
new file mode 100644
index 00000000..1526a6d2
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/UseCase/LocationServiceType.swift
@@ -0,0 +1,21 @@
+//
+//  LocationService.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 4/29/24.
+//
+
+import Foundation
+import RxSwift
+
+public protocol LocationServiceType {
+  var publisher: PublishSubject<LocationReq> { get }
+  func handleAuthorization(granted: @escaping (Bool) -> Void)
+  func requestLocation()
+  func requestAuthorization()
+}
+
+public enum LocationError: Error {
+  case denied
+  case invalidLocation
+}
diff --git a/Projects/Features/SignUp/Interface/Src/UseCase/SignUpError.swift b/Projects/Features/SignUp/Interface/Src/UseCase/SignUpError.swift
new file mode 100644
index 00000000..435a2025
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/UseCase/SignUpError.swift
@@ -0,0 +1,14 @@
+//
+//  SignUpError.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/1/24.
+//
+
+import Foundation
+
+public enum SignUpError: Error {
+  case alreadySignUp
+  case duplicateNickname
+  case invalidRequest
+}
diff --git a/Projects/Features/SignUp/Interface/Src/UseCase/SignUpUseCaseInterface.swift b/Projects/Features/SignUp/Interface/Src/UseCase/SignUpUseCaseInterface.swift
new file mode 100644
index 00000000..19980144
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/UseCase/SignUpUseCaseInterface.swift
@@ -0,0 +1,23 @@
+//
+//  SignUpUseCase.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/1/24.
+//
+
+import Foundation
+
+import RxSwift
+import Domain
+
+public protocol SignUpUseCaseInterface {
+  func checkNickname(nickname: String) -> Single<Bool>
+  func idealTypes() -> Single<[Domain.EmojiType]>
+  func interests() -> Single<[Domain.EmojiType]>
+  func block() -> Single<[ContactType]>
+  func fetchLocation() -> Single<LocationReq>
+  func fetchLocation(_ address: String) -> Single<LocationReq>
+  func signUp(request: SignUpReq) -> Single<Void>
+  func uploadImage(data: [Data]) -> Single<[String]>
+  func fetchAgreements() -> Single<Agreement>
+}
diff --git a/Projects/Features/SignUp/Interface/Src/UseCase/UserInfoUseCaseInterface.swift b/Projects/Features/SignUp/Interface/Src/UseCase/UserInfoUseCaseInterface.swift
new file mode 100644
index 00000000..41323721
--- /dev/null
+++ b/Projects/Features/SignUp/Interface/Src/UseCase/UserInfoUseCaseInterface.swift
@@ -0,0 +1,19 @@
+//
+//  UserInfoUseCaseInterface.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 5/29/24.
+//
+
+import Foundation
+import RxSwift
+
+public protocol UserInfoUseCaseInterface {
+  func savePhoneNumber(_ phoneNumber: String)
+  func fetchPhoneNumber() -> Single<String>
+  func fetchUserInfo() -> Single<UserInfo>
+  func updateUserInfo(userInfo: UserInfo)
+  func deleteUserInfo()
+  func fetchUserPhotos(key: String, fileNames: [String]) -> Single<[Data]>
+  func saveUserPhotos(key: String, datas: [Data]) -> Single<[String]>
+}
diff --git a/Projects/Features/SignUp/Project.swift b/Projects/Features/SignUp/Project.swift
index 22ee432c..5de1545d 100644
--- a/Projects/Features/SignUp/Project.swift
+++ b/Projects/Features/SignUp/Project.swift
@@ -16,6 +16,7 @@ let project = Project(
 					interface: .SignUp,
 						dependencies: [
 							.core,
+              .feature(interface: .Auth)
 						]
 				),
 				.feature(
@@ -29,7 +30,8 @@ let project = Project(
         .feature(
           demo: .SignUp,
           dependencies: [
-            .feature(implementation: .SignUp)
+            .feature(implementation: .SignUp),
+            .data
           ]
         )
 		]
diff --git a/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+BottomSheet.swift b/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+BottomSheet.swift
new file mode 100644
index 00000000..27e14678
--- /dev/null
+++ b/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+BottomSheet.swift
@@ -0,0 +1,30 @@
+//
+//  SignUpCoordinator+BottomSheet.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import Foundation
+
+import Core
+import SignUpInterface
+
+extension SignUpCoordinator: PickerBottomSheetCoordinator {
+  public func pickerBottomSheetFlow(_ item: BottomSheetValueType, listener: BottomSheetListener) {
+    let vm = PickerBottomSheetViewModel(initialValue: item)
+    vm.listener = listener
+    vm.delegate = self
+    let vc = PickerBottomSheet(viewModel: vm)
+
+    self.viewControllable.presentBottomSheet(vc, animated: true)
+  }
+
+  public func singlePickerBottomSheetFlow(_ item: BottomSheetValueType, listener: BottomSheetListener) {
+    let vm = SinglePickerBottomSheetViewModel(initialValue: item)
+    vm.listener = listener
+    vm.delegate = self
+    let vc = SinglePickerBottomSheet(viewModel: vm)
+    self.viewControllable.presentBottomSheet(vc, animated: true)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+Contacts.swift b/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+Contacts.swift
new file mode 100644
index 00000000..c2639d02
--- /dev/null
+++ b/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+Contacts.swift
@@ -0,0 +1,84 @@
+////
+////  SignUpCoordinator+Contacts.swift
+////  SignUp
+////
+////  Created by kangho lee on 5/6/24.
+////
+//
+//import Foundation
+//import ContactsUI
+//import SignUpInterface
+//
+//import Core
+//
+//public protocol UserContactPickerDelegate: CNContactPickerDelegate {
+//  var listener: UserContactListener? { get }
+//}
+//
+//public protocol UserContactListener: AnyObject {
+//  func picker(didFinishPicking contacts: [UserFriendContactReq.Contact])
+//}
+//
+//extension SignUpCoordinator {
+//  public func presentContactsUI(delegate: UserContactPickerDelegate) {
+//    let picker = CNContactPickerViewController()
+//    picker.delegate = delegate
+//    
+//    self.viewControllable.present(picker, animated: true)
+//  }
+//}
+//
+//extension CNContactPickerViewController: ViewControllable {
+//  public var uiController: UIViewController { return self }
+//}
+//
+//public class UserContactPickerDelegator: NSObject, UserContactPickerDelegate {
+//  public weak var listener: UserContactListener?
+//  
+//  init(listener: UserContactListener) {
+//    self.listener = listener
+//  }
+//  
+//  deinit {
+//    print("deinit: ContactsPickerDelegate!")
+//  }
+//  
+//  public func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
+//    var mutableContacts: [UserFriendContactReq.Contact] = []
+//    
+//    let store = CNContactStore()
+//    let keysToFetch = [
+//      CNContactGivenNameKey,
+//      CNContactFamilyNameKey,
+//      CNContactPhoneNumbersKey,
+//      CNContactOrganizationNameKey,
+//    ] as [CNKeyDescriptor]
+//    
+//    contacts.forEach { contact in
+//      do {
+//        let predicate = CNContact.predicateForContacts(withIdentifiers: [contact.identifier])
+//        guard let contact = try store.unifiedContacts(matching: predicate, keysToFetch: keysToFetch).first
+//        else { return }
+//        let name: String
+//        
+//        if contact.familyName.count == 0 && contact.givenName.count == 0 {
+//          name = "\(contact.organizationName)"
+//        } else {
+//          name = "\(contact.familyName)\(contact.givenName)"
+//        }
+//        
+//        if let phone = contact.phoneNumbers.first {
+//          let phoneNumber = "\(phone.value.stringValue)"
+//          mutableContacts.append(.init(name: name, phoneNumber: phoneNumber))
+//        }
+//        
+//      } catch {
+//        print("Failed to fetch, error: \(error)")
+//      }
+//    }
+//    
+//    listener?.picker(didFinishPicking: mutableContacts)
+//    picker.dismiss()
+//  }
+//
+//}
diff --git a/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+PHPicker.swift b/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+PHPicker.swift
new file mode 100644
index 00000000..b1e481cb
--- /dev/null
+++ b/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator+PHPicker.swift
@@ -0,0 +1,51 @@
+//
+//  SignUpCoordinator+PHPicker.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import Foundation
+import PhotosUI
+
+import Core
+
+extension SignUpCoordinator {
+  public func photoPickerFlow(delegate: PhotoPickerDelegate) {
+
+    // coordinator로 빼기
+    var configuration = PHPickerConfiguration(photoLibrary: .shared())
+
+    configuration.filter = PHPickerFilter.images
+
+    configuration.preferredAssetRepresentationMode = .current
+    configuration.selection = .ordered
+
+    configuration.selectionLimit = 1
+
+    let picker = PHPickerViewController(configuration: configuration)
+    picker.delegate = delegate
+    self.viewControllable.present(picker, animated: true)
+  }
+}
+
+extension PHPickerViewController: ViewControllable {
+  public var uiController: UIViewController { return self }
+}
+
+public protocol PhotoPickerDelegate: PHPickerViewControllerDelegate {
+  var listener: PhotoPickerListener? { get }
+}
+
+public class PhotoPickerDelegator: PhotoPickerDelegate {
+  weak public var listener: PhotoPickerListener?
+
+  public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
+    picker.dismiss()
+    listener?.picker(didFinishPicking: results)
+  }
+
+  deinit {
+    print("deinit: PhotoPickerDelegate!")
+  }
+}
diff --git a/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator.swift b/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator.swift
index 8d348383..da5218c9 100644
--- a/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator.swift
+++ b/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinator.swift
@@ -9,42 +9,48 @@ import Foundation
 
 import Core
 import SignUpInterface
+import AuthInterface
+import DSKit
+
+protocol SignUpCoordinatingActionDelegate: AnyObject {
+  func invoke(_ action: SignUpCoordinatingAction)
+}
 
 public final class SignUpCoordinator: BaseCoordinator, SignUpCoordinating {
-  public weak var delegate: SignUpCoordinatorDelegate?
   
+  @Injected private var useCase: SignUpUseCaseInterface
+  @Injected private var userInfoUseCase: UserInfoUseCaseInterface
+
+  public weak var delegate: SignUpCoordinatorDelegate?
+
   // TODO: UserDefaultStorage이용해서 어느 화면 띄워줄건지 결정
   public override func start() {
-    replaceWindowRootViewController(rootViewController: self.viewControllable)
-
-    rootFlow()
+    replaceWindowRootViewController(rootViewController: viewControllable)
+    emailFlow()
   }
-  
-  public func rootFlow() {
-    let viewModel = SignUpRootViewModel()
-    viewModel.delegate = self
-
-    let viewController = SignUpRootViewController()
-    viewController.viewModel = viewModel
 
-    self.viewControllable.setViewControllers([viewController])
+  public func locationFlow() {
+    let viewModel = LocationInputViewModel(useCase: useCase, userInfoUseCase: self.userInfoUseCase)
+    viewModel.delegate = self
+    let viewController = LocationInputViewController(viewModel: viewModel)
+    self.viewControllable.pushViewController(viewController, animated: true)
   }
-  
+
   public func finishFlow() {
     self.delegate?.detachSignUp(self)
   }
-  
+
   public func emailFlow() {
-    let viewModel = EmailInputViewModel()
+    let viewModel = EmailInputViewModel(userInfoUseCase: self.userInfoUseCase)
     viewModel.delegate = self
 
     let viewController = EmailInputViewController(viewModel: viewModel)
 
     self.viewControllable.pushViewController(viewController, animated: true)
   }
-  
+
   public func nicknameFlow() {
-    let viewModel = NicknameInputViewModel()
+    let viewModel = NicknameInputViewModel(useCase: useCase, userInfoUseCase: self.userInfoUseCase)
     viewModel.delegate = self
 
     let viewController = NicknameInputViewController(viewModel: viewModel)
@@ -52,7 +58,7 @@ public final class SignUpCoordinator: BaseCoordinator, SignUpCoordinating {
   }
 
   public func policyFlow() {
-    let viewModel = PolicyAgreementViewModel()
+    let viewModel = PolicyAgreementViewModel(useCase: self.useCase, userInfoUseCase: self.userInfoUseCase)
     viewModel.delegate = self
 
     let viewController = PolicyAgreementViewController(viewModel: viewModel)
@@ -60,42 +66,165 @@ public final class SignUpCoordinator: BaseCoordinator, SignUpCoordinating {
     self.viewControllable.pushViewController(viewController, animated: true)
   }
 
-  public func phoneNumberFlow() {
-    let viewModel = PhoneCertificationViewModel()
-    viewModel.delegate = self
+  public func genderPickerFlow() {
+    let vm = GenderPickerViewModel(userInfoUseCase: self.userInfoUseCase)
+    vm.delegate = self
+    let vc = GenderPickerViewController(viewModel: vm)
+
+    self.viewControllable.pushViewController(vc, animated: true)
+  }
 
-    let viewController = PhoneCertificationViewController(viewModel: viewModel)
+  public func preferGenderPickerFlow() {
+    let vm = PreferGenderPickerViewModel(userInfoUseCase: self.userInfoUseCase)
+    vm.delegate = self
+    let vc = PreferGenderPickerViewController(viewModel: vm)
 
-    self.viewControllable.pushViewController(viewController, animated: true)
+    self.viewControllable.pushViewController(vc, animated: true)
   }
-}
 
-extension SignUpCoordinator: SignUpRootDelegate {
-  func toPhoneButtonTap() {
-    phoneNumberFlow()
+  public func photoFlow() {
+    let vm = PhotoInputViewModel(userInfoUseCase: self.userInfoUseCase)
+    vm.delegate = self
+    let vc = PhotoInputViewController(viewModel: vm)
+    self.viewControllable.pushViewController(vc, animated: true)
   }
-}
 
-extension SignUpCoordinator: PhoneCertificationDelegate {
-  func finishAuth() {
-    emailFlow()
+  public func heightPickerFlow() {
+    let vm = HeightPickerViewModel(userInfoUseCase: self.userInfoUseCase)
+    vm.delegate = self
+    let vc = HeightPickerViewController(viewModel: vm)
+    self.viewControllable.pushViewController(vc, animated: true)
   }
-}
 
-extension SignUpCoordinator: EmailInputDelegate {
-  func emailNextButtonTap() {
-    policyFlow()
+  public func InterestTagPickerFlow() {
+    let vm = TagPickerViewModel(useCase: useCase, userInfoUseCase: self.userInfoUseCase)
+    vm.delegate = self
+    let vc = InterestPickerViewController(viewModel: vm)
+    self.viewControllable.pushViewController(vc, animated: true)
+  }
+
+  public func IdealTypeTagPickerFlow() {
+    let vm = IdealTypeTagPickerViewModel(useCase: useCase, userInfoUseCase: self.userInfoUseCase)
+    vm.delegate = self
+
+    let vc = IdealTypePickerViewController(viewModel: vm)
+    self.viewControllable.pushViewController(vc, animated: true)
+  }
+
+  public func IntroductFlow() {
+    let vm = IntroduceInputViewModel(userInfoUseCase: self.userInfoUseCase)
+    vm.delegate = self
+
+    let vc = IntroduceInputViewController(viewModel: vm)
+    self.viewControllable.pushViewController(vc, animated: true)
+  }
+
+  public func alcoholTobaccoFlow() {
+    let vm = AlcoholTobaccoPickerViewModel(userInfoUseCase: self.userInfoUseCase)
+    vm.delegate = self
+
+    let vc = AlcoholTobaccoPickerViewController(viewModel: vm)
+    self.viewControllable.pushViewController(vc, animated: true)
+  }
+
+  public func religionFlow() {
+    let vm = ReligionPickerViewModel(userInfoUseCase: self.userInfoUseCase)
+    vm.delegate = self
+    let vc = ReligionPickerViewController(viewModel: vm)
+    self.viewControllable.pushViewController(vc, animated: true)
+  }
+  
+  func webViewFlow(listener: WebViewDelegate) {
+    let vc = PostCodeWebViewController()
+    vc.delegate = listener
+    vc.modalPresentationStyle = .overFullScreen
+    self.viewControllable.present(vc, animated: true)
+  }
+  
+  func blockUserFriendContactFlow() {
+    let vm = UserContactViewModel(useCase: self.useCase)
+    vm.delegate = self
+    let vc = UserContactViewController(viewModel: vm)
+    self.viewControllable.pushViewController(vc, animated: true)
+  }
+
+  func signUpCompleteFlow(_ contacts: [ContactType]) {
+    let vm = SignUpCompleteViewModel(useCase: self.useCase, userInfoUseCase: userInfoUseCase, contacts: contacts)
+    vm.delegate = self
+    let vc = SignUpCompleteViewController(viewModel: vm)
+    self.viewControllable.pushViewController(vc, animated: true)
+  }
+
+  private func agreementWebViewFlow(url: URL) {
+    let vc = TFWebViewController(url: url)
+    let nav = NavigationViewControllable(rootViewControllable: vc)
+    self.viewControllable.present(nav, animated: true)
   }
 }
 
-extension SignUpCoordinator: PolicyAgreementDelegate {
-  func policyNextButtonTap() {
-    nicknameFlow()
+
+extension SignUpCoordinator: SignUpCoordinatingActionDelegate {
+  func invoke(_ action: SignUpCoordinatingAction) {
+    switch action {
+    case let .loginType(snsType):
+      print(snsType)
+    case .nextAtPhoneNumber:
+      emailFlow()
+    case .nextAtEmail:
+      policyFlow()
+    case .nextAtPolicy:
+      nicknameFlow()
+    case .nextAtNickname:
+      genderPickerFlow()
+    case let .birthdayTap(birthDay, listener):
+      pickerBottomSheetFlow(.date(date: birthDay), listener: listener)
+    case .nextAtGender:
+      preferGenderPickerFlow()
+    case .nextAtPreferGender:
+      photoFlow()
+
+    case let .photoCellTap(_, listener):
+      photoPickerFlow(delegate: listener)
+    case .nextAtPhoto:
+      heightPickerFlow()
+
+    case let .heightLabelTap(height, listener):
+      singlePickerBottomSheetFlow(.text(text: String(height)), listener: listener)
+
+    case .nextAtHeight:
+      alcoholTobaccoFlow()
+
+    case .nextAtAlcoholTobacco:
+      religionFlow()
+
+    case .nextAtReligion:
+      InterestTagPickerFlow()
+    case .nextAtInterest:
+      IdealTypeTagPickerFlow()
+
+    case .nextAtIdealType:
+      IntroductFlow()
+    case .nextAtIntroduce:
+      locationFlow()
+    case let .webViewTap(listener):
+      webViewFlow(listener: listener)
+    case .nextAtLocation:
+      blockUserFriendContactFlow()
+    case let .nextAtHideFriends(contacts):
+      signUpCompleteFlow(contacts)
+    case .nextAtSignUpComplete:
+      finishFlow()
+    case let .agreementWebView(url):
+      agreementWebViewFlow(url: url)
+    default: break
+    }
   }
 }
 
-extension SignUpCoordinator: NicknameInputDelegate {
-  func nicknameNextButtonTap() {
-    
+extension SignUpCoordinator: BottomSheetActionDelegate {
+  public func sheetInvoke(_ action: BottomSheetViewAction) {
+    if case .onDismiss = action {
+      self.viewControllable.uiController.dismiss(animated: true)
+    }
   }
 }
diff --git a/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinatorAction.swift b/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinatorAction.swift
new file mode 100644
index 00000000..6df00871
--- /dev/null
+++ b/Projects/Features/SignUp/Src/Coordinator/SignUpCoordinatorAction.swift
@@ -0,0 +1,43 @@
+//
+//  SignUpCoordinatorAction.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import Foundation
+import SignUpInterface
+import AuthInterface
+
+public enum SignUpCoordinatingAction {
+
+  case loginType(SNSType)
+
+  case nextAtPhoneNumber(phoneNumber: String)
+  case nextAtAuthCode
+  case nextAtEmail
+
+  case nextAtPolicy
+  case nextAtNickname
+  case nextAtGender
+  case nextAtPreferGender
+  case nextAtPhoto
+  case nextAtHeight
+  case nextAtAlcoholTobacco
+  case nextAtReligion
+  case nextAtInterest
+  case nextAtIdealType
+  case nextAtIntroduce
+  case nextAtLocation
+  
+  case nextAtHideFriends([ContactType])
+  case nextAtSignUpComplete
+
+  
+  case webViewTap(listner: WebViewDelegate)
+  case birthdayTap(Date, listener: BottomSheetListener)
+  case heightLabelTap(Int, listener: BottomSheetListener)
+  case photoCellTap(index: Int, listener: PhotoPickerDelegate)
+
+  case agreementWebView(_ url: URL)
+}
diff --git a/Projects/Features/SignUp/Src/Coordinator/SignUpStore.swift b/Projects/Features/SignUp/Src/Coordinator/SignUpStore.swift
new file mode 100644
index 00000000..f15bede0
--- /dev/null
+++ b/Projects/Features/SignUp/Src/Coordinator/SignUpStore.swift
@@ -0,0 +1,106 @@
+//
+//  SignUpStore.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/18.
+//
+
+import Foundation
+import SignUpInterface
+import Core
+
+protocol Signing {
+  
+}
+
+public struct SignUpStore {
+
+  enum Key: String {
+    case snsType
+    case phoneNumber
+    case email
+    case policy
+    case nickname
+    case gender
+    case birthday
+    case preferGender
+    case photo
+
+    // ------- API 미구현 ---------
+    case height
+    case smoke
+    case drink
+    case religion
+    // ------- -------- ---------
+
+    case interest
+    case ideal
+    case introduce
+    case address
+    case avoidFriends
+  }
+  @Storage<String>(key: Key.snsType.rawValue, defaultValue: "")
+  public static var snstype
+
+  @Storage<String>(key: Key.phoneNumber.rawValue, defaultValue: "")
+  public static var phoneNumber
+
+
+  @Storage<String>(key: Key.email.rawValue, defaultValue: "")
+  public static var email
+
+  @Storage<Int>(key: Key.email.rawValue, defaultValue: -1)
+  public static var policy
+
+  @Storage<String>(key: Key.nickname.rawValue, defaultValue: "")
+  public static var nickname
+
+  @Storage<String>(key: Key.gender.rawValue, defaultValue: "")
+  public static var gender
+
+  @Storage<String>(key: Key.preferGender.rawValue, defaultValue: "")
+  public static var preferGender
+
+  @Storage<String>(key: Key.birthday.rawValue, defaultValue: "")
+  public static var birthday
+
+  @Storage<String>(key: Key.photo.rawValue, defaultValue: "")
+  public static var photo
+
+  @Storage<Int>(key: Key.height.rawValue, defaultValue: 145)
+  public static var height
+
+  @Storage<String>(key: Key.smoke.rawValue, defaultValue: "")
+  public static var smoke
+
+  @Storage<String>(key: Key.drink.rawValue, defaultValue: "")
+  public static var drink
+
+  @Storage<String>(key: Key.religion.rawValue, defaultValue: "")
+  public static var religion
+
+  @Storage<[Int]>(key: Key.interest.rawValue, defaultValue: [])
+  public static var interest
+
+  @Storage<[Int]>(key: Key.ideal.rawValue, defaultValue: [])
+  public static var ideal
+
+  @Storage<String>(key: Key.introduce.rawValue, defaultValue: "")
+  public static var introduce
+
+  @CodableStorage<[ContactType]>(key: Key.avoidFriends.rawValue, defaultValue: [])
+  public static var avoidFriends
+
+  @CodableStorage<LocationReq>(key: Key.address.rawValue, defaultValue: nil)
+  public static var location
+}
+
+extension SignUpStore {
+  static func savePhotoData(_ data: Data) {
+    let base64String = data.base64EncodedString()
+    SignUpStore.photo = base64String
+  }
+//  func checkStartFlow() -> SignUpCoordinatingAction {
+//    if phone
+//  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoInputViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoInputViewController.swift
new file mode 100644
index 00000000..32842504
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoInputViewController.swift
@@ -0,0 +1,65 @@
+//
+//  AlcoholTobaccoInputViewControlle.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+
+import DSKit
+
+import RxSwift
+import RxCocoa
+
+final class AlcoholTobaccoPickerViewController: TFBaseViewController {
+  private let mainView = AlcoholTobaccoPickerView()
+  private let viewModel: AlcoholTobaccoPickerViewModel
+
+  init(viewModel: AlcoholTobaccoPickerViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func bindViewModel() {
+    let tobaccoTap = self.mainView.tobaccoPickerView
+      .rx.selectedOption
+      .asDriver()
+      .compactMap { $0 }
+      .map { FrequencyMapper.toFrequency($0) }
+
+    let alcoholTap = self.mainView.alcoholPickerView
+      .rx.selectedOption
+      .asDriver()
+      .compactMap { $0 }
+      .map { FrequencyMapper.toFrequency($0) }
+
+    let input = AlcoholTobaccoPickerViewModel.Input(
+      tobaccoTap: tobaccoTap,
+      alcoholTap: alcoholTap,
+      nextBtnTap: self.mainView.nextBtn.rx.tap.asDriver()
+    )
+
+    let output = viewModel.transform(input: input)
+
+    output.isNextBtnEnabled
+      .drive(with: self) { owner, status in
+        owner.mainView.nextBtn.updateColors(status: status)
+      }
+      .disposed(by: disposeBag)
+
+    output.initialFrequecy
+      .debug("initial")
+      .drive(mainView.rx.selectedFrequecy)
+      .disposed(by: disposeBag)
+  }
+}
+
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoInputViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoInputViewModel.swift
new file mode 100644
index 00000000..f4c3161d
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoInputViewModel.swift
@@ -0,0 +1,89 @@
+//
+//  AlcoholTobaccoInputViewModel.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import Foundation
+
+import Core
+import SignUpInterface
+
+import RxSwift
+import RxCocoa
+
+final class AlcoholTobaccoPickerViewModel: ViewModelType {
+  weak var delegate: SignUpCoordinatingActionDelegate?
+  private let userInfoUseCase: UserInfoUseCaseInterface
+
+  struct Input {
+    var tobaccoTap: Driver<Frequency>
+    var alcoholTap: Driver<Frequency>
+    var nextBtnTap: Driver<Void>
+  }
+
+  struct Output {
+    var isNextBtnEnabled: Driver<Bool>
+    let initialFrequecy: Driver<FrequencyType>
+  }
+
+  enum FrequencyType {
+    case smoking(Frequency)
+    case drinking(Frequency)
+  }
+
+  private var disposeBag = DisposeBag()
+
+  init(userInfoUseCase: UserInfoUseCaseInterface) {
+    self.userInfoUseCase = userInfoUseCase
+  }
+
+  func transform(input: Input) -> Output {
+    let userinfo = Driver.just(())
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+      .debug("userinfo")
+
+    let smoke = userinfo.compactMap { $0.smoking }.map { FrequencyType.smoking($0) }
+    let drink = userinfo.compactMap { $0.drinking }.map { FrequencyType.drinking($0) }
+
+    let initialFrequency = Driver.concat([smoke, drink])
+
+    let selectedTobacco = input.tobaccoTap
+    let selectedAlcohol = input.alcoholTap
+
+    let nextBtnisEnabled = Driver.combineLatest(selectedAlcohol, selectedTobacco).map { _ in true }
+
+    let updatedUserInfo = Driver.combineLatest(selectedAlcohol, selectedTobacco) { ($0, $1) }
+      .withLatestFrom(userinfo) { component, userinfo in
+        let (drink, smoke) = component
+        var mutable = userinfo
+        mutable.smoking = smoke
+        mutable.drinking = drink
+        return mutable
+      }
+
+    input.nextBtnTap
+      .withLatestFrom(nextBtnisEnabled)
+      .filter { $0 }
+      .map { _ in }
+      .throttle(.milliseconds(500), latest: false)
+      .withLatestFrom(updatedUserInfo)
+      .drive(with: self) { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtAlcoholTobacco)
+      }.disposed(by: disposeBag)
+
+    return Output(
+      isNextBtnEnabled: nextBtnisEnabled,
+      initialFrequecy: initialFrequency
+    )
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoPickerView.swift b/Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoPickerView.swift
new file mode 100644
index 00000000..19408106
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/AlcoholTobaccoInput/AlcoholTobaccoPickerView.swift
@@ -0,0 +1,160 @@
+//
+//  AlcoholTobaccoInputView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+import SignUpInterface
+import DSKit
+import RxSwift
+
+class AlcoholTobaccoPickerView: TFBaseView {
+  lazy var container = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+
+  lazy var titleLabel: UILabel = UILabel().then {
+    $0.text = "추가정보를 알려주세요"
+    $0.textColor = DSKitAsset.Color.neutral300.color
+    $0.font = .thtH1B
+    $0.asColor(targetString: "추가정보", color: DSKitAsset.Color.neutral50.color)
+  }
+
+  lazy var tobaccoPickerView = TFButtonPickerView(
+    title: "흡연 스타일을 선택해주세요.", targetString: "흡연",
+    options: [
+      "안함",
+      "가끔",
+      "자주"
+    ],
+    titleType: .sub
+  )
+
+  lazy var alcoholPickerView = TFButtonPickerView(
+    title: "주량을 선택해주세요.", targetString: "주량",
+    options: [
+      "안함",
+      "가끔",
+      "자주"
+    ],
+    titleType: .sub
+  )
+
+  lazy var infoImageView: UIImageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
+    $0.tintColor = DSKitAsset.Color.neutral400.color
+  }
+
+  lazy var descLabel: UILabel = UILabel().then {
+    $0.text = "마이페이지에서 변경가능합니다."
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 1
+  }
+
+  lazy var nextBtn = CTAButton(btnTitle: "->", initialStatus: false)
+
+  override func makeUI() {
+    addSubview(container)
+
+    container.addSubviews(
+      titleLabel,
+      tobaccoPickerView,
+      alcoholPickerView,
+      infoImageView, descLabel,
+      nextBtn
+    )
+    container.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+      $0.bottom.equalToSuperview()
+    }
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(76)
+      $0.leading.trailing.equalToSuperview().inset(30)
+    }
+
+    tobaccoPickerView.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(35)
+      $0.leading.trailing.equalToSuperview().inset(0)
+    }
+
+    alcoholPickerView.snp.makeConstraints {
+      $0.top.equalTo(tobaccoPickerView.snp.bottom).offset(30)
+      $0.leading.trailing.equalToSuperview().inset(0)
+    }
+
+    infoImageView.snp.makeConstraints {
+      $0.leading.equalTo(titleLabel.snp.leading)
+      $0.width.height.equalTo(16)
+      $0.top.equalTo(alcoholPickerView.snp.bottom).offset(16)
+    }
+
+    descLabel.snp.makeConstraints {
+      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
+      $0.top.equalTo(alcoholPickerView.snp.bottom).offset(16)
+      $0.trailing.equalToSuperview().inset(38)
+    }
+    nextBtn.snp.makeConstraints {
+      $0.top.equalTo(descLabel.snp.bottom).offset(30)
+      $0.trailing.equalTo(descLabel)
+      $0.height.equalTo(50)
+      $0.width.equalTo(88)
+    }
+  }
+}
+
+extension Reactive where Base == AlcoholTobaccoPickerView {
+  var selectedFrequecy: Binder<AlcoholTobaccoPickerViewModel.FrequencyType> {
+    Binder(base.self) { view, frequencyType in
+      switch frequencyType {
+      case let .drinking(frequency):
+        view.alcoholPickerView.handleSelectedState(FrequencyMapper.toOption(frequency))
+      case let .smoking(frequency):
+        view.tobaccoPickerView.handleSelectedState(FrequencyMapper.toOption(frequency))
+      }
+    }
+  }
+}
+
+struct FrequencyMapper {
+  static func toOption(_ frequency: Frequency) -> TFButtonPickerView.Option {
+    switch frequency {
+    case .none:
+      return .init(key: 0, value: "안함")
+    case .sometimes:
+      return .init(key: 1, value: "가끔")
+    case .frequently:
+      return .init(key: 2, value: "자주")
+    }
+  }
+
+  static func toFrequency(_ option: TFButtonPickerView.Option) -> Frequency {
+    switch option.key {
+    case 0: return .none
+    case 1: return .sometimes
+    default: return .frequently
+    }
+  }
+
+  static var options: [String] {
+    return ["안함", "가끔", "자주"]
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct AlcoholTobaccoPickerViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      return AlcoholTobaccoPickerView()
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Email/EmailInputViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/Email/EmailInputViewController.swift
index c34f3913..369de850 100644
--- a/Projects/Features/SignUp/Src/SignUpRoot/Email/EmailInputViewController.swift
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Email/EmailInputViewController.swift
@@ -21,6 +21,7 @@ class EmailInputViewController: TFBaseViewController {
     $0.placeholder = "welcome@falling.com"
     $0.textColor = DSKitAsset.Color.primary500.color
     $0.font = .thtH2B
+    $0.autocapitalizationType = .none
   }
 
   private lazy var clearBtn: UIButton = UIButton().then {
@@ -34,7 +35,7 @@ class EmailInputViewController: TFBaseViewController {
   }
 
   private lazy var descView = UIView().then {
-    $0.backgroundColor = .cyan
+    $0.backgroundColor = .clear
   }
 
   private lazy var descImageView: UIImageView = UIImageView().then {
@@ -80,11 +81,7 @@ class EmailInputViewController: TFBaseViewController {
     super.viewDidAppear(animated)
     emailTextField.becomeFirstResponder()
   }
-
-  override func viewDidLoad() {
-    super.viewDidLoad()
-    keyboardSetting()
-  }
+  
 
   override func makeUI() {
     [
@@ -156,7 +153,7 @@ class EmailInputViewController: TFBaseViewController {
 
     nextButton.snp.makeConstraints {
       $0.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(38)
-      $0.bottom.equalTo(view.safeAreaLayoutGuide).inset(14)
+      $0.bottom.equalTo(view.keyboardLayoutGuide.snp.top).offset(-16)
       $0.height.equalTo(54)
     }
   }
@@ -169,6 +166,7 @@ class EmailInputViewController: TFBaseViewController {
       .disposed(by: disposeBag)
 
     let input = EmailInputViewModel.Input(
+      viewDidAppear: rx.viewDidAppear.asDriver().map { _ in },
       emailText: emailTFStrDriver,
       clearBtnTapped: clearBtn.rx.tap.asDriver(),
       nextBtnTap: nextButton.rx.tap.asDriver(),
@@ -183,10 +181,6 @@ class EmailInputViewController: TFBaseViewController {
       .drive(nextButton.rx.buttonStatus, nextButton.rx.isEnabled)
       .disposed(by: disposeBag)
 
-    output.buttonTappedResult
-      .drive()
-      .disposed(by: disposeBag)
-
     output.emailTextStatus
       .drive(with: self, onNext: { vc, state in
         switch state {
@@ -222,42 +216,9 @@ class EmailInputViewController: TFBaseViewController {
       .disposed(by: disposeBag)
 
     output.emailText
-      .drive(emailTextField.rx.text)
-      .disposed(by: disposeBag)
-  }
-
-  func keyboardSetting() {
-    view.rx.tapGesture()
-      .when(.recognized)
-      .withUnretained(self)
-      .subscribe { vc, _ in
-        vc.view.endEditing(true)
-      }
-      .disposed(by: disposeBag)
-
-    RxKeyboard.instance.visibleHeight
-      .skip(1)
-      .drive(onNext: { [weak self] keyboardHeight in
-        guard let self else { return }
-        if keyboardHeight == 0 {
-          self.nextButton.snp.updateConstraints {
-            $0.bottom.equalTo(self.view.safeAreaLayoutGuide).inset(14)
-          }
-        } else {
-          self.nextButton.snp.updateConstraints {
-            $0.bottom.equalTo(self.view.safeAreaLayoutGuide).inset(keyboardHeight - self.view.safeAreaInsets.bottom + 14)
-          }
-        }
-
-        if keyboardHeight == 0 {
-          self.titleLable.snp.updateConstraints {
-            $0.top.equalTo(self.view.safeAreaLayoutGuide).inset(76)
-          }
-        } else {
-          self.titleLable.snp.updateConstraints {
-            $0.top.equalTo(self.view.safeAreaLayoutGuide).inset(20)
-          }
-        }
+      .drive(with: self, onNext: { owner, text in
+        owner.emailTextField.text = text
+        owner.emailTextField.sendActions(for: .valueChanged)
       })
       .disposed(by: disposeBag)
   }
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Email/EmailInputViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/Email/EmailInputViewModel.swift
index b2f483a4..8d430a6f 100644
--- a/Projects/Features/SignUp/Src/SignUpRoot/Email/EmailInputViewModel.swift
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Email/EmailInputViewModel.swift
@@ -11,10 +11,7 @@ import Core
 
 import RxSwift
 import RxCocoa
-
-protocol EmailInputDelegate: AnyObject {
-  func emailNextButtonTap()
-}
+import SignUpInterface
 
 final class EmailInputViewModel: ViewModelType {
   enum EmailTextState {
@@ -23,9 +20,8 @@ final class EmailInputViewModel: ViewModelType {
     case invalid
   }
 
-  weak var delegate: EmailInputDelegate?
-  
   struct Input {
+    let viewDidAppear: Driver<Void>
     let emailText: Driver<String>
     let clearBtnTapped: Driver<Void>
     let nextBtnTap: Driver<Void>
@@ -38,12 +34,38 @@ final class EmailInputViewModel: ViewModelType {
     let buttonState: Driver<Bool>
     let warningLblState: Driver<Bool>
     let emailTextStatus: Driver<EmailTextState>
-    let buttonTappedResult: Driver<Void>
     let emailText: Driver<String>
   }
 
+  weak var delegate: SignUpCoordinatingActionDelegate?
+
+  private var disposeBag = DisposeBag()
+  private let userInfoUseCase: UserInfoUseCaseInterface
+
+  init(userInfoUseCase: UserInfoUseCaseInterface) {
+    self.userInfoUseCase = userInfoUseCase
+  }
+
   func transform(input: Input) -> Output {
+
+    let userinfo = input.viewDidAppear
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    let initialEmail = userinfo
+      .map { $0.email ?? "" }
+
     let text = input.emailText
+      .distinctUntilChanged()
+      .debounce(.milliseconds(300))
+      .debug()
+
     let autoComplete = Driver
       .merge([input.naverBtnTapped.map { "@naver.com" },
               input.gmailBtnTapped.map { "@gmail.com" },
@@ -53,7 +75,6 @@ final class EmailInputViewModel: ViewModelType {
     let outputText = Driver.merge(text, autoComplete, input.clearBtnTapped.map { "" })
 
     let emailValidate = outputText
-      .debug("emailValidate")
       .map {
         if $0.isEmpty {
           return EmailTextState.empty
@@ -65,7 +86,6 @@ final class EmailInputViewModel: ViewModelType {
           }
         }
       }
-      .asObservable()
 
     let buttonState = emailValidate
       .map {
@@ -76,7 +96,6 @@ final class EmailInputViewModel: ViewModelType {
           return true
         }
       }
-      .asDriver(onErrorJustReturn: false)
 
     let warningLblState = emailValidate
       .map {
@@ -87,22 +106,30 @@ final class EmailInputViewModel: ViewModelType {
           return false
         }
       }
-      .asDriver(onErrorJustReturn: false)
 
     let emailTextStatus = emailValidate.asDriver(onErrorJustReturn: .empty)
 
+    let updatedUserInfo = Driver.combineLatest(userinfo, outputText) { userinfo, email in
+      var userinfo = userinfo
+      userinfo.email = email
+      return userinfo
+    }
+
     // TODO: Email 로 로그인 문제 생겼을때 계정 복구 진행하는데 저장하는 api 를 찾을수 없음. 추후 저장로직 개발 필요해 보임
-    let buttonTappedResult = input.nextBtnTap
-      .do(onNext: { [weak self] in
-        self?.delegate?.emailNextButtonTap()
+    input.nextBtnTap
+      .throttle(.milliseconds(500), latest: false)
+      .withLatestFrom(updatedUserInfo)
+      .drive(with: self, onNext: { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtEmail)
       })
+      .disposed(by: disposeBag)
 
     return Output(
       buttonState: buttonState,
       warningLblState: warningLblState,
       emailTextStatus: emailTextStatus,
-      buttonTappedResult: buttonTappedResult,
-      emailText: outputText
+      emailText:  Driver.merge(outputText, initialEmail)
     )
   }
 }
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerView.swift b/Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerView.swift
new file mode 100644
index 00000000..94a2a12d
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerView.swift
@@ -0,0 +1,110 @@
+//
+//  GenderPickerView.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/16.
+//
+
+import UIKit
+
+import DSKit
+import RxSwift
+import SignUpInterface
+
+class GenderPickerView: TFBaseView {
+  lazy var container = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+
+  lazy var genderPickerView = ButtonPickerView(
+    title: "성별, 생일을 입력해주세요.", targetString: "성별, 생일",
+    option1: "여자", option2: "남자"
+  )
+  
+  lazy var birthdayLabel: UILabel = UILabel().then {
+    $0.textAlignment = .left
+    $0.font = .thtH2B
+    $0.textColor = DSKitAsset.Color.neutral400.color
+  }
+
+  lazy var infoImageView: UIImageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
+    $0.tintColor = DSKitAsset.Color.neutral400.color
+  }
+
+  lazy var descLabel: UILabel = UILabel().then {
+    $0.text = "입력하신 나이와 성별은 추후 변경할 수 없습니다."
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 1
+  }
+
+  lazy var nextBtn = CTAButton(btnTitle: "->", initialStatus: false)
+
+  override func makeUI() {
+    addSubview(container)
+
+    container.addSubviews(
+      genderPickerView,
+      birthdayLabel,
+      infoImageView, descLabel,
+      nextBtn
+    )
+    container.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+      $0.bottom.equalToSuperview()
+    }
+
+    genderPickerView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(76)
+      $0.leading.trailing.equalToSuperview()
+    }
+
+    birthdayLabel.snp.makeConstraints {
+      $0.leading.trailing.equalToSuperview().inset(30)
+      $0.top.equalTo(genderPickerView.snp.bottom).offset(10)
+      $0.height.equalTo(50)
+    }
+
+    infoImageView.snp.makeConstraints {
+      $0.leading.equalTo(birthdayLabel.snp.leading)
+      $0.width.height.equalTo(16)
+      $0.top.equalTo(birthdayLabel.snp.bottom).offset(16)
+    }
+
+    descLabel.snp.makeConstraints {
+      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
+      $0.top.equalTo(birthdayLabel.snp.bottom).offset(16)
+      $0.trailing.equalToSuperview().inset(38)
+    }
+    nextBtn.snp.makeConstraints {
+      $0.top.equalTo(descLabel.snp.bottom).offset(30)
+      $0.trailing.equalTo(birthdayLabel)
+      $0.height.equalTo(50)
+      $0.width.equalTo(88)
+    }
+  }
+}
+
+extension Reactive where Base: GenderPickerView {
+  var selectedGender: Binder<Gender> {
+    return Binder(base.self) { view, gender in
+      view.genderPickerView.handleSelectedState(gender == .female ? .left : .right)
+    }
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct GenderPickerViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      return GenderPickerView()
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerViewController.swift
new file mode 100644
index 00000000..daf8f376
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerViewController.swift
@@ -0,0 +1,80 @@
+//
+//  GenderPickerViewController.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/16.
+//
+
+import UIKit
+
+import DSKit
+
+import SignUpInterface
+import RxSwift
+import RxCocoa
+import RxGesture
+
+final class GenderPickerViewController: TFBaseViewController {
+  private let mainView = GenderPickerView()
+  private let viewModel: GenderPickerViewModel
+
+  init(viewModel: GenderPickerViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func bindViewModel() {
+    let birthdayTap = self.mainView.birthdayLabel
+      .rx
+      .tapGesture()
+      .when(.recognized)
+      .map { _ in }
+      .asDriverOnErrorJustEmpty()
+
+    let genderTap = self.mainView.genderPickerView
+      .rx.selectedOption
+      .asDriver()
+      .compactMap { $0 }
+      .map { option -> Gender in
+        switch option {
+        case .left:
+          return .female
+        case .right:
+          return .male
+        }
+      }
+
+    let input = GenderPickerViewModel.Input(
+      genderTap: genderTap,
+      birthdayTap: birthdayTap,
+      nextBtnTap: self.mainView.nextBtn.rx.tap.asDriver()
+    )
+    
+    let output = viewModel.transform(input: input)
+
+    output.initialGender
+      .compactMap { $0 }
+      .drive(self.mainView.rx.selectedGender)
+      .disposed(by: disposeBag)
+
+    output.birthday
+      .drive(with: self) { owner, selectedDate in
+        owner.mainView.birthdayLabel.text = selectedDate
+        owner.mainView.birthdayLabel.textColor = DSKitAsset.Color.primary500.color
+      }
+      .disposed(by: disposeBag)
+
+    output.isNextBtnEnabled
+      .drive(self.mainView.nextBtn.rx.buttonStatus)
+      .disposed(by: disposeBag)
+  }
+}
+
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerViewModel.swift
new file mode 100644
index 00000000..e72cbbb7
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/GenderPick/GenderPickerViewModel.swift
@@ -0,0 +1,96 @@
+//
+//  GenderPickerViewModel.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/16.
+//
+
+import Foundation
+
+import Core
+import SignUpInterface
+
+import RxSwift
+import RxCocoa
+
+final class GenderPickerViewModel: ViewModelType {
+  weak var delegate: SignUpCoordinatingActionDelegate?
+
+  struct Input {
+    var genderTap: Driver<Gender>
+    var birthdayTap: Driver<Void>
+    var nextBtnTap: Driver<Void>
+  }
+
+  struct Output {
+    var initialGender: Driver<Gender?>
+    var birthday: Driver<String>
+    var isNextBtnEnabled: Driver<Bool>
+  }
+
+  private var disposeBag = DisposeBag()
+
+  private var selectedBirthday = PublishRelay<Date?>()
+  private let userInfoUseCase: UserInfoUseCaseInterface
+
+  init(userInfoUseCase: UserInfoUseCaseInterface) {
+    self.userInfoUseCase = userInfoUseCase
+  }
+
+  func transform(input: Input) -> Output {
+
+    let userInfo = Observable.just(())
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    let initialGender = userInfo.map { $0.gender }
+    let initialBirthday = userInfo.map { $0.birthday?.toDate() }
+
+    let selectedGender = Driver.merge(input.genderTap, initialGender.compactMap { $0 })
+    let birthday = Driver.merge(self.selectedBirthday.asDriverOnErrorJustEmpty(), initialBirthday)
+      .map { $0 ?? Date.currentAdultDateOrNil() ?? Date() }
+
+    let nextBtnisEnabled = Driver.zip(selectedGender, birthday).map { _ in true }
+
+    input.birthdayTap
+      .withLatestFrom(birthday)
+      .debug("tapped")
+      .drive(with: self) { owner, birthday in
+        owner.delegate?.invoke(.birthdayTap(birthday, listener: owner))
+      }.disposed(by: disposeBag)
+
+    input.nextBtnTap
+      .throttle(.milliseconds(500), latest: false)
+      .withLatestFrom(birthday)
+      .withLatestFrom(selectedGender) { (date: $0, gender: $1) }
+      .withLatestFrom(userInfo) { info, userInfo in
+        var mutable = userInfo
+        mutable.birthday = info.date.toYMDDotDateString()
+        mutable.gender = info.gender
+        return mutable
+      }
+      .drive(with: self) { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtGender)
+      }.disposed(by: disposeBag)
+
+    return Output(
+      initialGender: initialGender,
+      birthday: birthday.map { $0.toYMDDotDateString() },
+      isNextBtnEnabled: nextBtnisEnabled
+    )
+  }
+}
+
+extension GenderPickerViewModel: BottomSheetListener {
+  func sendData(item: BottomSheetValueType) {
+    if case let .date(selectedDate) = item {
+      self.selectedBirthday.accept(selectedDate)
+    }
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightPickerView.swift b/Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightPickerView.swift
new file mode 100644
index 00000000..7e8c7be2
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightPickerView.swift
@@ -0,0 +1,103 @@
+//
+//  HeightView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+
+import DSKit
+
+class HeightPickerView: TFBaseView {
+  lazy var container = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+
+  lazy var titleLabel: UILabel = UILabel().then {
+    $0.text = "키을 입력해주세요"
+    $0.textColor = DSKitAsset.Color.neutral300.color
+    $0.font = .thtH1B
+    $0.asColor(targetString: "키", color: DSKitAsset.Color.neutral50.color)
+  }
+
+  lazy var heightLabel: UILabel = UILabel().then {
+    $0.textAlignment = .left
+    $0.font = .thtH2B
+    $0.text = "145 cm"
+    $0.textColor = DSKitAsset.Color.neutral400.color
+  }
+
+  lazy var infoImageView: UIImageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
+    $0.tintColor = DSKitAsset.Color.neutral400.color
+  }
+
+  lazy var descLabel: UILabel = UILabel().then {
+    $0.text = "마이페이지에서 변경가능합니다."
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 1
+  }
+
+  lazy var nextBtn = CTAButton(btnTitle: "->", initialStatus: false)
+
+  override func makeUI() {
+    addSubview(container)
+
+    container.addSubviews(
+      titleLabel,
+      heightLabel,
+      infoImageView, descLabel,
+      nextBtn
+    )
+    container.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+      $0.bottom.equalToSuperview()
+    }
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(76)
+      $0.leading.trailing.equalToSuperview().inset(30)
+    }
+
+    heightLabel.snp.makeConstraints {
+      $0.leading.trailing.equalToSuperview().inset(30)
+      $0.top.equalTo(titleLabel.snp.bottom).offset(10)
+      $0.height.equalTo(50)
+    }
+
+    infoImageView.snp.makeConstraints {
+      $0.leading.equalTo(heightLabel.snp.leading)
+      $0.width.height.equalTo(16)
+      $0.top.equalTo(heightLabel.snp.bottom).offset(16)
+    }
+
+    descLabel.snp.makeConstraints {
+      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
+      $0.top.equalTo(heightLabel.snp.bottom).offset(16)
+      $0.trailing.equalToSuperview().inset(38)
+    }
+    nextBtn.snp.makeConstraints {
+      $0.top.equalTo(descLabel.snp.bottom).offset(30)
+      $0.trailing.equalTo(heightLabel)
+      $0.height.equalTo(50)
+      $0.width.equalTo(88)
+    }
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct HeightViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      return HeightPickerView()
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightVIewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightVIewController.swift
new file mode 100644
index 00000000..926c09e2
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightVIewController.swift
@@ -0,0 +1,62 @@
+//
+//  HeightVIewController.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+
+import DSKit
+
+import RxSwift
+import RxCocoa
+import RxGesture
+
+final class HeightPickerViewController: TFBaseViewController {
+  private let mainView = HeightPickerView()
+  private let viewModel: HeightPickerViewModel
+
+  init(viewModel: HeightPickerViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func bindViewModel() {
+    let pickerLabelTap = self.mainView.heightLabel
+      .rx
+      .tapGesture()
+      .when(.recognized)
+      .map { _ in }
+      .asDriverOnErrorJustEmpty()
+
+    let input = HeightPickerViewModel.Input(
+      pickerLabelTap: pickerLabelTap,
+      nextBtnTap: self.mainView.nextBtn.rx.tap.asDriver()
+    )
+
+    let output = viewModel.transform(input: input)
+
+    output.height
+      .drive(with: self) { owner, value in
+        owner.mainView.heightLabel.text = value
+        owner.mainView.heightLabel.textColor = DSKitAsset.Color.primary500.color
+      }
+      .disposed(by: disposeBag)
+
+    output.isNextBtnEnabled
+      .drive(with: self) { owner, status in
+        owner.mainView.nextBtn.updateColors(status: status)
+      }
+      .disposed(by: disposeBag)
+  }
+}
+
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightVIewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightVIewModel.swift
new file mode 100644
index 00000000..847e3dad
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/HeightPicker/HeightVIewModel.swift
@@ -0,0 +1,94 @@
+//
+//  HeightVIewModel.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import Foundation
+
+import Core
+import SignUpInterface
+
+import RxSwift
+import RxCocoa
+
+final class HeightPickerViewModel: ViewModelType {
+  weak var delegate: SignUpCoordinatingActionDelegate?
+
+  struct Input {
+    var pickerLabelTap: Driver<Void>
+    var nextBtnTap: Driver<Void>
+  }
+
+  struct Output {
+    var height: Driver<String>
+    var isNextBtnEnabled: Driver<Bool>
+  }
+
+  private var disposeBag = DisposeBag()
+  private let userInfoUseCase: UserInfoUseCaseInterface
+
+  private var selectedHeight = BehaviorRelay<Int>(value: 145)
+
+  init(userInfoUseCase: UserInfoUseCaseInterface) {
+    self.userInfoUseCase = userInfoUseCase
+  }
+
+  func transform(input: Input) -> Output {
+    let userinfo = Driver.just(())
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    userinfo
+      .compactMap { $0.tall }
+      .drive(selectedHeight)
+      .disposed(by: disposeBag)
+
+    let height = self.selectedHeight
+      .asDriver()
+
+    let nextBtnisEnabled = height
+      .map { _ in return true }
+
+    input.pickerLabelTap
+      .withLatestFrom(height)
+      .drive(with: self) { owner, height in
+        owner.delegate?.invoke(.heightLabelTap(height, listener: owner))
+      }.disposed(by: disposeBag)
+
+    input.nextBtnTap
+      .withLatestFrom(nextBtnisEnabled)
+      .filter { $0 }
+      .throttle(.milliseconds(500), latest: false)
+      .withLatestFrom(height)
+      .withLatestFrom(userinfo) { height, userinfo in
+        var mutable = userinfo
+        mutable.tall = height
+        return mutable
+      }
+      .drive(with: self) { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtHeight)
+      }.disposed(by: disposeBag)
+
+    return Output(
+      height: height.map { String($0) + " cm" },
+      isNextBtnEnabled: nextBtnisEnabled
+    )
+  }
+}
+
+extension HeightPickerViewModel: BottomSheetListener {
+  func sendData(item: BottomSheetValueType) {
+    if case let .text(text) = item {
+      self.selectedHeight.accept(Int(text) ?? 145)
+    }
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/IdealTypePicker/IdealTypePickerViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/IdealTypePicker/IdealTypePickerViewController.swift
new file mode 100644
index 00000000..c882cb11
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/IdealTypePicker/IdealTypePickerViewController.swift
@@ -0,0 +1,59 @@
+//
+//  IdealTypeViewController.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/24.
+//
+
+import UIKit
+
+import DSKit
+
+import RxSwift
+import RxCocoa
+
+final class IdealTypePickerViewController: TFBaseViewController {
+  typealias VMType = IdealTypeTagPickerViewModel
+  private(set) var mainView = TagPickerView(
+    titleInfo: .init(title: "이상형을 알려주세요.", targetText: "이상형"),
+    subTitleInfo: .init(title: "내 이상형 3개를 선택해주세요.", targetText: "내 이상형")
+  )
+  private let viewModel: VMType
+
+  init(viewModel: VMType) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func bindViewModel() {
+
+    let nextBtnTap = mainView.nextBtn.rx.tap.asDriver()
+
+    let input = VMType.Input(
+      chipTap: mainView.collectionView.rx.itemSelected.asDriver(),
+      nextBtnTap: nextBtnTap
+    )
+
+    let output = viewModel.transform(input: input)
+
+    output.chips
+      .drive(mainView.collectionView.rx.items(cellType: InputTagCollectionViewCell.self)) { index, viewModel, cell in
+        cell.bind(viewModel)
+      }
+      .disposed(by: disposeBag)
+
+    output.isNextBtnEnabled
+      .drive(with: self) { owner, status in
+        owner.mainView.nextBtn.updateColors(status: status)
+      }
+      .disposed(by: disposeBag)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/IdealTypePicker/IdealTypePickerViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/IdealTypePicker/IdealTypePickerViewModel.swift
new file mode 100644
index 00000000..0a28af4b
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/IdealTypePicker/IdealTypePickerViewModel.swift
@@ -0,0 +1,122 @@
+//
+//  IdealTypeViewModel.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/24.
+//
+
+import Foundation
+
+import Core
+import SignUpInterface
+
+import RxSwift
+import RxCocoa
+
+final class IdealTypeTagPickerViewModel: ViewModelType {
+  private let useCase: SignUpUseCaseInterface
+  private let userInfoUseCase: UserInfoUseCaseInterface
+  weak var delegate: SignUpCoordinatingActionDelegate?
+  
+  init(useCase: SignUpUseCaseInterface, userInfoUseCase: UserInfoUseCaseInterface) {
+    self.useCase = useCase
+    self.userInfoUseCase = userInfoUseCase
+  }
+
+  struct Input {
+    var chipTap: Driver<IndexPath>
+    var nextBtnTap: Driver<Void>
+  }
+
+  struct Output {
+    var chips: Driver<[InputTagItemViewModel]>
+    var isNextBtnEnabled: Driver<Bool>
+  }
+
+  private var disposeBag = DisposeBag()
+
+  func transform(input: Input) -> Output {
+
+    let chips = BehaviorRelay<[InputTagItemViewModel]>(value: [])
+
+    let userinfo = Driver.just(())
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    let local = userinfo.map { $0.idealTypeList }
+
+    let remote = Driver.just(())
+      .flatMapLatest { [unowned self] _ in
+        self.useCase.idealTypes()
+          .asDriver(onErrorJustReturn: [])
+      }
+      .map { $0.map { InputTagItemViewModel(item: $0, isSelected: false) } }
+
+    Driver.zip(local, remote) { local, remote in
+      var mutable = remote
+
+      local.forEach { selectedIndex in
+        if let index = mutable.firstIndex(where: { $0.emojiType.idx == selectedIndex }) {
+          mutable[index].isSelected = true
+        }
+      }
+      return mutable
+    }
+    .drive(chips)
+    .disposed(by: disposeBag)
+
+    input.chipTap.map { $0.item }
+      .withLatestFrom(chips.asDriver()) { index, chips in
+        var prev = chips.enumerated().filter { $0.element.isSelected }.map { $0.offset }
+
+        if prev.contains(index) {
+          prev.removeAll { $0 == index }
+        } else if prev.count < 3 {
+          prev.append(index)
+        }
+        var mutable = chips.map {
+          var model = $0
+          model.isSelected = false
+          return model
+        }
+
+        prev.forEach { index in
+          mutable[index].isSelected = true
+        }
+
+        return mutable
+      }.drive(chips)
+      .disposed(by: disposeBag)
+
+    let isNextBtnEnabled = chips.asDriver()
+      .map { $0.filter { $0.isSelected }.count == 3 }
+
+    input.nextBtnTap
+      .withLatestFrom(isNextBtnEnabled)
+      .filter { $0 }
+      .withLatestFrom(chips.asDriver()) { _, chips in
+        chips.filter { $0.isSelected }.map { $0.emojiType.idx }
+      }
+      .withLatestFrom(userinfo) { items, userinfo in
+        var mutable = userinfo
+        mutable.idealTypeList = items
+        return mutable
+      }
+      .drive(with: self, onNext: { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtIdealType)
+      })
+      .disposed(by: disposeBag)
+
+    return Output(
+      chips: chips.asDriver(),
+      isNextBtnEnabled: isNextBtnEnabled
+    )
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/InterestPicker/InterestPickerViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/InterestPicker/InterestPickerViewController.swift
new file mode 100644
index 00000000..2a7f500c
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/InterestPicker/InterestPickerViewController.swift
@@ -0,0 +1,59 @@
+//
+//  InterestPickerViewController.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+
+import DSKit
+
+import RxSwift
+import RxCocoa
+
+final class InterestPickerViewController: TFBaseViewController {
+  typealias VMType = TagPickerViewModel
+  private(set) var mainView = TagPickerView(
+    titleInfo: .init(title: "관심사를 알려주세요.", targetText: "관심사"),
+    subTitleInfo: .init(title: "내 관심사 3개를 선택해주세요.", targetText: "내 관심사")
+  )
+  private let viewModel: VMType
+
+  init(viewModel: VMType) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func bindViewModel() {
+
+    let nextBtnTap = mainView.nextBtn.rx.tap.asDriver()
+
+    let input = VMType.Input(
+      chipTap: mainView.collectionView.rx.itemSelected.asDriver(),
+      nextBtnTap: nextBtnTap
+    )
+
+    let output = viewModel.transform(input: input)
+
+    output.chips
+      .drive(mainView.collectionView.rx.items(cellType: InputTagCollectionViewCell.self)) { index, viewModel, cell in
+        cell.bind(viewModel)
+      }
+      .disposed(by: disposeBag)
+
+    output.isNextBtnEnabled
+      .drive(with: self) { owner, status in
+        owner.mainView.nextBtn.updateColors(status: status)
+      }
+      .disposed(by: disposeBag)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/InterestPicker/TagPickerViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/InterestPicker/TagPickerViewModel.swift
new file mode 100644
index 00000000..96e136f6
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/InterestPicker/TagPickerViewModel.swift
@@ -0,0 +1,123 @@
+//
+//  InterestPickerViewModel.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import Foundation
+
+import Core
+import SignUpInterface
+
+import RxSwift
+import RxCocoa
+import Domain
+
+final class TagPickerViewModel: ViewModelType {
+  private let useCase: SignUpUseCaseInterface
+  private let userInfoUseCase: UserInfoUseCaseInterface
+  weak var delegate: SignUpCoordinatingActionDelegate?
+
+  struct Input {
+    var chipTap: Driver<IndexPath>
+    var nextBtnTap: Driver<Void>
+  }
+
+  struct Output {
+    var chips: Driver<[InputTagItemViewModel]>
+    var isNextBtnEnabled: Driver<Bool>
+  }
+
+  private var disposeBag = DisposeBag()
+
+  init(useCase: SignUpUseCaseInterface, userInfoUseCase: UserInfoUseCaseInterface) {
+    self.useCase = useCase
+    self.userInfoUseCase = userInfoUseCase
+  }
+
+  func transform(input: Input) -> Output {
+
+    let chips = BehaviorRelay<[InputTagItemViewModel]>(value: [])
+
+    let userinfo = Driver.just(())
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    let local = userinfo.map { $0.interestsList }
+
+    let remote = Driver.just(())
+      .flatMapLatest { [unowned self] _ in
+        self.useCase.interests()
+          .asDriver(onErrorJustReturn: [])
+      }
+      .map { $0.map { InputTagItemViewModel(item: $0, isSelected: false) } }
+
+    Driver.zip(local, remote) { local, remote in
+      var mutable = remote
+
+      local.forEach { selectedIndex in
+        if let index = mutable.firstIndex(where: { $0.emojiType.idx == selectedIndex }) {
+          mutable[index].isSelected = true
+        }
+      }
+      return mutable
+    }
+    .drive(chips)
+    .disposed(by: disposeBag)
+
+    input.chipTap.map { $0.item }
+      .withLatestFrom(chips.asDriver()) { index, chips in
+        var prev = chips.enumerated().filter { $0.element.isSelected }.map { $0.offset }
+
+        if prev.contains(index) {
+          prev.removeAll { $0 == index }
+        } else if prev.count < 3 {
+          prev.append(index)
+        }
+        var mutable = chips.map {
+          var model = $0
+          model.isSelected = false
+          return model
+        }
+
+        prev.forEach { index in
+          mutable[index].isSelected = true
+        }
+
+        return mutable
+      }.drive(chips)
+      .disposed(by: disposeBag)
+
+    let isNextBtnEnabled = chips.asDriver()
+      .map { $0.filter { $0.isSelected }.count == 3 }
+
+    input.nextBtnTap
+      .withLatestFrom(isNextBtnEnabled)
+      .filter { $0 }
+      .withLatestFrom(chips.asDriver()) { _, chips in
+        chips.filter { $0.isSelected }.map { $0.emojiType.idx }
+      }
+      .withLatestFrom(userinfo) { items, userinfo in
+        var mutable = userinfo
+        mutable.interestsList = items
+        return mutable
+      }
+      .drive(with: self, onNext: { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtInterest)
+      })
+      .disposed(by: disposeBag)
+
+    return Output(
+      chips: chips.asDriver(),
+      isNextBtnEnabled: isNextBtnEnabled
+    )
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputView.swift b/Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputView.swift
new file mode 100644
index 00000000..9a7ca27a
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputView.swift
@@ -0,0 +1,80 @@
+//
+//  IntroduceInputView.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/24.
+//
+
+import UIKit
+
+import DSKit
+
+final class IntroduceInputView: TFBaseView {
+
+  lazy var introduceInputView = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+
+  lazy var titleLabel: UILabel = UILabel().then {
+    $0.text = "나를 알려주세요"
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.asColor(targetString: "나를 알려", color: DSKitAsset.Color.neutral50.color)
+    $0.font = .thtH1B
+  }
+
+  lazy var introduceInputField = TFResizableTextView(
+    description: "자유롭게 소개해주세요",
+    totalCount: 200
+  ).then {
+    $0.placeholder = "저의 MBTI는요"
+  }
+
+  lazy var nextBtn = CTAButton(btnTitle: "->", initialStatus: false)
+
+  override func makeUI() {
+    addSubview(introduceInputView)
+
+    introduceInputView.addSubviews(
+      titleLabel,
+      introduceInputField,
+      nextBtn
+    )
+
+    introduceInputView.snp.makeConstraints {
+      $0.edges.equalTo(safeAreaLayoutGuide)
+    }
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().inset(76)
+      $0.leading.equalToSuperview().inset(38)
+    }
+
+    introduceInputField.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(32)
+      $0.leading.trailing.equalToSuperview().inset(38)
+    }
+
+    nextBtn.snp.makeConstraints {
+      $0.trailing.equalToSuperview().inset(38)
+      $0.height.equalTo(54)
+      $0.width.equalTo(88)
+      $0.bottom.equalTo(keyboardLayoutGuide.snp.top).offset(-16)
+    }
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct IntroduceInputViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      let view = IntroduceInputView()
+//      view.introduceInputField.render(state: .text(text: "입력"))
+      return view
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputViewController.swift
new file mode 100644
index 00000000..bd3ac073
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputViewController.swift
@@ -0,0 +1,50 @@
+//
+//  IntroduceInputViewController.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/24.
+//
+
+import UIKit
+
+import DSKit
+
+final class IntroduceInputViewController: TFBaseViewController {
+
+  private let viewModel: IntroduceInputViewModel
+  private lazy var mainView = IntroduceInputView()
+
+  init(viewModel: IntroduceInputViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func loadView() {
+    self.view = mainView
+  }
+
+
+  override func bindViewModel() {
+    super.bindViewModel()
+
+    let input = IntroduceInputViewModel.Input(
+      nextBtn: self.mainView.nextBtn.rx.tap.asDriver(),
+      introduceText: self.mainView.introduceInputField.rx.text.orEmpty.asDriver()
+    )
+
+    let output = viewModel.transform(input: input)
+
+    output.isEnableNextBtn
+      .drive(self.mainView.nextBtn.rx.buttonStatus)
+      .disposed(by: disposeBag)
+
+    output.initialValue
+      .drive(self.mainView.introduceInputField.rx.text)
+      .disposed(by: disposeBag)
+  }
+}
+
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputViewModel.swift
new file mode 100644
index 00000000..9d0f9e5b
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/IntroduceInput/IntroduceInputViewModel.swift
@@ -0,0 +1,73 @@
+//
+//  IntroduceInputViewModel.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/24.
+//
+
+import Foundation
+
+import Core
+
+import RxCocoa
+import RxSwift
+import SignUpInterface
+
+final class IntroduceInputViewModel: ViewModelType {
+  private let userInfoUseCase: UserInfoUseCaseInterface
+
+  struct Input {
+    let nextBtn: Driver<Void>
+    let introduceText: Driver<String>
+  }
+
+  struct Output {
+    let initialValue: Driver<String?>
+    let isEnableNextBtn: Driver<Bool>
+  }
+
+  weak var delegate: SignUpCoordinatingActionDelegate?
+  private let disposeBag = DisposeBag()
+
+  init(userInfoUseCase: UserInfoUseCaseInterface) {
+    self.userInfoUseCase = userInfoUseCase
+  }
+
+  func transform(input: Input) -> Output {
+    let userinfo = Driver.just(())
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    let local = userinfo.map { $0.introduction }
+
+    let isEnableNextBtn = input.introduceText
+      .map { !$0.isEmpty && $0.count < 201 }
+    
+    input.nextBtn
+      .withLatestFrom(isEnableNextBtn)
+      .filter{ $0 }
+      .withLatestFrom(input.introduceText)
+      .withLatestFrom(userinfo) { text, userinfo in
+        var mutable = userinfo
+        mutable.introduction = text
+        return mutable
+      }
+      .drive(with: self, onNext: { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtIntroduce)
+      })
+      .disposed(by: disposeBag)
+
+
+    return Output(
+      initialValue: local,
+      isEnableNextBtn: isEnableNextBtn
+    )
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputView.swift b/Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputView.swift
new file mode 100644
index 00000000..33f4e31a
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputView.swift
@@ -0,0 +1,80 @@
+//
+//  LocationInputView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/27.
+//
+
+import UIKit
+
+import DSKit
+
+final class LocationInputView: TFBaseView {
+  lazy var containerView = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+
+  lazy var titleLabel: UILabel = UILabel().then {
+    $0.text = "현재 위치를 알려주세요"
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.asColor(targetString: "현재 위치를", color: DSKitAsset.Color.neutral50.color)
+    $0.font = .thtH1B
+  }
+
+  lazy var locationField = LocationInputField()
+
+  lazy var nextBtn = CTAButton(btnTitle: "->", initialStatus: false)
+
+  override func makeUI() {
+    addSubview(containerView)
+    containerView.addSubviews(
+      titleLabel,
+      locationField,
+      nextBtn
+    )
+    
+    containerView.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+      $0.bottom.equalToSuperview()
+    }
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().inset(76)
+      $0.leading.equalToSuperview().inset(38)
+    }
+
+    locationField.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(32)
+      $0.leading.trailing.equalToSuperview().inset(38)
+    }
+
+    // webview -> 주소, 법정동코드 수집 -> KAKAO API 좌표 추가 수집
+    // 매니저에서 -> 주소(로드 정확하게), 좌표 -> 벙정동코드 추가 수집
+    // 파라미터 저장, 이미지 저장
+    // 파라미터로 값 초기화
+
+    // 
+
+    nextBtn.snp.makeConstraints {
+      $0.trailing.equalToSuperview().inset(38)
+      $0.height.equalTo(54)
+      $0.width.equalTo(88)
+      $0.bottom.equalTo(keyboardLayoutGuide.snp.top).offset(-16)
+    }
+  }
+}
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct LocationInputViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      let component = LocationInputView()
+      component.locationField.bind("서울시 성북구 성북동")
+      return component
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputViewController.swift
new file mode 100644
index 00000000..9658b3b9
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputViewController.swift
@@ -0,0 +1,55 @@
+//
+//  LocationInputViewController.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/27.
+//
+
+import UIKit
+
+import DSKit
+import RxGesture
+
+final class LocationInputViewController: TFBaseViewController {
+  typealias ViewModel = LocationInputViewModel
+  private let mainView = LocationInputView()
+  private let viewModel: LocationInputViewModel
+
+  init(viewModel: ViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+  
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+  
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func bindViewModel() {
+    
+    let fieldTap = mainView.locationField.rx
+      .tapGesture()
+      .when(.recognized)
+      .map { _ in }
+      .asDriverOnErrorJustEmpty()
+    
+    let input = ViewModel.Input(
+      locationBtnTap: fieldTap,
+      nextBtn: self.mainView.nextBtn.rx.tap.asDriver()
+    )
+
+    let output = viewModel.transform(input: input)
+
+    output.isNextBtnEnabled
+      .drive(self.mainView.nextBtn.rx.buttonStatus)
+      .disposed(by: disposeBag)
+    
+    output.currentLocation
+      .drive(self.mainView.locationField.rx.location)
+      .disposed(by: disposeBag)
+  }
+}
+
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputViewModel.swift
new file mode 100644
index 00000000..01c25cf1
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Location/LocationInputViewModel.swift
@@ -0,0 +1,127 @@
+//
+//  LocationInputViewModel.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/27.
+//
+
+import Foundation
+
+import Core
+
+import RxSwift
+import RxCocoa
+import SignUpInterface
+
+final class LocationInputViewModel: ViewModelType {
+  private var disposeBag = DisposeBag()
+  weak var delegate: SignUpCoordinatingActionDelegate?
+
+  private let locationTrigger = PublishSubject<String>()
+  private let useCase: SignUpUseCaseInterface
+  private let userInfoUseCase: UserInfoUseCaseInterface
+
+  struct Input {
+    let locationBtnTap: Driver<Void>
+    let nextBtn: Driver<Void>
+  }
+
+  struct Output {
+    let isNextBtnEnabled: Driver<Bool>
+    let currentLocation: Driver<String>
+  }
+
+  init(useCase: SignUpUseCaseInterface, userInfoUseCase: UserInfoUseCaseInterface) {
+    self.useCase = useCase
+    self.userInfoUseCase = userInfoUseCase
+  }
+
+  // 필드 클릭하면 퍼미션 리퀘스트 후 -> granted: Bool
+  // granted 면 시작, 아니면,
+
+  func transform(input: Input) -> Output {
+
+    let addressTrigger = self.locationTrigger
+    let currentLocation = BehaviorRelay<LocationReq?>(value: nil)
+
+    let userinfo = Driver.just(())
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    userinfo.map { $0.address }
+      .drive(currentLocation)
+      .disposed(by: disposeBag)
+
+    input.locationBtnTap
+      .throttle(.milliseconds(500), latest: false)
+      .asObservable()
+      .withUnretained(self)
+      .flatMapLatest { owner, _ in
+
+        return owner.useCase.fetchLocation()
+          .debug("location usecase")
+          .catch { error in
+            print(error.localizedDescription)
+            owner.delegate?.invoke(.webViewTap(listner: self))
+            return .error(error)
+          }
+          .asDriver(onErrorDriveWith: .empty())
+      }
+      .asDriverOnErrorJustEmpty()
+      .drive(currentLocation)
+      .disposed(by: disposeBag)
+
+    addressTrigger
+      .asDriverOnErrorJustEmpty()
+      .flatMap { [unowned self] address in
+        self.useCase.fetchLocation(address)
+          .asDriver(onErrorDriveWith: .empty())
+      }
+      .drive(currentLocation)
+      .disposed(by: disposeBag)
+
+    input.nextBtn
+      .throttle(.milliseconds(300), latest: false)
+      .withLatestFrom(currentLocation.asDriver())
+      .withLatestFrom(userinfo) { location, userinfo in
+        var mutable = userinfo
+        mutable.address = location
+        return mutable
+      }
+      .drive(with: self, onNext: { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtLocation)
+      })
+      .disposed(by: disposeBag)
+
+    let isNextBtnEnabled = currentLocation
+      .map(validator)
+      .asDriver(onErrorJustReturn: false)
+
+    return Output(
+      isNextBtnEnabled: isNextBtnEnabled,
+      currentLocation: currentLocation.compactMap { $0?.address }.asDriverOnErrorJustEmpty()
+    )
+  }
+
+  func validator(_ location: LocationReq?) -> Bool {
+    guard
+      let location,
+      location.address.isEmpty == false,
+      location.lat != 0, location.lon != 0
+    else { return false }
+    return true
+  }
+}
+
+extension LocationInputViewModel: WebViewDelegate {
+  func didReceiveAddress(_ address: String) {
+    self.locationTrigger.onNext(address)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Location/PostCodeWebViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/Location/PostCodeWebViewController.swift
new file mode 100644
index 00000000..ad2ebb29
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Location/PostCodeWebViewController.swift
@@ -0,0 +1,124 @@
+//
+//  PostCodeWebViewController.swift
+//  SignUp
+//
+//  Created by kangho lee on 4/29/24.
+//
+
+import UIKit
+import WebKit
+
+import Core
+import DSKit
+
+public protocol WebViewDelegate: AnyObject {
+  func didReceiveAddress(_ address: String)
+}
+
+public class PostCodeWebViewController: TFBaseViewController {
+  
+  enum WebViewKey: String {
+    case bridge = "callBackHandler"
+    case jibunAddress
+    
+  }
+  
+  // MARK: - Properties
+  var webView: WKWebView?
+  let indicator = UIActivityIndicatorView(style: .medium)
+  var address = ""
+  
+  weak var delegate: WebViewDelegate?
+  
+  // MARK: - Lifecycle
+  
+  public override func makeUI() {
+    view.backgroundColor = .white
+    setAttributes()
+    setContraints()
+    
+  }
+  
+  private func setAttributes() {
+    let contentController = WKUserContentController()
+    contentController.add(self, name: WebViewKey.bridge.rawValue)
+    
+    let configuration = WKWebViewConfiguration()
+    configuration.userContentController = contentController
+    
+    webView = WKWebView(frame: .zero, configuration: configuration)
+    self.webView?.navigationDelegate = self
+    
+    guard let url = URL(string: "https://ibcylon.github.io/DaumAPI/"),
+          let webView = webView
+    else { return }
+    let request = URLRequest(url: url)
+    webView.load(request)
+    indicator.startAnimating()
+  }
+  
+  private func setContraints() {
+    guard let webView = webView else { return }
+    view.addSubview(webView)
+    view.backgroundColor = .clear
+    webView.translatesAutoresizingMaskIntoConstraints = false
+    
+    webView.addSubview(indicator)
+    indicator.translatesAutoresizingMaskIntoConstraints = false
+    
+    NSLayoutConstraint.activate([
+      webView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
+      webView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30),
+      webView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30),
+      webView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -200),
+      
+      indicator.centerXAnchor.constraint(equalTo: webView.centerXAnchor),
+      indicator.centerYAnchor.constraint(equalTo: webView.centerYAnchor),
+    ])
+  }
+}
+
+extension PostCodeWebViewController: WKScriptMessageHandler {
+  public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
+    if message.name == WebViewKey.bridge.rawValue {
+      if let data = message.body as? [String: Any] {
+        if let address = data[WebViewKey.jibunAddress.rawValue] as? String {
+          var formatted = address.components(separatedBy: " ")
+          var returnValue = ""
+          if formatted[0] != "서울" {
+            formatted.removeFirst()
+          } else {
+            formatted[0] = "서울시"
+          }
+          returnValue = formatted.prefix(3).joined(separator: " ")
+          self.delegate?.didReceiveAddress(returnValue)
+        }
+      }
+    }
+    self.dismiss(animated: true, completion: nil)
+  }
+}
+
+extension PostCodeWebViewController: WKNavigationDelegate {
+  public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
+    indicator.startAnimating()
+  }
+  
+  public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
+    indicator.stopAnimating()
+  }
+}
+//
+//#if canImport(SwiftUI) && DEBUG
+//import SwiftUI
+//
+//struct Location_ViewController_Preview: PreviewProvider {
+//    static var previews: some View {
+//      let vm = LocationInputViewModel(locationservice: LocationService())
+//      let vc = LocationInputViewController(viewModel: vm)
+//      return vc.showPreview()
+//    }
+//}
+//#endif
+//
+//
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Location/SubView/LocationInputField.swift b/Projects/Features/SignUp/Src/SignUpRoot/Location/SubView/LocationInputField.swift
new file mode 100644
index 00000000..55fff209
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Location/SubView/LocationInputField.swift
@@ -0,0 +1,146 @@
+//
+//  LocationInputView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/27.
+//
+
+import UIKit
+
+import DSKit
+
+class LocationInputField: UIControl {
+
+  private lazy var containView = UIControl().then {
+    $0.layer.borderWidth = 1
+    $0.layer.borderColor = DSKitAsset.Color.neutral300.color.cgColor
+    $0.clipsToBounds = true
+    $0.layer.cornerRadius = 12
+  }
+
+  private lazy var pinImageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Icons.pin.image.withTintColor(DSKitAsset.Color.neutral50.color, renderingMode: .alwaysOriginal)
+  }
+  private lazy var locationLabel = UILabel().then {
+    $0.text = "서울시 강남구 대치동"
+    $0.textColor = DSKitAsset.Color.neutral300.color
+    $0.font = .thtH5M
+  }
+
+  lazy var infoImageView: UIImageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
+    $0.tintColor = DSKitAsset.Color.neutral400.color
+  }
+
+  lazy var descLabel: UILabel = UILabel().then {
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 3
+    $0.text = "내 주변의 친구들과 더 많은 대화를 나눌 수 있어요."
+  }
+
+  var location: String = "" {
+    didSet {
+      locationLabel.text = location
+      locationLabel.textColor = DSKitAsset.Color.neutral50.color
+      self.containView.layer.borderColor = DSKitAsset.Color.primary500.color.cgColor
+    }
+  }
+
+  init() {
+    super.init(frame: .zero)
+    makeUI()
+  }
+  
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  func makeUI() {
+    addSubviews(
+      containView,
+      infoImageView, descLabel
+    )
+
+    containView.addSubviews(
+      pinImageView, locationLabel
+    )
+    
+    containView.snp.makeConstraints {
+      $0.top.leading.trailing.equalToSuperview()
+    }
+
+    pinImageView.snp.makeConstraints {
+      $0.leading.equalToSuperview().inset(10)
+      $0.centerY.equalTo(locationLabel)
+      $0.size.equalTo(30)
+    }
+
+    locationLabel.snp.makeConstraints {
+      $0.leading.equalTo(pinImageView.snp.trailing).offset(5)
+      $0.top.equalToSuperview().offset(16)
+      $0.bottom.trailing.equalToSuperview().offset(-16)
+    }
+
+    infoImageView.snp.makeConstraints {
+      $0.leading.equalTo(containView)
+      $0.size.equalTo(16)
+      $0.top.equalTo(containView.snp.bottom).offset(10)
+      $0.bottom.equalToSuperview()
+    }
+
+    descLabel.snp.makeConstraints {
+      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
+      $0.trailing.equalTo(containView)
+      $0.centerY.equalTo(infoImageView)
+    }
+
+    let action = UIAction { [weak self] _ in self?.sendActions(for: .touchUpInside) }
+
+    containView.addAction(action, for: .touchUpInside)
+    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapAction))
+    self.locationLabel.addGestureRecognizer(tapGesture)
+  }
+
+  @objc func tapAction() {
+    sendActions(for: .touchUpInside)
+  }
+}
+
+extension LocationInputField {
+  func bind(_ location: String) {
+    self.location = location
+    self.containView.layer.borderColor = DSKitAsset.Color.primary500.color.cgColor
+    self.locationLabel.textColor = DSKitAsset.Color.neutral50.color
+  }
+}
+
+extension Reactive where Base: LocationInputField {
+  var location: Binder<String> {
+    return Binder(self.base) { view, location in
+      view.bind(location)
+    }
+  }
+
+  var tap: ControlEvent<Void> {
+      return controlEvent(.touchUpInside)
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct LocationInputFieldPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      let component =  LocationInputField()
+      component.bind("서울시 성북구 성북동")
+      return component
+    }
+    .previewLayout(.sizeThatFits)
+    .frame(width: 375, height: 100)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknamInputeViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknamInputeViewController.swift
index dba5f1b9..d1d256f7 100644
--- a/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknamInputeViewController.swift
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknamInputeViewController.swift
@@ -13,10 +13,6 @@ final class NicknameInputViewController: TFBaseViewController {
 
   fileprivate let mainView = NicknameView()
 
-  override func loadView() {
-    self.view = mainView
-  }
-  
   private let viewModel: NicknameInputViewModel
 
   init(viewModel: NicknameInputViewModel) {
@@ -28,109 +24,49 @@ final class NicknameInputViewController: TFBaseViewController {
     fatalError("init(coder:) has not been implemented")
   }
 
-  override func viewDidLoad() {
-    super.viewDidLoad()
+  override func viewDidAppear(_ animated: Bool) {
+    super.viewDidAppear(animated)
+
+    mainView.nicknameInputField.textField.becomeFirstResponder()
+  }
 
-    keyBoardSetting()
+  override func makeUI() {
+    view.addSubview(mainView)
+    mainView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+    }
   }
 
   override func bindViewModel() {
     let viewWillAppear =  rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))
       .do(onNext: { [weak self] _ in
-        self?.mainView.nicknameTextField.becomeFirstResponder()
+        self?.mainView.nicknameInputField.textField.becomeFirstResponder()
       })
       .map { _ in }
       .asDriver(onErrorDriveWith: .empty())
 
     let input = NicknameInputViewModel.Input(
       viewWillAppear: viewWillAppear,
-      nickname: mainView.nicknameTextField.rx.text.orEmpty.asDriver(),
-      clearBtn: mainView.clearBtn.rx.tap.asDriver(),
+      nickname: mainView.nicknameInputField.textField.rx.text.orEmpty.asDriver(),
       nextBtn: mainView.nextBtn.rx.tap.asDriver()
     )
 
     let output = viewModel.transform(input: input)
-//
-//    output.phoneNum
-//      .drive(phoneNumTextField.rx.text)
-//      .disposed(by: disposeBag)
-//
-//    output.phoneNum
-//      .map { $0 + "으로\n전송된 코드를 입력해주세요."}
-//      .drive(codeInputDescLabel.rx.text)
-//      .disposed(by: disposeBag)
-//
-//    output.validate
-//      .filter { $0 == true }
-//      .map { _ in DSKitAsset.Color.primary500.color }
-//      .drive(verifyBtn.rx.backgroundColor)
-//      .disposed(by: disposeBag)
-//
-//    output.validate
-//      .filter { $0 == false }
-//      .map { _ in DSKitAsset.Color.disabled.color }
-//      .drive(verifyBtn.rx.backgroundColor)
-//      .disposed(by: disposeBag)
-//
-//    output.validate
-//      .map { $0 == true }
-//      .drive(verifyBtn.rx.isEnabled)
-//      .disposed(by: disposeBag)
-//
-//    output.error
-//      .asSignal()
-//      .emit {
-//        print($0)
-//      }.disposed(by: disposeBag)
-//
-//    output.clearButtonTapped
-//      .drive(phoneNumTextField.rx.text)
-//      .disposed(by: disposeBag)
-//
-//
-//    output.viewStatus
-//      .map { $0 != .authCode }
-//      .drive(codeInputView.rx.isHidden)
-//      .disposed(by: disposeBag)
-//
-//    output.viewStatus
-//      .map { $0 != .phoneNumber }
-//      .drive(onNext: { [weak self] in
-//        guard let self else { return }
-//        if $0 {
-//          self.codeInputTextField.becomeFirstResponder()
-//        }
-//      })
-//      .disposed(by: disposeBag)
-
-//
-//    output.navigatorDisposble
-//      .drive()
-//      .disposed(by: disposeBag)
-  }
-
-  func keyBoardSetting() {
-    view.rx.tapGesture()
-      .when(.recognized)
-      .withUnretained(self)
-      .subscribe { vc, _ in
-        vc.view.endEditing(true)
-      }
+    
+    output.errorField
+      .debug("errorField")
+      .drive(with: self, onNext: { owner, message in
+        owner.mainView.nicknameInputField.render(state: .error(error: .validate(text: message)))
+      })
+      .disposed(by: disposeBag)
+    
+    output.validate
+      .drive(mainView.nextBtn.rx.buttonStatus)
       .disposed(by: disposeBag)
 
-    RxKeyboard.instance.visibleHeight
-      .skip(1)
-      .drive(with: self, onNext: { owner, keyboardHeight in
-        if keyboardHeight == 0 {
-          owner.mainView.snp.updateConstraints {
-            $0.bottom.equalToSuperview().inset(14)
-          }
-        } else {
-          owner.mainView.nextBtn.snp.updateConstraints {
-            $0.bottom.equalToSuperview().inset(keyboardHeight - owner.view.safeAreaInsets.bottom + 14)
-          }
-        }
-      })
+    output.initialValue
+      .debug("initial")
+      .drive(mainView.nicknameInputField.rx.text)
       .disposed(by: disposeBag)
   }
 }
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknameView.swift b/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknameView.swift
index 90a8e358..fbb52b59 100644
--- a/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknameView.swift
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknameView.swift
@@ -17,97 +17,62 @@ final class NicknameView: TFBaseView {
 
   lazy var titleLabel: UILabel = UILabel().then {
     $0.text = "닉네임을 알려주세요"
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.asColor(targetString: "닉네임", color: DSKitAsset.Color.neutral50.color)
     $0.font = .thtH1B
-    $0.textColor = DSKitAsset.Color.neutral50.color
   }
 
-  lazy var nicknameTextField: UITextField = UITextField().then {
+  lazy var nicknameInputField = TFTextField(
+    description: "폴링에서 활동할 자유로운 호칭을 설정해주세요",
+    totalCount: 12
+  ).then {
     $0.placeholder = "닉네임"
-    $0.textColor = DSKitAsset.Color.primary500.color
-    $0.font = .thtH2B
-    $0.keyboardType = .numberPad
-  }
-
-  lazy var clearBtn: UIButton = UIButton().then {
-    $0.setImage(DSKitAsset.Image.Icons.closeCircle.image, for: .normal)
-    $0.setTitle(nil, for: .normal)
-    $0.backgroundColor = .clear
   }
 
-  lazy var divider: UIView = UIView().then {
-    $0.backgroundColor = DSKitAsset.Color.neutral300.color
-  }
-
-  lazy var infoImageView: UIImageView = UIImageView().then {
-    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
-    $0.tintColor = DSKitAsset.Color.neutral400.color
-  }
-
-  lazy var descLabel: UILabel = UILabel().then {
-    $0.text = "폴링에서 활동할 자유로운 호칭을 설정해주세요"
-    $0.font = .thtCaption1M
-    $0.textColor = DSKitAsset.Color.neutral400.color
-    $0.textAlignment = .left
-    $0.numberOfLines = 1
-  }
-
-  lazy var nextBtn: UIButton = UIButton().then {
-    $0.setTitle("화살표", for: .normal)
-    $0.setTitleColor(DSKitAsset.Color.neutral600.color, for: .normal)
-    $0.titleLabel?.font = .thtH5B
-    $0.backgroundColor = DSKitAsset.Color.primary500.color
-    $0.layer.cornerRadius = 16
-  }
+  lazy var nextBtn = CTAButton(btnTitle: "->", initialStatus: false)
 
   override func makeUI() {
     addSubview(nicknameInputView)
 
-    nicknameInputView.addSubviews(titleLabel, nicknameTextField, clearBtn, divider, infoImageView, descLabel, nextBtn)
+    nicknameInputView.addSubviews(
+      titleLabel,
+      nicknameInputField,
+      nextBtn
+    )
 
     nicknameInputView.snp.makeConstraints {
       $0.edges.equalTo(safeAreaLayoutGuide)
     }
 
     titleLabel.snp.makeConstraints {
-      $0.top.equalToSuperview().inset(frame.height * 0.09)
+      $0.top.equalToSuperview().inset(76)
       $0.leading.equalToSuperview().inset(38)
     }
 
-    nicknameTextField.snp.makeConstraints {
+    nicknameInputField.snp.makeConstraints {
       $0.top.equalTo(titleLabel.snp.bottom).offset(32)
-      $0.leading.equalToSuperview().inset(38)
-      $0.trailing.equalTo(clearBtn.snp.trailing)
+      $0.leading.trailing.equalToSuperview().inset(38)
     }
 
-    clearBtn.snp.makeConstraints {
-      $0.centerY.equalTo(nicknameTextField.snp.centerY)
-      $0.width.height.equalTo(24)
+    nextBtn.snp.makeConstraints {
       $0.trailing.equalToSuperview().inset(38)
+      $0.height.equalTo(54)
+      $0.width.equalTo(88)
+      $0.bottom.equalTo(keyboardLayoutGuide.snp.top).offset(-16)
     }
+  }
+}
 
-    divider.snp.makeConstraints {
-      $0.leading.equalTo(nicknameTextField.snp.leading)
-      $0.trailing.equalTo(clearBtn.snp.trailing)
-      $0.height.equalTo(2)
-      $0.top.equalTo(nicknameTextField.snp.bottom).offset(2)
-    }
-
-    infoImageView.snp.makeConstraints {
-      $0.leading.equalTo(nicknameTextField.snp.leading)
-      $0.width.height.equalTo(16)
-      $0.top.equalTo(divider.snp.bottom).offset(16)
-    }
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
 
-    descLabel.snp.makeConstraints {
-      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
-      $0.top.equalTo(divider.snp.bottom).offset(16)
-      $0.trailing.equalToSuperview().inset(38)
-    }
+struct NicknameViewPreview: PreviewProvider {
 
-    nextBtn.snp.makeConstraints {
-      $0.leading.trailing.equalToSuperview().inset(38)
-      $0.bottom.equalToSuperview().offset(14)
-      $0.height.equalTo(54)
+  static var previews: some View {
+    UIViewPreview {
+      return NicknameView()
     }
+    .previewLayout(.sizeThatFits)
   }
 }
+#endif
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknameViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknameViewModel.swift
index 79c06240..b0b871df 100644
--- a/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknameViewModel.swift
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Nickname/NicknameViewModel.swift
@@ -6,33 +6,112 @@
 //
 
 import Foundation
+import SignUpInterface
 
 import Core
 
 import RxCocoa
 import RxSwift
 
-protocol NicknameInputDelegate: AnyObject {
-  func nicknameNextButtonTap()
-}
-
 final class NicknameInputViewModel: ViewModelType {
+  private let useCase: SignUpUseCaseInterface
+  private let userInfoUseCase: UserInfoUseCaseInterface
 
-  weak var delegate: NicknameInputDelegate?
+  weak var delegate: SignUpCoordinatingActionDelegate?
+
+  private var disposeBag = DisposeBag()
+
+  init(useCase: SignUpUseCaseInterface, userInfoUseCase: UserInfoUseCaseInterface) {
+    self.useCase = useCase
+    self.userInfoUseCase = userInfoUseCase
+  }
 
   struct Input {
     let viewWillAppear: Driver<Void>
     let nickname: Driver<String>
-    let clearBtn: Driver<Void>
     let nextBtn: Driver<Void>
   }
 
   struct Output {
-
+    let initialValue: Driver<String?>
+    let validate: Driver<Bool>
+    let errorField: Driver<String>
   }
 
   func transform(input: Input) -> Output {
+    let text = input.nickname
+    let errorTracker = PublishSubject<Error>()
+    let outputText = BehaviorRelay<String?>(value: nil)
+    let userinfo = input.viewWillAppear
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    let initialNickname = userinfo.map { $0.name }
+
+    let isDuplicate = text
+      .debounce(.milliseconds(500))
+      .distinctUntilChanged()
+      .filter(validateNickname)
+      .asObservable()
+      .withUnretained(self)
+      .flatMapLatest { owner, text in
+        owner.useCase.checkNickname(nickname: text)
+          .asObservable()
+          .catch({ error in
+            errorTracker.onNext(error)
+            return .empty()
+          })
+      }
+      .asDriverOnErrorJustEmpty()
+
+    let isAvailableNickname = isDuplicate.map { $0 == false }
+
+    isDuplicate
+      .filter { $0 }
+      .debug("isDuplicate to ErrorTracker")
+      .map { _ in SignUpError.duplicateNickname }
+      .drive(errorTracker)
+      .disposed(by: disposeBag)
+
+    let updatedUserInfo = Driver.combineLatest(text, userinfo) {
+      var mutable = $1
+      mutable.name = $0
+      return mutable
+    }
+
+    input.nextBtn
+      .throttle(.milliseconds(500), latest: false)
+      .withLatestFrom(updatedUserInfo)
+      .drive(with: self) { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtNickname)
+      }
+      .disposed(by: disposeBag)
+    
+    let errorField = errorTracker
+      .compactMap { error in
+        switch error {
+        case SignUpError.duplicateNickname:
+          return "이미 사용중인 닉네임입니다."
+        default:
+          return error.localizedDescription
+        }
+      }.asDriverOnErrorJustEmpty()
+
+    return Output(
+      initialValue: initialNickname,
+      validate: isAvailableNickname,
+      errorField: errorField
+    )
+  }
 
-    return Output()
+  func validateNickname(_ text: String) -> Bool {
+    !text.isEmpty && text.count < 13 && text.count > 5
   }
 }
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/PhoneNumber/PhoneCertificationViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/PhoneNumber/PhoneCertificationViewModel.swift
deleted file mode 100644
index a7c68e1a..00000000
--- a/Projects/Features/SignUp/Src/SignUpRoot/PhoneNumber/PhoneCertificationViewModel.swift
+++ /dev/null
@@ -1,161 +0,0 @@
-//
-//  PhoneCertificationViewModel.swift
-//  DSKit
-//
-//  Created by Hoo's MacBookPro on 2023/08/03.
-//
-
-import Foundation
-
-import SignUpInterface
-import DSKit
-
-protocol PhoneCertificationDelegate: AnyObject {
-  func finishAuth()
-}
-
-final class PhoneCertificationViewModel: ViewModelType {
-
-  struct Input {
-    let viewWillAppear: Driver<Void>
-    let phoneNum: Driver<String>
-    let clearBtn: Driver<Void>
-    let verifyBtn: Driver<String>
-    let codeInput: Driver<String>
-    let finishAnimationTrigger: Driver<Void>
-  }
-
-  struct Output {
-    let phoneNum: Driver<String>
-    let validate: Driver<Bool>
-    let error: PublishRelay<Void>
-    let clearButtonTapped: Driver<String>
-    let viewStatus: Driver<ViewType>
-    let certificateSuccess: Driver<Bool>
-    let certificateFailuer: Driver<Bool>
-    let timeStampLabel: Driver<String>
-    let timeLabelTextColor: Driver<DSKitColors>
-    let navigatorDisposble: Driver<Void>
-  }
-
-  weak var delegate: PhoneCertificationDelegate?
-
-  func transform(input: Input) -> Output {
-
-    let error = PublishRelay<Void>()
-
-    let validate = input.phoneNum
-      .map { $0.phoneNumValidation() }
-
-    let phoneNum = input.phoneNum
-
-    let clearButtonTapped = input.clearBtn
-      .map { "" }.asDriver()
-
-    let verifyButtonTapped = input.verifyBtn
-      .throttle(.milliseconds(1500), latest: false)
-      .asObservable()
-      .withUnretained(self)
-      .flatMapLatest { owner, phoneNum in
-        owner.testApi(pNum: phoneNum)
-      }.asDriver(onErrorDriveWith: .empty())
-
-    let authNumber = verifyButtonTapped
-      .map { AuthCodeWithTimeStamp(authCode: $0.authNumber) }
-
-    let viewStatus = authNumber.map { _ in ViewType.authCode }
-      .startWith(.phoneNumber)
-
-    let inputtedCode = input.codeInput
-      .distinctUntilChanged()
-      .filter { $0.count == 6 }
-      .withLatestFrom(authNumber) { inputCode, authNumber -> Bool in
-        guard authNumber.isAvailableCode() else {
-          return false
-        }
-        return inputCode == "\(authNumber.authCode)"
-      }
-      .debug()
-      .asDriver(onErrorJustReturn: false)
-
-    let timer = authNumber
-      .flatMap { authNumber in
-        return Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
-          .filter { _ in authNumber.isAvailableCode() }
-          .take(until: inputtedCode.filter{ $0 == true }
-            .asObservable()
-          )
-          .share()
-          .asDriver(onErrorDriveWith: Driver<Int>.empty())
-      }
-
-    let timeLabelStr = timer
-      .withLatestFrom(authNumber) { _, authNumber in
-        authNumber.timeString
-      }
-      .debug("timer")
-      .asDriver(onErrorJustReturn: "")
-
-    let timerLabelColor = timer
-      .withLatestFrom(authNumber) { _, authNumber in
-        if authNumber.isAvailableCode() {
-          return DSKitAsset.Color.neutral50
-        } else {
-          return DSKitAsset.Color.error
-        }
-      }
-      .asDriver(onErrorJustReturn: DSKitAsset.Color.neutral50)
-
-    let finishAuth = input.finishAnimationTrigger
-      .do(onNext: { [weak self] _ in
-        self?.delegate?.finishAuth()
-      })
-
-    return Output(
-      phoneNum: phoneNum,
-      validate: validate,
-      error: error,
-      clearButtonTapped: clearButtonTapped,
-      viewStatus: viewStatus,
-      certificateSuccess: inputtedCode.filter { $0 == true },
-      certificateFailuer: inputtedCode.filter { $0 == false },
-      timeStampLabel: timeLabelStr,
-      timeLabelTextColor: timerLabelColor,
-      navigatorDisposble: finishAuth
-    )
-  }
-}
-
-// MARK: Test Code
-extension PhoneCertificationViewModel {
-  func testApi(pNum: String) -> Observable<PhoneValidationResponse> {
-    return Single<PhoneValidationResponse>
-      .just(PhoneValidationResponse(phoneNumber: pNum, authNumber: 123456))
-      .asObservable()
-  }
-
-  enum ViewType {
-    case phoneNumber
-    case authCode
-  }
-
-  struct AuthCodeWithTimeStamp {
-    let authCode: Int
-    let timeStamp = Date.now
-    var timeString: String {
-      let timeInterval = timeStamp.timeIntervalSinceNow
-      let min = abs(Int(timeInterval.truncatingRemainder(dividingBy: 3600)) / 60)
-      let sec = abs(Int(timeInterval.truncatingRemainder(dividingBy: 60)))
-      return String(format: "%02d:%02d", min, sec)
-    }
-    init(authCode: Int) {
-      self.authCode = authCode
-    }
-
-    func isAvailableCode() -> Bool {
-      let timeInterval = timeStamp.timeIntervalSinceNow
-      let sec = Int(timeInterval)
-      return abs(sec) <= 180
-    }
-  }
-}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoCell/PhotoCell.swift b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoCell/PhotoCell.swift
new file mode 100644
index 00000000..b02d2e2e
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoCell/PhotoCell.swift
@@ -0,0 +1,63 @@
+//
+//  PhotoCell.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+
+import DSKit
+
+import RxCocoa
+import RxSwift
+
+final class PhotoCell: TFBaseCollectionViewCell {
+  private var image: UIImage?
+
+  lazy var imageView = UIImageView().then {
+    $0.layer.cornerRadius = 14
+    $0.contentMode = .scaleAspectFill
+  }
+
+  lazy var addButton = UIButton.plusButton
+
+  override func makeUI() {
+    contentView.addSubview(addButton)
+    contentView.addSubview(imageView)
+
+    contentView.layer.borderColor = DSKitAsset.Color.primary500.color.cgColor
+    contentView.layer.borderWidth = 2
+    contentView.layer.masksToBounds = true
+    contentView.layer.cornerRadius = 10
+
+    imageView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+    }
+
+    addButton.snp.makeConstraints {
+      $0.width.equalTo(52)
+      $0.height.equalTo(24)
+      $0.center.equalToSuperview()
+    }
+  }
+
+  func bind(_ viewModel: PhotoCellViewModel) {
+    if let data = viewModel.data {
+      self.imageView.image = UIImage(data: data)
+    } else {
+      self.imageView.image = nil
+    }
+
+    contentView.layer.borderColor = viewModel.cellType == .required
+    ? DSKitAsset.Color.primary500.color.cgColor
+    : DSKitAsset.Color.neutral400.color.cgColor
+  }
+
+  override func prepareForReuse() {
+    super.prepareForReuse()
+
+    self.imageView.image = nil
+  }
+}
+
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoCell/PhotoCellViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoCell/PhotoCellViewModel.swift
new file mode 100644
index 00000000..a71593a9
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoCell/PhotoCellViewModel.swift
@@ -0,0 +1,25 @@
+//
+//  PhotoCellViewModel.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import Foundation
+
+struct PhotoCellViewModel {
+  enum CellType {
+    case required
+    case optional
+  }
+
+  let id = UUID()
+  var data: Data?
+  let cellType: CellType
+}
+
+extension PhotoCellViewModel: Hashable {
+  func hash(into hasher: inout Hasher) {
+    hasher.combine(self.id)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputView.swift b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputView.swift
new file mode 100644
index 00000000..5dbf9eeb
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputView.swift
@@ -0,0 +1,113 @@
+//
+//  PhotoInputView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/18.
+//
+
+import UIKit
+
+import DSKit
+
+
+final class PhotoInputView: TFBaseView {
+  
+  lazy var container = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+  lazy var titleLabel: UILabel = UILabel().then {
+    $0.text = "사진을 추가해주세요"
+    $0.textColor = DSKitAsset.Color.neutral300.color
+    $0.font = .thtH1B
+    $0.asColor(targetString: "사진", color: DSKitAsset.Color.neutral50.color)
+  }
+
+  lazy var photoCollectionView = UICollectionView(frame: .zero, collectionViewLayout: .photoPickLayout())
+
+  lazy var contentVStackView = UIStackView().then {
+    $0.axis = .vertical
+    $0.distribution = .fill
+  }
+
+  lazy var infoImageView: UIImageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
+    $0.tintColor = DSKitAsset.Color.neutral400.color
+  }
+
+  lazy var descLabel: UILabel = UILabel().then {
+    $0.text = "얼굴이 잘 나온 사진 2장을 필수로 넣어주세요."
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 1
+  }
+
+  lazy var nextBtn = CTAButton(btnTitle: "->", initialStatus: false)
+
+  override func makeUI() {
+    addSubview(container)
+
+    photoCollectionView.backgroundColor = .clear
+
+    container.addSubviews(
+      titleLabel,
+      contentVStackView,
+      infoImageView, descLabel,
+      nextBtn
+    )
+
+    contentVStackView.addArrangedSubview(photoCollectionView)
+    
+    container.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+      $0.bottom.equalToSuperview()
+    }
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(76)
+      $0.leading.trailing.equalToSuperview().inset(30)
+    }
+
+    contentVStackView.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(30)
+      $0.leading.trailing.equalTo(titleLabel)
+    }
+
+    photoCollectionView.snp.makeConstraints {
+      $0.height.equalTo(contentVStackView.snp.width)
+    }
+
+    infoImageView.snp.makeConstraints {
+      $0.leading.equalTo(titleLabel.snp.leading)
+      $0.width.height.equalTo(16)
+      $0.top.equalTo(contentVStackView.snp.bottom).offset(10)
+    }
+
+    descLabel.snp.makeConstraints {
+      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
+      $0.top.equalTo(infoImageView)
+      $0.trailing.equalTo(titleLabel)
+    }
+
+    nextBtn.snp.makeConstraints {
+      $0.top.equalTo(descLabel.snp.bottom).offset(30)
+      $0.trailing.equalTo(descLabel)
+      $0.height.equalTo(50)
+      $0.width.equalTo(88)
+    }
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct PhotoInputViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      return PhotoInputView()
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewController+Diff.swift b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewController+Diff.swift
new file mode 100644
index 00000000..178a05ee
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewController+Diff.swift
@@ -0,0 +1,44 @@
+//
+//  PhotoInputViewController+Diff.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+
+extension PhotoInputViewController {
+  enum Section {
+    case main
+  }
+
+  typealias CellType = PhotoCell
+  typealias SectionType = Section
+  typealias ModelType = PhotoCellViewModel
+  typealias DataSource = UICollectionViewDiffableDataSource<SectionType, ModelType>
+  typealias Snapshot = NSDiffableDataSourceSnapshot<SectionType, ModelType>
+
+  func configureDataSource() {
+      let cellRegistration = UICollectionView.CellRegistration<PhotoCell, ModelType> { (cell, indexPath, model) in
+        cell.bind(model)
+      }
+    dataSource = UICollectionViewDiffableDataSource<SectionType, ModelType>(collectionView: mainView.photoCollectionView) {
+          (collectionView: UICollectionView, indexPath: IndexPath, model: ModelType) -> UICollectionViewCell? in
+          // Return the cell.
+          return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: model)
+      }
+
+      // initial data
+      var snapshot = Snapshot()
+      snapshot.appendSections([Section.main])
+      snapshot.appendItems([])
+      dataSource.apply(snapshot, animatingDifferences: false)
+  }
+
+  func updateSnapshot(items: [ModelType]) {
+    var snapshot = Snapshot()
+    snapshot.appendSections([.main])
+    snapshot.appendItems(items)
+    dataSource.applySnapshotUsingReloadData(snapshot)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewController.swift
new file mode 100644
index 00000000..6e1af4ce
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewController.swift
@@ -0,0 +1,91 @@
+//
+//  PhotoInputViewController.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/18.
+//
+
+import UIKit
+import PhotosUI
+
+import DSKit
+
+import RxSwift
+import RxCocoa
+
+final class PhotoInputViewController: TFBaseViewController {
+  var dataSource: DataSource!
+  private(set) var mainView = PhotoInputView()
+  private let viewModel: PhotoInputViewModel
+
+  init(viewModel: PhotoInputViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func makeUI() {
+    configureDataSource()
+    mainView.photoCollectionView.backgroundColor = .clear
+  }
+
+  override func bindViewModel() {
+
+    let requiredCell = mainView.photoCollectionView.rx.itemSelected
+      .asDriver()
+      .filter { $0.item < 2 }
+    let optionalCell = mainView.photoCollectionView.rx.itemSelected
+      .filter { $0.item == 2 }
+
+    let alertTrigger = optionalCell
+      .asObservable()
+      .withUnretained(self)
+      .flatMapLatest { owner, indexPath in
+        return Observable<PhotoAlertAction>.create { observer in
+          let alert = UIAlertController(title: "사진 수정하기",
+                                        message: "",
+                                        preferredStyle: .actionSheet
+          )
+          let editAction = UIAlertAction(title: "수정", style: .default, handler: { _ -> () in observer.onNext(.edit(indexPath)) })
+          let deleteAction = UIAlertAction(title: "제거", style: .destructive, handler: { _ -> () in observer.onNext(.delete(indexPath)) })
+          let noAction = UIAlertAction(title: "취소", style: .cancel, handler: { _ -> () in
+            observer.onCompleted()
+          })
+          alert.addAction(editAction)
+          alert.addAction(deleteAction)
+          alert.addAction(noAction)
+
+          owner.present(alert, animated: true, completion: nil)
+          return Disposables.create {
+            owner.dismiss()
+          }
+        }
+      }.asDriverOnErrorJustEmpty()
+
+    let nextBtnTap = mainView.nextBtn.rx.tap.asDriver()
+
+    let input = PhotoInputViewModel.Input(
+      cellTap: requiredCell, alertTap: alertTrigger,
+      nextBtnTap: nextBtnTap
+    )
+
+    let output = viewModel.transform(input: input)
+
+    output.images
+      .drive(with: self) { owner, items in
+        owner.updateSnapshot(items: items)
+      }.disposed(by: disposeBag)
+    output.nextBtn
+      .drive(with: self) { owner, status in
+        owner.mainView.nextBtn.updateColors(status: status)
+      }
+      .disposed(by: disposeBag)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewModel.swift
new file mode 100644
index 00000000..9defcaff
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoInputViewModel.swift
@@ -0,0 +1,192 @@
+//
+//  PhotoInputViewModel.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/18.
+//
+
+import UIKit
+import PhotosUI
+
+import Core
+
+import RxSwift
+import RxCocoa
+import SignUpInterface
+
+enum PhotoAlertAction {
+  case edit(IndexPath)
+  case delete(IndexPath)
+}
+
+public protocol PhotoPickerListener: AnyObject {
+  func picker(didFinishPicking results: [PHPickerResult])
+}
+
+final class PhotoInputViewModel: ViewModelType {
+  weak var delegate: SignUpCoordinatingActionDelegate?
+  var pickerDelegate: PhotoPickerDelegate?
+  var service: PHPickerHandler = PhotoService()
+
+  struct Input {
+    let cellTap: Driver<IndexPath>
+    let alertTap: Driver<PhotoAlertAction>
+    let nextBtnTap: Driver<Void>
+  }
+
+  struct Output {
+    let images: Driver<[PhotoCellViewModel]>
+    let nextBtn: Driver<Bool>
+  }
+
+  private let userInfoUseCase: UserInfoUseCaseInterface
+
+  init(userInfoUseCase: UserInfoUseCaseInterface) {
+    self.userInfoUseCase = userInfoUseCase
+  }
+
+  deinit {
+    print("deinit: PhotoInputViewModel")
+  }
+
+  private let selectedPHResult =  PublishSubject<PHPickerResult>()
+  private var disposeBag = DisposeBag()
+
+  func transform(input: Input) -> Output {
+    let imageDataArray = BehaviorRelay<[PhotoCellViewModel]>(value: [
+      PhotoCellViewModel(data: nil, cellType: .required),
+      PhotoCellViewModel(data: nil, cellType: .required),
+      PhotoCellViewModel(data: nil, cellType: .optional)
+    ])
+    let userinfo = Driver.just(())
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    userinfo.map { (key: $0.phoneNumber,fileNames: $0.photos) }
+      .asObservable()
+      .withUnretained(self)
+      .flatMapLatest { owner, components in
+        owner.userInfoUseCase.fetchUserPhotos(key: components.key, fileNames: components.fileNames)
+          .catchAndReturn([])
+          .asObservable()
+      }
+      .debug("fetched photo fileURLs")
+      .withLatestFrom(imageDataArray) { dataArray, models in
+        var mutable = models
+        for (index, data) in dataArray.enumerated() {
+          mutable[index].data = data
+        }
+        return mutable
+      }
+      .asDriverOnErrorJustEmpty()
+      .drive(imageDataArray)
+      .disposed(by: disposeBag)
+
+    let alertEditAction = input.alertTap
+      .compactMap { action -> IndexPath? in
+        switch action {
+        case let .edit(indexPath):
+          return indexPath
+        case .delete:
+          return nil
+        }
+      }
+
+    input.alertTap
+      .compactMap { action in
+        if case let .delete(indexPath) = action {
+          return indexPath.item
+        }
+        return nil
+      }.drive(onNext: { item in
+        var mutable = imageDataArray.value
+        mutable[item].data = nil
+        imageDataArray.accept(mutable)
+      }).disposed(by: disposeBag)
+
+    let index = Driver.merge(input.cellTap, alertEditAction)
+      .map {
+        $0.item
+      }
+      .do(onNext: { [weak self] index in
+        guard let self = self else { return }
+        let pickerDelegate = PhotoPickerDelegator()
+        pickerDelegate.listener = self
+        self.pickerDelegate = pickerDelegate
+        self.delegate?.invoke(.photoCellTap(index: index, listener: pickerDelegate))
+      })
+
+    let data = self.selectedPHResult
+      .withUnretained(self)
+      .flatMapLatest { owner, asset -> Driver<Data> in
+        owner.service.bind(asset)
+          .debug("image")
+          .asDriver(onErrorDriveWith: .empty())
+      }
+      .asDriverOnErrorJustEmpty()
+
+    Driver.zip(index, data) { index, data in
+      var mutable = imageDataArray.value
+      mutable[index].data = data
+      return mutable
+    }.drive(imageDataArray)
+      .disposed(by: disposeBag)
+
+    let nextBtnStatus = imageDataArray
+      .map { $0.prefix(2) }
+      .map { cells in
+        for cell in cells {
+          if cell.data == nil {
+            return false
+          }
+        }
+        return true
+      }
+      .asDriver(onErrorJustReturn: false)
+
+    input.nextBtnTap
+      .withLatestFrom(nextBtnStatus)
+      .filter { $0 }
+      .throttle(.milliseconds(400), latest: false)
+      .asObservable()
+      .withLatestFrom(imageDataArray)
+      .map { $0.compactMap { $0.data } }
+      .withLatestFrom(userinfo) { (key: $1.phoneNumber, datas: $0) }
+      .withUnretained(self)
+      .flatMapLatest { owner, components  in
+        owner.userInfoUseCase.saveUserPhotos(key: components.key, datas: components.datas)
+          .catchAndReturn([])
+          .asObservable()
+      }
+      .debug("saved filed URLS:")
+      .withLatestFrom(userinfo) { urls, userinfo in
+        var mutable = userinfo
+        mutable.photos = urls
+        return mutable
+      }
+      .asDriverOnErrorJustEmpty()
+      .drive(with: self) { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtPhoto)
+      }.disposed(by: disposeBag)
+
+    return Output(
+      images: imageDataArray.asDriver(),
+      nextBtn: nextBtnStatus
+    )
+  }
+}
+
+extension PhotoInputViewModel: PhotoPickerListener {
+  func picker(didFinishPicking results: [PHPickerResult]) {
+    if let item = results.first {
+      self.selectedPHResult.onNext(item)
+    }
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoPicker.swift b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoPicker.swift
new file mode 100644
index 00000000..b66a665f
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoPicker.swift
@@ -0,0 +1,221 @@
+//
+//  PhotoPicker.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+import PhotosUI
+import AVKit
+
+class ViewController: UIViewController {
+
+    @IBOutlet weak var progressView: UIProgressView!
+    @IBOutlet weak var imageView: UIImageView!
+    @IBOutlet weak var livePhotoView: PHLivePhotoView! {
+        didSet {
+            livePhotoView.contentMode = .scaleAspectFit
+        }
+    }
+    @IBOutlet weak var playButton: UIButton!
+    private var playButtonVideoURL: URL?
+
+    private var selection = [String: PHPickerResult]()
+    private var selectedAssetIdentifiers = [String]()
+    private var selectedAssetIdentifierIterator: IndexingIterator<[String]>?
+    private var currentAssetIdentifier: String?
+
+    @IBAction func presentPickerForImagesAndVideos(_ sender: Any) {
+        presentPicker(filter: nil)
+    }
+
+    @IBAction func presentPickerForImagesIncludingLivePhotos(_ sender: Any) {
+        presentPicker(filter: PHPickerFilter.images)
+    }
+
+    @IBAction func presentPickerForLivePhotosOnly(_ sender: Any) {
+        presentPicker(filter: PHPickerFilter.livePhotos)
+    }
+
+    /// - Tag: PresentPicker
+    private func presentPicker(filter: PHPickerFilter?) {
+        var configuration = PHPickerConfiguration(photoLibrary: .shared())
+
+        // Set the filter type according to the user’s selection.
+        configuration.filter = filter
+        // Set the mode to avoid transcoding, if possible, if your app supports arbitrary image/video encodings.
+        configuration.preferredAssetRepresentationMode = .current
+        // Set the selection behavior to respect the user’s selection order.
+        configuration.selection = .ordered
+        // Set the selection limit to enable multiselection.
+        configuration.selectionLimit = 0
+        // Set the preselected asset identifiers with the identifiers that the app tracks.
+        configuration.preselectedAssetIdentifiers = selectedAssetIdentifiers
+
+        let picker = PHPickerViewController(configuration: configuration)
+        picker.delegate = self
+        present(picker, animated: true)
+    }
+
+    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
+        displayNext()
+    }
+
+    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+        if let viewController = segue.destination as? AVPlayerViewController, let videoURL = playButtonVideoURL {
+            viewController.player = AVPlayer(url: videoURL)
+            viewController.player?.play()
+        }
+    }
+}
+
+private extension ViewController {
+
+    /// - Tag: LoadItemProvider
+    func displayNext() {
+        guard let assetIdentifier = selectedAssetIdentifierIterator?.next() else { return }
+        currentAssetIdentifier = assetIdentifier
+
+        let progress: Progress?
+        let itemProvider = selection[assetIdentifier]!.itemProvider
+        if itemProvider.canLoadObject(ofClass: PHLivePhoto.self) {
+            progress = itemProvider.loadObject(ofClass: PHLivePhoto.self) { [weak self] livePhoto, error in
+                DispatchQueue.main.async {
+                    self?.handleCompletion(assetIdentifier: assetIdentifier, object: livePhoto, error: error)
+                }
+            }
+        }
+        else if itemProvider.canLoadObject(ofClass: UIImage.self) {
+            progress = itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
+                DispatchQueue.main.async {
+                    self?.handleCompletion(assetIdentifier: assetIdentifier, object: image, error: error)
+                }
+            }
+        } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
+            progress = itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { [weak self] url, error in
+                do {
+                    guard let url = url, error == nil else {
+                        throw error ?? NSError(domain: NSFileProviderErrorDomain, code: -1, userInfo: nil)
+                    }
+                    let localURL = FileManager.default.temporaryDirectory.appendingPathComponent(url.lastPathComponent)
+                    try? FileManager.default.removeItem(at: localURL)
+                    try FileManager.default.copyItem(at: url, to: localURL)
+                    DispatchQueue.main.async {
+                        self?.handleCompletion(assetIdentifier: assetIdentifier, object: localURL)
+                    }
+                } catch let catchedError {
+                    DispatchQueue.main.async {
+                        self?.handleCompletion(assetIdentifier: assetIdentifier, object: nil, error: catchedError)
+                    }
+                }
+            }
+        } else {
+            progress = nil
+        }
+
+        displayProgress(progress)
+    }
+
+    func handleCompletion(assetIdentifier: String, object: Any?, error: Error? = nil) {
+        guard currentAssetIdentifier == assetIdentifier else { return }
+        if let livePhoto = object as? PHLivePhoto {
+            displayLivePhoto(livePhoto)
+        } else if let image = object as? UIImage {
+            displayImage(image)
+        } else if let url = object as? URL {
+            displayVideoPlayButton(forURL: url)
+        } else if let error = error {
+            print("Couldn't display \(assetIdentifier) with error: \(error)")
+            displayErrorImage()
+        } else {
+            displayUnknownImage()
+        }
+    }
+
+}
+
+private extension ViewController {
+
+    func displayEmptyImage() {
+        displayImage(UIImage(systemName: "photo.on.rectangle.angled"))
+    }
+
+    func displayErrorImage() {
+        displayImage(UIImage(systemName: "exclamationmark.circle"))
+    }
+
+    func displayUnknownImage() {
+        displayImage(UIImage(systemName: "questionmark.circle"))
+    }
+
+    func displayProgress(_ progress: Progress?) {
+        imageView.image = nil
+        imageView.isHidden = true
+        livePhotoView.livePhoto = nil
+        livePhotoView.isHidden = true
+        playButtonVideoURL = nil
+        playButton.isHidden = true
+        progressView.observedProgress = progress
+        progressView.isHidden = progress == nil
+    }
+
+    func displayVideoPlayButton(forURL videoURL: URL?) {
+        imageView.image = nil
+        imageView.isHidden = true
+        livePhotoView.livePhoto = nil
+        livePhotoView.isHidden = true
+        playButtonVideoURL = videoURL
+        playButton.isHidden = videoURL == nil
+        progressView.observedProgress = nil
+        progressView.isHidden = true
+    }
+
+    func displayLivePhoto(_ livePhoto: PHLivePhoto?) {
+        imageView.image = nil
+        imageView.isHidden = true
+        livePhotoView.livePhoto = livePhoto
+        livePhotoView.isHidden = livePhoto == nil
+        playButtonVideoURL = nil
+        playButton.isHidden = true
+        progressView.observedProgress = nil
+        progressView.isHidden = true
+    }
+
+    func displayImage(_ image: UIImage?) {
+        imageView.image = image
+        imageView.isHidden = image == nil
+        livePhotoView.livePhoto = nil
+        livePhotoView.isHidden = true
+        playButtonVideoURL = nil
+        playButton.isHidden = true
+        progressView.observedProgress = nil
+        progressView.isHidden = true
+    }
+
+}
+
+extension ViewController: PHPickerViewControllerDelegate {
+    /// - Tag: ParsePickerResults
+    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
+        dismiss(animated: true)
+
+        let existingSelection = self.selection
+        var newSelection = [String: PHPickerResult]()
+        for result in results {
+            let identifier = result.assetIdentifier!
+            newSelection[identifier] = existingSelection[identifier] ?? result
+        }
+
+        // Track the selection in case the user deselects it later.
+        selection = newSelection
+        selectedAssetIdentifiers = results.map(\.assetIdentifier!)
+        selectedAssetIdentifierIterator = selectedAssetIdentifiers.makeIterator()
+
+        if selection.isEmpty {
+            displayEmptyImage()
+        } else {
+            displayNext()
+        }
+    }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoService.swift b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoService.swift
new file mode 100644
index 00000000..3f27650a
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Photo/PhotoService.swift
@@ -0,0 +1,42 @@
+//
+//  PhotoService.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+import PhotosUI
+
+import RxSwift
+
+enum AssetError: Error {
+  case invalidAsset
+  case cannotLoad
+}
+
+protocol PHPickerHandler {
+  func bind(_ asset: PHPickerResult) -> Single<Data>
+}
+
+final class PhotoService: PHPickerHandler {
+  func bind(_ asset: PHPickerResult) -> Single<Data> {
+    return .create { observer in
+      let itemProvider = asset.itemProvider
+      if itemProvider.canLoadObject(ofClass: UIImage.self) {
+        itemProvider.loadObject(ofClass: UIImage.self) { item, error in
+          if let image = item as? UIImage,
+             let imageData = image.jpegData(compressionQuality: 1.0)
+          {
+            observer(.success(imageData))
+          } else {
+            observer(.failure(error ?? AssetError.invalidAsset))
+          }
+        }
+      } else {
+        observer(.failure(AssetError.invalidAsset))
+      }
+      return Disposables.create { }
+    }
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementView.swift b/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementView.swift
index 799fa1e1..437ea3bb 100644
--- a/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementView.swift
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementView.swift
@@ -8,33 +8,32 @@
 import UIKit
 
 import DSKit
+import SignUpInterface
 
 final class PolicyAgreementView: TFBaseView {
   private lazy var logoView: UIImageView = UIImageView().then {
     $0.image = DSKitAsset.Image.Component.fallingLogo.image
   }
 
-  lazy var selectAllBtn: UIButton = UIButton().then {
-    $0.setImage(DSKitAsset.Image.Component.checkCir.image, for: .normal)
-    $0.setTitle("전체 동의", for: .normal)
-    $0.setTitleColor(DSKitAsset.Color.neutral50.color, for: .normal)
-    $0.titleLabel?.font = .thtH5B
-    $0.imageEdgeInsets = UIEdgeInsets(top: 0, left: -7, bottom: 0, right: 7)
-    $0.titleEdgeInsets = UIEdgeInsets(top: 0, left: 7, bottom: 0, right: -7)
-  }
-
-  private lazy var serviceRowsView = UIStackView().then {
-    $0.axis = .vertical
-    $0.spacing = 20
+  private lazy var titleLabel = UILabel().then {
+    $0.text = "폴링을 이용하려면 동의가 필요해요."
+    $0.font = .thtH5Sb
+    $0.textColor = DSKitAsset.Color.neutral50.color
   }
-  
-  lazy var termsOfServiceRow = ServiceAgreementRowView(serviceType: .termsOfServie)
 
-  lazy var privacyPolicyRow = ServiceAgreementRowView(serviceType: .privacyPolicy)
+  lazy var selectAllBtn = TFCheckButton(btnTitle: "전체 동의", initialStatus: false).then {
 
-  lazy var locationServiceRow = ServiceAgreementRowView(serviceType: .locationService)
+    $0.titleLabel?.font = .thtH5B
+  }
 
-  lazy var marketingServiceRow = ServiceAgreementRowView(serviceType: .marketingService)
+  private(set) lazy var tableView = UITableView().then {
+    $0.separatorStyle = .none
+    $0.backgroundColor = .clear
+    $0.register(cellType: ServiceAgreementRowView.self)
+    $0.estimatedRowHeight = 110
+    $0.rowHeight = UITableView.automaticDimension
+    $0.allowsSelection = true
+  }
 
   lazy var nextBtn = CTAButton(btnTitle: "시작하기", initialStatus: false)
 
@@ -50,8 +49,7 @@ final class PolicyAgreementView: TFBaseView {
   }
 
   override func makeUI() {
-    addSubviews([logoView, selectAllBtn, serviceRowsView, nextBtn, discriptionText])
-
+    addSubviews([logoView, titleLabel, selectAllBtn, tableView, nextBtn, discriptionText])
 
     logoView.snp.makeConstraints {
       $0.leading.equalToSuperview().inset(30)
@@ -60,22 +58,25 @@ final class PolicyAgreementView: TFBaseView {
       $0.width.equalTo(77)
     }
 
-    selectAllBtn.snp.makeConstraints {
-      $0.leading.equalToSuperview().inset(30)
-      $0.top.equalTo(logoView.snp.bottom).offset(83)
+    titleLabel.snp.makeConstraints {
+      $0.top.equalTo(logoView.snp.bottom).offset(30)
+      $0.leading.equalTo(logoView)
+      $0.trailing.equalToSuperview()
+      $0.height.equalTo(40)
     }
 
-    serviceRowsView.snp.makeConstraints {
+    tableView.snp.makeConstraints {
       $0.leading.equalToSuperview().inset(30)
       $0.trailing.equalToSuperview().inset(28)
-      $0.top.equalTo(selectAllBtn.snp.bottom).offset(34)
+      $0.top.equalTo(titleLabel.snp.bottom).offset(34)
+      $0.height.lessThanOrEqualTo(400).priority(.low)
+      $0.bottom.equalTo(selectAllBtn.snp.top).offset(20)
     }
 
-    serviceRowsView.addArrangedSubviews([termsOfServiceRow, privacyPolicyRow, locationServiceRow, marketingServiceRow])
-
-    discriptionText.snp.makeConstraints {
-      $0.bottom.equalToSuperview().inset(61)
-      $0.leading.equalToSuperview().offset(38)
+    selectAllBtn.snp.makeConstraints {
+      $0.leading.trailing.equalTo(nextBtn)
+      $0.bottom.equalTo(nextBtn.snp.top).offset(-20)
+      $0.height.equalTo(54)
     }
 
     nextBtn.snp.makeConstraints {
@@ -83,8 +84,23 @@ final class PolicyAgreementView: TFBaseView {
       $0.leading.trailing.equalToSuperview().inset(38)
       $0.height.equalTo(54)
     }
+    discriptionText.snp.makeConstraints {
+      $0.bottom.equalToSuperview().inset(61)
+      $0.leading.equalToSuperview().offset(38)
+    }
+  }
+}
 
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
 
-  }
+struct TFCheckButtonwPreview: PreviewProvider {
 
+  static var previews: some View {
+    UIViewPreview {
+      PolicyAgreementView()
+    }
+    .previewLayout(.sizeThatFits)
+  }
 }
+#endif
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementViewController.swift
index 397648c7..ad92e222 100644
--- a/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementViewController.swift
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementViewController.swift
@@ -32,51 +32,43 @@ final class PolicyAgreementViewController: TFBaseViewController {
   }
 
   override func bindViewModel() {
+    let cellAction = PublishRelay<(IndexPath, PolicyAgreementViewModel.CellAction)>()
+
+    customView.tableView.rx.itemSelected.asDriver()
+      .do(onNext: { [weak self] IndexPath in
+//        self?.customView.tableView.deselectRow(at: IndexPath, animated: true)
+      })
+      .drive(onNext: { indexPath in
+        cellAction.accept((indexPath, .Agree))
+      }).disposed(by: disposeBag)
+
     let input = PolicyAgreementViewModel.Input(
+      viewDidAppear: rx.viewDidAppear.asDriver().map { _ in },
       agreeAllBtn: customView.selectAllBtn.rx.tap.asDriver(),
-      tosAgreeBtn: customView.termsOfServiceRow.agreeBtn.rx.tap.asDriver(),
-      showTosDetailBtn: customView.termsOfServiceRow.goWebviewBtn.rx.tap.asDriver(),
-      privacyAgreeBtn: customView.privacyPolicyRow.agreeBtn.rx.tap.asDriver(),
-      showPrivacyDetailBtn: customView.privacyPolicyRow.goWebviewBtn.rx.tap.asDriver(),
-      locationServiceAgreeBtn: customView.locationServiceRow.agreeBtn.rx.tap.asDriver(),
-      showLocationServiceDetailBtn: customView.locationServiceRow.goWebviewBtn.rx.tap.asDriver(),
-      marketingServiceAgreeBtn: customView.marketingServiceRow.agreeBtn.rx.tap.asDriver(),
+      cellTap: cellAction.asDriverOnErrorJustEmpty(),
       nextBtn: customView.nextBtn.rx.tap.asDriver()
     )
 
     let output = viewModel.transform(input: input)
 
-    output.agreeAllRowImage
-      .map { $0.image }
-      .drive(customView.selectAllBtn.rx.image())
-      .disposed(by: disposeBag)
-
-    output.tosAgreeRowImage
-      .map { $0.image }
-      .drive(customView.termsOfServiceRow.agreeBtn.rx.image())
-      .disposed(by: disposeBag)
-
-    output.privacyAgreeRowImage
-      .map { $0.image }
-      .drive(customView.privacyPolicyRow.agreeBtn.rx.image())
-      .disposed(by: disposeBag)
-
-    output.locationServiceAgreeRowImage
-      .map { $0.image }
-      .drive(customView.locationServiceRow.agreeBtn.rx.image())
+    output.cellViewModels
+      .drive(customView.tableView.rx.items(cellType: ServiceAgreementRowView.self)) { index, viewModel, cell in
+        cell.bind(viewModel)
+        cell.agreeBtnOnCliek = {
+          cellAction.accept((IndexPath(row: index, section: 0), .Agree))
+        }
+        cell.goWebviewBtnOnClick = {
+          cellAction.accept((IndexPath(row: index, section: 0), .WebView))
+        }
+      }
       .disposed(by: disposeBag)
 
-    output.marketingServiceRowImage
-      .map { $0.image }
-      .drive(customView.marketingServiceRow.agreeBtn.rx.image())
+    output.agreeAllBtnStatus
+      .drive(customView.selectAllBtn.rx.buttonStatus)
       .disposed(by: disposeBag)
 
     output.nextBtnStatus
       .drive(customView.nextBtn.rx.buttonStatus)
       .disposed(by: disposeBag)
-
-    output.nextButtonTap
-      .drive()
-      .disposed(by: disposeBag)
   }
 }
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementViewModel.swift
index cf1ced04..5b4a1fe9 100644
--- a/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementViewModel.swift
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Policy/PolicyAgreementViewModel.swift
@@ -6,38 +6,29 @@
 //
 
 import Foundation
-
+import SignUpInterface
 import DSKit
 
-protocol PolicyAgreementDelegate: AnyObject {
-  func policyNextButtonTap()
-}
-
 final class PolicyAgreementViewModel: ViewModelType {
-  
-  weak var delegate: PolicyAgreementDelegate?
+
+  weak var delegate: SignUpCoordinatingActionDelegate?
+
+  enum CellAction {
+    case Agree
+    case WebView
+  }
 
   struct Input {
+    let viewDidAppear: Driver<Void>
     let agreeAllBtn: Driver<Void>
-    let tosAgreeBtn: Driver<Void>
-    let showTosDetailBtn: Driver<Void>
-    let privacyAgreeBtn: Driver<Void>
-    let showPrivacyDetailBtn: Driver<Void>
-    let locationServiceAgreeBtn: Driver<Void>
-    let showLocationServiceDetailBtn: Driver<Void>
-    let marketingServiceAgreeBtn: Driver<Void>
+    let cellTap: Driver<(IndexPath, CellAction)>
     let nextBtn: Driver<Void>
   }
 
   struct Output {
-    let agreeAllRowImage: Driver<DSKitImages>
-    let tosAgreeRowImage: Driver<DSKitImages>
-    let privacyAgreeRowImage: Driver<DSKitImages>
-    let locationServiceAgreeRowImage: Driver<DSKitImages>
-    let marketingServiceRowImage: Driver<DSKitImages>
+    let agreeAllBtnStatus: Driver<Bool>
+    let cellViewModels: Driver<[ServiceAgreementRowViewModel]>
     let nextBtnStatus: Driver<Bool>
-    let nextButtonTap: Driver<Void>
-    let disposeble: Disposable
   }
 
   struct AgreeStatus {
@@ -57,96 +48,144 @@ final class PolicyAgreementViewModel: ViewModelType {
     }
   }
 
-  func transform(input: Input) -> Output {
-    let agreeStatus = BehaviorRelay<AgreeStatus>(value: AgreeStatus(
-      tos: false, privacy: false, location: false, marketing: false
-    ))
+  private let useCase: SignUpUseCaseInterface
+  private let userInfoUseCase: UserInfoUseCaseInterface
+  private var disposeBag = DisposeBag()
 
-    let totalStatus = input.agreeAllBtn
-      .withLatestFrom(agreeStatus.asDriver()) { _, status in
-        return status.reverse()
-      }
-      .do(onNext: { agreeStatus.accept($0) })
+  init(useCase: SignUpUseCaseInterface, userInfoUseCase: UserInfoUseCaseInterface) {
+    self.useCase = useCase
+    self.userInfoUseCase = userInfoUseCase
+  }
 
-    let tosStatus = input.tosAgreeBtn
-      .withLatestFrom(agreeStatus.asDriver()) { _, status in
-        var mutable = status
-        mutable.tos.toggle()
-        return mutable
+  func transform(input: Input) -> Output {
+    let agreeStates = BehaviorRelay<[ServiceAgreementRowViewModel]>(value: [])
+    let webViewTrigger = PublishRelay<String?>()
+
+    let userinfo = input.viewDidAppear
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
       }
-      .do { agreeStatus.accept($0) }
+      .asDriverOnErrorJustEmpty()
 
-    let privacyStatus = input.privacyAgreeBtn
-      .withLatestFrom(agreeStatus.asDriver()) { _, status in
-        var mutable = status
-        mutable.privacy.toggle()
-        return mutable
-      }
-      .do { agreeStatus.accept($0) }
+    let localAgreements = userinfo.map { $0.userAgreements }
 
-    let locationStatus = input.locationServiceAgreeBtn
-      .withLatestFrom(agreeStatus.asDriver()) { _, status in
-        var mutable = status
-        mutable.location.toggle()
-        return mutable
+    let remoteAgreements = input.viewDidAppear
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.useCase.fetchAgreements()
+          .catchAndReturn([])
+          .asObservable()
+      }
+      .asDriver(onErrorJustReturn: [])
+      .map { array in
+        array.map {
+          ServiceAgreementRowViewModel.init(model: $0, checkImage: DSKitAsset.Image.Component.check)
+        }
+      }
+    Driver.zip(localAgreements, remoteAgreements) { local, remote in
+      guard let local else { return remote }
+      var mutableRemoteArray = remote
+
+      local.forEach { key, value in
+        if let index = mutableRemoteArray.firstIndex (where: { $0.model.name == key }) {
+          mutableRemoteArray[index].checkImage = value ? DSKitAsset.Image.Component.checkSelect : DSKitAsset.Image.Component.check
+        }
+      }
+      return mutableRemoteArray
+    }.drive(agreeStates)
+      .disposed(by: disposeBag)
+
+    input.cellTap
+      .withLatestFrom(agreeStates.asDriver()) { cellAction, rows in
+        let (indexPath, action) = cellAction
+        var rows = rows
+        var row = rows[indexPath.row]
+
+        switch action {
+        case .Agree:
+          print(row.checkImage.name)
+          row.checkImage = row.checkImage.name == DSKitAsset.Image.Component.check.name ? DSKitAsset.Image.Component.checkSelect : DSKitAsset.Image.Component.check
+          rows[indexPath.row] = row
+          agreeStates.accept(rows)
+          break
+        case .WebView:
+          print(row.model.detailLink)
+          webViewTrigger.accept(row.model.detailLink)
+          break
+        }
+      }.drive()
+      .disposed(by: disposeBag)
+
+    webViewTrigger.asDriverOnErrorJustEmpty()
+      .compactMap { $0 }
+      .compactMap { URL(string: $0) }
+      .drive(with: self) { owner, url in
+        owner.delegate?.invoke(.agreementWebView(url))
+      }.disposed(by: disposeBag)
+
+    let agreeAllBtnStatus = agreeStates
+      .asDriver()
+      .map { $0.allSatisfy { row in
+        row.checkImage.name == DSKitAsset.Image.Component.checkSelect.name
+      } }
+
+    input.agreeAllBtn
+      .withLatestFrom(agreeAllBtnStatus)
+      .withLatestFrom(agreeStates.asDriver()) { buttonStatus, models in
+        models.map {
+          var row = $0
+          row.checkImage = !buttonStatus ? DSKitAsset.Image.Component.checkSelect : DSKitAsset.Image.Component.check
+          return row
+        }
+      }
+      .drive(agreeStates)
+      .disposed(by: disposeBag)
+
+    let nextBtnStatus = agreeStates
+      .asDriver()
+      .map { $0.filter { row in row.model.isRequired }
+          .allSatisfy { row in
+            row.checkImage.name == DSKitAsset.Image.Component.checkSelect.name
+          }
       }
-      .do { agreeStatus.accept($0) }
 
-    let marketingStatus = input.marketingServiceAgreeBtn
-      .withLatestFrom(agreeStatus.asDriver()) { _, status in
-        var mutable = status
-        mutable.marketing.toggle()
-        return mutable
+    input.nextBtn
+      .throttle(.milliseconds(500), latest: false)
+      .withLatestFrom(agreeStates.asDriver())
+      .filter { $0
+        .filter { row in
+          row.model.isRequired
+        }
+        .allSatisfy { row in
+          row.checkImage.name == DSKitAsset.Image.Component.checkSelect.name
+        }
       }
-      .do { agreeStatus.accept($0) }
-
-    let agreeAllRowImage = agreeStatus.asDriver()
-      .map { $0.total }
-      .map { $0 ? DSKitAsset.Image.Component.checkCirSelect : DSKitAsset.Image.Component.checkCir }
-      .asDriver(onErrorJustReturn: DSKitAsset.Image.Component.checkCir)
-
-    let tosAgreeRowImage = agreeStatus.asDriver()
-      .map { $0.tos }
-      .map { $0 ? DSKitAsset.Image.Component.checkSelect : DSKitAsset.Image.Component.check }
-      .asDriver(onErrorJustReturn: DSKitAsset.Image.Component.check)
-
-    let privacyAgreeRowImage = agreeStatus.asDriver()
-      .map { $0.privacy }
-      .map { $0 ? DSKitAsset.Image.Component.checkSelect : DSKitAsset.Image.Component.check }
-      .asDriver(onErrorJustReturn: DSKitAsset.Image.Component.check)
-
-    let locationServiceAgreeRowImage = agreeStatus.asDriver()
-      .map { $0.location }
-      .map { $0 ? DSKitAsset.Image.Component.checkSelect : DSKitAsset.Image.Component.check }
-      .asDriver(onErrorJustReturn: DSKitAsset.Image.Component.check)
-
-    let marketingServiceRowImage = agreeStatus.asDriver()
-      .map { $0.marketing }
-      .map { $0 ? DSKitAsset.Image.Component.checkSelect : DSKitAsset.Image.Component.check }
-      .asDriver(onErrorJustReturn: DSKitAsset.Image.Component.check)
-
-    let nextBtnStatus = agreeStatus.asDriver()
-      .debug("agreeStatus")
-      .map { $0.isValid }
-
-    let nextButtonTap = input.nextBtn
-      .do(onNext: { [weak self] in
-        self?.delegate?.policyNextButtonTap()
+      .map { $0.map {
+        let key = $0.model.name
+        let value = $0.checkImage.name == DSKitAsset.Image.Component.checkSelect.name ? true : false
+        return (key, value)
+      } }
+      .map { Dictionary(uniqueKeysWithValues: $0) }
+      .withLatestFrom(userinfo) { agreements, userinfo in
+        var mutableUserInfo = userinfo
+        mutableUserInfo.userAgreements = agreements
+        return mutableUserInfo
+      }
+      .drive(with: self, onNext: { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtPolicy)
       })
-
-    // TODO: Disposable로 만들어서 VC로 넘겨야할지 여기서 Disposed해야할지 아니면
-    // 더 좋은 방법이 있을지 고민
-    let disposeble = Driver.merge(totalStatus, tosStatus, privacyStatus, locationStatus, marketingStatus).drive()
+      .disposed(by: disposeBag)
 
     return Output(
-      agreeAllRowImage: agreeAllRowImage,
-      tosAgreeRowImage: tosAgreeRowImage,
-      privacyAgreeRowImage: privacyAgreeRowImage,
-      locationServiceAgreeRowImage: locationServiceAgreeRowImage,
-      marketingServiceRowImage: marketingServiceRowImage,
-      nextBtnStatus: nextBtnStatus,
-      nextButtonTap: nextButtonTap,
-      disposeble: disposeble
+      agreeAllBtnStatus: agreeAllBtnStatus,
+      cellViewModels: agreeStates.asDriver(),
+      nextBtnStatus: nextBtnStatus
     )
   }
 }
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerView.swift b/Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerView.swift
new file mode 100644
index 00000000..b927e568
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerView.swift
@@ -0,0 +1,121 @@
+//
+//  PreferGenderPickerView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/18.
+//
+import UIKit
+
+import DSKit
+import RxSwift
+import SignUpInterface
+
+final class PreferGenderPickerView: TFBaseView {
+  lazy var container = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+
+  lazy var genderPickerView = TFButtonPickerView(
+    title: "선호 성별을 알려주세요.", targetString: "선호 성별",
+    options: GenderMapper.options
+  )
+
+  lazy var infoImageView: UIImageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
+    $0.tintColor = DSKitAsset.Color.neutral400.color
+  }
+
+  lazy var descLabel: UILabel = UILabel().then {
+    $0.text = "마이페이지에서 변경가능합니다."
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 1
+  }
+
+  lazy var nextBtn = CTAButton(btnTitle: "->", initialStatus: false)
+
+  override func makeUI() {
+    addSubview(container)
+
+    container.addSubviews(
+      genderPickerView,
+      infoImageView, descLabel,
+      nextBtn
+    )
+    container.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+      $0.bottom.equalToSuperview()
+    }
+
+    genderPickerView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(76)
+      $0.leading.trailing.equalToSuperview().inset(0)
+    }
+
+    infoImageView.snp.makeConstraints {
+      $0.leading.equalTo(genderPickerView.snp.leading).offset(30)
+      $0.width.height.equalTo(16)
+      $0.top.equalTo(genderPickerView.snp.bottom).offset(10)
+    }
+
+    descLabel.snp.makeConstraints {
+      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
+      $0.top.equalTo(infoImageView)
+      $0.trailing.equalToSuperview().inset(38)
+    }
+    nextBtn.snp.makeConstraints {
+      $0.top.equalTo(descLabel.snp.bottom).offset(30)
+      $0.trailing.equalTo(descLabel)
+      $0.height.equalTo(50)
+      $0.width.equalTo(88)
+    }
+  }
+}
+
+extension Reactive where Base: PreferGenderPickerView {
+  var selectedGender: Binder<Gender> {
+    Binder(base.self) { view, gender in
+      view.genderPickerView.handleSelectedState(GenderMapper.toOption(gender))
+    }
+  }
+}
+
+struct GenderMapper {
+  static func toOption(_ gender: Gender) -> TFButtonPickerView.Option {
+    switch gender {
+    case .female:
+      return .init(key: 0, value: "여자")
+    case .male:
+      return .init(key: 1, value: "남자")
+    case .both:
+      return .init(key: 2, value: "모두")
+    }
+  }
+
+  static func toGender(_ option: TFButtonPickerView.Option) -> Gender {
+    switch option.key {
+    case 0: return .female
+    case 1: return .male
+    default: return .both
+    }
+  }
+
+  static var options: [String] {
+    return ["여자", "남자", "모두"]
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct PreferGenderPickerViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      return PreferGenderPickerView()
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerViewController.swift
new file mode 100644
index 00000000..316dd50c
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerViewController.swift
@@ -0,0 +1,59 @@
+//
+//  PreferGenderPickerViewController.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/18.
+//
+
+import UIKit
+
+import DSKit
+import SignUpInterface
+
+import RxSwift
+import RxCocoa
+
+final class PreferGenderPickerViewController: TFBaseViewController {
+  private let mainView = PreferGenderPickerView()
+  private let viewModel: PreferGenderPickerViewModel
+
+  init(viewModel: PreferGenderPickerViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func bindViewModel() {
+    let genderTap = self.mainView.genderPickerView
+      .rx.selectedOption
+      .asDriver()
+      .compactMap { $0 }
+      .compactMap { GenderMapper.toGender($0) }
+
+    let input = PreferGenderPickerViewModel.Input(
+      genderTap: genderTap,
+      nextBtnTap: self.mainView.nextBtn.rx.tap.asDriver()
+    )
+
+    let output = viewModel.transform(input: input)
+
+    output.isNextBtnEnabled
+      .drive(with: self) { owner, status in
+        owner.mainView.nextBtn.updateColors(status: status)
+      }
+      .disposed(by: disposeBag)
+
+    output.initialGender
+      .debug("initialGender")
+      .drive(mainView.rx.selectedGender)
+      .disposed(by: disposeBag)
+  }
+}
+
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerViewModel.swift
new file mode 100644
index 00000000..f4daf5c0
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/PreferGenderPick/PreferGenderPickerViewModel.swift
@@ -0,0 +1,82 @@
+//
+//  PreferGenderPickerViewModel.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/18.
+//
+
+import Foundation
+
+import Core
+import SignUpInterface
+
+import RxSwift
+import RxCocoa
+
+final class PreferGenderPickerViewModel: ViewModelType {
+  weak var delegate: SignUpCoordinatingActionDelegate?
+  private let userInfoUseCase: UserInfoUseCaseInterface
+
+  struct Input {
+    var genderTap: Driver<Gender>
+    var nextBtnTap: Driver<Void>
+  }
+
+  struct Output {
+    var isNextBtnEnabled: Driver<Bool>
+    var initialGender: Driver<Gender>
+  }
+
+  private var disposeBag = DisposeBag()
+
+  init(userInfoUseCase: UserInfoUseCaseInterface) {
+    self.userInfoUseCase = userInfoUseCase
+  }
+
+  deinit {
+    print("deinit: PreferGenderPickerViewModel")
+  }
+
+  func transform(input: Input) -> Output {
+
+    let userinfo = Driver.just(())
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+      .debug("fetched UserInfo")
+
+    let initialGender = userinfo.map { $0.preferGender }
+      .compactMap { $0 }
+      .debug("fetched preferGender")
+
+    let selectedGender = input.genderTap
+      .debug("tapped Gender")
+
+    let nextBtnisEnabled = selectedGender.map { _ in true }
+
+    input.nextBtnTap
+      .throttle(.milliseconds(500), latest: false)
+      .withLatestFrom(selectedGender)
+      .debug("toSave preferGender")
+      .withLatestFrom(userinfo) { preferGender, info in
+        var mutable = info
+        mutable.preferGender = preferGender
+        print("mutable - preferGender: \(preferGender)")
+        return mutable
+      }
+      .drive(with: self) { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtPreferGender)
+      }.disposed(by: disposeBag)
+
+    return Output(
+      isNextBtnEnabled: nextBtnisEnabled,
+      initialGender: initialGender
+    )
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionPickerView.swift b/Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionPickerView.swift
new file mode 100644
index 00000000..b1a24273
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionPickerView.swift
@@ -0,0 +1,111 @@
+//
+//  ReligionPickerView.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import UIKit
+
+import DSKit
+
+final class ReligionPickerView: TFBaseView {
+  lazy var container = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+
+  lazy var titleLabel: UILabel = UILabel().then {
+    $0.text = "종교를 알려주세요"
+    $0.textColor = DSKitAsset.Color.neutral300.color
+    $0.font = .thtH1B
+    $0.asColor(targetString: "종교", color: DSKitAsset.Color.neutral50.color)
+  }
+
+  lazy var ReligionPickerView = UICollectionView(
+    frame: .zero,
+    collectionViewLayout: UICollectionViewFlowLayout()
+      .then {
+        $0.itemSize = CGSize(width: 90, height: 60)
+      }
+  ).then {
+    $0.register(cellType: ReligionPickerCell.self)
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+    $0.showsHorizontalScrollIndicator = false
+    $0.showsVerticalScrollIndicator = false
+    $0.isScrollEnabled = false
+  }
+
+  lazy var infoImageView: UIImageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
+    $0.tintColor = DSKitAsset.Color.neutral400.color
+  }
+
+  lazy var descLabel: UILabel = UILabel().then {
+    $0.text = "마이페이지에서 변경가능합니다."
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 1
+  }
+
+  lazy var nextBtn = CTAButton(btnTitle: "->", initialStatus: false)
+
+  override func makeUI() {
+    addSubview(container)
+
+    container.addSubviews(
+      titleLabel,
+      ReligionPickerView,
+      infoImageView, descLabel,
+      nextBtn
+    )
+    
+    container.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+      $0.bottom.equalToSuperview()
+    }
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(76)
+      $0.leading.trailing.equalToSuperview().inset(30)
+    }
+
+    ReligionPickerView.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(35)
+      $0.leading.trailing.equalToSuperview().inset(30)
+      $0.height.equalTo(140)
+    }
+
+    infoImageView.snp.makeConstraints {
+      $0.leading.equalTo(titleLabel.snp.leading)
+      $0.width.height.equalTo(16)
+      $0.top.equalTo(ReligionPickerView.snp.bottom).offset(16)
+    }
+
+    descLabel.snp.makeConstraints {
+      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
+      $0.top.equalTo(ReligionPickerView.snp.bottom).offset(16)
+      $0.trailing.equalToSuperview().inset(38)
+    }
+    nextBtn.snp.makeConstraints {
+      $0.top.equalTo(descLabel.snp.bottom).offset(30)
+      $0.trailing.equalTo(descLabel)
+      $0.height.equalTo(50)
+      $0.width.equalTo(88)
+    }
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct ReligionPickerViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      return ReligionPickerView()
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionPickerViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionPickerViewController.swift
new file mode 100644
index 00000000..f278c129
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionPickerViewController.swift
@@ -0,0 +1,59 @@
+//
+//  ReligionPickerViewController.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import UIKit
+
+import DSKit
+
+import RxSwift
+import RxCocoa
+
+final class ReligionPickerViewController: TFBaseViewController {
+  typealias VMType = ReligionPickerViewModel
+  private(set) var mainView = ReligionPickerView()
+
+  private let viewModel: VMType
+
+  init(viewModel: VMType) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func bindViewModel() {
+
+    let nextBtnTap = mainView.nextBtn.rx.tap.asDriver()
+
+    let input = VMType.Input(
+      chipTap: mainView.ReligionPickerView.rx.itemSelected.asDriver(),
+      nextBtnTap: nextBtnTap
+    )
+
+    let output = viewModel.transform(input: input)
+
+    output.chips
+      .debug("chips")
+      .drive(mainView.ReligionPickerView.rx.items(cellType: ReligionPickerCell.self)) { index, item, cell in
+        cell.bind(item.0)
+        cell.updateCell(item.1)
+      }
+      .disposed(by: disposeBag)
+
+    output.isNextBtnEnabled
+      .drive(with: self) { owner, status in
+        owner.mainView.nextBtn.updateColors(status: status)
+      }
+      .disposed(by: disposeBag)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionViewModel.swift
new file mode 100644
index 00000000..7728059a
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/Religion/ReligionViewModel.swift
@@ -0,0 +1,127 @@
+//
+//  ReligionViewModel.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import Foundation
+
+import Core
+
+import RxSwift
+import RxCocoa
+import Domain
+import SignUpInterface
+
+final class ReligionPickerViewModel: ViewModelType {
+  weak var delegate: SignUpCoordinatingActionDelegate?
+  private let userInfoUseCase: UserInfoUseCaseInterface
+
+  struct Input {
+    var chipTap: Driver<IndexPath>
+    var nextBtnTap: Driver<Void>
+  }
+
+  struct Output {
+    var chips: Driver<[(Religion, Bool)]>
+    var isNextBtnEnabled: Driver<Bool>
+  }
+
+  private var disposeBag = DisposeBag()
+
+  init(userInfoUseCase: UserInfoUseCaseInterface) {
+    self.userInfoUseCase = userInfoUseCase
+  }
+
+  func transform(input: Input) -> Output {
+    let userinfo = Driver.just(())
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .catchAndReturn(UserInfo(phoneNumber: ""))
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    let initialReligion = userinfo.compactMap { $0.religion }
+
+    let chips = BehaviorRelay<[(Religion, Bool)]>(value: Religion.allCases.map { ($0, false) })
+
+    initialReligion
+      .withLatestFrom(chips.asDriver()) { initial, chips in
+        var mutable = chips
+        guard let index = chips.firstIndex(where: { (item, _) in
+          item == initial
+        }) else { return mutable}
+        mutable[index] = (initial, true)
+        return mutable
+      }
+      .drive(chips)
+      .disposed(by: disposeBag)
+
+    input.chipTap
+      .withLatestFrom(chips.asDriver()) { indexPath, array in
+        var mutable = array.map { ($0.0, false) }
+        mutable[indexPath.row] = (mutable[indexPath.row].0, true)
+        return mutable
+      }
+      .debug("tap chips")
+      .drive(chips)
+      .disposed(by: disposeBag)
+
+    let selectedChip = chips
+      .asDriver()
+      .map {
+      $0.first { (_, isSelected) in
+        isSelected
+      }.map { $0.0 }
+    }.compactMap { $0 }
+
+    let isNextBtnEnabled = chips
+      .asDriver()
+      .map { $0.map { $0.1 } }
+      .map {  
+        $0.filter { $0 == true }.count == 1
+      }
+
+    input.nextBtnTap
+      .withLatestFrom(isNextBtnEnabled)
+      .filter { $0 }
+      .withLatestFrom(selectedChip)
+      .withLatestFrom(userinfo) { item, userinfo in
+        var mutable = userinfo
+        mutable.religion = item
+        return mutable
+      }
+      .drive(with: self, onNext: { owner, userinfo in
+        owner.userInfoUseCase.updateUserInfo(userInfo: userinfo)
+        owner.delegate?.invoke(.nextAtReligion)
+        })
+      .disposed(by: disposeBag)
+
+    return Output(
+      chips: chips.asDriver(),
+      isNextBtnEnabled: isNextBtnEnabled
+    )
+  }
+}
+
+extension Religion: CaseIterable {
+  public static var allCases: [Religion] = [
+    .none, .christian, .buddhism,
+    .catholic, .wonBuddhism, .other
+  ]
+
+  public var label: String {
+    switch self {
+    case .none: return "무교"
+    case .christian: return "기독교"
+    case .buddhism: return "불교"
+    case .catholic: return "천주교"
+    case .wonBuddhism: return "원불교"
+    case .other: return "기타"
+    }
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteView.swift b/Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteView.swift
new file mode 100644
index 00000000..ac03b40a
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteView.swift
@@ -0,0 +1,128 @@
+//
+//  SignUpCompleteView.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 5/28/24.
+//
+
+import UIKit
+
+import DSKit
+
+final class SignUpCompleteView: TFBaseView {
+  lazy var containerView = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+
+  lazy var titleLabel: UILabel = UILabel().then {
+    $0.text = "환영해요! 💫\n모든 준비가 끝났어요"
+    $0.textColor = DSKitAsset.Color.neutral50.color
+    $0.font = .thtH2B
+    $0.numberOfLines = 0
+    $0.textAlignment = .center
+  }
+
+  private lazy var conicGradient: CAGradientLayer = {
+    let gradient = CAGradientLayer()
+    gradient.type = .conic
+    gradient.colors = [
+      DSKitAsset.Color.primary500.color.cgColor,
+      DSKitAsset.Color.thtOrange400.color.cgColor
+    ]
+    gradient.locations = [0]
+
+    // startPoint: 원의 중심, endPoint: 첫 번째 색상과 마지막 색상이 결합되는 지점
+    // (0,0)우측하단, (1,1)은 (0,0)에서 한바퀴 돌은 지점
+    gradient.startPoint = CGPoint(x: 0.5, y: 0.5)
+    gradient.endPoint = CGPoint(x: 0.5, y: 1)
+    return gradient
+  }()
+
+  private lazy var gradientView = UIView().then {
+    $0.backgroundColor = .green
+    $0.layer.addSublayer(conicGradient)
+    $0.layer.cornerRadius = 32
+    $0.clipsToBounds = true
+  }
+
+  lazy var descLabel = UILabel().then {
+    $0.text = "폴링에 한 번 빠져 보시겠어요"
+    $0.font = .thtSubTitle1R
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .center
+    $0.numberOfLines = 2
+  }
+
+  lazy var imageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Test.test1.image
+    $0.contentMode = .scaleAspectFill
+    $0.clipsToBounds = true
+    $0.layer.cornerRadius = 20
+    $0.layer.borderColor = DSKitAsset.Color.neutral700.color.cgColor
+    $0.layer.borderWidth = 10
+  }
+
+  lazy var nextBtn = CTAButton(btnTitle: "네, 좋아요", initialStatus: true)
+
+  override func makeUI() {
+    addSubview(containerView)
+    containerView.addSubviews(
+      titleLabel,
+      descLabel,
+      gradientView,
+      imageView,
+      nextBtn
+    )
+
+    containerView.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+      $0.bottom.equalToSuperview()
+    }
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().inset(76)
+      $0.leading.trailing.equalToSuperview().inset(38)
+    }
+
+    descLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(20)
+      $0.leading.trailing.equalToSuperview().inset(38)
+    }
+    gradientView.snp.makeConstraints {
+      $0.center.equalToSuperview()
+      $0.size.equalTo(250)
+    }
+
+    imageView.snp.makeConstraints {
+      $0.center.equalToSuperview()
+      $0.size.equalTo(235)
+    }
+
+    nextBtn.snp.makeConstraints {
+      $0.leading.trailing.equalToSuperview().inset(20)
+      $0.bottom.equalToSuperview().offset(-60)
+      $0.height.equalTo(54)
+    }
+
+  }
+
+  override func layoutSubviews() {
+    super.layoutSubviews()
+    conicGradient.frame = gradientView.bounds
+  }
+}
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct SignUpCompleteViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      let component = SignUpCompleteView()
+      return component
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
+
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteViewController.swift
new file mode 100644
index 00000000..10afe77f
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteViewController.swift
@@ -0,0 +1,55 @@
+//
+//  SignUpCompleteViewController.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 5/28/24.
+//
+
+import UIKit
+import DSKit
+
+final class SignUpCompleteViewController: TFBaseViewController {
+  typealias ViewModel = SignUpCompleteViewModel
+
+  private let mainView = SignUpCompleteView()
+  private let viewModel: ViewModel
+
+  init(viewModel: ViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func bindViewModel() {
+    let input = ViewModel.Input(
+      nextBtnTap: mainView.nextBtn.rx.tap.asDriver()
+    )
+
+    let output = viewModel.transform(input: input)
+
+    output.toast
+      .emit(with: self) { owner, message in
+        let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert)
+
+        owner.present(alert, animated: true)
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+          owner.dismiss(animated: true)
+        }
+      }
+      .disposed(by: disposeBag)
+
+    output.profileImage
+      .compactMap { $0 }
+      .drive(with: self, onNext: { owner, data in
+        owner.mainView.imageView.image = UIImage(data: data)
+      })
+      .disposed(by: disposeBag)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteViewModel.swift
new file mode 100644
index 00000000..1a9f3fee
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/SignUpComplete/SignUpCompleteViewModel.swift
@@ -0,0 +1,130 @@
+//
+//  SignUpCompleteViewModel.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 5/28/24.
+//
+
+import Foundation
+
+import Core
+
+import RxSwift
+import RxCocoa
+
+import SignUpInterface
+
+final class SignUpCompleteViewModel: ViewModelType {
+  private let useCase: SignUpUseCaseInterface
+  private let userInfoUseCase: UserInfoUseCaseInterface
+  private let contacts: [ContactType]
+  weak var delegate: SignUpCoordinatingActionDelegate?
+  private let disposeBag = DisposeBag()
+
+  init(useCase: SignUpUseCaseInterface, userInfoUseCase: UserInfoUseCaseInterface, contacts: [ContactType]) {
+    self.useCase = useCase
+    self.userInfoUseCase = userInfoUseCase
+    self.contacts = contacts
+  }
+
+  struct Input {
+    let nextBtnTap: Driver<Void>
+  }
+
+  struct Output {
+    let loadTrigger: Driver<Bool>
+    let toast: Signal<String>
+    let profileImage: Driver<Data?>
+  }
+
+  func transform(input: Input) -> Output {
+    let toast = PublishSubject<String>()
+    let loadTrigger = PublishRelay<Bool>()
+
+    let contacts = Driver.just(self.contacts)
+
+    let userinfo = Driver.just(())
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        owner.userInfoUseCase.fetchUserInfo()
+          .asObservable()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    let photos = userinfo.map { $0.photos }
+
+    let phoneNum = userinfo.map { $0.phoneNumber }
+
+    let imagesData = photos
+      .withLatestFrom(phoneNum) { (key: $1, filenames: $0) }
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, components in
+        owner.userInfoUseCase.fetchUserPhotos(key: components.key, fileNames: components.filenames)
+          .catch { error in
+            toast.onNext("사진을 불러오는데 실패했습니다.")
+            return .just([])
+          }
+          .asObservable()
+      }.asDriverOnErrorJustEmpty()
+
+    // 이미지 업로드
+    // SignUpRequest
+
+    let imageUrls = input.nextBtnTap
+      .throttle(.milliseconds(500), latest: false)
+      .withLatestFrom(imagesData)
+      .asObservable()
+      .withUnretained(self)
+      .flatMapLatest { owner, data in
+        owner.useCase.uploadImage(data: data)
+          .catch { error in
+            toast.onNext("이미지 업로드에 실패했습니다.")
+            return .just([])
+          }
+      }
+      .asDriverOnErrorJustEmpty()
+
+    let signUpRequest = Driver.combineLatest(imageUrls, userinfo, contacts) { urls, userinfo, contacts in
+        var mutable = userinfo
+        mutable.photos = urls
+      return mutable.toRequest(contacts: contacts)
+      }
+      .flatMap { requestOrNil -> Driver<SignUpReq> in
+        guard let request = requestOrNil else {
+          toast.onNext("입력하신 회원 정보가 올바르지 않습니다.")
+          return Driver.empty()
+        }
+        return Driver.just(request)
+      }
+      .debug("signUpRequest")
+
+    let signUpResult = signUpRequest
+      .asObservable()
+      .withUnretained(self)
+      .flatMap { owner, request in
+        owner.useCase.signUp(request: request)
+          .asObservable()
+          .catch { error in
+            toast.onNext("회원가입에 실패했습니다.")
+            return .empty()
+          }
+      }
+      .asDriverOnErrorJustEmpty()
+
+    signUpResult
+      .withLatestFrom(phoneNum)
+      .drive(with: self) { owner, phoneNum in
+        owner.userInfoUseCase.savePhoneNumber(phoneNum)
+        owner.delegate?.invoke(.nextAtSignUpComplete)
+      }
+      .disposed(by: disposeBag)
+
+    return Output(
+      loadTrigger: loadTrigger.asDriverOnErrorJustEmpty(),
+      toast: toast.asSignal(onErrorSignalWith: .empty()),
+      profileImage: imagesData.map { $0.first }
+    )
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/SignUpRootViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/SignUpRootViewModel.swift
deleted file mode 100644
index 065e0809..00000000
--- a/Projects/Features/SignUp/Src/SignUpRoot/SignUpRootViewModel.swift
+++ /dev/null
@@ -1,66 +0,0 @@
-//
-//  SignUpViewModel.swift
-//  Falling
-//
-//  Created by Hoo's MacBookPro on 2023/07/22.
-//
-
-import Foundation
-
-import RxSwift
-import RxCocoa
-
-import Core
-
-// FIXME: Refactor Need
-protocol SignUpRootDelegate: AnyObject {
-  func toPhoneButtonTap()
-}
-
-final class SignUpRootViewModel: ViewModelType {
-  struct Input {
-    let phoneBtn: Driver<Void>
-    let kakaoBtn: Driver<Void>
-    let googleBtn: Driver<Void>
-    let naverBtn: Driver<Void>
-  }
-
-  struct Output {
-
-  }
-
-  weak var delegate: SignUpRootDelegate?
-
-  var disposeBag: DisposeBag = DisposeBag()
-
-  func transform(input: Input) -> Output {
-    input.phoneBtn
-      .drive(with: self, onNext: { owner, _ in
-        owner.delegate?.toPhoneButtonTap()
-      })
-      .disposed(by: disposeBag)
-
-    input.kakaoBtn
-      .drive(onNext: { [weak self] in
-        guard let self else { return }
-        print("kakao button")
-      })
-      .disposed(by: disposeBag)
-
-    input.naverBtn
-      .drive(onNext: { [weak self] in
-        guard let self else { return }
-        print("naver button")
-      })
-      .disposed(by: disposeBag)
-
-    input.googleBtn
-      .drive(onNext: { [weak self] in
-        guard let self else { return }
-        print("google button")
-      })
-      .disposed(by: disposeBag)
-
-    return Output()
-  }
-}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactView.swift b/Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactView.swift
new file mode 100644
index 00000000..53683613
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactView.swift
@@ -0,0 +1,86 @@
+//
+//  UserContactView.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/2/24.
+//
+
+import UIKit
+
+import DSKit
+
+final class UserContactView: TFBaseView {
+  lazy var containerView = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+  
+  lazy var titleLabel: UILabel = UILabel().then {
+    $0.text = "혹시 폴링에서\n아는 사람을 만날까봐\n걱정되시나요?"
+    $0.textColor = DSKitAsset.Color.neutral50.color
+    $0.font = .thtH2B
+    $0.numberOfLines = 0
+  }
+  
+  lazy var descLabel = UILabel().then {
+    $0.text = "폴링에서 아는 사람을 마주치고 싶지 않다면\n해당 기능을 켜주세요."
+    $0.font = .thtSubTitle1R
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 2
+  }
+  
+  lazy var blockBtn = CTAButton(btnTitle: "아는 사람 만나지 않기", initialStatus: true)
+  lazy var layterBtn = CTAButton(btnTitle: "나중에 하기", initialStatus: false)
+  
+  override func makeUI() {
+    addSubview(containerView)
+    containerView.addSubviews(
+      titleLabel,
+      descLabel,
+      blockBtn,
+      layterBtn
+    )
+    
+    containerView.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+      $0.bottom.equalToSuperview()
+    }
+    
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().inset(76)
+      $0.leading.equalToSuperview().inset(38)
+    }
+    
+    descLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(20)
+      $0.leading.trailing.equalToSuperview().inset(38)
+    }
+    
+    blockBtn.snp.makeConstraints {
+      $0.leading.trailing.equalToSuperview().inset(20)
+      $0.bottom.equalTo(layterBtn.snp.top).offset(-16)
+      $0.height.equalTo(54)
+    }
+    
+    layterBtn.snp.makeConstraints {
+      $0.leading.trailing.equalToSuperview().inset(20)
+      $0.bottom.equalToSuperview().offset(-60)
+      $0.height.equalTo(54)
+    }
+  }
+}
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct InputViewPreview: PreviewProvider {
+  
+  static var previews: some View {
+    UIViewPreview {
+      let component = UserContactView()
+      return component
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
+
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactViewController.swift b/Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactViewController.swift
new file mode 100644
index 00000000..4a0f9668
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactViewController.swift
@@ -0,0 +1,54 @@
+//
+//  UserContactViewController.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/2/24.
+//
+
+import UIKit
+import DSKit
+
+final class UserContactViewController: TFBaseViewController {
+  typealias ViewModel = UserContactViewModel
+  typealias Action = ViewModel.Action
+  
+  private let mainView = UserContactView()
+  private let viewModel: UserContactViewModel
+  
+  init(viewModel: UserContactViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+  
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+  
+  override func loadView() {
+    self.view = mainView
+  }
+  
+  override func bindViewModel() {
+    let block = mainView.blockBtn.rx.tap
+      .asDriver()
+      .map { Action.block }
+
+    let skip = mainView.layterBtn.rx.tap
+      .asDriver()
+      .map { Action.skip }
+    let actiontrigger = Driver.merge(block, skip)
+    let input = UserContactViewModel.Input(actionTrigger: actiontrigger)
+    let output = viewModel.transform(input: input)
+    
+    output.toast
+      .emit(with: self) { owner, message in
+        let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert)
+
+        owner.present(alert, animated: true)
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+          owner.dismiss(animated: true)
+        }
+      }
+      .disposed(by: disposeBag)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactViewModel.swift b/Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactViewModel.swift
new file mode 100644
index 00000000..5d3b1a2d
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SignUpRoot/UserContact/UserContactViewModel.swift
@@ -0,0 +1,90 @@
+//
+//  UserContactViewModel.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/2/24.
+//
+
+import Foundation
+
+import Core
+
+import RxSwift
+import RxCocoa
+
+import SignUpInterface
+
+final class UserContactViewModel: ViewModelType {
+  private let useCase: SignUpUseCaseInterface
+  weak var delegate: SignUpCoordinatingActionDelegate?
+  private let disposeBag = DisposeBag()
+
+  enum Action {
+    case block
+    case skip
+  }
+
+  struct Input {
+    let actionTrigger: Driver<Action>
+  }
+  
+  struct Output {
+    let toast: Signal<String>
+  }
+  
+  init(useCase: SignUpUseCaseInterface) {
+    self.useCase = useCase
+  }
+  
+  func transform(input: Input) -> Output {
+    let toast = PublishSubject<String>()
+    let nextTrigger = PublishSubject<Void>()
+    let blockTrigger = PublishSubject<Void>()
+    let fetchedContacts = BehaviorRelay<[ContactType]>(value: [])
+
+    input.actionTrigger
+      .drive(onNext: { action in
+        switch action {
+        case .block:
+          blockTrigger.onNext(())
+        case .skip:
+          nextTrigger.onNext(())
+        }
+      })
+      .disposed(by: disposeBag)
+    
+    blockTrigger
+      .observe(on: ConcurrentDispatchQueueScheduler(qos: .background))
+      .flatMapLatest { [unowned self] _ in
+        self.useCase.block()
+          .flatMap { contacts in
+            fetchedContacts.accept(contacts)
+            toast.onNext("\(contacts.count)개의 연락처를 차단했습니다.")
+            return .just(true)
+          }
+          .catch { error in
+            toast.onNext("친구 목록을 불러오는데 실패했습니다. 다시 시도해주세요.")
+            return .just(false)
+          }
+      }
+      .asDriver(onErrorJustReturn: false)
+      .filter { $0 }
+      .map { _ in }
+      .delay(.seconds(2))
+      .drive(nextTrigger)
+      .disposed(by: disposeBag)
+    // TODO: Block 성공하면 toast 띄우고, 2초 뒤 next, 실패하면 toast 띄우고, next 안함
+    
+    nextTrigger
+      .withLatestFrom(fetchedContacts)
+      .asDriverOnErrorJustEmpty()
+      .drive(with: self) { owner, contacts in
+        owner.delegate?.invoke(.nextAtHideFriends(contacts))
+      }
+      .disposed(by: disposeBag)
+    
+    return Output(
+      toast: toast.asSignal(onErrorJustReturn: "")
+    )
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerAdapter.swift b/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerAdapter.swift
new file mode 100644
index 00000000..d5c892f2
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerAdapter.swift
@@ -0,0 +1,51 @@
+//
+//  PickerAdapter.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/16.
+//
+
+import UIKit
+
+import DSKit
+import RxSwift
+import RxCocoa
+
+final class PickerViewViewAdapter
+: NSObject
+, UIPickerViewDataSource
+, UIPickerViewDelegate
+, RxPickerViewDataSourceType
+, SectionedViewDataSourceType {
+  typealias Element = [[Int]]
+
+  private var items: Element = []
+
+  func model(at indexPath: IndexPath) throws -> Any {
+    items[indexPath.section][indexPath.row]
+  }
+
+  func numberOfComponents(in pickerView: UIPickerView) -> Int {
+    items.count
+  }
+
+  func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
+    items[component].count
+  }
+
+  func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
+    let label: UILabel = (view as? UILabel) ?? UILabel()
+    label.text = "\(items[component][row])"
+    label.textColor = DSKitAsset.Color.primary500.color
+    label.font = UIFont.thtH2B
+    label.textAlignment = .center
+    return label
+  }
+
+  func pickerView(_ pickerView: UIPickerView, observedEvent: Event<Element>) {
+    Binder(self) { (adapter, items) in
+      adapter.items = items
+      pickerView.reloadAllComponents()
+    }.on(observedEvent)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheet.swift b/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheet.swift
new file mode 100644
index 00000000..bb1290b3
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheet.swift
@@ -0,0 +1,60 @@
+//
+//  PickerButtonSheet.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/16.
+//
+
+import UIKit
+
+import Core
+import DSKit
+
+import RxSwift
+import RxCocoa
+
+final class PickerBottomSheet: TFBaseViewController {
+
+  private let mainView = PickerBottomSheetView()
+  private let viewModel: PickerBottomSheetViewModel
+
+  init(viewModel: PickerBottomSheetViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+  
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+  
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func viewDidLayoutSubviews() {
+    self.mainView.drawSelectedLine()
+    self.navigationController?.isNavigationBarHidden = true
+  }
+
+  override func bindViewModel() {
+    let itemSelected = mainView.pickerView.rx.itemSelected.asDriver()
+
+    let confirmTap = mainView.initializeButton.rx.tap.asDriver()
+//
+    let input = PickerBottomSheetViewModel.Input(
+      selectedItem: itemSelected,
+      initializeButtonTap: confirmTap
+    )
+//
+    let output = viewModel.transform(input: input)
+
+    output.items
+      .drive(mainView.pickerView.rx.items(adapter: PickerViewViewAdapter()))
+      .disposed(by: disposeBag)
+    output.initialDate
+      .drive(with: self) { owner, components in
+        owner.mainView.pickerView.selectRow(components.0, inComponent: components.1, animated: false)
+      }
+      .disposed(by: disposeBag)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheetView.swift b/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheetView.swift
new file mode 100644
index 00000000..3a99d6f7
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheetView.swift
@@ -0,0 +1,88 @@
+//
+//  PickerBottomSheetView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/16.
+//
+
+import UIKit
+
+import Core
+import DSKit
+
+class PickerBottomSheetView: TFBaseView {
+
+  private(set) lazy var pickerView: UIPickerView = {
+    let pickerView = UIPickerView()
+
+    return pickerView
+  }()
+
+  private(set) lazy var buttonHStackView: UIStackView = {
+    let stackView = UIStackView()
+    stackView.axis = .horizontal
+    stackView.spacing = 10
+    stackView.distribution = .fillEqually
+    return stackView
+  }()
+
+  private(set) lazy var initializeButton = CTAButton(btnTitle: "확인", initialStatus: true)
+
+  override func makeUI() {
+    self.backgroundColor = DSKitAsset.Color.neutral600.color
+
+    addSubviews(pickerView, buttonHStackView)
+
+    [initializeButton].forEach {
+      buttonHStackView.addArrangedSubview($0)
+    }
+
+    pickerView.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+    }
+
+    buttonHStackView.snp.makeConstraints {
+      $0.top.equalTo(pickerView.snp.bottom).offset(10)
+      $0.height.equalTo(50)
+      $0.leading.trailing.equalTo(safeAreaLayoutGuide).inset(30)
+      $0.bottom.equalTo(self.safeAreaLayoutGuide)
+    }
+
+    self.clipsToBounds = true
+  }
+
+  func drawSelectedLine() {
+    var selectedView = pickerView.subviews[1]
+        selectedView.backgroundColor = .clear
+
+    let space = 10.0
+    let fwidth = pickerView.frame.width - (space * 2)
+
+    let fragmentWidth = fwidth / 3 - 3
+    let fragmentY = selectedView.frame.height - 3
+    var dx = 0.0
+    for _ in 0..<3 {
+      var topLine = UIView(frame: CGRect(x: dx, y: 0, width: fragmentWidth, height: 3))
+      var bottomLine = UIView(frame: CGRect(x: dx, y: fragmentY, width: fragmentWidth, height: 3))
+      dx += fragmentWidth + 5
+      topLine.backgroundColor = DSKitAsset.Color.neutral500.color
+      bottomLine.backgroundColor = DSKitAsset.Color.neutral500.color
+      selectedView.addSubview(topLine)
+      selectedView.addSubview(bottomLine)
+    }
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct PickerBottomSheetViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      return PickerBottomSheetView()
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheetViewModel.swift b/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheetViewModel.swift
new file mode 100644
index 00000000..636aecca
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/BottomSheet/PickerBottomSheet/PickerBottomSheetViewModel.swift
@@ -0,0 +1,133 @@
+//
+//  PickerBottomSheetViewModel.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/16.
+//
+import Foundation
+
+import SignUpInterface
+
+import RxSwift
+import RxCocoa
+
+import Core
+
+class PickerBottomSheetViewModel: ViewModelType {
+//  private let filterType: Item
+  private let initialValue: BottomSheetValueType
+
+  weak var listener: BottomSheetListener?
+  weak var delegate: BottomSheetActionDelegate?
+
+  private var disposeBag = DisposeBag()
+
+  init(initialValue: BottomSheetValueType) {
+    self.initialValue = initialValue
+  }
+
+  public struct Input {
+    let selectedItem: Driver<(row: Int, component: Int)>
+    let initializeButtonTap: Driver<Void>
+  }
+
+  public struct Output {
+    let items: Driver<[[Int]]>
+    let initialDate: Driver<(Int, Int)>
+  }
+
+  public func transform(input: Input) -> Output {
+    let thisYear = Calendar.current.component(.year, from: Date())
+    let adultYear = thisYear - 21
+    let years = Array(((adultYear - 20)...adultYear).reversed())
+    let months = Array(1...12)
+
+    let selectedYear = BehaviorSubject(value: adultYear)
+    let selectedMonth = BehaviorSubject(value: 1)
+    let selectedDay = BehaviorSubject(value: 1)
+
+    let loadTrigger = Observable.just(Void())
+    let calculateDaysTrigger = PublishSubject<Void>() // 월마다 일 수가 달라서 만듦.
+    let outputItems = Observable.merge(loadTrigger, calculateDaysTrigger)
+      .map { _ -> [[Int]] in
+        let numberOfDays = Date().daysInMonth(month: try selectedMonth.value(), year: try selectedYear.value())
+        return [years, months, Array(1...numberOfDays)]
+      }
+      .asDriver(onErrorJustReturn: [[1],[2],[3]])
+
+    let initialDate = outputItems
+      .map { _ in }
+      .asObservable()
+      .take(1)
+      .withUnretained(self)
+      .flatMap { owner, _ in
+        if case let .date(date) = owner.initialValue {
+          // 값 동기화 위해서 사용함
+          // 하지 않으면, 연/월/일 컴포넌트 중 하나만 움직였을 시, VC값과 VM값 차이가 발생함.
+          let compoents = Calendar.current.dateComponents([.year, .month, .day], from: date)
+          selectedYear.onNext(compoents.year ?? adultYear)
+          selectedMonth.onNext(compoents.month ?? 1)
+          selectedDay.onNext(compoents.day ?? 1)
+          return Observable.just(date)
+        }
+        return Observable.empty()
+      }
+      .asDriverOnErrorJustEmpty()
+
+    let initialComponents = initialDate
+      .map { date in
+        // TODO: (Int, Int) tuple 형태로 만들고 reduce하여 array 형태로 전달
+        let calendar = Calendar.current
+        let components = calendar.dateComponents([.year, .month, .day], from: date)
+        let thisYear = Calendar.current.component(.year, from: Date())
+        let adultYear = thisYear - 21 // max값
+        
+        let yearIdx = adultYear - calendar.component(.year, from: date)
+        let monthIdx = calendar.component(.month, from: date) - 1
+        let dayIdx = calendar.component(.day, from: date) - 1
+
+        return [(yearIdx, 0), (monthIdx, 1), (dayIdx, 2)]
+      }
+      .flatMap { components in
+        return Driver.from(components)
+      }
+//
+
+    let selectedDate = input.selectedItem
+      .debug("selected")
+      .withLatestFrom(outputItems) { picker, items in
+          let selectedValue = items[picker.component][picker.row]
+          if picker.component < 2 {
+            if picker.component == 0 {
+              selectedYear.onNext(selectedValue)
+            } else {
+              selectedMonth.onNext(selectedValue)
+            }
+            calculateDaysTrigger.onNext(())
+          } else {
+            selectedDay.onNext(selectedValue)
+          }
+        }.compactMap {
+          var dateComponent = DateComponents()
+          dateComponent.day = try? selectedDay.value()
+          dateComponent.month = try? selectedMonth.value()
+          dateComponent.year = try? selectedYear.value()
+
+          return Calendar.current.date(from: dateComponent)
+        }
+    let outputDate = Driver.merge(initialDate, selectedDate)
+
+    input.initializeButtonTap
+      .withLatestFrom(outputDate)
+      .drive(with: self, onNext: { owner, date in
+        owner.listener?.sendData(item: .date(date: date))
+        owner.delegate?.sheetInvoke(.onDismiss)
+      })
+    .disposed(by: disposeBag)
+
+    return Output(
+      items: outputItems,
+      initialDate: initialComponents
+    )
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerBottomSheet.swift b/Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerBottomSheet.swift
new file mode 100644
index 00000000..0decb1b3
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerBottomSheet.swift
@@ -0,0 +1,67 @@
+//
+//  SinglePickerBottomSheet.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+
+import Core
+import DSKit
+
+import RxSwift
+import RxCocoa
+
+final class SinglePickerBottomSheet: TFBaseViewController {
+
+  private let mainView = SinglePickerBottomSheetView()
+  private let viewModel: SinglePickerBottomSheetViewModel
+
+  init(viewModel: SinglePickerBottomSheetViewModel) {
+    self.viewModel = viewModel
+    super.init(nibName: nil, bundle: nil)
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func loadView() {
+    self.view = mainView
+  }
+
+  override func viewDidLayoutSubviews() {
+    self.mainView.drawSelectedLine()
+    self.navigationController?.isNavigationBarHidden = true
+  }
+
+  override func bindViewModel() {
+    let itemSelected = mainView.pickerView.rx.itemSelected.asDriver()
+
+    let confirmTap = mainView.initializeButton.rx.tap.asDriver()
+//
+    let input = SinglePickerBottomSheetViewModel.Input(
+      selectedItem: itemSelected,
+      initializeButtonTap: confirmTap
+    )
+//
+    let output = viewModel.transform(input: input)
+
+    output.items
+      .drive(mainView.pickerView.rx.items(adapter: PickerViewViewAdapter()))
+      .disposed(by: disposeBag)
+  }
+}
+
+//#if canImport(SwiftUI) && DEBUG
+//import SwiftUI
+//
+//struct PickerViewController_Preview: PreviewProvider {
+//    static var previews: some View {
+//      let vm = PickerBottomSheetViewModel(initialValue: .date(date: Date()))
+//      let vc = PickerBottomSheet(viewModel: vm)
+//      return vc.showPreview()
+//    }
+//}
+//#endif
diff --git a/Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerBottomSheetViewModel.swift b/Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerBottomSheetViewModel.swift
new file mode 100644
index 00000000..045801f7
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerBottomSheetViewModel.swift
@@ -0,0 +1,65 @@
+//
+//  SinglePickerBottomSheetViewModel.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import Foundation
+
+import SignUpInterface
+
+import RxSwift
+import RxCocoa
+
+import Core
+
+class SinglePickerBottomSheetViewModel: ViewModelType {
+//  private let filterType: Item
+  private let initialValue: BottomSheetValueType
+
+  weak var listener: BottomSheetListener?
+  weak var delegate: BottomSheetActionDelegate?
+
+  private var disposeBag = DisposeBag()
+
+  init(initialValue: BottomSheetValueType) {
+    self.initialValue = initialValue
+  }
+
+  public struct Input {
+    let selectedItem: Driver<(row: Int, component: Int)>
+    let initializeButtonTap: Driver<Void>
+  }
+
+  public struct Output {
+    let items: Driver<[[Int]]>
+  }
+
+  public func transform(input: Input) -> Output {
+    let loadTrigger = Observable.just(Void())
+    let outputItems = loadTrigger
+      .map { _ -> [[Int]] in
+        let heightArray = Array(145...200)
+        return [heightArray]
+      }
+
+    let selectedValue = input.selectedItem
+      .asObservable()
+      .withLatestFrom(outputItems) { picker, items in
+          return items[picker.component][picker.row]
+      }.map { String($0) }
+
+    input.initializeButtonTap
+      .withLatestFrom(selectedValue.asDriverOnErrorJustEmpty())
+      .drive(with: self, onNext: { owner, value in
+        owner.listener?.sendData(item: .text(text: value))
+        owner.delegate?.sheetInvoke(.onDismiss)
+      })
+    .disposed(by: disposeBag)
+
+    return Output(
+      items: outputItems.asDriverOnErrorJustEmpty()
+    )
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerViewController.swift b/Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerViewController.swift
new file mode 100644
index 00000000..01edaa1c
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/BottomSheet/SinglePickerBottomSheet/SinglePickerViewController.swift
@@ -0,0 +1,92 @@
+//
+//  SinglePickerView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+
+import Core
+import DSKit
+
+class SinglePickerBottomSheetView: TFBaseView {
+
+  private(set) lazy var pickerView: UIPickerView = {
+    let pickerView = UIPickerView()
+
+    return pickerView
+  }()
+
+  private(set) lazy var buttonHStackView: UIStackView = {
+    let stackView = UIStackView()
+    stackView.axis = .horizontal
+    stackView.spacing = 10
+    stackView.distribution = .fillEqually
+    return stackView
+  }()
+
+  private(set) lazy var initializeButton = CTAButton(btnTitle: "확인", initialStatus: true)
+
+  override func makeUI() {
+    self.backgroundColor = DSKitAsset.Color.neutral600.color
+
+    addSubviews(pickerView, buttonHStackView)
+
+    [initializeButton].forEach {
+      buttonHStackView.addArrangedSubview($0)
+    }
+
+    pickerView.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+    }
+
+    buttonHStackView.snp.makeConstraints {
+      $0.top.equalTo(pickerView.snp.bottom).offset(10)
+      $0.height.equalTo(50)
+      $0.leading.trailing.equalTo(safeAreaLayoutGuide).inset(30)
+      $0.bottom.equalTo(self.safeAreaLayoutGuide)
+    }
+
+    self.clipsToBounds = true
+  }
+
+  func drawSelectedLine() {
+    var selectedView = pickerView.subviews[1]
+        selectedView.backgroundColor = .clear
+
+    let space = 10.0
+    let fwidth = pickerView.frame.width - (space * 2)
+
+    let fragmentWidth = fwidth / 3 - 3
+    let fragmentY = selectedView.frame.height - 3
+    var dx = 0.0
+    for _ in 0..<3 {
+      var topLine = UIView(
+        frame:
+          CGRect(x: dx, y: 0, width: fragmentWidth, height: 3))
+      var bottomLine = UIView(
+        frame:
+          CGRect(x: dx, y: fragmentY, width: fragmentWidth, height: 3))
+      dx += fragmentWidth + 5
+      topLine.backgroundColor = DSKitAsset.Color.neutral500.color
+      bottomLine.backgroundColor = DSKitAsset.Color.neutral500.color
+      selectedView.addSubview(topLine)
+      selectedView.addSubview(bottomLine)
+    }
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct SinglePickerBottomSheetViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      return SinglePickerBottomSheetView()
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SubView/ButtonPickerView+Rx.swift b/Projects/Features/SignUp/Src/SubView/ButtonPickerView+Rx.swift
new file mode 100644
index 00000000..65519f04
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/ButtonPickerView+Rx.swift
@@ -0,0 +1,43 @@
+//
+//  ButtonPickerView+Rx.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/18.
+//
+
+import UIKit
+
+import RxSwift
+import RxCocoa
+
+extension Reactive where Base: ButtonPickerView {
+  var selectedOption: ControlProperty<ButtonPickerView.ButtonOption?> {
+    return base.rx.controlProperty(
+      editingEvents: .valueChanged,
+      getter: { base in
+        base.selectedOption
+      },
+      setter: { base, value in
+        if base.selectedOption != value {
+          base.selectedOption = value
+        }
+      }
+    )
+  }
+}
+
+extension Reactive where Base: TFButtonPickerView {
+  var selectedOption: ControlProperty<TFButtonPickerView.Option?> {
+    return base.rx.controlProperty(
+      editingEvents: .valueChanged,
+      getter: { base in
+        base.selectedOption
+      },
+      setter: { base, value in
+        if base.selectedOption != value {
+          base.selectedOption = value
+        }
+      }
+    )
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SubView/ButtonPickerView.swift b/Projects/Features/SignUp/Src/SubView/ButtonPickerView.swift
new file mode 100644
index 00000000..6653eb45
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/ButtonPickerView.swift
@@ -0,0 +1,129 @@
+//
+//  ButtonPickerView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/16.
+//
+
+import UIKit
+
+import DSKit
+
+class ButtonPickerView: UIControl {
+
+  private let title: String
+  private let targetString: String
+  private let option1: String
+  private let option2: String
+
+  var tapAction: (() -> ())?
+
+  var selectedOption: ButtonOption? = nil {
+    didSet {
+      if let selectedOption {
+        changeButtonStatus(option: selectedOption)
+        sendActions(for: .valueChanged)
+      }
+    }
+  }
+
+  enum ButtonOption {
+    case left
+    case right
+  }
+
+  init(title: String, targetString: String, option1: String, option2: String) {
+    self.title = title
+    self.targetString = targetString
+    self.option1 = option1
+    self.option2 = option2
+
+    super.init(frame: .zero)
+
+    makeUI()
+  }
+  
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+  
+  lazy var titleLabel: UILabel = UILabel().then {
+    $0.text = title
+    $0.textColor = DSKitAsset.Color.neutral300.color
+    $0.font = .thtH1B
+    $0.asColor(targetString: targetString, color: DSKitAsset.Color.neutral50.color)
+  }
+
+  lazy var buttonHStackView = UIStackView().then {
+    $0.axis = .horizontal
+    $0.addArrangedSubviews([option1Btn, option2Btn])
+    $0.distribution = .fillEqually
+    $0.alignment = .fill
+    $0.spacing = 10
+  }
+
+  lazy var option1Btn = CTAButton(btnTitle: option1, initialStatus: false)
+
+  lazy var option2Btn = CTAButton(btnTitle: option2, initialStatus: false)
+
+  func makeUI() {
+
+    addSubviews(
+      titleLabel,
+      buttonHStackView
+    )
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().inset(frame.height * 0.09)
+      $0.leading.trailing.equalToSuperview().inset(38)
+    }
+
+    buttonHStackView.snp.makeConstraints {
+      $0.leading.trailing.equalTo(titleLabel)
+      $0.top.equalTo(titleLabel.snp.bottom).offset(30)
+      $0.height.equalTo(50)
+      $0.bottom.equalToSuperview().offset(-10)
+    }
+
+    option1Btn.addAction(UIAction { [weak self] _ in
+      self?.selectedOption = .left
+    }, for: .touchUpInside)
+
+    option2Btn.addAction(UIAction { [weak self] _ in
+      self?.selectedOption = .right
+    }, for: .touchUpInside)
+  }
+
+  func handleSelectedState(_ option: ButtonOption) {
+    self.selectedOption = option
+  }
+
+  func changeButtonStatus(option: ButtonOption) {
+    switch option {
+    case .left:
+      option1Btn.updateColors(status: true)
+      option2Btn.updateColors(status: false)
+    case .right:
+      option1Btn.updateColors(status: false)
+      option2Btn.updateColors(status: true)
+    }
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+import DSKit
+
+struct ButtonPickerViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      let text = "성별, 생일을 입력해주세요"
+      let target = "성별, 생일"
+
+      return ButtonPickerView(title: text, targetString: target, option1: "여자", option2: "남자")
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SubView/CTAButton.swift b/Projects/Features/SignUp/Src/SubView/CTAButton.swift
index cf0873e8..dd11b16d 100644
--- a/Projects/Features/SignUp/Src/SubView/CTAButton.swift
+++ b/Projects/Features/SignUp/Src/SubView/CTAButton.swift
@@ -9,7 +9,7 @@ import UIKit
 
 import DSKit
 
-final class CTAButton: UIButton {
+class CTAButton: UIButton {
   init(btnTitle: String, initialStatus: Bool) {
     super.init(frame: .zero)
     titleLabel?.font = .thtH5B
diff --git a/Projects/Features/SignUp/Src/SubView/Cell/InputTagCollectionViewCell.swift b/Projects/Features/SignUp/Src/SubView/Cell/InputTagCollectionViewCell.swift
new file mode 100644
index 00000000..ade4eece
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/Cell/InputTagCollectionViewCell.swift
@@ -0,0 +1,121 @@
+//
+//  InputTagChip.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/24.
+//
+
+import UIKit
+
+import DSKit
+import Domain
+
+// Suggest Selectable tag chip confirmed collectionView cell
+// it has to status selected and non-selected
+public final class InputTagCollectionViewCell: TFBaseCollectionViewCell {
+  private lazy var stackView: UIStackView = {
+    let stackView = UIStackView()
+    stackView.axis = .horizontal
+    stackView.spacing = 5
+    stackView.alignment = .center
+    stackView.distribution = .fill
+    return stackView
+  }()
+
+  private lazy var emojiView: UILabel = {
+    let label = UILabel()
+    label.font = UIFont.thtSubTitle1R
+    label.text = ""
+    label.numberOfLines = 1
+    return label
+  }()
+
+  private lazy var titleLabel: UILabel = {
+    let label = UILabel()
+    label.font = UIFont.thtSubTitle1R
+    label.textColor = DSKitAsset.Color.neutral50.color
+    label.text = "칩 텍스트"
+    label.textAlignment = .left
+    label.numberOfLines = 1
+    return label
+  }()
+
+  public override func makeUI() {
+    contentView.addSubview(stackView)
+
+    stackView.addArrangedSubviews([emojiView, titleLabel])
+    stackView.arrangedSubviews.forEach { subViews in
+      subViews.snp.makeConstraints {
+        $0.height.equalTo(40)
+      }
+    }
+    stackView.snp.makeConstraints {
+      $0.top.bottom.equalToSuperview()
+      $0.leading.equalToSuperview().offset(10)
+      $0.trailing.equalToSuperview().offset(-15)
+    }
+
+    updateStatus(isSelected: false)
+
+    contentView.layer.masksToBounds = true
+    contentView.layer.borderColor = DSKitAsset.Color.neutral300.color.cgColor
+  }
+
+  public override func layoutSubviews() {
+    super.layoutSubviews()
+    setUpLayer()
+  }
+
+  private func setUpLayer() {
+    contentView.layer.cornerRadius = contentView.frame.height / 2
+  }
+
+  public func bind(_ viewModel: InputTagItemViewModel) {
+    self.titleLabel.text = viewModel.emojiType.name
+    self.emojiView.text = viewModel.emoji
+    updateStatus(isSelected: viewModel.isSelected)
+  }
+
+  public func updateStatus(isSelected: Bool) {
+    contentView.backgroundColor = isSelected
+    ? DSKitAsset.Color.primary500.color
+    : DSKitAsset.Color.neutral700.color
+
+    titleLabel.textColor = isSelected
+    ? DSKitAsset.Color.neutral700.color
+    : DSKitAsset.Color.neutral50.color
+
+    contentView.layer.borderWidth = isSelected ? 0 : 1
+  }
+}
+
+public struct InputTagItemViewModel {
+  public let emojiType: EmojiType
+  public var isSelected: Bool
+
+  public var emoji: String {
+    return emojiType.emojiCode.unicodeToEmoji()
+  }
+
+  public init(item: EmojiType, isSelected: Bool) {
+    self.emojiType = item
+    self.isSelected = isSelected
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct InputTagCellViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      let component =  InputTagCollectionViewCell()
+      component.bind(.init(item: .init(idx: 1, name: "샘플", emojiCode: "U+1F457"), isSelected: false))
+      return component
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
+
diff --git a/Projects/Features/SignUp/Src/SubView/Cell/ReligionPickerCell.swift b/Projects/Features/SignUp/Src/SubView/Cell/ReligionPickerCell.swift
new file mode 100644
index 00000000..263b5e16
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/Cell/ReligionPickerCell.swift
@@ -0,0 +1,57 @@
+//
+//  ReligionPickerCell.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/25.
+//
+
+import UIKit
+
+import DSKit
+import SignUpInterface
+
+final class ReligionPickerCell: TFBaseCollectionViewCell {
+
+  lazy var religionLabel: UILabel = UILabel().then {
+    $0.font = .thtH4Sb
+    $0.backgroundColor = .clear
+    $0.textAlignment = .center
+    $0.textColor = DSKitAsset.Color.neutral900.color
+  }
+
+  override func makeUI() {
+    contentView.addSubview(religionLabel)
+    contentView.layer.masksToBounds = true
+    contentView.layer.cornerRadius = 10
+
+    religionLabel.snp.makeConstraints {
+      $0.edges.equalToSuperview().inset(15)
+    }
+  }
+
+  func bind(_ model: Religion) {
+    let text: String
+
+    switch model {
+    case .christian:
+      text = "기독교"
+    case .catholic:
+      text = "천주교"
+    case .buddhism:
+      text = "불교"
+    case .wonBuddhism:
+      text = "원불교"
+    case .none:
+      text = "무교"
+    case .other:
+      text = "기타"
+    }
+    religionLabel.text = text
+  }
+
+  func updateCell(_ isSelected: Bool) {
+    contentView.backgroundColor = isSelected
+    ? DSKitAsset.Color.primary500.color
+    : DSKitAsset.Color.disabled.color
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SubView/DatePickerView.swift b/Projects/Features/SignUp/Src/SubView/DatePickerView.swift
new file mode 100644
index 00000000..932665ad
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/DatePickerView.swift
@@ -0,0 +1,69 @@
+////
+////  DatePickerView.swift
+////  SignUp
+////
+////  Created by Kanghos on 2024/04/16.
+////
+//
+//import UIKit
+//
+//import DSKit
+//
+//final class DatePickerView: TFBaseView {
+//
+//  lazy var pickerView = UIPickerView().then {
+//    
+//  }
+//
+//  lazy var infoImageView: UIImageView = UIImageView().then {
+//    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
+//    $0.tintColor = DSKitAsset.Color.neutral400.color
+//  }
+//
+//  lazy var descLabel: UILabel = UILabel().then {
+//    $0.text = "폴링에서 활동할 자유로운 호칭을 설정해주세요"
+//    $0.font = .thtCaption1M
+//    $0.textColor = DSKitAsset.Color.neutral400.color
+//    $0.textAlignment = .left
+//    $0.numberOfLines = 1
+//  }
+//
+//  override func makeUI() {
+//    addSubviews(
+//      pickerView,
+//      infoImageView, descLabel
+//    )
+//
+//    pickerView.snp.makeConstraints {
+//      $0.top.equalToSuperview().offset(14)
+//      $0.leading.trailing.equalToSuperview()
+//      $0.height.equalTo(60)
+//    }
+//
+//    infoImageView.snp.makeConstraints {
+//      $0.leading.equalTo(pickerView.snp.leading)
+//      $0.width.height.equalTo(16)
+//      $0.top.equalTo(pickerView.snp.bottom).offset(16)
+//    }
+//
+//    descLabel.snp.makeConstraints {
+//      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
+//      $0.top.equalTo(pickerView.snp.bottom).offset(16)
+//      $0.trailing.equalToSuperview().inset(38)
+//    }
+//  }
+//}
+//
+//#if canImport(SwiftUI) && DEBUG
+//import SwiftUI
+//
+//struct DatePickerViewPreview: PreviewProvider {
+//
+//  static var previews: some View {
+//    UIViewPreview {
+//      return DatePickerView()
+//    }
+//    .previewLayout(.sizeThatFits)
+//  }
+//}
+//#endif
diff --git a/Projects/Features/SignUp/Src/SubView/ResizableTextView.swift b/Projects/Features/SignUp/Src/SubView/ResizableTextView.swift
new file mode 100644
index 00000000..73685588
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/ResizableTextView.swift
@@ -0,0 +1,20 @@
+//
+//  ResizableTextView.swift
+//  SignUp
+//
+//  Created by kangho lee on 4/27/24.
+//
+
+import UIKit
+
+public class ResizableTextView: UITextView {
+    let minimumHeight: CGFloat = 40
+    let maximumHeight: CGFloat = 120
+    
+    public override var intrinsicContentSize: CGSize {
+        var newSize = super.intrinsicContentSize
+        newSize.height = min(maximumHeight, max(minimumHeight, newSize.height))
+        self.isScrollEnabled = newSize.height == maximumHeight
+        return newSize
+    }
+}
diff --git a/Projects/Features/SignUp/Src/SubView/ServiceAgreementRowView.swift b/Projects/Features/SignUp/Src/SubView/ServiceAgreementRowView.swift
index 1d31bce6..31428dff 100644
--- a/Projects/Features/SignUp/Src/SubView/ServiceAgreementRowView.swift
+++ b/Projects/Features/SignUp/Src/SubView/ServiceAgreementRowView.swift
@@ -8,104 +8,122 @@
 import UIKit
 
 import DSKit
+import SignUpInterface
 
-enum AgreementType {
-  case termsOfServie
-  case privacyPolicy
-  case locationService
-  case marketingService
-
-  var labelTitle: String {
-    switch self {
-    case .termsOfServie:
-      return "(필수) 이용 약관 동의"
-    case .privacyPolicy:
-      return "(필수) 개인 정보 수집 및 이용 동의"
-    case .locationService:
-      return "(필수) 위치 기반 서비스 약관 동의"
-    case .marketingService:
-      return "(선택) 마케팅 정보 수신 동의"
-    }
-  }
+final class ServiceAgreementRowView: UITableViewCell {
 
-  var isConnectWebView: Bool {
-    switch self {
-    case .locationService, .privacyPolicy, .termsOfServie:
-      return true
-    case .marketingService:
-      return false
-    }
-  }
+  private var disposeBag = DisposeBag()
+  private var model: AgreementElement?
 
-  var isAddDiscription: Bool {
-    switch self {
-    case .locationService, .privacyPolicy, .termsOfServie:
-      return false
-    case .marketingService:
-      return true
-    }
-  }
-}
+  var agreeBtnOnCliek: (() -> Void)?
+  var goWebviewBtnOnClick: (() -> Void)?
 
-final class ServiceAgreementRowView: TFBaseView {
+  lazy var checkmark = UIImageView().then {
+    $0.image = DSKitAsset.Image.Component.check.image
+  }
 
-  private let serviceType: AgreementType
 
-  lazy var agreeBtn: UIButton = UIButton().then {
-    $0.setImage(DSKitAsset.Image.Component.check.image, for: .normal)
-    $0.setTitle(serviceType.labelTitle, for: .normal)
-    $0.imageEdgeInsets = UIEdgeInsets(top: 0, left: -6, bottom: 0, right: 6)
-    $0.titleEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: -6)
-    $0.titleLabel?.font = .thtSubTitle1R
-    $0.setTitleColor(DSKitAsset.Color.neutral50.color, for: .normal)
+  lazy var titleLabel = UILabel().then {
+    $0.font = .thtSubTitle1R
+    $0.numberOfLines = 2
+    $0.textColor = DSKitAsset.Color.neutral50.color
+    $0.lineBreakStrategy = .hangulWordPriority
   }
 
   lazy var goWebviewBtn: UIButton = UIButton().then {
     $0.setImage(DSKitAsset.Image.Component.chevronRight.image.withRenderingMode(.alwaysTemplate), for: .normal)
     $0.imageView?.contentMode = .scaleAspectFit
     $0.tintColor = DSKitAsset.Color.neutral400.color
+    $0.addAction(UIAction { [weak self] _ in
+      self?.goWebviewBtnOnClick?()
+    }, for: .touchUpInside)
   }
 
-  private lazy var discriptionText: UILabel = UILabel().then {
-    $0.text = "폴링에서 제공하는 이벤트/혜택 등 다양한 정보를\nPush 알림으로 받아보실 수 있습니다."
+  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+    super.init(style: style, reuseIdentifier: reuseIdentifier)
+
+    makeUI()
+  }
+  
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+  
+  lazy var descriptionText: UILabel = UILabel().then {
     $0.font = .thtCaption1M
     $0.textColor = DSKitAsset.Color.neutral400.color
     $0.numberOfLines = 2
   }
 
-  init(serviceType: AgreementType) {
-    self.serviceType = serviceType
-    super.init(frame: .zero)
-  }
-
-  required init?(coder: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
+  override func prepareForReuse() {
+    self.disposeBag = DisposeBag()
+    super.prepareForReuse()
+    agreeBtnOnCliek = nil
+    goWebviewBtnOnClick = nil
   }
 
-  override func makeUI() {
-    addSubviews(agreeBtn, goWebviewBtn, discriptionText)
+  private lazy var containerView = UIView()
+
+  func makeUI() {
+    contentView.addSubview(goWebviewBtn)
+    contentView.addSubview(checkmark)
+    contentView.addSubview(titleLabel)
+    contentView.addSubview(descriptionText)
+    contentView.backgroundColor = DSKitAsset.Color.neutral700.color
+    goWebviewBtn.snp.makeConstraints {
+      $0.size.equalTo(30)
+      $0.centerY.equalToSuperview()
+      $0.trailing.equalToSuperview()
+    }
 
-    agreeBtn.snp.makeConstraints {
-      $0.leading.top.bottom.equalToSuperview()
+    titleLabel.snp.makeConstraints {
+      $0.leading.equalTo(checkmark.snp.trailing).offset(10)
+      $0.top.equalToSuperview().offset(10)
+      $0.trailing.equalTo(goWebviewBtn.snp.leading)
     }
 
-    if serviceType.isConnectWebView {
-      goWebviewBtn.snp.makeConstraints {
-        $0.top.bottom.trailing.equalToSuperview()
-        $0.width.height.equalTo(24)
-      }
-    } else {
-      goWebviewBtn.isHidden = true
+    checkmark.snp.makeConstraints {
+      $0.size.equalTo(20)
+      $0.top.equalTo(titleLabel).offset(5)
+      $0.leading.equalToSuperview()
     }
 
-    if serviceType.isAddDiscription {
-      discriptionText.snp.makeConstraints {
-        $0.top.equalTo(agreeBtn.snp.bottom).offset(2)
-        $0.leading.equalToSuperview().offset(30)
-      }
-    } else {
-      discriptionText.isHidden = true
+    descriptionText.setContentHuggingPriority(.defaultHigh, for: .vertical)
+    descriptionText.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(10)
+      $0.leading.equalTo(titleLabel)
+      $0.trailing.equalTo(titleLabel)
+      $0.height.lessThanOrEqualTo(30)
+      $0.bottom.equalToSuperview().offset(-5)
     }
   }
 
+  func bind(_ viewModel: ServiceAgreementRowViewModel) {
+    self.model = viewModel.model
+    self.titleLabel.text = viewModel.model.subject
+    self.descriptionText.text = viewModel.model.description
+    self.checkmark.image = viewModel.checkImage.image
+    goWebviewBtn.isHidden = (viewModel.model.detailLink ?? "").isEmpty
+  }
+}
+
+struct ServiceAgreementRowViewModel {
+  let model: AgreementElement
+  var checkImage: DSKitImages
 }
+//
+//#if canImport(SwiftUI) && DEBUG
+//import SwiftUI
+//
+//struct ServiceAgreementRowViewPreview: PreviewProvider {
+//
+//  static var previews: some View {
+//    UIViewPreview {
+//      let view = ServiceAgreementRowView()
+//      return ServiceAgreementRowView()
+//    }
+//    .frame(width: 375, height: 100)
+//    .previewLayout(.sizeThatFits)
+//  }
+//}
+//#endif
diff --git a/Projects/Features/SignUp/Src/SubView/TFButtonPickerView.swift b/Projects/Features/SignUp/Src/SubView/TFButtonPickerView.swift
new file mode 100644
index 00000000..3bf81c6d
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/TFButtonPickerView.swift
@@ -0,0 +1,110 @@
+//
+//  TFButtonPickerView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/18.
+//
+
+import UIKit
+
+import DSKit
+
+class TFButtonPickerView: UIControl {
+  enum TitleType {
+    case header
+    case sub
+  }
+
+  struct Option: Equatable {
+    let key: Int
+    let value: String
+  }// = (key: Int, value: String)
+
+  private let title: String
+  private let targetString: String
+  private var options: [CTAButton] = []
+
+  var selectedOption: Option? = nil {
+    didSet {
+      if let selectedOption {
+        changeButtonStatus(selectedOption)
+        sendActions(for: .valueChanged)
+      }
+    }
+  }
+
+  private let titleType: TitleType
+
+  init(title: String, targetString: String, options: [String], titleType: TitleType = .header) {
+    self.title = title
+    self.targetString = targetString
+    self.titleType = titleType
+
+    super.init(frame: .zero)
+    createButtons(options)
+    makeUI()
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  lazy var titleLabel = UILabel().then {
+    $0.text = title
+    $0.textColor = DSKitAsset.Color.neutral300.color
+    $0.font = self.titleType == .header ? .thtH1B : .thtH4M
+    $0.asColor(targetString: targetString, color: DSKitAsset.Color.neutral50.color)
+  }
+
+  lazy var buttonHStackView = UIStackView().then {
+    $0.axis = .horizontal
+    $0.distribution = .fillEqually
+    $0.alignment = .fill
+    $0.spacing = 10
+  }
+
+  func makeUI() {
+
+    addSubviews(
+      titleLabel,
+      buttonHStackView
+    )
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().inset(frame.height * 0.09)
+      $0.leading.trailing.equalToSuperview().inset(38)
+    }
+
+    buttonHStackView.snp.makeConstraints {
+      $0.leading.trailing.equalTo(titleLabel)
+      $0.top.equalTo(titleLabel.snp.bottom).offset(30)
+      $0.height.equalTo(50)
+      $0.bottom.equalToSuperview().offset(-10)
+    }
+  }
+
+  func createButtons(_ pairs: [String]) {
+    pairs.enumerated().forEach { index, value in
+      let button = CTAButton(btnTitle: value, initialStatus: false)
+      button.addAction(UIAction { [weak self] _ in
+        self?.handleSelectedState(Option(key: index, value: value))
+      }, for: .touchUpInside)
+      self.buttonHStackView.addArrangedSubview(button)
+      self.options.append(button)
+    }
+  }
+
+  func handleSelectedState(_ option: Option) {
+    self.selectedOption = option
+  }
+
+  func changeButtonStatus(_ selectedOption: Option) {
+    options.enumerated().forEach { (index, element) in
+      if index == selectedOption.key {
+        element.updateColors(status: true)
+      } else {
+        element.updateColors(status: false)
+      }
+    }
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SubView/TFCheckButton.swift b/Projects/Features/SignUp/Src/SubView/TFCheckButton.swift
new file mode 100644
index 00000000..ea8125ff
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/TFCheckButton.swift
@@ -0,0 +1,54 @@
+//
+//  TFCheckButton.swift
+//  SignUp
+//
+//  Created by Kanghos on 5/30/24.
+//
+
+import UIKit
+
+import RxSwift
+
+import DSKit
+
+final class TFCheckButton: UIButton {
+
+  init(btnTitle: String, initialStatus: Bool) {
+    super.init(frame: .zero)
+    makeUI(title: btnTitle)
+    updateColors(status: initialStatus)
+  }
+  
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+  
+  func makeUI(title: String) {
+    setTitleColor(DSKitAsset.Color.neutral50.color, for: .normal)
+    self.setTitle(title, for: .normal)
+    self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -200, bottom: 0, right: 0)
+    self.titleEdgeInsets = UIEdgeInsets(top: 0, left: -30, bottom: 0, right: 0)
+
+    self.layer.cornerRadius = 16
+    self.layer.masksToBounds = true
+  }
+
+  /// update CTA button color by status
+  func updateColors(status: Bool) {
+    if status {
+      backgroundColor = DSKitAsset.Color.payment.color
+      setImage(DSKitAsset.Image.Component.checkCirSelect.image, for: .normal)
+    } else {
+      backgroundColor = DSKitAsset.Color.neutral600.color
+      setImage(DSKitAsset.Image.Component.checkCir.image, for: .normal)
+    }
+  }
+}
+
+extension Reactive where Base: TFCheckButton {
+  var buttonStatus: Binder<Bool> {
+    return Binder(base.self) { btn, status in
+      btn.updateColors(status: status)
+    }
+  }
+}
diff --git a/Projects/Features/SignUp/Src/SubView/TFGenderPickerView.swift b/Projects/Features/SignUp/Src/SubView/TFGenderPickerView.swift
new file mode 100644
index 00000000..44c0bb07
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/TFGenderPickerView.swift
@@ -0,0 +1,8 @@
+//
+//  TFGenderPickerView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/16.
+//
+
+import Foundation
diff --git a/Projects/Features/SignUp/Src/SubView/TFResizableTextView.swift b/Projects/Features/SignUp/Src/SubView/TFResizableTextView.swift
new file mode 100644
index 00000000..df345c4d
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/TFResizableTextView.swift
@@ -0,0 +1,287 @@
+//
+//  TFResizableTextView.swift
+//  SignUp
+//
+//  Created by kangho lee on 4/27/24.
+//
+
+import UIKit
+
+import DSKit
+
+import RxSwift
+
+public class TFResizableTextView: UIControl {
+  private let maxHeight: CGFloat = 120
+  private let minHeight: CGFloat = 40
+  private var totalCount: Int = 20
+  
+  enum State {
+    case text(text: String?)
+    case error(error: InputError)
+    case focus
+    case focusOut
+  }
+  
+  enum InputError: Error {
+    case validate(text: String)
+    case overflow
+  }
+  
+  var text: String? {
+    set {
+      self.textView.text = newValue
+    } get {
+      return self.textView.text
+    }
+  }
+  
+  var placeholder: String? {
+    didSet {
+      placeholderLabel.text = placeholder
+    }
+  }
+  
+  private lazy var placeholderLabel = UILabel().then {
+    $0.textColor = DSKitAsset.Color.disabled.color
+    $0.font = .thtSubTitle1R
+    $0.backgroundColor = .clear
+  }
+  
+  lazy var textView = ResizableTextView().then {
+    $0.textColor = DSKitAsset.Color.primary500.color
+    $0.font = .thtSubTitle1R
+    $0.isScrollEnabled = false
+    $0.isEditable = true
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+  
+  lazy var clearBtn: UIButton = UIButton().then {
+    $0.setImage(DSKitAsset.Image.Icons.closeCircle.image, for: .normal)
+    $0.setTitle(nil, for: .normal)
+    $0.backgroundColor = .clear
+    $0.isHidden = true
+  }
+  
+  lazy var divider: UIView = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral300.color
+  }
+  
+  lazy var infoImageView: UIImageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
+    $0.tintColor = DSKitAsset.Color.neutral400.color
+  }
+  
+  lazy var descLabel: UILabel = UILabel().then {
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 3
+  }
+  
+  lazy var errorDescriptionLabel = UILabel().then {
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.error.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 3
+  }
+  
+  lazy var countLabel = UILabel().then {
+    $0.text = "(0/\(totalCount)"
+    $0.font = .thtCaption1R
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 2
+  }
+  
+  public init(description: String = "", totalCount: Int = 20, placeholder: String? = nil) {
+    self.totalCount = totalCount
+    super.init(frame: .zero)
+    self.descLabel.text = description
+    
+    makeUI()
+    bindAction()
+  }
+  
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+  
+  func makeUI() {
+    addSubviews(
+      textView, clearBtn,
+      placeholderLabel,
+      divider,
+      errorDescriptionLabel,
+      infoImageView, descLabel, countLabel
+    )
+    
+    textView.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(10)
+      $0.leading.equalToSuperview()
+      $0.trailing.equalTo(clearBtn.snp.leading)
+    }
+    
+    clearBtn.snp.makeConstraints {
+      $0.leading.equalTo(textView.snp.trailing)
+      $0.bottom.equalTo(textView)
+      $0.trailing.equalToSuperview()
+      $0.size.equalTo(24)
+    }
+    
+    placeholderLabel.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(textView)
+      $0.height.equalTo(40)
+    }
+
+    divider.snp.makeConstraints {
+      $0.leading.trailing.equalToSuperview()
+      $0.top.equalTo(textView.snp.bottom).offset(2)
+      $0.height.equalTo(2)
+    }
+
+    errorDescriptionLabel.snp.makeConstraints {
+      $0.leading.trailing.equalToSuperview()
+      $0.top.equalTo(divider.snp.bottom)
+      $0.height.equalTo(20).priority(.low)
+    }
+
+    infoImageView.snp.makeConstraints {
+      $0.leading.equalTo(textView)
+      $0.size.equalTo(16)
+      $0.top.equalTo(errorDescriptionLabel.snp.bottom)
+    }
+
+    descLabel.snp.makeConstraints {
+      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
+      $0.trailing.equalTo(countLabel.snp.leading)
+      $0.top.equalTo(infoImageView)
+      $0.bottom.equalToSuperview()
+    }
+
+    countLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+    countLabel.snp.makeConstraints {
+      $0.trailing.equalToSuperview()
+      $0.top.equalTo(descLabel)
+    }
+  }
+  
+  func bindAction() {
+    textView.delegate = self
+    
+    clearBtn.addAction(UIAction(handler: { [weak self] _ in
+      self?.text = ""
+      self?.clearBtn.isHidden = true
+      self?.sendActions(for: .editingChanged)
+      self?.checkPlaceholderLabel("")
+    }), for: .touchUpInside)
+
+    updateDividerColor(.focusOut)
+  }
+  
+  private func calculateCount(count: Int) {
+    let fullText = "(\(count)/\(totalCount))"
+    let target = "\(count)"
+    self.countLabel.text = fullText
+    self.countLabel.asFont(targetString: target, font: .thtCaption1B)
+
+    if count > totalCount {
+      self.render(state: .error(error: .overflow))
+      if let text {
+        let endIndex = text.index(text.startIndex, offsetBy: 200)
+        let range = text[text.startIndex..<text.index(before: endIndex)]
+        self.text = String(range)
+      }
+    }
+  }
+  
+  func render(state: State) {
+    switch state {
+    case let .text(text):
+      self.errorDescriptionLabel.isHidden = true
+      self.text = text
+      self.errorDescriptionLabel.text = ""
+
+    case .error(let inputError):
+      self.updateErrorView(inputError)
+    default: break
+    }
+  }
+  
+  private func updateDividerColor(_ state: State) {
+    switch state {
+    case .error:
+      self.divider.backgroundColor = DSKitAsset.Color.error.color
+    case .focus:
+      self.divider.backgroundColor = DSKitAsset.Color.primary500.color
+    default:
+      self.divider.backgroundColor = self.textView.text.isEmpty
+      ? DSKitAsset.Color.neutral300.color
+      : DSKitAsset.Color.primary500.color
+    }
+    self.clearBtn.isHidden = self.textView.text.isEmpty
+  }
+
+  private func updateErrorView(_ inputError: InputError) {
+    self.errorDescriptionLabel.isHidden = false
+
+    if case let .validate(description) = inputError {
+      self.errorDescriptionLabel.text = description
+      self.countLabel.asColor(targetString: "\(self.text?.count ?? 0)", color: DSKitAsset.Color.neutral400.color)
+    }
+    if case .overflow = inputError {
+      self.errorDescriptionLabel.text = "\(self.totalCount)자 이상 입력할 수 없습니다."
+      self.countLabel.asColor(targetString: "\(self.text?.count ?? 0)", color: DSKitAsset.Color.error.color)
+      }
+    updateDividerColor(.error(error: inputError))
+  }
+}
+extension TFResizableTextView: UITextViewDelegate {
+  public func textViewDidBeginEditing(_ textView: UITextView) {
+    updateDividerColor(.focus)
+    
+    checkPlaceholderLabel(textView.text)
+  }
+  
+  public func textViewDidEndEditing(_ textView: UITextView) {
+    updateDividerColor(.focusOut)
+    
+    checkPlaceholderLabel(textView.text)
+  }
+  
+  public func textViewDidChange(_ textView: UITextView) {
+    self.calculateCount(count: textView.text.count)
+    self.sendActions(for: .valueChanged)
+    updateDividerColor(.text(text: textView.text))
+    checkPlaceholderLabel(textView.text)
+  }
+  
+  private func checkPlaceholderLabel(_ text: String) {
+    if text.isEmpty {
+      placeholderLabel.isHidden = false
+    } else {
+      placeholderLabel.isHidden = true
+    }
+  }
+}
+extension Reactive where Base: TFResizableTextView {
+  var text: ControlProperty<String?> {
+    self.base.textView.rx.text
+  }
+}
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct TFResizableTextViewPreview: PreviewProvider {
+  
+  static var previews: some View {
+    UIViewPreview {
+      let component = TFResizableTextView()
+      return component
+    }
+    .frame(width: 375, height: 120)
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
+
diff --git a/Projects/Features/SignUp/Src/SubView/TFTextField.swift b/Projects/Features/SignUp/Src/SubView/TFTextField.swift
new file mode 100644
index 00000000..9c965830
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/TFTextField.swift
@@ -0,0 +1,234 @@
+//
+//  TFTextField.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 2024/04/18.
+//
+
+import UIKit
+
+import DSKit
+
+// TODO: RxExtension 및 기존 프로퍼티 private
+// TODO: textField focus
+class TFTextField: UIControl {
+
+  enum State {
+    case text(text: String?)
+    case error(error: InputError)
+  }
+
+  enum InputError: Error {
+    case validate(text: String)
+    case overflow
+  }
+
+  var text: String? {
+    set {
+      self.textField.text = newValue
+      self.divider.backgroundColor = (newValue?.isEmpty ?? true) == false
+      ? DSKitAsset.Color.primary500.color
+      : DSKitAsset.Color.neutral300.color
+      self.calculateCount(count: newValue?.count ?? 0)
+    } get {
+      return self.textField.text
+    }
+  }
+
+  var placeholder: String? = nil {
+    didSet {
+      self.textField.placeholder = placeholder
+    }
+  }
+
+  /// set Total Count
+  private var totalCount: Int
+
+  var hasText: Bool {
+     return (text?.isEmpty ?? true) == false
+  }
+
+  lazy var textField: UITextField = UITextField().then {
+    $0.placeholder = "입력"
+    $0.textColor = DSKitAsset.Color.primary500.color
+    $0.font = .thtH2B
+    $0.autocapitalizationType = .none
+//    $0.keyboardType = .numberPad
+  }
+
+  lazy var clearBtn: UIButton = UIButton().then {
+    $0.setImage(DSKitAsset.Image.Icons.closeCircle.image, for: .normal)
+    $0.setTitle(nil, for: .normal)
+    $0.backgroundColor = .clear
+    $0.isHidden = true
+  }
+
+  lazy var divider: UIView = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral300.color
+  }
+
+  lazy var infoImageView: UIImageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
+    $0.tintColor = DSKitAsset.Color.neutral400.color
+  }
+
+  lazy var descLabel: UILabel = UILabel().then {
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 3
+  }
+
+  lazy var errorDescriptionLabel = UILabel().then {
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.error.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 3
+  }
+
+  lazy var countLabel = UILabel().then {
+    $0.text = "(0/12)"
+    $0.font = .thtCaption1R
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 2
+  }
+
+  init(description: String = "", totalCount: Int = 20, placeholder: String? = nil) {
+    self.totalCount = totalCount
+    super.init(frame: .zero)
+    self.descLabel.text = description
+    self.placeholder = placeholder
+    makeUI()
+  }
+  
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  func makeUI() {
+    addSubviews(
+      textField, clearBtn,
+      divider,
+      errorDescriptionLabel,
+      infoImageView, descLabel, countLabel
+    )
+
+    textField.setContentHuggingPriority(.defaultLow, for: .horizontal)
+    textField.snp.makeConstraints {
+      $0.top.leading.equalToSuperview()
+    }
+
+    clearBtn.snp.makeConstraints {
+      $0.leading.equalTo(textField.snp.trailing)
+      $0.centerY.equalTo(textField)
+      $0.trailing.equalToSuperview()
+      $0.size.equalTo(24)
+    }
+
+    divider.snp.makeConstraints {
+      $0.leading.trailing.equalToSuperview()
+      $0.top.equalTo(textField.snp.bottom).offset(2)
+      $0.height.equalTo(2)
+    }
+
+    errorDescriptionLabel.snp.makeConstraints {
+      $0.leading.trailing.equalToSuperview()
+      $0.top.equalTo(divider.snp.bottom)
+      $0.height.equalTo(20).priority(.low)
+    }
+
+    infoImageView.snp.makeConstraints {
+      $0.leading.equalTo(textField)
+      $0.size.equalTo(16)
+      $0.top.equalTo(errorDescriptionLabel.snp.bottom)
+    }
+
+    descLabel.snp.makeConstraints {
+      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
+      $0.trailing.equalTo(countLabel.snp.leading)
+      $0.top.equalTo(infoImageView)
+      $0.bottom.equalToSuperview()
+    }
+
+    countLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+    countLabel.snp.makeConstraints {
+      $0.trailing.equalToSuperview()
+      $0.top.equalTo(descLabel)
+    }
+
+    calculateCount(count: 0)
+    bindAction()
+  }
+
+  func bindAction() {
+    clearBtn.addAction(UIAction(handler: { [weak self] _ in
+      self?.textField.text = ""
+      self?.clearBtn.isHidden = true
+      self?.textField.sendActions(for: .editingChanged)
+      self?.sendActions(for: .editingChanged)
+    }), for: .touchUpInside)
+
+    textField.addAction(UIAction(handler: { [weak self] _ in
+      self?.clearBtn.isHidden = self?.textField.text?.isEmpty == true
+      self?.text = self?.textField.text
+      self?.render(state: .text(text: self?.text))
+      if let totalCount = self?.totalCount, let count = self?.text?.count, count > totalCount {
+        self?.render(state: .error(error: InputError.overflow))
+      }
+      self?.sendActions(for: .editingChanged)
+    }), for: .editingChanged)
+  }
+  
+  override func becomeFirstResponder() -> Bool {
+    self.textField.becomeFirstResponder()
+  }
+
+  func render(state: State) {
+    switch state {
+    case let .text(text):
+      self.errorDescriptionLabel.isHidden = true
+      self.text = text
+      self.errorDescriptionLabel.text = ""
+
+    case .error(let inputError):
+      self.errorDescriptionLabel.isHidden = false
+      self.divider.backgroundColor = DSKitAsset.Color.error.color
+
+      if case let .validate(description) = inputError {
+        self.errorDescriptionLabel.text = description
+        self.countLabel.asColor(targetString: "\(self.text?.count ?? 0)", color: DSKitAsset.Color.neutral400.color)
+      }
+      if case .overflow = inputError {
+        self.errorDescriptionLabel.text = "\(self.totalCount)자 이상 입력할 수 없습니다."
+        self.countLabel.asColor(targetString: "\(self.text?.count ?? 0)", color: DSKitAsset.Color.error.color)
+      }
+    }
+  }
+
+  private func calculateCount(count: Int) {
+    let fullText = "(\(count)/\(totalCount))"
+    let target = "\(count)"
+    self.countLabel.text = fullText
+    self.countLabel.asFont(targetString: target, font: .thtCaption1B)
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct TFTextFieldPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      let component = TFTextField()
+      component.render(state: .text(text: "닉네임닉네임"))
+      component.render(state: .error(error: .overflow))
+      component.render(state: .error(error: .validate(text: "중복된 닉네임입니다.")))
+//      component.render(state: .text(text: nil))
+      return component
+    }
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SubView/TFTextView.swift b/Projects/Features/SignUp/Src/SubView/TFTextView.swift
new file mode 100644
index 00000000..e3fd8aa4
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/TFTextView.swift
@@ -0,0 +1,261 @@
+//
+//  TFTextView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/24.
+//
+
+import UIKit
+
+import DSKit
+
+// TODO: RxExtension 및 기존 프로퍼티 private
+// TODO: textField focus
+class TFTextView: UIControl {
+  private let maxHeight: CGFloat = 120
+  private let minHeight: CGFloat = 40
+  private var textViewHeightConstraint: NSLayoutConstraint?
+
+  enum State {
+    case text(text: String?)
+    case error(error: InputError)
+    case focus
+    case focusOut
+  }
+
+  enum InputError: Error {
+    case validate(text: String)
+    case overflow
+  }
+
+  var text: String? {
+    set {
+      self.textField.text = newValue
+      self.calculateCount(count: newValue?.count ?? 0)
+    } get {
+      return self.textField.text
+    }
+  }
+
+  /// set Total Count
+  private var totalCount: Int
+
+  lazy var textField = UITextView().then {
+    $0.textColor = DSKitAsset.Color.primary500.color
+    $0.font = .thtSubTitle1R
+    $0.isScrollEnabled = false
+    $0.isEditable = true
+    $0.keyboardType = .asciiCapable
+  }
+
+  lazy var clearBtn: UIButton = UIButton().then {
+    $0.setImage(DSKitAsset.Image.Icons.closeCircle.image, for: .normal)
+    $0.setTitle(nil, for: .normal)
+    $0.backgroundColor = .clear
+    $0.isHidden = true
+  }
+
+  lazy var divider: UIView = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral300.color
+  }
+
+  lazy var infoImageView: UIImageView = UIImageView().then {
+    $0.image = DSKitAsset.Image.Icons.explain.image.withRenderingMode(.alwaysTemplate)
+    $0.tintColor = DSKitAsset.Color.neutral400.color
+  }
+
+  lazy var descLabel: UILabel = UILabel().then {
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 3
+  }
+
+  lazy var errorDescriptionLabel = UILabel().then {
+    $0.font = .thtCaption1M
+    $0.textColor = DSKitAsset.Color.error.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 3
+  }
+
+  lazy var countLabel = UILabel().then {
+    $0.text = "(0/12)"
+    $0.font = .thtCaption1R
+    $0.textColor = DSKitAsset.Color.neutral400.color
+    $0.textAlignment = .left
+    $0.numberOfLines = 2
+  }
+
+  init(description: String = "", totalCount: Int = 20, placeholder: String? = nil) {
+    self.totalCount = totalCount
+    super.init(frame: .zero)
+    self.descLabel.text = description
+    makeUI()
+  }
+
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  func makeUI() {
+    addSubviews(
+      textField, clearBtn,
+      divider,
+      errorDescriptionLabel,
+      infoImageView, descLabel, countLabel
+    )
+
+    textField.setContentHuggingPriority(.defaultLow, for: .horizontal)
+    textField.snp.makeConstraints {
+      $0.top.leading.equalToSuperview()
+      $0.trailing.equalTo(clearBtn.snp.leading)
+    }
+    self.textViewHeightConstraint = textField.heightAnchor.constraint(equalToConstant: minHeight)
+    self.textViewHeightConstraint?.isActive = true
+
+    clearBtn.snp.makeConstraints {
+      $0.leading.equalTo(textField.snp.trailing)
+      $0.bottom.equalTo(textField)
+      $0.trailing.equalToSuperview()
+      $0.size.equalTo(24)
+    }
+
+    divider.snp.makeConstraints {
+      $0.leading.trailing.equalToSuperview()
+      $0.top.equalTo(textField.snp.bottom).offset(2)
+      $0.height.equalTo(2)
+    }
+
+    errorDescriptionLabel.snp.makeConstraints {
+      $0.leading.trailing.equalToSuperview()
+      $0.top.equalTo(divider.snp.bottom)
+      $0.height.equalTo(20).priority(.low)
+    }
+
+    infoImageView.snp.makeConstraints {
+      $0.leading.equalTo(textField)
+      $0.size.equalTo(16)
+      $0.top.equalTo(errorDescriptionLabel.snp.bottom)
+    }
+
+    descLabel.snp.makeConstraints {
+      $0.leading.equalTo(infoImageView.snp.trailing).offset(6)
+      $0.trailing.equalTo(countLabel.snp.leading)
+      $0.top.equalTo(infoImageView)
+      $0.bottom.equalToSuperview()
+    }
+
+    countLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
+    countLabel.snp.makeConstraints {
+      $0.trailing.equalToSuperview()
+      $0.top.equalTo(descLabel)
+    }
+
+    calculateCount(count: 0)
+    bindAction()
+  }
+
+  func bindAction() {
+    clearBtn.addAction(UIAction(handler: { [weak self] _ in
+      self?.textField.text = ""
+      self?.clearBtn.isHidden = true
+      self?.sendActions(for: .editingChanged)
+    }), for: .touchUpInside)
+
+    textField.delegate = self
+    updateDividerColor(.focusOut)
+  }
+
+  func render(state: State) {
+    switch state {
+    case let .text(text):
+      self.errorDescriptionLabel.isHidden = true
+      self.text = text
+      self.errorDescriptionLabel.text = ""
+
+    case .error(let inputError):
+      self.updateErrorView(inputError)
+    default: break
+    }
+  }
+
+  private func calculateCount(count: Int) {
+    let fullText = "(\(count)/\(totalCount))"
+    let target = "\(count)"
+    self.countLabel.text = fullText
+    self.countLabel.asFont(targetString: target, font: .thtCaption1B)
+
+    if count > totalCount {
+      self.render(state: .error(error: .overflow))
+    }
+  }
+}
+
+extension TFTextView: UITextViewDelegate {
+  public func textViewDidBeginEditing(_ textView: UITextView) {
+    updateDividerColor(.focus)
+  }
+
+  public func textViewDidEndEditing(_ textView: UITextView) {
+    updateDividerColor(.focusOut)
+  }
+
+  public func textViewDidChange(_ textView: UITextView) {
+    updateDividerColor(.focusOut)
+    self.calculateCount(count: textView.text.count)
+    self.sendActions(for: .valueChanged)
+    updateDividerColor(.text(text: textField.text))
+  }
+
+  func updateDividerColor(_ state: State) {
+    switch state {
+    case .error:
+      self.divider.backgroundColor = DSKitAsset.Color.error.color
+    case .focus:
+      self.divider.backgroundColor = DSKitAsset.Color.primary500.color
+    default:
+      self.divider.backgroundColor = self.textField.text.isEmpty
+      ? DSKitAsset.Color.neutral300.color
+      : DSKitAsset.Color.primary500.color
+    }
+    self.clearBtn.isHidden = self.textField.text.isEmpty
+  }
+
+  func updateErrorView(_ inputError: InputError) {
+    self.errorDescriptionLabel.isHidden = false
+
+    if case let .validate(description) = inputError {
+      self.errorDescriptionLabel.text = description
+      self.countLabel.asColor(targetString: "\(self.text?.count ?? 0)", color: DSKitAsset.Color.neutral400.color)
+    }
+    if case .overflow = inputError {
+      self.errorDescriptionLabel.text = "\(self.totalCount)자 이상 입력할 수 없습니다."
+      self.countLabel.asColor(targetString: "\(self.text?.count ?? 0)", color: DSKitAsset.Color.error.color)
+    }
+    updateDividerColor(.error(error: inputError))
+  }
+
+}
+
+extension Reactive where Base: TFTextView {
+  var text: ControlProperty<String?> {
+    self.base.textField.rx.text
+  }
+}
+
+#if canImport(SwiftUI) && DEBUG
+import SwiftUI
+
+struct TFTextViewPreview: PreviewProvider {
+
+  static var previews: some View {
+    UIViewPreview {
+      let component = TFTextView()
+//      component.render(state: .error(error: .validate(text: "중복된 닉네임입니다.")))
+      return component
+    }
+    .frame(width: 375, height: 120)
+    .previewLayout(.sizeThatFits)
+  }
+}
+#endif
diff --git a/Projects/Features/SignUp/Src/SubView/TagPickerView.swift b/Projects/Features/SignUp/Src/SubView/TagPickerView.swift
new file mode 100644
index 00000000..c13017c9
--- /dev/null
+++ b/Projects/Features/SignUp/Src/SubView/TagPickerView.swift
@@ -0,0 +1,103 @@
+//
+//  InterestPickerView.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+import SignUpInterface
+
+import DSKit
+
+final class TagPickerView: TFBaseView {
+  struct AttributedTitleInfo {
+    let title: String
+    let targetText: String
+  }
+
+  private let titleInfo: AttributedTitleInfo
+  private let subTitleInfo: AttributedTitleInfo
+
+  init(titleInfo: AttributedTitleInfo, subTitleInfo: AttributedTitleInfo) {
+    self.titleInfo = titleInfo
+    self.subTitleInfo = subTitleInfo
+    super.init(frame: .zero)
+  }
+  
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+  
+  lazy var container = UIView().then {
+    $0.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
+  lazy var titleLabel: UILabel = UILabel().then {
+    $0.text = self.titleInfo.title
+    $0.textColor = DSKitAsset.Color.neutral300.color
+    $0.font = .thtH1B
+    $0.asColor(targetString: self.titleInfo.targetText, color: DSKitAsset.Color.neutral50.color)
+  }
+
+  lazy var subTitleLabel: UILabel = UILabel().then {
+    $0.text = self.subTitleInfo.title
+    $0.textColor = DSKitAsset.Color.neutral300.color
+    $0.font = .thtH4B
+    $0.asColor(targetString: self.subTitleInfo.targetText, color: DSKitAsset.Color.neutral50.color)
+  }
+
+  lazy var collectionView: UICollectionView = {
+    let layout = LeftAlignCollectionViewFlowLayout(sidePadding: 0)
+    layout.scrollDirection = .vertical
+    layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
+
+    let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+    collectionView.register(cellType: InputTagCollectionViewCell.self)
+    collectionView.isScrollEnabled = true
+    collectionView.showsVerticalScrollIndicator = false
+    collectionView.backgroundColor = .clear
+    return collectionView
+  }()
+
+  lazy var nextBtn = CTAButton(btnTitle: "->", initialStatus: false)
+
+  override func makeUI() {
+    addSubview(container)
+
+    container.addSubviews(
+      titleLabel,
+      subTitleLabel,
+      collectionView,
+      nextBtn
+    )
+
+    container.snp.makeConstraints {
+      $0.top.leading.trailing.equalTo(safeAreaLayoutGuide)
+      $0.bottom.equalToSuperview()
+    }
+
+    titleLabel.snp.makeConstraints {
+      $0.top.equalToSuperview().offset(76)
+      $0.leading.trailing.equalToSuperview().inset(30)
+    }
+
+    subTitleLabel.snp.makeConstraints {
+      $0.top.equalTo(titleLabel.snp.bottom).offset(30)
+      $0.leading.trailing.equalTo(titleLabel)
+    }
+
+    collectionView.setContentHuggingPriority(.defaultLow, for: .vertical)
+    collectionView.snp.makeConstraints {
+      $0.top.equalTo(subTitleLabel.snp.bottom).offset(30)
+      $0.leading.trailing.equalTo(titleLabel)
+      $0.height.equalTo(350).priority(.low)
+    }
+
+    nextBtn.snp.makeConstraints {
+      $0.top.equalTo(collectionView.snp.bottom).offset(30)
+      $0.trailing.equalTo(titleLabel)
+      $0.height.equalTo(50)
+      $0.width.equalTo(88)
+    }
+  }
+}
diff --git a/Projects/Features/SignUp/Src/UseCase/SignUpUseCase.swift b/Projects/Features/SignUp/Src/UseCase/SignUpUseCase.swift
new file mode 100644
index 00000000..cd7cdca3
--- /dev/null
+++ b/Projects/Features/SignUp/Src/UseCase/SignUpUseCase.swift
@@ -0,0 +1,111 @@
+//
+//  SignUpUseCase.swift
+//  SignUpInterface
+//
+//  Created by kangho lee on 5/1/24.
+//
+
+import Foundation
+import RxSwift
+import SignUpInterface
+import AuthInterface
+import Domain
+
+public final class SignUpUseCase: SignUpUseCaseInterface {
+
+  private let repository: SignUpRepositoryInterface
+  private let tokenStore: TokenStore
+  private let locationService: LocationServiceType
+  private let kakaoAPIService: KakaoAPIServiceType
+  private let contactService: ContactServiceType
+
+  public init(repository: SignUpRepositoryInterface, locationService: LocationServiceType, kakaoAPIService: KakaoAPIServiceType, contactService: ContactServiceType, tokenStore: TokenStore) {
+    self.repository = repository
+    self.kakaoAPIService = kakaoAPIService
+    self.contactService = contactService
+    self.locationService = locationService
+    self.tokenStore = tokenStore
+  }
+
+  public func checkNickname(nickname: String) -> Single<Bool> {
+    return repository.checkNickname(nickname: nickname)
+  }
+
+  public func idealTypes() -> Single<[Domain.EmojiType]> {
+    return repository.idealTypes()
+      .catchAndReturn([])
+      .map { $0.map { $0.toDomain() }}
+  }
+
+  public func interests() -> Single<[Domain.EmojiType]> {
+    return repository.interests()
+      .catchAndReturn([])
+      .map { $0.map { $0.toDomain() }}
+  }
+
+  public func block() -> Single<[ContactType]> {
+    self.contactService.fetchContact()
+      .map { contacts in
+        contacts.map { contact in
+          let phoneNumber = contact.phoneNumber.replacingOccurrences(of: "\\D", with: "", options: .regularExpression)
+          return ContactType(name: contact.name, phoneNumber: phoneNumber)
+        }
+      }
+  }
+
+  public func signUp(request: SignUpReq) -> Single<Void> {
+    return repository.signUp(request)
+      .flatMap { [weak self] token in
+        self?.tokenStore.saveToken(token: token)
+        return .just(())
+      }
+  }
+
+  public func uploadImage(data: [Data]) -> Single<[String]> {
+    return repository.uploadImage(data: data)
+  }
+
+  public func fetchAgreements() -> Single<Agreement> {
+    repository.fetchAgreements()
+  }
+
+  @MainActor
+  public func fetchLocation() -> Single<LocationReq> { //
+    self.locationService.requestAuthorization()
+
+    self.locationService.handleAuthorization { [weak self] granted in
+      guard granted else {
+        return
+      }
+      self?.locationService.requestLocation()
+    }
+
+    return self.locationService.publisher
+      .take(1)
+      .asSingle()
+      .flatMap { [unowned self] locationReq in
+        self.kakaoAPIService.fetchLocationByCoordinate2d(longitude: locationReq.lon, latitude: locationReq.lat)
+      }.map { model in
+        guard let model else {
+          throw LocationError.invalidLocation
+        }
+        return model
+      }
+  }
+
+  public func fetchLocation(_ address: String) -> Single<LocationReq> {
+    self.kakaoAPIService.fetchLocationByAddress(address: address)
+      .map { model in
+        guard let model else {
+          throw LocationError.invalidLocation
+        }
+        return model
+      }
+  }
+}
+
+extension SignUpInterface.EmojiType {
+  func toDomain() -> Domain.EmojiType {
+    Domain.EmojiType(idx: self.index, name: self.name, emojiCode: self.emojiCode)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/UseCase/UserInfoUseCase.swift b/Projects/Features/SignUp/Src/UseCase/UserInfoUseCase.swift
new file mode 100644
index 00000000..045e05b6
--- /dev/null
+++ b/Projects/Features/SignUp/Src/UseCase/UserInfoUseCase.swift
@@ -0,0 +1,47 @@
+//
+//  UserInfoUseCase.swift
+//  SignUpInterface
+//
+//  Created by Kanghos on 5/29/24.
+//
+
+import Foundation
+import SignUpInterface
+import RxSwift
+
+public class UserInfoUseCase: UserInfoUseCaseInterface {
+  private let repository: UserInfoRepositoryInterface
+  
+  public init(repository: UserInfoRepositoryInterface) {
+    self.repository = repository
+  }
+
+  public func savePhoneNumber(_ phoneNumber: String) {
+    repository.savePhoneNumber(phoneNumber)
+    repository.deleteUserInfo()
+  }
+
+  public func fetchPhoneNumber() -> Single<String> {
+    repository.fetchPhoneNumber()
+  }
+
+  public func fetchUserInfo() -> Single<UserInfo> {
+    repository.fetchUserInfo()
+  }
+  
+  public func updateUserInfo(userInfo: UserInfo) {
+    return repository.updateUserInfo(userInfo: userInfo)
+  }
+  
+  public func deleteUserInfo() {
+    return repository.deleteUserInfo()
+  }
+
+  public func fetchUserPhotos(key: String, fileNames: [String]) -> Single<[Data]> {
+    return repository.fetchUserPhotos(key: key, fileNames: fileNames)
+  }
+
+  public func saveUserPhotos(key: String, datas: [Data]) -> Single<[String]> {
+    repository.saveUserPhotos(key: key, datas: datas)
+  }
+}
diff --git a/Projects/Features/SignUp/Src/Util/CollectionViewLayout+Util.swift b/Projects/Features/SignUp/Src/Util/CollectionViewLayout+Util.swift
new file mode 100644
index 00000000..b8e4e336
--- /dev/null
+++ b/Projects/Features/SignUp/Src/Util/CollectionViewLayout+Util.swift
@@ -0,0 +1,43 @@
+//
+//  CollectionViewLayout+Util.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+
+extension UICollectionViewLayout {
+  static func photoPickLayout(edge: CGFloat = 5) -> UICollectionViewLayout {
+    let edgeInset = NSDirectionalEdgeInsets(top: edge, leading: edge, bottom: edge, trailing: edge)
+
+    let layout = UICollectionViewCompositionalLayout {
+      (sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
+
+      let leadingItem = NSCollectionLayoutItem(
+        layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.65),
+                                           heightDimension: .fractionalHeight(1.0)))
+      leadingItem.contentInsets = edgeInset
+
+      let trailingItem = NSCollectionLayoutItem(
+        layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
+                                           heightDimension: .fractionalHeight(0.5)))
+      trailingItem.contentInsets = edgeInset
+
+      let trailingGroup = NSCollectionLayoutGroup.vertical(
+        layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.35),
+                                           heightDimension: .fractionalHeight(1.0)),
+        repeatingSubitem: trailingItem, count: 2)
+
+      let nestedGroup = NSCollectionLayoutGroup.horizontal(
+        layoutSize: NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
+                                           heightDimension: .fractionalHeight(1.0)),
+        subitems: [leadingItem, trailingGroup])
+      let section = NSCollectionLayoutSection(group: nestedGroup)
+      return section
+
+    }
+    return layout
+  }
+}
+
diff --git a/Projects/Features/SignUp/Src/Util/Date+Util.swift b/Projects/Features/SignUp/Src/Util/Date+Util.swift
new file mode 100644
index 00000000..92458ea8
--- /dev/null
+++ b/Projects/Features/SignUp/Src/Util/Date+Util.swift
@@ -0,0 +1,24 @@
+//
+//  Date+Util.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/16.
+//
+
+import Foundation
+
+// https://stackoverflow.com/questions/31590316/how-do-i-find-the-number-of-days-in-given-month-and-year-using-swift
+extension Date {
+  func daysInMonth(month: Int, year: Int) -> Int {
+    var dateComponents = DateComponents()
+    dateComponents.year = year
+    dateComponents.month = month
+    if let d = Calendar.current.date(from: dateComponents),
+       let interval = Calendar.current.dateInterval(of: .month, for: d),
+       let days = Calendar.current.dateComponents([.day], from: interval.start, to: interval.end).day {
+      return days }
+    else {
+      return 30
+    }
+  }
+}
diff --git a/Projects/Features/SignUp/Src/Util/UIButton+Util.swift b/Projects/Features/SignUp/Src/Util/UIButton+Util.swift
new file mode 100644
index 00000000..e1c46e30
--- /dev/null
+++ b/Projects/Features/SignUp/Src/Util/UIButton+Util.swift
@@ -0,0 +1,27 @@
+//
+//  UIButton+Util.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/21.
+//
+
+import UIKit
+
+import DSKit
+
+extension UIButton {
+  static var plusButton: UIButton {
+    let button = UIButton()
+    var config = UIButton.Configuration.filled()
+
+    config.baseBackgroundColor = DSKitAsset.Color.disabled.color
+    let imageConfig = UIImage.SymbolConfiguration(pointSize: 10)
+    config.image = UIImage(systemName: "plus", withConfiguration: imageConfig)?.withTintColor(DSKitAsset.Color.neutral50.color, renderingMode: .alwaysOriginal)
+    config.imagePadding = 14
+    config.cornerStyle = .capsule
+
+    button.configuration = config
+
+    return button
+  }
+}
diff --git a/Projects/Features/SignUp/Src/Util/UILabel+Util.swift b/Projects/Features/SignUp/Src/Util/UILabel+Util.swift
new file mode 100644
index 00000000..afa2ee1a
--- /dev/null
+++ b/Projects/Features/SignUp/Src/Util/UILabel+Util.swift
@@ -0,0 +1,45 @@
+//
+//  UIFont+Util.swift
+//  SignUp
+//
+//  Created by Kanghos on 2024/04/16.
+//
+
+import UIKit
+import DSKit
+
+extension UILabel {
+  @discardableResult
+  func asColor(targetString: String, color: UIColor) -> Self {
+    let fullText = self.text ?? ""
+    let range = (fullText as NSString).range(of: targetString)
+    var mutable: NSMutableAttributedString
+
+    if let attributed = self.attributedText {
+      mutable = NSMutableAttributedString(attributedString: attributed)
+    } else {
+      mutable = NSMutableAttributedString(string: fullText)
+    }
+    mutable.addAttributes([.foregroundColor: color], range: range)
+    self.attributedText = mutable
+    return self
+  }
+
+  @discardableResult
+  func asFont(targetString: String, font: UIFont) -> Self {
+    let fullText = self.text ?? ""
+    let range = (fullText as NSString).range(of: targetString)
+
+    var mutable: NSMutableAttributedString
+
+    if let attributed = self.attributedText {
+      mutable = NSMutableAttributedString(attributedString: attributed)
+    } else {
+      mutable = NSMutableAttributedString(string: fullText)
+    }
+    mutable.addAttribute(.font, value: font, range: range)
+    self.attributedText = mutable
+
+    return self
+  }
+}
diff --git a/Projects/Features/Src/Application/Coordinator/AppCoordinator.swift b/Projects/Features/Src/Application/Coordinator/AppCoordinator.swift
index 2661d21f..cdd6e9b0 100644
--- a/Projects/Features/Src/Application/Coordinator/AppCoordinator.swift
+++ b/Projects/Features/Src/Application/Coordinator/AppCoordinator.swift
@@ -10,42 +10,51 @@ import Foundation
 
 import Core
 import SignUpInterface
+import AuthInterface
 import DSKit
 
 protocol AppCoordinating {
-  func signUpFlow()
+  func launchFlow()
+  func authFlow()
   func mainFlow()
 }
 
 final class AppCoordinator: LaunchCoordinator, AppCoordinating {
 
   private let mainBuildable: MainBuildable
-  private let signUpBuildable: SignUpBuildable
+  private let authBuildable: AuthBuildable
+  private let launchBuildable: LaunchBuildable
 
   init(
     viewControllable: ViewControllable,
     mainBuildable: MainBuildable,
-    signUpBuildable: SignUpBuildable
+    authBuildable: AuthBuildable,
+    launchBUidlable: LaunchBuildable
   ) {
     self.mainBuildable = mainBuildable
-    self.signUpBuildable = signUpBuildable
+    self.authBuildable = authBuildable
+    self.launchBuildable = launchBUidlable
     super.init(viewControllable: viewControllable)
   }
 
   public override func start() {
-    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
-      self.selectFlow()
-    }
+    launchFlow()
   }
 
-  // MARK: - public
-  func signUpFlow() {
-    let signUpCoordinator = self.signUpBuildable.build()
+  func launchFlow() {
+    let coordinator = self.launchBuildable.build(rootViewControllable: self.viewControllable)
+    attachChild(coordinator)
+    coordinator.delegate = self
+    coordinator.start()
+  }
 
-    attachChild(signUpCoordinator)
-    signUpCoordinator.delegate = self
+  // MARK: - public
+  func authFlow() {
+    let coordinator = self.authBuildable.build()
 
-    signUpCoordinator.start()
+    attachChild(coordinator)
+    coordinator.delegate = self
+    coordinator.start()
   }
 
   func mainFlow() {
@@ -56,26 +65,32 @@ final class AppCoordinator: LaunchCoordinator, AppCoordinating {
 
     mainCoordinator.start()
   }
-
-  // MARK: - Private
-  private func selectFlow() {
-    mainFlow()
-  }
 }
 
 extension AppCoordinator: MainCoordinatorDelegate {
   func detachTab(_ coordinator: Coordinator) {
     detachChild(coordinator)
 
-    signUpFlow()
+    authFlow()
   }
 }
 
-extension AppCoordinator: SignUpCoordinatorDelegate {
-  
-  func detachSignUp(_ coordinator: Coordinator) {
+extension AppCoordinator: AuthCoordinatingDelegate {
+  func detachAuth(_ coordinator: Core.Coordinator) {
     detachChild(coordinator)
-    
+
     mainFlow()
   }
 }
+
+extension AppCoordinator: LaunchCoordinatingDelegate {
+  func finishFlow(_ coordinator: Core.Coordinator, _ action: AuthInterface.LaunchAction) {
+    switch action {
+    case .needAuth:
+      authFlow()
+    case .toMain:
+      mainFlow()
+    }
+    detachChild(coordinator)
+  }
+}
diff --git a/Projects/Features/Src/Application/Coordinator/AppRootBuilder.swift b/Projects/Features/Src/Application/Coordinator/AppRootBuilder.swift
index f782eed4..7e42c40b 100644
--- a/Projects/Features/Src/Application/Coordinator/AppRootBuilder.swift
+++ b/Projects/Features/Src/Application/Coordinator/AppRootBuilder.swift
@@ -9,8 +9,8 @@ import UIKit
 
 import Core
 import DSKit
+import Auth
 import SignUp
-import SignUpInterface
 
 public protocol AppRootBuildable {
   func build() -> LaunchCoordinating
@@ -19,22 +19,31 @@ public protocol AppRootBuildable {
 public final class AppRootBuilder: AppRootBuildable {
   public init() { }
 
-  lazy var mainBuildable: MainBuildable = {
+  private lazy var mainBuildable: MainBuildable = {
     MainBuilder()
   }()
 
-  lazy var signUpBuildable: SignUpBuildable = {
+  private lazy var signUpBuildable: SignUpBuildable = {
     SignUpBuilder()
   }()
 
+  private lazy var authBuildable: AuthBuildable = {
+    AuthBuilder(signUpBuilable: signUpBuildable)
+  }()
+
+  private lazy var launchBuildable: LaunchBuildable = {
+    LaunchBuilder()
+  }()
+
   public func build() -> LaunchCoordinating {
 
-    let viewController = TFLaunchViewController() 
+    let viewController = NavigationViewControllable()
 
     let coordinator = AppCoordinator(
       viewControllable: viewController,
       mainBuildable: self.mainBuildable,
-      signUpBuildable: self.signUpBuildable
+      authBuildable: self.authBuildable,
+      launchBUidlable: self.launchBuildable
     )
     return coordinator
   }
diff --git a/Projects/Modules/DesignSystem/Resources/Color.xcassets/THTOrange400.colorset/Contents.json b/Projects/Modules/DesignSystem/Resources/Color.xcassets/THTOrange400.colorset/Contents.json
new file mode 100644
index 00000000..70be8277
--- /dev/null
+++ b/Projects/Modules/DesignSystem/Resources/Color.xcassets/THTOrange400.colorset/Contents.json
@@ -0,0 +1,20 @@
+{
+  "colors" : [
+    {
+      "color" : {
+        "color-space" : "srgb",
+        "components" : {
+          "alpha" : "1.000",
+          "blue" : "0x39",
+          "green" : "0x75",
+          "red" : "0xFF"
+        }
+      },
+      "idiom" : "universal"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}
diff --git a/Projects/Modules/DesignSystem/Src/BaseViewController/TFBaseViewController.swift b/Projects/Modules/DesignSystem/Src/BaseViewController/TFBaseViewController.swift
index 11353241..82c5636e 100644
--- a/Projects/Modules/DesignSystem/Src/BaseViewController/TFBaseViewController.swift
+++ b/Projects/Modules/DesignSystem/Src/BaseViewController/TFBaseViewController.swift
@@ -14,13 +14,18 @@ open class TFBaseViewController: UIViewController, ViewControllable {
     super.init(nibName: nil, bundle: nil)
     TFLogger.cycle(name: self)
   }
+  
+  open override func loadView() {
+    super.loadView()
+    
+    view.backgroundColor = DSKitAsset.Color.neutral700.color
+  }
 
   open override func viewDidLoad() {
     super.viewDidLoad()
     
     TFLogger.cycle(name: self)
 
-//    self.view.backgroundColor = DSKitAsset.Color.neutral700.color
     makeUI()
     bindViewModel()
     navigationSetting()
@@ -39,18 +44,27 @@ open class TFBaseViewController: UIViewController, ViewControllable {
 
   open func bindViewModel() { }
 
+//  https://ios-development.tistory.com/697
   open func navigationSetting() {
-//    navigationController?.navigationBar.topItem?.title = ""
-    navigationController?.navigationBar.backIndicatorImage = DSKitAsset.Image.Icons.chevron.image
-    navigationController?.navigationBar.backIndicatorTransitionMaskImage = DSKitAsset.Image.Icons.chevron.image
-    navigationController?.navigationBar.tintColor = DSKitAsset.Color.neutral50.color
-
+    
+    let backButtonImage = DSKitAsset.Image.Icons.chevron.image.withAlignmentRectInsets(.init(top: 0, left: -10, bottom: 0, right: 0))
+    
+    let backButtonAppearence = UIBarButtonItemAppearance()
+    backButtonAppearence.normal.titleTextAttributes = [.foregroundColor: UIColor.clear, .font: UIFont.systemFont(ofSize: 0)]
+    
     let navBarAppearance = UINavigationBarAppearance()
     navBarAppearance.titlePositionAdjustment.horizontal = -CGFloat.greatestFiniteMagnitude
     navBarAppearance.titleTextAttributes = [.font: UIFont.thtH4Sb, .foregroundColor: DSKitAsset.Color.neutral50.color]
     navBarAppearance.backgroundColor = DSKitAsset.Color.neutral700.color
-    navBarAppearance.shadowColor = nil
-    navigationItem.standardAppearance = navBarAppearance
-    navigationItem.scrollEdgeAppearance = navBarAppearance
+    navBarAppearance.shadowColor = .clear
+    navBarAppearance.setBackIndicatorImage(backButtonImage, transitionMaskImage: backButtonImage)
+    navBarAppearance.backButtonAppearance = backButtonAppearence
+    
+    // Bar button title color
+    navigationController?.navigationBar.tintColor = DSKitAsset.Color.neutral50.color
+
+    navigationController?.navigationBar.standardAppearance = navBarAppearance
+    navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance
+    navigationController?.navigationBar.isTranslucent = false
   }
 }
diff --git a/Projects/Modules/DesignSystem/Src/BaseViewController/TFLaunchVIewController.swift b/Projects/Modules/DesignSystem/Src/BaseViewController/TFLaunchVIewController.swift
index bbf224db..50ea1c6c 100644
--- a/Projects/Modules/DesignSystem/Src/BaseViewController/TFLaunchVIewController.swift
+++ b/Projects/Modules/DesignSystem/Src/BaseViewController/TFLaunchVIewController.swift
@@ -9,19 +9,40 @@ import UIKit
 
 import Lottie
 
-public final class TFLaunchViewController: TFBaseViewController {
+open class TFLaunchViewController: TFBaseViewController {
   private lazy var splashLottieView = LottieAnimationView(animation: AnimationAsset.logoSplash.animation)
-  
+
   public override func loadView() {
     super.loadView()
     view.backgroundColor = DSKitAsset.Color.neutral700.color
-    
-    self.view.addSubview(splashLottieView)
+    //
+    //    self.view.addSubview(splashLottieView)
+    //    splashLottieView.snp.makeConstraints {
+    //      $0.center.equalToSuperview()
+    //    $0.height.width.equalTo(view.bounds.height * 0.7)
+    //      .inset(view.bounds.height * 0.162)
+    //    }
+    //    splashLottieView.snp.makeConstraints {
+    //      $0.centerX.equalToSuperview()
+    //      $0.top.equalTo(view.safeAreaLayoutGuide)
+    //      $0.height.width.equalTo(view.bounds.height * 0.7)
+    //        .inset(view.bounds.height * 0.162)
+    ////      $0.height.equalTo(180)
+    //    }
+    //
+    //    splashLottieView.play()
+  }
+  open override func makeUI() {
+    view.addSubview(splashLottieView)
+    splashLottieView.contentMode = .scaleAspectFit
+
     splashLottieView.snp.makeConstraints {
-      $0.center.equalToSuperview()
-      $0.height.width.equalTo(view.bounds.height * 0.7)
+      $0.edges.equalToSuperview().inset(10)
+      //      $0.height.width.equalTo(view.bounds.height * 0.7)
+      //        .inset(view.bounds.height * 0.162)
     }
-    
+
     splashLottieView.play()
+
   }
 }
diff --git a/Projects/Modules/DesignSystem/Src/UIComponent/Cell/TagCollectionViewCell.swift b/Projects/Modules/DesignSystem/Src/UIComponent/Cell/TagCollectionViewCell.swift
index 474f7af7..2a991328 100644
--- a/Projects/Modules/DesignSystem/Src/UIComponent/Cell/TagCollectionViewCell.swift
+++ b/Projects/Modules/DesignSystem/Src/UIComponent/Cell/TagCollectionViewCell.swift
@@ -9,7 +9,7 @@ import UIKit
 
 //import LikeInterface
 
-final class TagCollectionViewCell: UICollectionViewCell {
+public final class TagCollectionViewCell: UICollectionViewCell {
   private lazy var stackView: UIStackView = {
     let stackView = UIStackView()
     stackView.axis = .horizontal
@@ -64,7 +64,7 @@ final class TagCollectionViewCell: UICollectionViewCell {
     }
   }
 //
-  override func layoutSubviews() {
+  public override func layoutSubviews() {
     super.layoutSubviews()
     setUpLayer()
   }
@@ -74,34 +74,22 @@ final class TagCollectionViewCell: UICollectionViewCell {
     contentView.layer.masksToBounds = true
   }
 
-  func bind(_ viewModel: TagItemViewModel) {
+  public func bind(_ viewModel: TagItemViewModel) {
     self.titleLabel.text = viewModel.title
     self.emojiView.text = viewModel.emoji
   }
 }
 
-struct TagItemViewModel {
-  let emojiCode: String
-  let title: String
+public struct TagItemViewModel {
+  public let emojiCode: String
+  public let title: String
 
-  var emoji: String {
+  public var emoji: String {
     emojiCode.unicodeToEmoji()
   }
 
-  init(emojiCode: String, title: String) {
+  public init(emojiCode: String, title: String) {
     self.emojiCode = emojiCode
     self.title = title
   }
 }
-#if canImport(SwiftUI) && DEBUG
-import SwiftUI
-
-//struct TagCellPreview: PreviewProvider {
-////    static var previews: some View {
-////          PreviewRepresentable {
-////
-////          }.frame(width: 200, height: 150)
-////        .previewLayout(.sizeThatFits)
-////    }
-//}
-#endif
diff --git a/Projects/Modules/DesignSystem/Src/UIComponent/TagCollectionView.swift b/Projects/Modules/DesignSystem/Src/UIComponent/TagCollectionView.swift
index f30c1edf..e51d78c9 100644
--- a/Projects/Modules/DesignSystem/Src/UIComponent/TagCollectionView.swift
+++ b/Projects/Modules/DesignSystem/Src/UIComponent/TagCollectionView.swift
@@ -88,14 +88,24 @@ extension TagCollectionView: UICollectionViewDataSource {
   }
 }
 
-class LeftAlignCollectionViewFlowLayout: UICollectionViewFlowLayout {
+public class LeftAlignCollectionViewFlowLayout: UICollectionViewFlowLayout {
 
   let cellSpacing: CGFloat = 10
-
-  override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
+  let sidePadding: CGFloat
+  
+  public init(sidePadding: CGFloat = 10) {
+    self.sidePadding = sidePadding
+    super.init()
+  }
+  
+  required init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+  
+  public override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
 
     self.minimumLineSpacing = 10.0
-        sectionInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
+        sectionInset = UIEdgeInsets(top: 0, left: sidePadding, bottom: 0, right: sidePadding)
 
       let attributes = super.layoutAttributesForElements(in: rect)
 
diff --git a/Projects/Modules/DesignSystem/Src/Util/Emoji+Util.swift b/Projects/Modules/DesignSystem/Src/Util/Emoji+Util.swift
index 24408684..30e76f2c 100644
--- a/Projects/Modules/DesignSystem/Src/Util/Emoji+Util.swift
+++ b/Projects/Modules/DesignSystem/Src/Util/Emoji+Util.swift
@@ -9,7 +9,7 @@ import Foundation
 
 public extension String {
   func unicodeToEmoji() -> String {
-    guard let hex = Int(self, radix: 16),
+    guard let hex = Int(self.dropFirst(2), radix: 16),
           let scalar = UnicodeScalar(hex)
     else {
       return "🧩"
diff --git a/Projects/Modules/DesignSystem/Src/Util/Preview+UIView.swift b/Projects/Modules/DesignSystem/Src/Util/Preview+UIView.swift
index 27f8eea2..a4f271d4 100644
--- a/Projects/Modules/DesignSystem/Src/Util/Preview+UIView.swift
+++ b/Projects/Modules/DesignSystem/Src/Util/Preview+UIView.swift
@@ -24,6 +24,4 @@ public struct UIViewPreview<View: UIView>: UIViewRepresentable {
     view.setContentHuggingPriority(.defaultHigh, for: .vertical)
   }
 }
-
-
 #endif
diff --git a/Projects/Modules/DesignSystem/Src/Util/UICollectionView+Utils.swift b/Projects/Modules/DesignSystem/Src/Util/UICollectionView+Utils.swift
index be50921c..4c994237 100644
--- a/Projects/Modules/DesignSystem/Src/Util/UICollectionView+Utils.swift
+++ b/Projects/Modules/DesignSystem/Src/Util/UICollectionView+Utils.swift
@@ -33,3 +33,13 @@ public extension UICollectionView {
     return reusableView
   }
 }
+
+extension Reactive where Base: UICollectionView {
+  public func items<Sequence: Swift.Sequence, Cell: UICollectionViewCell, Source: ObservableType>
+  (cellType: Cell.Type = Cell.self)
+  -> (_ source: Source)
+  -> (_ configureCell: @escaping (Int, Sequence.Element, Cell) -> Void)
+  -> Disposable where Source.Element == Sequence {
+    return self.items(cellIdentifier: Cell.reuseIdentifier, cellType: cellType)
+  }
+}
diff --git a/Projects/Modules/DesignSystem/Src/Util/UITableView+Utils.swift b/Projects/Modules/DesignSystem/Src/Util/UITableView+Utils.swift
index 73ad644d..c0fa5a18 100644
--- a/Projects/Modules/DesignSystem/Src/Util/UITableView+Utils.swift
+++ b/Projects/Modules/DesignSystem/Src/Util/UITableView+Utils.swift
@@ -32,3 +32,13 @@ public extension UITableView {
     return cell
   }
 }
+
+extension Reactive where Base: UITableView {
+  public func items<Sequence: Swift.Sequence, Cell: UITableViewCell, Source: ObservableType>
+  (cellType: Cell.Type = Cell.self)
+  -> (_ source: Source)
+  -> (_ configureCell: @escaping (Int, Sequence.Element, Cell) -> Void)
+  -> Disposable where Source.Element == Sequence {
+    return self.items(cellIdentifier: Cell.reuseIdentifier, cellType: cellType)
+  }
+}
diff --git a/Projects/Modules/DesignSystem/Src/WebView/TFWebViewController.swift b/Projects/Modules/DesignSystem/Src/WebView/TFWebViewController.swift
new file mode 100644
index 00000000..72bed884
--- /dev/null
+++ b/Projects/Modules/DesignSystem/Src/WebView/TFWebViewController.swift
@@ -0,0 +1,87 @@
+//
+//  TFWebViewController.swift
+//  DSKit
+//
+//  Created by Kanghos on 5/30/24.
+//
+
+import UIKit
+import WebKit
+
+import Core
+
+public class TFWebViewController: TFBaseViewController {
+
+  // MARK: - Properties
+  private var webView: WKWebView?
+  private let indicator = UIActivityIndicatorView(style: .medium)
+  private let url: URL
+  // MARK: - Lifecycle
+
+  private let closeButton: UIBarButtonItem = .exit
+
+  public init(title: String? = nil, url: URL) {
+    self.url = url
+    super.init(nibName: nil, bundle: nil)
+  }
+  
+  required public init?(coder: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+  
+  public override func makeUI() {
+    view.backgroundColor = .white
+    setAttributes()
+    setContraints()
+
+    self.navigationItem.rightBarButtonItem = closeButton
+  }
+
+  private func setAttributes() {
+
+    let configuration = WKWebViewConfiguration()
+
+    webView = WKWebView(frame: .zero, configuration: configuration)
+    self.webView?.navigationDelegate = self
+
+    guard
+          let webView = webView
+    else { return }
+    let request = URLRequest(url: url)
+    webView.load(request)
+    indicator.startAnimating()
+  }
+
+  private func setContraints() {
+    guard let webView = webView else { return }
+    view.addSubview(webView)
+    view.backgroundColor = .clear
+    webView.addSubview(indicator)
+
+    webView.snp.makeConstraints {
+      $0.edges.equalToSuperview()
+    }
+
+    indicator.snp.makeConstraints {
+      $0.center.equalToSuperview()
+    }
+
+  }
+
+  public override func bindViewModel() {
+    closeButton.rx.tap
+      .subscribe(onNext: { [weak self] in
+        self?.dismiss(animated: true, completion: nil)
+      }).disposed(by: disposeBag)
+  }
+}
+
+extension TFWebViewController: WKNavigationDelegate {
+  public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
+    indicator.startAnimating()
+  }
+
+  public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
+    indicator.stopAnimating()
+  }
+}
diff --git a/Projects/Modules/Network/Src/Network/BaseTargetType.swift b/Projects/Modules/Network/Src/Network/BaseTargetType.swift
index 0ebe9c3d..599dedff 100644
--- a/Projects/Modules/Network/Src/Network/BaseTargetType.swift
+++ b/Projects/Modules/Network/Src/Network/BaseTargetType.swift
@@ -7,16 +7,20 @@
 
 import Foundation
 import Moya
+import Core
 
 public protocol BaseTargetType: TargetType { }
 
 public extension BaseTargetType {
   var baseURL: URL {
-    return URL(string: "http://tht-talk.store/")!
+    return URL(string: "http://tht-talk.co.kr/")!
   }
 
   var headers: [String: String]? {
-    return nil
+    return [:]
+  }
+//    return nil
+
 //    if let accessToken = Keychain.shared.get(.accessToken) {
 //      return [
 //        "Authorization": "Bearer \(accessToken)",
@@ -25,6 +29,8 @@ public extension BaseTargetType {
 //    } else {
 //      return nil
 //    }
+  var validationType: ValidationType {
+    return.customCodes(Array(200..<500).filter { $0 != 401 })
   }
 }
 
diff --git a/Projects/Modules/Network/Src/Network/ProviderProtocol.swift b/Projects/Modules/Network/Src/Network/ProviderProtocol.swift
index b3cd452d..11bed986 100644
--- a/Projects/Modules/Network/Src/Network/ProviderProtocol.swift
+++ b/Projects/Modules/Network/Src/Network/ProviderProtocol.swift
@@ -13,7 +13,6 @@ import RxSwift
 
 public protocol ProviderProtocol: AnyObject, Networkable {
   var provider: MoyaProvider<Target> { get set }
-  init(isStub: Bool, sampleStatusCode: Int, customEndpointClosure: ((Target) -> Endpoint)?)
 }
 
 public extension ProviderProtocol {
@@ -47,6 +46,35 @@ public extension ProviderProtocol {
   func request<D: Decodable>(type: D.Type, target: Target) -> Single<D> {
     provider.rx.request(target)
       .map(type)
+      .catch { error in
+        if let error = error as? MoyaError {
+          print(error.localizedDescription)
+          return .error(error)
+        }
+        if let error = error as? DecodingError {
+          print(error.localizedDescription)
+          return .error(error)
+        }
+        print(error.localizedDescription)
+        return .error(error)
+      }
+  }
+
+  func request<D: Decodable>(target: Target, completion: @escaping (Result<D, Error>) -> Void) {
+    provider.request(target) { result in
+      switch result {
+      case let .success(response):
+        let decoder = JSONDecoder()
+        do {
+          let model = try decoder.decode(D.self, from: response.data)
+          completion(.success(model))
+        } catch {
+          completion(.failure(error))
+        }
+      case let .failure(error):
+        completion(.failure(error))
+      }
+    }
   }
 
   func requestWithNoContent(target: Target) -> Single<Void> {
diff --git a/Tuist/ProjectDescriptionHelpers/Target+Templates.swift b/Tuist/ProjectDescriptionHelpers/Target+Templates.swift
index b1d9df06..9076faac 100644
--- a/Tuist/ProjectDescriptionHelpers/Target+Templates.swift
+++ b/Tuist/ProjectDescriptionHelpers/Target+Templates.swift
@@ -38,7 +38,7 @@ public extension Target {
 			product: .app,
 			bundleId: makeBundleID(with: "app"),
 			deploymentTarget: basicDeployment,
-			infoPlist: .extendingDefault(with: infoPlistExtension),
+			infoPlist: .extendingDefault(with: infoPlistExtension(name: name)),
 			sources: sources,
 			resources:  [.glob(pattern: .relativeToRoot("Projects/App/Resources/**"))],
 			dependencies: dependencies