diff --git a/.github/.gitMessage.md b/.github/.gitMessage.md
new file mode 100644
index 0000000..c2d3596
--- /dev/null
+++ b/.github/.gitMessage.md
@@ -0,0 +1,31 @@
+# Commit 규칙
+> 커밋 제목은 최대 50자 입력
+본문은 한 줄 최대 72자 입력
+Commit 메세지
+
+🪛[chore]: 코드 수정, 내부 파일 수정.
+✨[feat]: 새로운 기능 구현.
+🎨[style]: 스타일 관련 기능.(코드의 구조/형태 개선)
+➕[add]: Feat 이외의 부수적인 코드 추가, 라이브러리 추가
+🔧[file]: 새로운 파일 생성, 삭제 시
+🐛[fix]: 버그, 오류 해결.
+🔥[del]: 쓸모없는 코드/파일 삭제.
+📝[docs]: README나 WIKI 등의 문서 개정.
+💄[mod]: storyboard 파일,UI 수정한 경우.
+✏️[correct]: 주로 문법의 오류나 타입의 변경, 이름 변경 등에 사용합니다.
+🚚[move]: 프로젝트 내 파일이나 코드(리소스)의 이동.
+⏪️[rename]: 파일 이름 변경이 있을 때 사용합니다.
+⚡️[improve]: 향상이 있을 때 사용합니다.
+♻️[refactor]: 전면 수정이 있을 때 사용합니다.
+🔀[merge]: 다른브렌치를 merge 할 때 사용합니다.
+✅ [test]: 테스트 코드를 작성할 때 사용합니다.
+
+
+
+### Commit Body 규칙
+> 제목 끝에 마침표(.) 금지
+한글로 작성
+브랜치 이름 규칙
+
+- `STEP1`, `STEP2`, `STEP3`
+
diff --git a/.github/Convention/Common.md b/.github/Convention/Common.md
new file mode 100644
index 0000000..d6fb58c
--- /dev/null
+++ b/.github/Convention/Common.md
@@ -0,0 +1,572 @@
+# 공통
+
+## 목차
+[한줄 최대 길이](#한-줄-최대-길이)
+[들여쓰기 규칙](#들여쓰기-규칙)
+[Guard 규칙](#guard-규칙)
+[final 규칙](#final-규칙)
+[접근자 규칙](#접근자-규칙)
+[함수정의 줄내림 규칙](#함수정의-줄내림-규칙)
+[Enum 줄내림 규칙](#enum-줄내림-규칙)
+[조건문 줄내림 규칙](#조건문-줄내림-규칙)
+[연산자 줄내림 규칙](#연산자-줄내림-규칙)
+[삼항연산자 규칙](#삼항연산자-규칙)
+[self 규칙](#self-규칙)
+[Array 선언 규칙](#array-선언-규칙)
+[메모리 관리 규칙](#메모리-관리-규칙)
+[클로저 사용 규칙](#클로저-사용-규칙)
+[Unwrapping 규칙](#unwrapping-규칙)
+[자주 사용되는 값 체크에 확장 변수 사용하기](#자주-사용되는-값-체크에-확장-변수-사용하기)
+[상수 선언 규칙](#상수-선언-규칙)
+[RxSwift 스케쥴러 지정 규칙](#rxswift-스케쥴러-지정-규칙)
+[VIPER 모듈 사이의 콜백 전달 규칙](#viper-모듈-사이의-콜백-전달-규칙)
+
+## 코드 컨벤션
+
+### 한 줄 최대 길이
+
+- 한 줄은 최대 120자를 넘지 않도록 합니다.
+- Xcode에서 **Preferences -> Text Editing -> Display -> Page guide at column** 부분을 120로 설정해서 사용해주세요.
+
+### 들여쓰기 규칙
+
+- Indent는 2칸으로 지정합니다.
+- Xcode에서 **Preferences -> Text Editing -> Display -> Line wrapping** 부분을 2 spaces로 설정해서 사용해주세요.
+
+### Guard 규칙
+
+- `guard`는 코드에서 분기를 빨리 끝낼 때, 과도한 조건문 복잡도가 생길 때 사용합니다.
+- `guard ~ else` 문이 한줄에 써진다면, 한줄로 사용합니다.
+- `Swift 5.7`부터 Shorthand syntax 사용이 가능하여 Optional Binding에 단축 구문을 사용합니다.
+
+ ```swift
+ // Preferred
+ var number: Int?
+ guard let number else { return }
+
+ // Not Preffered
+ var number: Int?
+ guard let number = number else { return }
+ ```
+
+- `guard`의 condition이 하나이고, `else`가 여러줄이라면 다음과 같이 사용합니다.
+
+ ```swift
+ guard let number else {
+ ....
+ return
+ }
+ ```
+
+- `guard`의 condition이 여러개라면 다음과 같이 사용합니다. ( 단, 최대 한줄로 작성이 가능한 경우는 한줄로 작성합니다. )
+
+ ```swift
+ // Preferred
+ guard let number, number > 0 else {
+ ....
+ return
+ }
+
+ guard
+ let name,
+ let number,
+ isFavorited
+ else { return }
+
+ // Not Preffered
+ guard let name,
+ let number,
+ isFavorited
+ else {
+ return
+ }
+ ```
+
+- `guard`가 끝난 이후, 한 줄 띄우고 코드를 작성합니다.
+
+ ```swift
+ guard let number else { return }
+
+ if number > 0 {
+ ...
+ } else {
+ ...
+ }
+ ```
+
+- `guard`가 중간에 오는 경우, 위 아래로 한줄씩 띄우고 코드를 작성합니다.
+
+ ```swift
+ let number: Int? = 0
+
+ guard let number else { return }
+
+ if number > 0 {
+ ...
+ } else {
+ ...
+ }
+ ```
+
+
+### final 규칙
+
+- 더이상 상속이 일어나지 않는 class는 `final`을 붙여서 명시해줍니다.
+
+ ```swift
+ final class Channel {
+ ...
+ }
+ ```
+
+### 접근자 규칙
+
+- class 내부에서만 쓰이는 변수는 `private`으로 명시해줍니다.
+- `fileprivate`는 필요한 경우가 아니면 피하고, `private`으로 써줍니다.
+
+ ```swift
+ final class Channel {
+ private var number = 0
+ ...
+ }
+
+### 함수정의 줄내림 규칙
+
+- 함수 정의가 길 경우 다음과 같이 줄내림 합니다.
+
+ ```swift
+ // Preferred
+ func changeChannel(
+ name: String,
+ number: Int,
+ isFavorited: Bool
+ ) {
+ ...
+ }
+
+ // Not Preferred
+ func changeChannel(
+ name: String,
+ number: Int,
+ isFavorited: Bool) {
+ ...
+ }
+ ```
+
+### Enum 줄내림 규칙
+
+- 모든 `case` 내의 코드가 한줄이고 return 문이라면 붙여서 사용합니다.
+ ```swift
+ // Preferred
+ switch channelNumber {
+ case .main: return 0
+ case .sub: return 1
+ }
+
+ // Not Preferred
+ switch channelNumber {
+ case .main:
+ return 0
+ case .sub:
+ return 1
+ }
+ ```
+
+- 단, switch 문 case 내 로직이 포함되는 경우 아래와 같이 줄내림 후 한 줄 띄우고 다음 case를 작성합니다.
+ ```swift
+ // Preferred
+ switch checkChannel {
+ case .main:
+ guard channel.number != 0 else { return }
+
+ channel.changeChannel(number: 0)
+
+ case .sub:
+ channel.changeChannel(number: 1)
+ }
+
+ // Not Preferred
+ switch checkChannel {
+ case .main:
+ guard channel.number != 0 else { return }
+
+ channel.changeChannel(number: 0)
+ case .sub:
+ channel.changeChannel(number: 1)
+ }
+ ```
+
+### 조건문 줄내림 규칙
+
+- `if` 나 `guard`에서 조건문이 여러개인 경우 줄이 길면 다음과 같이 줄내림 해줍니다.
+ - `,`로 나뉠 땐 인덴트를 한번 넣어줍니다.
+ - `||`, `&&`는 한줄 내에서 줄내림 된거처럼 한번 더 인덴트를 넣어줍니다.
+
+ ```swift
+ if let currentNumber,
+ let number = channel.number,
+ currentNumber > 0
+ || number > 0
+ || currentNumber == number,
+ let isFavorited = isFavorited {
+ ....
+ }
+
+ guard
+ let currentNumber,
+ let number = channel.number,
+ currentNumber > 0
+ || number > 0
+ || currentNumber == number,
+ let isFavorited = isFavorited {
+ ....
+ }
+
+ ```
+
+### 연산자 줄내림 규칙
+
+- `+`, `||`, `&&` 등의 연산자에 대한 줄내림은 연산자를 같이 내려줍니다.
+
+ ```swift
+ // Preferred
+ if number > 0
+ || isFavorited == ture {
+ ...
+ }
+
+ var name = "this"
+ + " is"
+ + " main"
+
+ let isSuccess = !channel.isEmpty
+ && isFavorited
+ && channel.number > 0
+
+ // Not Preferred
+ if number > 0 ||
+ isFavorited == ture {
+ ...
+ }
+
+ var name = "this" +
+ " is" +
+ " main"
+
+ let isSuccess = !channel.isEmpty &&
+ isFavorited &&
+ channel.number > 5
+ ```
+
+### 삼항연산자 규칙
+
+- `if ~ else`로 묶인 `return` 또는 값대입인 경우 삼항연산자로 줄일 수 있으면 줄여줍니다.
+
+ ```swift
+ // Preferred
+ return number == 0 ? .main : .sub
+
+ var channel = number == 0 ? .main : .sub
+
+ // Not Preferred
+ if number == 0 {
+ return .main
+ } else {
+ return .sub
+ }
+
+ var result: Channel
+ if number > 0 {
+ result = .main
+ } else {
+ result = .sub
+ }
+ ```
+
+- 줄내림이 필요한 경우 `?`를 기준으로 내려줍니다.
+
+ ```swift
+ // Preferred
+ return number == 0
+ ? .main : .sub
+
+ // Not Preferred
+ return number == 0 ?
+ .main : .sub
+ ```
+
+- 조건에 따른 단순 분기일 때는 삼항연산자를 피해줍니다.
+
+ ```swift
+ // Preferred
+ if isFavorited {
+ channel.turnOn()
+ } else {
+ channel.turnOff()
+ }
+
+ // Not Preferred
+ isFavorited ? channel.turnOn() : channel.turnOff()
+ ```
+
+- 삼항 연산자가 너무 길어질 경우 가독성을 위해 분리해줍니다.
+
+ ```swift
+ // Preferred
+ let firstCondition = c == 2 ? d : e
+ let secondCondition = b == 1 : firstCondition : f
+ return test == 0 ? a : secondCondition
+ // 또는 적절히 if를 나눠서 구현해준다.
+
+ // Not Preferred
+ return test == 0
+ ? a
+ : b == 1
+ ? c == 2
+ : d
+ : e
+ : f
+ ```
+
+### self 규칙
+
+- 클래스와 구조체 내부에서는 `self`를 명시적으로 표시해줍니다.
+
+### Array 선언 규칙
+
+- Array를 선언할 때는 다음과 같은 포맷을 지향합니다.
+
+ ```swift
+ // Preferred
+ var managers: [Manager] = []
+ var counts: [String: Int] = [:]
+
+ // Not Preferred
+ var managers = [Manager]()
+ var counts = [String: Int]()
+ ```
+
+### 메모리 관리 규칙
+
+- Retain cycle이 발생하지 않도록 `weak self`를 이용합니다. 필요하다면 `guard let self = self else`를 통해서 unwrapping을 해줍니다.
+
+ ```swift
+ self.closePopup() { [weak self] _ in
+ guard let self else { return }
+
+ self.popAllController()
+ }
+ ```
+
+### 클로저 사용 규칙
+
+- 클로저의 파라미터는 괄호를 빼고 사용합니다.
+
+ ```swift
+ // Preferred
+ { manager, user in
+ ...
+ }
+
+ // Not Preferred
+ { (manager, user) in
+ ...
+ }
+ ```
+
+- 클로져가 파라미터중 하나만 있고, 마지막에 항목이 클로져라면 파라미터 명을 생략해줍니다.
+ ```swift
+ // Preferred
+ UIView.animate(withDuration: 0.25) {
+ ...
+ }
+
+ // Not Preferred
+ UIView.animate(withDuration: 0.25, animations: { () -> Void in
+ ...
+ })
+ ```
+- 파라미터의 타입 정의는 가능하다면 생략해줍니다.
+
+ ```swift
+ // Preferred
+ { manager, user in
+ ...
+ }
+
+ // Not Preferred
+ { (manager: Manager, user: User) -> Void in
+ ...
+ }
+ ```
+
+- 클로져 밖의 괄호는 가능한 생략해 줍니다.
+ ```swift
+ // Preferred
+ self.channelView.snp.makeConstraints {
+ $0.leading.equalToSuperview().inset(xMargin)
+ $0.trailing.equalToSuperview().inset(xMargin)
+ }
+
+ // Not Preferred
+ self.channelView.snp.makeConstraints ({
+ $0.left.equalToSuperview().offset(-xMargin)
+ $0.right.equalToSuperview().offset(xMargin)
+ })
+ ```
+
+### unwrapping 규칙
+
+- 최대한 force unwrapping은 피해줍니다. 옵셔널(`?`)의 경우는 optional chaining 등으로 풀어서 사용해주시고 `!`는 최대한 피해줍니다.
+
+ ```swift
+ // Preferred
+ func getResultText(with text: String?) -> String {
+ if let resultText = text {
+ return resultText
+ }
+ ...
+ }
+
+ // Not Preferred
+ func getResultText(with text: String?) -> String {
+ return text!
+ ...
+ }
+ ```
+
+### 자주 사용되는 값 체크에 확장 변수 사용하기
+
+- `nil`이나 `0`과 같은 값을 체크할 때 정의된 확장 변수가 있다면 등호/부등호를 사용하는 대신 해당 확장 변수를 사용합니다.
+
+ ```swift
+ // Preferred
+ if optionalValue.isNil { ... }
+ if optionalValue.isNotNil { ... }
+ if numberValue.isZero { ... }
+ if optionalBoolValue.beTrue { ... }
+ if optionalBoolValue.beFalse { ... }
+
+ // Not Preferred
+ if optionalValue == nil { ... }
+ if optionalValue != nil { ... }
+ if numberValue == 0 { ... }
+ if optionalBoolValue == true { ... }
+ if optionalBoolValue == false { ... }
+ ```
+
+### 상수 선언 규칙
+
+- 코드 상단에 `private`로 정의하여 일반적인 상수를 단순히 정의할때는 `struct` 대신 `enum`을 사용해줍니다.
+ Generic 사용 시 class 내부에서 static 선언이 어렵기 때문에 class 밖에서 사용합니다.
+
+ - Snapkit 등에서 autolayout을 설정할 때 상수는 위쪽에 `Metric`으로 빼줍니다.
+ - 여러번 쓰이는 폰트는 `Font`로 빼줍니다
+ - 테이블뷰 등의 Section 관련은 `Section`으로 빼줍니다.
+ - 테이블뷰 등의 row 관련은 `Row`로 빼줍니다.
+ - 그외 내부적으로 쓰이는 상수는 `Constant`로 빼줍니다.
+
+ ```swift
+ private enum Metric {
+ static let avatarLength = 3.f
+ ...
+ }
+
+ private enum Font {
+ static let titleLabel = UIFont.boldSystemFont(ofSize: 14)
+ ...
+ }
+
+ private enum Section {
+ static let managers = 0
+ static let users = 1
+ ...
+ }
+
+ private enum Row {
+ static let name = 0 // managers 섹션의 0번째 row
+ static let username = 0 // users 섹션의 0번째 row
+ ...
+ }
+
+ private enum Constant {
+ static let maxLinesWithOnlyText = 2
+ ...
+ }
+ ```
+- Localize 상수는 기존 상수 규칙과 다르게, enum - case로 정리합니다. 규칙은 다음과 같습니다.
+ ```swift
+ private enum Localized {
+ case leavedThread
+ case managersCount(String)
+ ...
+
+ var rawValue: String {
+ switch self {
+ case .leaveThread: return "thread.header.leave_thread".localized
+ case .managersCount(let count): return "team_chat_info.manager_list.title".localized(with: count)
+ ...
+ }
+ }
+ }
+ ```
+
+### RxSwift 스케쥴러 지정 규칙
+
+- `Observable`에 대해서 `subscribe(on:)`과 `observe(on:)` 함수를 호출해 어떤 어떤 스케쥴러에서 작동할지 지정할 수 있는데, 이를 `Observable`을 생성하는 곳이 아닌 생성된 `Observable`을 사용하는 곳에서 지정합니다.
+- 자세한 내용은 ["SubscribeOn / ObserveOn 논의 정리"](https://www.notion.so/channelio/SubscribeOn-ObserveOn-dfd918eee039412ea3ac9d72d3da08fd?pvs=4)를 확인해주세요.
+
+ ```swift
+ // Preferred
+ func someObservable() -> Observable { ... }
+
+ someObservable()
+ .subscribe(on: ConcurrentDispatchQueueScheduler(qos: .background))
+ .observe(on: MainScheduler.instance)
+ .subscribe { _ in
+ ...
+ }
+
+ // Not Preferred
+ Observable
+ .create { ... }
+ .subscribe(on: ConcurrentDispatchQueueScheduler(qos: .background))
+ .observe(on: MainScheduler.instance)
+ ...
+ ```
+
+### VIPER 모듈 사이의 콜백 전달 규칙
+
+- VIPER 모듈이 다른 VIPER 모듈을 생성하면서 생성한 모듈로부터 어떤 동작이 완료되었다는 콜백을 받고 싶을 때, 이를 클로져를 사용하기보다는 `PublishRelay`, `PublishSubject`와 같은 옵저버 타입을 전달하도록 합니다.
+
+ ```swift
+ // Preferred
+ extension CreateValueRouter {
+ static func createModule(createValueSignal: PublishRelay) {
+ ...
+ }
+ }
+
+ class CreateValueModuleClass {
+ var createValueSignal: PublishRelay?
+ ...
+ func valueCreated(_ value: Value) {
+ self.createValueSignal?.accept(value)
+ }
+ }
+
+ // Not Preferred
+ extension CreateValueRouter {
+ static func createModule(valueCreated: (Value) -> Void) {
+ ...
+ }
+ }
+
+ class CreateValueModuleClass {
+ var valueCreated: ((Value) -> Void)?
+ ...
+ func valueCreated(_ value: Value) {
+ self.valueCreated?(value)
+ }
+ }
+ ```
diff --git a/.github/ISSUE_TEMPLATE/issue_template.md b/.github/ISSUE_TEMPLATE/issue_template.md
new file mode 100644
index 0000000..4cc7673
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/issue_template.md
@@ -0,0 +1,15 @@
+---
+name: ISSUE_TEMPLATE
+about: 이슈탬플릿
+title: "✨[feat]:"
+labels: ''
+assignees: ''
+
+---
+
+# 목적
+
+# 작업상세내역
+- []
+
+# 참고사항
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..1eefbaf
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,50 @@
+## 🔗 관련 이슈
+
+- 관련 이슈: #
+
+---
+
+## ✨ 작업 내용
+
+-
+-
+-
+
+---
+
+## 📸 Showcase
+
+| 변경 전 | 변경 후 |
+|--------|--------|
+| 이미지 | 이미지 |
+
+> 📌 이미지가 없다면 이 섹션은 생략해도 됩니다.
+
+---
+
+## 📝 참고 사항
+
+-
+
+## Motivation 🥳 (코드를 추가/변경하게 된 이유)
+
+## Key Changes 🔥 (주요 구현/변경 사항)
+
+## To Reviewers 🙏 (리뷰어에게 전달하고 싶은 말)
+
+## Reference 🔗
+
+
+## Close Issues 🔒 (닫을 Issue)
+Close #No.
+
+## Checklist
+- [ ] 브랜치를 가져와 작업한 경우 이전 브랜치에 PR을 보냈는지 확인
+- [ ] 빌드를 위해 SceneDelegate 수정한 것 PR로 올리지 않았는지 확인
+- [ ] 필요없는 주석, 프린트문 제거했는지 확인
+- [ ] 컨벤션 지켰는지 확인
+- [ ] final, private 제대로 넣었는지 확인
+- [ ] 다양한 디바이스에 레이아웃이 대응되는지 확인
+ - [ ] iPhone SE
+ - [ ] iPhone 13
+ - [ ] iPhone 13 Pro Max
diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml
new file mode 100644
index 0000000..71a1396
--- /dev/null
+++ b/.github/auto_assign.yml
@@ -0,0 +1,22 @@
+addReviewers: true
+
+# Set to true to add assignees to pull requests
+addAssignees: author
+
+# A list of reviewers to be added to pull requests (GitHub user name)
+reviewers:
+ - minneee
+ - Peter1119
+ - Roy-wonji
+
+# A number of reviewers added to the pull request
+# Set 0 to add all the reviewers (default: 0)
+numberOfReviewers: 2
+
+# A number of assignees to add to the pull request
+# Set to 0 to add all of the assignees.
+# Uses numberOfReviewers if unset.
+numberOfAssignees: 2
+
+skipKeywords:
+ - wip
diff --git a/.github/workflows/AutoAssign.yml b/.github/workflows/AutoAssign.yml
new file mode 100644
index 0000000..e29cbd3
--- /dev/null
+++ b/.github/workflows/AutoAssign.yml
@@ -0,0 +1,12 @@
+name: 'Auto Assign'
+on:
+ pull_request:
+ types: [opened, ready_for_review]
+
+jobs:
+ add-reviews:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: kentaro-m/auto-assign-action@v2.0.0
+ with:
+ configuration-path: '.github/auto_assign.yml' # Only needed if you use something other than .github/auto_assign.yml
diff --git a/MovieBooking.xcodeproj/project.pbxproj b/MovieBooking.xcodeproj/project.pbxproj
index 544918b..532e300 100644
--- a/MovieBooking.xcodeproj/project.pbxproj
+++ b/MovieBooking.xcodeproj/project.pbxproj
@@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
+ 7F6F8B432E9D27850046FA0E /* Auth in Frameworks */ = {isa = PBXBuildFile; productRef = 7F6F8B422E9D27850046FA0E /* Auth */; };
+ 7F6F8B452E9D27850046FA0E /* Supabase in Frameworks */ = {isa = PBXBuildFile; productRef = 7F6F8B442E9D27850046FA0E /* Supabase */; };
+ 7F6F8B482E9D279B0046FA0E /* WeaveDI in Frameworks */ = {isa = PBXBuildFile; productRef = 7F6F8B472E9D279B0046FA0E /* WeaveDI */; };
F24B91D52E9D1A5800EF7944 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = F24B91D42E9D1A5800EF7944 /* ComposableArchitecture */; };
F24B91D82E9D1A8B00EF7944 /* TCACoordinators in Frameworks */ = {isa = PBXBuildFile; productRef = F24B91D72E9D1A8B00EF7944 /* TCACoordinators */; };
/* End PBXBuildFile section */
@@ -34,9 +37,22 @@
F24B90012E9D0D2C00EF7944 /* MovieBookingUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MovieBookingUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
+/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
+ 7F6F8C092E9DCCD60046FA0E /* Exceptions for "MovieBooking" folder in "MovieBooking" target */ = {
+ isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
+ membershipExceptions = (
+ Resources/Info.plist,
+ );
+ target = F24B8FE92E9D0D2900EF7944 /* MovieBooking */;
+ };
+/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
+
/* Begin PBXFileSystemSynchronizedRootGroup section */
F24B8FEC2E9D0D2900EF7944 /* MovieBooking */ = {
isa = PBXFileSystemSynchronizedRootGroup;
+ exceptions = (
+ 7F6F8C092E9DCCD60046FA0E /* Exceptions for "MovieBooking" folder in "MovieBooking" target */,
+ );
path = MovieBooking;
sourceTree = "";
};
@@ -57,8 +73,11 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 7F6F8B452E9D27850046FA0E /* Supabase in Frameworks */,
+ 7F6F8B482E9D279B0046FA0E /* WeaveDI in Frameworks */,
F24B91D82E9D1A8B00EF7944 /* TCACoordinators in Frameworks */,
F24B91D52E9D1A5800EF7944 /* ComposableArchitecture in Frameworks */,
+ 7F6F8B432E9D27850046FA0E /* Auth in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -121,6 +140,9 @@
packageProductDependencies = (
F24B91D42E9D1A5800EF7944 /* ComposableArchitecture */,
F24B91D72E9D1A8B00EF7944 /* TCACoordinators */,
+ 7F6F8B422E9D27850046FA0E /* Auth */,
+ 7F6F8B442E9D27850046FA0E /* Supabase */,
+ 7F6F8B472E9D279B0046FA0E /* WeaveDI */,
);
productName = MovieBooking;
productReference = F24B8FEA2E9D0D2900EF7944 /* MovieBooking.app */;
@@ -207,6 +229,8 @@
packageReferences = (
F24B91D32E9D1A5800EF7944 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */,
F24B91D62E9D1A8B00EF7944 /* XCRemoteSwiftPackageReference "TCACoordinators" */,
+ 7F6F8B412E9D27850046FA0E /* XCRemoteSwiftPackageReference "supabase-swift" */,
+ 7F6F8B462E9D279B0046FA0E /* XCRemoteSwiftPackageReference "WeaveDI" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = F24B8FEB2E9D0D2900EF7944 /* Products */;
@@ -284,6 +308,8 @@
/* Begin XCBuildConfiguration section */
F24B90092E9D0D2C00EF7944 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = F24B8FEC2E9D0D2900EF7944 /* MovieBooking */;
+ baseConfigurationReferenceRelativePath = Config/Dev.xcconfig;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -348,6 +374,8 @@
};
F24B900A2E9D0D2C00EF7944 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = F24B8FEC2E9D0D2900EF7944 /* MovieBooking */;
+ baseConfigurationReferenceRelativePath = Config/Realse.xcconfig;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@@ -405,14 +433,18 @@
};
F24B900C2E9D0D2C00EF7944 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = F24B8FEC2E9D0D2900EF7944 /* MovieBooking */;
+ baseConfigurationReferenceRelativePath = Config/Dev.xcconfig;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = MovieBooking/Resources/MovieBooking.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = URUB4S4795;
+ DEVELOPMENT_TEAM = N94CS4N6VR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = MovieBooking/Resources/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -437,14 +469,18 @@
};
F24B900D2E9D0D2C00EF7944 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = F24B8FEC2E9D0D2900EF7944 /* MovieBooking */;
+ baseConfigurationReferenceRelativePath = Config/Realse.xcconfig;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = MovieBooking/Resources/MovieBooking.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = URUB4S4795;
+ DEVELOPMENT_TEAM = N94CS4N6VR;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = MovieBooking/Resources/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
@@ -469,6 +505,8 @@
};
F24B900F2E9D0D2C00EF7944 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = F24B8FEC2E9D0D2900EF7944 /* MovieBooking */;
+ baseConfigurationReferenceRelativePath = Config/Dev.xcconfig;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -491,6 +529,8 @@
};
F24B90102E9D0D2C00EF7944 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = F24B8FEC2E9D0D2900EF7944 /* MovieBooking */;
+ baseConfigurationReferenceRelativePath = Config/Realse.xcconfig;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@@ -513,6 +553,8 @@
};
F24B90122E9D0D2C00EF7944 /* Debug */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = F24B8FEC2E9D0D2900EF7944 /* MovieBooking */;
+ baseConfigurationReferenceRelativePath = Config/Dev.xcconfig;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@@ -533,6 +575,8 @@
};
F24B90132E9D0D2C00EF7944 /* Release */ = {
isa = XCBuildConfiguration;
+ baseConfigurationReferenceAnchor = F24B8FEC2E9D0D2900EF7944 /* MovieBooking */;
+ baseConfigurationReferenceRelativePath = Config/Realse.xcconfig;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@@ -593,6 +637,22 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
+ 7F6F8B412E9D27850046FA0E /* XCRemoteSwiftPackageReference "supabase-swift" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/supabase/supabase-swift.git";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 2.5.1;
+ };
+ };
+ 7F6F8B462E9D279B0046FA0E /* XCRemoteSwiftPackageReference "WeaveDI" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/Roy-wonji/WeaveDI";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 3.3.1;
+ };
+ };
F24B91D32E9D1A5800EF7944 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture";
@@ -612,6 +672,21 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
+ 7F6F8B422E9D27850046FA0E /* Auth */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 7F6F8B412E9D27850046FA0E /* XCRemoteSwiftPackageReference "supabase-swift" */;
+ productName = Auth;
+ };
+ 7F6F8B442E9D27850046FA0E /* Supabase */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 7F6F8B412E9D27850046FA0E /* XCRemoteSwiftPackageReference "supabase-swift" */;
+ productName = Supabase;
+ };
+ 7F6F8B472E9D279B0046FA0E /* WeaveDI */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = 7F6F8B462E9D279B0046FA0E /* XCRemoteSwiftPackageReference "WeaveDI" */;
+ productName = WeaveDI;
+ };
F24B91D42E9D1A5800EF7944 /* ComposableArchitecture */ = {
isa = XCSwiftPackageProductDependency;
package = F24B91D32E9D1A5800EF7944 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */;
diff --git a/MovieBooking.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/MovieBooking.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index fc8bc82..eeb7cba 100644
--- a/MovieBooking.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/MovieBooking.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,5 +1,5 @@
{
- "originHash" : "0c1eca297b548e9ec69bfb69a1bc5aa2267a470775adc7e482e6d899e20ee3cd",
+ "originHash" : "561b1586e0fb6f910745bf5bfd05cfc1cf6da60209bfc8223e7d3958750b34c4",
"pins" : [
{
"identity" : "combine-schedulers",
@@ -19,6 +19,33 @@
"version" : "0.4.1"
}
},
+ {
+ "identity" : "logmacro",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/Roy-wonji/LogMacro.git",
+ "state" : {
+ "revision" : "593b210346cf7145074c2d94fa4206bbc70198e0",
+ "version" : "1.1.1"
+ }
+ },
+ {
+ "identity" : "supabase-swift",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/supabase/supabase-swift.git",
+ "state" : {
+ "revision" : "21425be5a493bb24bfde51808ccfa82a56111430",
+ "version" : "2.34.0"
+ }
+ },
+ {
+ "identity" : "swift-asn1",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-asn1.git",
+ "state" : {
+ "revision" : "f70225981241859eb4aa1a18a75531d26637c8cc",
+ "version" : "1.4.0"
+ }
+ },
{
"identity" : "swift-case-paths",
"kind" : "remoteSourceControl",
@@ -64,6 +91,15 @@
"version" : "1.3.2"
}
},
+ {
+ "identity" : "swift-crypto",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-crypto.git",
+ "state" : {
+ "revision" : "95ba0316a9b733e92bb6b071255ff46263bbe7dc",
+ "version" : "3.15.1"
+ }
+ },
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
@@ -76,12 +112,21 @@
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
- "location" : "https://github.com/pointfreeco/swift-dependencies",
+ "location" : "https://github.com/pointfreeco/swift-dependencies.git",
"state" : {
"revision" : "a10f9feeb214bc72b5337b6ef6d5a029360db4cc",
"version" : "1.10.0"
}
},
+ {
+ "identity" : "swift-http-types",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/apple/swift-http-types.git",
+ "state" : {
+ "revision" : "a0a57e949a8903563aba4615869310c0ebf14c03",
+ "version" : "1.4.0"
+ }
+ },
{
"identity" : "swift-identified-collections",
"kind" : "remoteSourceControl",
@@ -121,10 +166,10 @@
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
- "location" : "https://github.com/swiftlang/swift-syntax",
+ "location" : "https://github.com/apple/swift-syntax.git",
"state" : {
- "revision" : "4799286537280063c85a32f09884cfbca301b1a1",
- "version" : "602.0.0"
+ "revision" : "0687f71944021d616d34d922343dcef086855920",
+ "version" : "600.0.1"
}
},
{
@@ -136,6 +181,15 @@
"version" : "0.13.0"
}
},
+ {
+ "identity" : "weavedi",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/Roy-wonji/WeaveDI",
+ "state" : {
+ "revision" : "89728f122d41633d8ebac182fc15040065e2492c",
+ "version" : "3.3.1"
+ }
+ },
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
diff --git a/MovieBooking/App/Application/AppDelegate.swift b/MovieBooking/App/Application/AppDelegate.swift
new file mode 100644
index 0000000..9bb2e2f
--- /dev/null
+++ b/MovieBooking/App/Application/AppDelegate.swift
@@ -0,0 +1,21 @@
+//
+// AppDelegate.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import UIKit
+import WeaveDI
+
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ WeaveDI.Container.bootstrapInTask { _ in
+ await AppDIManager.shared.registerDefaultDependencies()
+ }
+
+ return true
+ }
+}
+
diff --git a/MovieBooking/App/Application/MovieBookingApp.swift b/MovieBooking/App/Application/MovieBookingApp.swift
new file mode 100644
index 0000000..12d9a4d
--- /dev/null
+++ b/MovieBooking/App/Application/MovieBookingApp.swift
@@ -0,0 +1,30 @@
+//
+// MovieBookingApp.swift
+// MovieBooking
+//
+// Created by 김민희 on 10/13/25.
+//
+
+import SwiftUI
+import ComposableArchitecture
+
+@main
+struct MovieBookingApp: App {
+ @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
+
+ init() {
+
+ }
+
+ var body: some Scene {
+ WindowGroup {
+ let store = Store(initialState: AppReducer.State()) {
+ AppReducer()
+ ._printChanges()
+ ._printChanges(.actionLabels)
+ }
+
+ AppView(store: store)
+ }
+ }
+}
diff --git a/MovieBooking/App/Di/DIRegistry.swift b/MovieBooking/App/Di/DIRegistry.swift
new file mode 100644
index 0000000..8267346
--- /dev/null
+++ b/MovieBooking/App/Di/DIRegistry.swift
@@ -0,0 +1,36 @@
+//
+// DIRegistry.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import WeaveDI
+
+/// 모든 의존성을 자동으로 등록하는 레지스트리
+extension WeaveDI.Container {
+ private static let helper = RegisterModule()
+
+ /// Repository 등록
+ static func registerRepositories() async {
+ let repositories: [Module] = [
+
+ ]
+
+ await repositories.asyncForEach { module in
+ await module.register()
+ }
+ }
+
+ /// UseCase 등록
+ static func registerUseCases() async {
+
+ let useCases: [Module] = [
+
+ ]
+
+ await useCases.asyncForEach { module in
+ await module.register()
+ }
+ }
+}
diff --git a/MovieBooking/App/Di/Extension+AppDIContainer.swift b/MovieBooking/App/Di/Extension+AppDIContainer.swift
new file mode 100644
index 0000000..eebaf12
--- /dev/null
+++ b/MovieBooking/App/Di/Extension+AppDIContainer.swift
@@ -0,0 +1,21 @@
+//
+// Extension+AppDIContainer.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import WeaveDI
+
+extension AppWeaveDI.Container {
+ @DIContainerActor
+ func registerDefaultDependencies() async {
+ await registerDependencies(logLevel: .errors) { container in
+ // Repository 먼저 등록
+ let factory = ModuleFactoryManager()
+
+ await factory.registerAll(to: container)
+ }
+ }
+}
+
diff --git a/MovieBooking/App/MovieBookingApp.swift b/MovieBooking/App/MovieBookingApp.swift
deleted file mode 100644
index a0dfaf8..0000000
--- a/MovieBooking/App/MovieBookingApp.swift
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// MovieBookingApp.swift
-// MovieBooking
-//
-// Created by 김민희 on 10/13/25.
-//
-
-import SwiftUI
-
-@main
-struct MovieBookingApp: App {
- var body: some Scene {
- WindowGroup {
- ContentView()
- }
- }
-}
diff --git a/MovieBooking/App/Reducer/AppReducer.swift b/MovieBooking/App/Reducer/AppReducer.swift
new file mode 100644
index 0000000..65ae415
--- /dev/null
+++ b/MovieBooking/App/Reducer/AppReducer.swift
@@ -0,0 +1,111 @@
+//
+// AppReducer.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import ComposableArchitecture
+
+@Reducer
+struct AppReducer {
+
+ @ObservableState
+ enum State {
+ case splash(Splash.State)
+ case auth(AuthCoordinator.State)
+
+
+
+ init() {
+ self = .splash(.init())
+ }
+ }
+
+ enum Action: ViewAction {
+ case view(View)
+ case scope(ScopeAction)
+ }
+
+ @CasePathable
+ enum View {
+ case presentAuth
+ case presentMain
+ }
+
+
+ @CasePathable
+ enum ScopeAction {
+ case splash(Splash.Action)
+ case auth(AuthCoordinator.Action)
+ }
+
+ @Dependency(\.continuousClock) var clock
+
+ public var body: some Reducer {
+ Reduce { state, action in
+ switch action {
+ case .view(let viewAction):
+ return handleViewAction(&state, action: viewAction)
+
+ case .scope(let scopeAction):
+ return handleScopeAction(&state, action: scopeAction)
+ }
+ }
+ .ifCaseLet(\.splash, action: \.scope.splash) {
+ Splash()
+ }
+ .ifCaseLet(\.auth, action: \.scope.auth) {
+ AuthCoordinator()
+ }
+ }
+}
+
+extension AppReducer {
+ func handleViewAction(
+ _ state: inout State,
+ action: View
+ ) -> Effect {
+ switch action {
+ // MARK: - 로그인 화면으로
+ case .presentAuth:
+ state = .auth(.init())
+ return .none
+
+ case .presentMain:
+ return .none
+
+ }
+ }
+
+
+ func handleScopeAction(
+ _ state: inout State,
+ action: ScopeAction
+ ) -> Effect {
+ switch action {
+ case .splash(.navigation(.presentLogin)):
+ return .run { send in
+ try await clock.sleep(for: .seconds(1))
+ await send(.view(.presentAuth))
+ }
+
+ case .splash(.navigation(.presentMain)):
+ return .run { send in
+ try await clock.sleep(for: .seconds(1))
+ await send(.view(.presentMain))
+ }
+
+
+// case .auth(.navigation(.presentMain)):
+// return .send(.view(.presentMain))
+//
+// case .auth(.navigation(.presentMain)):
+// return .send(.view(.presentMain))
+
+
+ default:
+ return .none
+ }
+ }
+}
diff --git a/MovieBooking/App/View/AppView.swift b/MovieBooking/App/View/AppView.swift
new file mode 100644
index 0000000..1a72cea
--- /dev/null
+++ b/MovieBooking/App/View/AppView.swift
@@ -0,0 +1,30 @@
+//
+// AppView.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import SwiftUI
+
+import ComposableArchitecture
+
+struct AppView: View {
+ @Bindable var store: StoreOf
+
+ var body: some View {
+ SwitchStore(store) { state in
+ switch state {
+ case .splash:
+ if let store = store.scope(state: \.splash, action: \.scope.splash) {
+ SplashView(store: store)
+ }
+
+ case .auth:
+ if let store = store.scope(state: \.auth, action: \.scope.auth) {
+ AuthCoordinatorView(store: store)
+ }
+ }
+ }
+ }
+}
diff --git a/MovieBooking/Config/Dev.xcconfig b/MovieBooking/Config/Dev.xcconfig
new file mode 100644
index 0000000..901b4ac
--- /dev/null
+++ b/MovieBooking/Config/Dev.xcconfig
@@ -0,0 +1,14 @@
+//
+// Shared.xcconfig
+// GoGo
+//
+// Created by ha sungyong on 10/29/24.
+//
+
+#include "./Shared.xcconfig"
+
+OTHER_SWIFT_FLAGS[config=STAGE][sdk=*] = $(inherited) -DDEBUG
+SWIFT_ACTIVE_COMPILATION_CONDITIONS = STAGE
+SUPERBASE_URL=depahiavjicplpqpcbwd.supabase.co
+SUPERBASE_KEY=sb_publishable_DnDwIbBsCHselcdXZrgt7A_OiIO7BAd
+
diff --git a/MovieBooking/Config/Realse.xcconfig b/MovieBooking/Config/Realse.xcconfig
new file mode 100644
index 0000000..1c1a15a
--- /dev/null
+++ b/MovieBooking/Config/Realse.xcconfig
@@ -0,0 +1,14 @@
+//
+// Shared.xcconfig
+// GoGo
+//
+// Created by ha sungyong on 10/29/24.
+//
+
+#include "./Shared.xcconfig"
+
+OTHER_SWIFT_FLAGS[config=PROD][sdk=*] = $(inherited) -PROD
+SWIFT_ACTIVE_COMPILATION_CONDITIONS = PROD
+SUPERBASE_URL=depahiavjicplpqpcbwd.supabase.co
+SUPERBASE_KEY=sb_publishable_DnDwIbBsCHselcdXZrgt7A_OiIO7BAd
+
diff --git a/MovieBooking/Config/Shared.xcconfig b/MovieBooking/Config/Shared.xcconfig
new file mode 100644
index 0000000..5ad0be4
--- /dev/null
+++ b/MovieBooking/Config/Shared.xcconfig
@@ -0,0 +1,10 @@
+//
+// Shared.xcconfig
+// GoGo
+//
+// Created by ha sungyong on 10/29/24.
+//
+
+MARKETING_VERSION=1.0.0
+CURRENT_PROJECT_VERSION=20
+
diff --git a/MovieBooking/DesignSystem/Color/Colors.swift b/MovieBooking/DesignSystem/Color/Colors.swift
new file mode 100644
index 0000000..9697496
--- /dev/null
+++ b/MovieBooking/DesignSystem/Color/Colors.swift
@@ -0,0 +1,14 @@
+//
+// Colors.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import SwiftUI
+
+public extension ShapeStyle where Self == Color {
+ static var basicPurple: Color { .init(hex: "503396") }
+ static var violet: Color { .init(hex: "9333EA") }
+ static var brightYellow: Color { .init(hex: "FEE500") }
+}
diff --git a/MovieBooking/DesignSystem/Color/Extension+Color.swift b/MovieBooking/DesignSystem/Color/Extension+Color.swift
new file mode 100644
index 0000000..3dd907f
--- /dev/null
+++ b/MovieBooking/DesignSystem/Color/Extension+Color.swift
@@ -0,0 +1,23 @@
+//
+// Extension+Color.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import SwiftUI
+
+public extension Color {
+ init(hex: String) {
+ let scanner = Scanner(string: hex)
+ _ = scanner.scanString("#")
+
+ var rgb: UInt64 = 0
+ scanner.scanHexInt64(&rgb)
+
+ let r = Double((rgb >> 16) & 0xFF) / 255.0
+ let g = Double((rgb >> 8) & 0xFF) / 255.0
+ let b = Double((rgb >> 0) & 0xFF) / 255.0
+ self.init(red: r, green: g, blue: b)
+ }
+}
diff --git a/MovieBooking/DesignSystem/Font/PretendardFont.swift b/MovieBooking/DesignSystem/Font/PretendardFont.swift
new file mode 100644
index 0000000..b710d99
--- /dev/null
+++ b/MovieBooking/DesignSystem/Font/PretendardFont.swift
@@ -0,0 +1,37 @@
+//
+// PretendardFont.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import SwiftUI
+
+struct PretendardFont: ViewModifier {
+ public let family: PretendardFontFamily
+ public let size: CGFloat
+
+ public func body(content: Content) -> some View {
+ return content.font(.custom("PretendardVariable-\(family)", fixedSize: size))
+ }
+}
+
+ extension View {
+ func pretendardFont(family: PretendardFontFamily, size: CGFloat) -> some View {
+ return self.modifier(PretendardFont(family: family, size: size))
+ }
+}
+
+ extension UIFont {
+ static func pretendardFontFamily(family: PretendardFontFamily, size: CGFloat) -> UIFont {
+ let fontName = "PretendardVariable-\(family)"
+ return UIFont(name: fontName, size: size) ?? UIFont.systemFont(ofSize: size, weight: .regular)
+ }
+}
+
+ extension Font {
+ static func pretendardFont(family: PretendardFontFamily, size: CGFloat) -> Font{
+ let font = Font.custom("PretendardVariable-\(family)", size: size)
+ return font
+ }
+}
diff --git a/MovieBooking/DesignSystem/Font/PretendardFontFamily.swift b/MovieBooking/DesignSystem/Font/PretendardFontFamily.swift
new file mode 100644
index 0000000..ea8473d
--- /dev/null
+++ b/MovieBooking/DesignSystem/Font/PretendardFontFamily.swift
@@ -0,0 +1,43 @@
+//
+// PretendardFontFamily.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import Foundation
+
+public enum PretendardFontFamily: CustomStringConvertible {
+ case black
+ case bold
+ case extraBold
+ case extraLight
+ case light
+ case medium
+ case regular
+ case semiBold
+ case thin
+
+ public var description: String {
+ switch self {
+ case .black:
+ return "Black"
+ case .bold:
+ return "Bold"
+ case .extraBold:
+ return "ExtraBold"
+ case .extraLight:
+ return "ExtraLight"
+ case .light:
+ return "Light"
+ case .medium:
+ return "Medium"
+ case .regular:
+ return "Regular"
+ case .semiBold:
+ return "SemiBold"
+ case .thin:
+ return "Thin"
+ }
+ }
+}
diff --git a/MovieBooking/Feature/Auth/Coordinator/Reducer/AuthCoordinator.swift b/MovieBooking/Feature/Auth/Coordinator/Reducer/AuthCoordinator.swift
new file mode 100644
index 0000000..a6fefb8
--- /dev/null
+++ b/MovieBooking/Feature/Auth/Coordinator/Reducer/AuthCoordinator.swift
@@ -0,0 +1,66 @@
+//
+// AuthCoordinator.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import Foundation
+import ComposableArchitecture
+import TCACoordinators
+
+@Reducer
+public struct AuthCoordinator {
+ public init() {}
+
+ @ObservableState
+ public struct State: Equatable {
+ var routes: [Route]
+
+ public init() {
+ self.routes = [.root(.login(.init()), embedInNavigationView: true)]
+ }
+ }
+
+ public enum Action: BindableAction {
+ case binding(BindingAction)
+ case router(IndexedRouterActionOf)
+
+ }
+
+
+ public var body: some Reducer {
+ BindingReducer()
+ Reduce { state, action in
+ switch action {
+ case .binding(_):
+ return .none
+
+ case .router(let routeAction):
+ return routerAction(state: &state, action: routeAction)
+
+ }
+ }
+ .forEachRoute(\.routes, action: \.router)
+ }
+}
+
+extension AuthCoordinator {
+ private func routerAction(
+ state: inout State,
+ action: IndexedRouterActionOf
+ ) -> Effect {
+ switch action {
+ default:
+ return .none
+ }
+ }
+}
+
+
+extension AuthCoordinator {
+ @Reducer(state: .equatable, .hashable)
+ public enum AuthScreen {
+ case login(Login)
+ }
+}
diff --git a/MovieBooking/Feature/Auth/Coordinator/View/AuthCoordinatorView.swift b/MovieBooking/Feature/Auth/Coordinator/View/AuthCoordinatorView.swift
new file mode 100644
index 0000000..c62c4f5
--- /dev/null
+++ b/MovieBooking/Feature/Auth/Coordinator/View/AuthCoordinatorView.swift
@@ -0,0 +1,33 @@
+//
+// AuthCoordinatorView.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import SwiftUI
+
+import ComposableArchitecture
+import TCACoordinators
+
+public struct AuthCoordinatorView: View {
+ @Bindable private var store: StoreOf
+
+ public init(
+ store: StoreOf
+ ) {
+ self.store = store
+ }
+
+ public var body: some View {
+ TCARouter(store.scope(state: \.routes, action: \.router)) { screens in
+ switch screens.case {
+ case .login(let loginStore):
+ LoginView(store: loginStore)
+ .navigationBarBackButtonHidden()
+
+
+ }
+ }
+ }
+}
diff --git a/MovieBooking/Feature/Auth/Login/Reducer/Login.swift b/MovieBooking/Feature/Auth/Login/Reducer/Login.swift
new file mode 100644
index 0000000..29e4437
--- /dev/null
+++ b/MovieBooking/Feature/Auth/Login/Reducer/Login.swift
@@ -0,0 +1,115 @@
+//
+// Login.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+
+import Foundation
+import ComposableArchitecture
+
+
+@Reducer
+public struct Login {
+ public init() {}
+
+ @ObservableState
+ public struct State: Equatable, Hashable {
+ public init() {}
+ }
+
+ public enum Action: ViewAction, BindableAction, Equatable {
+ case binding(BindingAction)
+ case view(View)
+ case async(AsyncAction)
+ case inner(InnerAction)
+ case navigation(NavigationAction)
+
+ }
+
+ //MARK: - ViewAction
+ @CasePathable
+ public enum View: Equatable {
+
+ }
+
+
+
+ //MARK: - AsyncAction 비동기 처리 액션
+ public enum AsyncAction: Equatable {
+
+ }
+
+ //MARK: - 앱내에서 사용하는 액션
+ public enum InnerAction: Equatable {
+ }
+
+ //MARK: - NavigationAction
+ public enum NavigationAction: Equatable {
+
+
+ }
+
+
+ public var body: some Reducer {
+ BindingReducer()
+ Reduce { state, action in
+ switch action {
+ case .binding(_):
+ return .none
+
+ case .view(let viewAction):
+ return handleViewAction(state: &state, action: viewAction)
+
+ case .async(let asyncAction):
+ return handleAsyncAction(state: &state, action: asyncAction)
+
+ case .inner(let innerAction):
+ return handleInnerAction(state: &state, action: innerAction)
+
+ case .navigation(let navigationAction):
+ return handleNavigationAction(state: &state, action: navigationAction)
+ }
+ }
+ }
+}
+
+extension Login {
+ private func handleViewAction(
+ state: inout State,
+ action: View
+ ) -> Effect {
+ switch action {
+
+ }
+ }
+
+ private func handleAsyncAction(
+ state: inout State,
+ action: AsyncAction
+ ) -> Effect {
+ switch action {
+
+ }
+ }
+
+ private func handleNavigationAction(
+ state: inout State,
+ action: NavigationAction
+ ) -> Effect {
+ switch action {
+
+ }
+ }
+
+ private func handleInnerAction(
+ state: inout State,
+ action: InnerAction
+ ) -> Effect {
+ switch action {
+
+ }
+ }
+}
+
diff --git a/MovieBooking/Feature/Auth/Login/View/LoginView.swift b/MovieBooking/Feature/Auth/Login/View/LoginView.swift
new file mode 100644
index 0000000..c299eae
--- /dev/null
+++ b/MovieBooking/Feature/Auth/Login/View/LoginView.swift
@@ -0,0 +1,76 @@
+//
+// LoginView.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import SwiftUI
+
+import ComposableArchitecture
+
+public struct LoginView: View {
+ @Bindable var store: StoreOf
+
+ init(store: StoreOf) {
+ self.store = store
+ }
+
+ public var body: some View {
+ VStack {
+ Spacer()
+
+ loginLogo
+
+ loginTitle()
+
+ Spacer()
+ }
+
+ }
+}
+
+extension LoginView {
+ private var loginLogo: some View {
+ RoundedRectangle(cornerRadius: 24, style: .continuous)
+ .fill(
+ LinearGradient(
+ colors: [
+ .basicPurple,
+ .violet
+ ],
+ startPoint: .topLeading,
+ endPoint: .bottomTrailing
+ )
+ )
+ .frame(width: 120, height: 120)
+ .shadow(color: .basicPurple.opacity(0.3), radius: 16, x: 0, y: 8)
+ .overlay(
+ Image(systemName: "film.fill")
+ .font(.pretendardFont(family: .medium, size: 48))
+ .foregroundColor(.white)
+ )
+ }
+
+ @ViewBuilder
+ private func loginTitle() -> some View {
+ VStack(spacing: 6) {
+ Text("MEGABOX")
+ .font(.pretendardFont(family: .semiBold, size: 32))
+ .foregroundColor(.primary)
+
+ Text("간편하게 로그인하고 예매를 시작하세요")
+ .font(.pretendardFont(family: .medium, size: 16))
+ .foregroundColor(.secondary)
+ }
+ }
+}
+
+
+#Preview {
+ LoginView(store: .init(initialState: Login.State(), reducer: {
+ Login()
+ }))
+}
+
+
diff --git a/MovieBooking/Feature/Splash/Reducer/Splash.swift b/MovieBooking/Feature/Splash/Reducer/Splash.swift
new file mode 100644
index 0000000..0cd41c0
--- /dev/null
+++ b/MovieBooking/Feature/Splash/Reducer/Splash.swift
@@ -0,0 +1,153 @@
+//
+// Splash.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+
+import Foundation
+import ComposableArchitecture
+import SwiftUI
+
+
+@Reducer
+public struct Splash {
+ public init() {}
+
+ @ObservableState
+ public struct State: Equatable, Hashable {
+
+ var fadeOut: Bool = false
+ var pulse: Bool = false
+ public init() {}
+ }
+
+ @CasePathable
+ public enum Action: ViewAction, Equatable, BindableAction {
+ case binding(BindingAction)
+ case view(View)
+ case async(AsyncAction)
+ case inner(InnerAction)
+ case navigation(NavigationAction)
+
+ }
+
+ //MARK: - ViewAction
+ @CasePathable
+ public enum View : Equatable{
+ case onAppear
+ case startAnimation
+
+ }
+
+
+ //MARK: - AsyncAction 비동기 처리 액션
+ @CasePathable
+ public enum AsyncAction: Equatable {
+
+ }
+
+ //MARK: - 앱내에서 사용하는 액션
+ @CasePathable
+ public enum InnerAction: Equatable {
+ case setPulse(Bool)
+ case setFadeOut(Bool)
+ }
+
+ //MARK: - NavigationAction
+ @CasePathable
+ public enum NavigationAction: Equatable {
+ case presentLogin
+ case presentMain
+
+
+ }
+
+ @Dependency(\.continuousClock) var clock
+
+ public var body: some Reducer {
+ BindingReducer()
+ Reduce { state, action in
+ switch action {
+ case .binding(_):
+ return .none
+
+ case .view(let viewAction):
+ return handleViewAction(state: &state, action: viewAction)
+
+ case .async(let asyncAction):
+ return handleAsyncAction(state: &state, action: asyncAction)
+
+ case .inner(let innerAction):
+ return handleInnerAction(state: &state, action: innerAction)
+
+ case .navigation(let navigationAction):
+ return handleNavigationAction(state: &state, action: navigationAction)
+ }
+ }
+ }
+}
+
+extension Splash {
+ private func handleViewAction(
+ state: inout State,
+ action: View
+ ) -> Effect {
+ switch action {
+ case .onAppear:
+ return .send(.view(.startAnimation))
+
+ case .startAnimation:
+ return .run { send in
+ await send(.inner(.setPulse(true)))
+
+ try await clock.sleep(for: .seconds(1.3))
+ await send(.inner(.setFadeOut(true)))
+ await send(.navigation(.presentLogin))
+ }
+ }
+ }
+
+ private func handleAsyncAction(
+ state: inout State,
+ action: AsyncAction
+ ) -> Effect {
+ switch action {
+
+ }
+ }
+
+ private func handleNavigationAction(
+ state: inout State,
+ action: NavigationAction
+ ) -> Effect {
+ switch action {
+ // 로그인 안했을경우
+ case .presentLogin:
+ return .none
+
+ // 로그인 했을경우
+ case .presentMain:
+ return .none
+ }
+ }
+
+ private func handleInnerAction(
+ state: inout State,
+ action: InnerAction
+ ) -> Effect {
+ switch action {
+ case .setPulse(let on):
+ state.pulse = on
+ return .none
+
+ case .setFadeOut(let on):
+ withAnimation(.easeInOut(duration: 3)) {
+ state.fadeOut = on
+ }
+ return .none
+ }
+ }
+}
+
diff --git a/MovieBooking/Feature/Splash/View/SplashView.swift b/MovieBooking/Feature/Splash/View/SplashView.swift
new file mode 100644
index 0000000..82a2f72
--- /dev/null
+++ b/MovieBooking/Feature/Splash/View/SplashView.swift
@@ -0,0 +1,96 @@
+//
+// SplashView.swift
+// MovieBooking
+//
+// Created by Wonji Suh on 10/14/25.
+//
+
+import SwiftUI
+import ComposableArchitecture
+
+@ViewAction(for: Splash.self)
+struct SplashView: View {
+ @State var store: StoreOf
+
+ var body: some View {
+ ZStack {
+ LinearGradient(
+ gradient: Gradient(colors: [
+ .white,
+ .white,
+ .basicPurple.opacity(0.05)
+ ]),
+ startPoint: .top,
+ endPoint: .bottom
+ )
+ .ignoresSafeArea()
+
+ VStack(spacing: 24) {
+
+ splashLogo()
+
+ titleView()
+ }
+ .scaleEffect(store.fadeOut ? 0.95 : 1.0)
+ .opacity(store.fadeOut ? 0.0 : 1.0)
+ .animation(.easeInOut(duration: 1), value: store.fadeOut)
+ .onAppear {
+ send(.onAppear)
+ }
+ }
+ }
+}
+
+
+extension SplashView {
+
+ @ViewBuilder
+ fileprivate func splashLogo() -> some View {
+ ZStack {
+ Circle()
+ .fill(.basicPurple.opacity(0.20))
+ .blur(radius: 24)
+ .scaleEffect(store.pulse ? 1.08 : 0.95)
+ .animation(.easeInOut(duration: 1.2).repeatForever(autoreverses: true), value: store.pulse)
+
+ RoundedRectangle(cornerRadius: 24, style: .continuous)
+ .fill(
+ LinearGradient(
+ colors: [
+ .basicPurple,
+ Color.purple
+ ],
+ startPoint: .topLeading,
+ endPoint: .bottomTrailing
+ )
+ )
+ .shadow(color: .black.opacity(0.25), radius: 18, x: 0, y: 10)
+ .frame(width: 120, height: 120)
+ .overlay(
+ Image(systemName: "film.fill")
+ .font(.pretendardFont(family: .medium, size: 48))
+ .foregroundColor(.white)
+ )
+ }
+ }
+
+ @ViewBuilder
+ fileprivate func titleView() -> some View {
+ VStack(spacing: 6) {
+ Text("MEGABOX")
+ .font(.pretendardFont(family: .semiBold, size: 32))
+ .foregroundColor(.primary)
+
+ Text("나만의 영화관, 지금 시작합니다 🎬")
+ .font(.pretendardFont(family: .medium, size: 16))
+ .foregroundColor(.secondary)
+ }
+ }
+}
+
+
+#Preview {
+ SplashView(store: .init(initialState: Splash.State(), reducer: {
+ Splash()
+ }))
+}
diff --git a/MovieBooking/App/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/MovieBooking/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
similarity index 100%
rename from MovieBooking/App/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
rename to MovieBooking/Resources/Assets.xcassets/AccentColor.colorset/Contents.json
diff --git a/MovieBooking/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/MovieBooking/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
similarity index 100%
rename from MovieBooking/App/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
rename to MovieBooking/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json
diff --git a/MovieBooking/App/Resources/Assets.xcassets/Contents.json b/MovieBooking/Resources/Assets.xcassets/Contents.json
similarity index 100%
rename from MovieBooking/App/Resources/Assets.xcassets/Contents.json
rename to MovieBooking/Resources/Assets.xcassets/Contents.json
diff --git a/MovieBooking/Resources/FontAsset/PretendardVariable.ttf b/MovieBooking/Resources/FontAsset/PretendardVariable.ttf
new file mode 100644
index 0000000..19063ad
Binary files /dev/null and b/MovieBooking/Resources/FontAsset/PretendardVariable.ttf differ
diff --git a/MovieBooking/Resources/Info.plist b/MovieBooking/Resources/Info.plist
new file mode 100644
index 0000000..188e71a
--- /dev/null
+++ b/MovieBooking/Resources/Info.plist
@@ -0,0 +1,16 @@
+
+
+
+
+ UIUserInterfaceStyle
+ Light
+ UIAppFonts
+
+ PretendardVariable.ttf
+
+ SuperBaseKey
+ ${SUPERBASE_KEY}
+ SuperBaseURL
+ ${SUPERBASE_URL}
+
+
diff --git a/MovieBooking/Resources/MovieBooking.entitlements b/MovieBooking/Resources/MovieBooking.entitlements
new file mode 100644
index 0000000..a812db5
--- /dev/null
+++ b/MovieBooking/Resources/MovieBooking.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.developer.applesignin
+
+ Default
+
+
+