Skip to content

Commit 5ad2984

Browse files
authored
Feat: 로그아웃 · 탈퇴 기능 디자인 및 서버 v2 적용 (#T3-185)
* Feat: 탈퇴하기 api v2 연동 (#T3-185) * Feat: WithdrawViewController 구현 (#T3-185) * Feat: BitnagilConfirmDialog UI 구현 (#T3-185) * Refactor: BitnagilAlert 디자인 v2 적용 (#T3-185) * Fix: WithdrawViewController SE 기기 대응 * Fix: WithdrawViewController SE 기기 대응 * Refactor: 코드 리뷰 반영
1 parent 37d650e commit 5ad2984

20 files changed

Lines changed: 633 additions & 134 deletions

Projects/DataSource/Sources/Endpoint/AuthEndpoint.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Domain
1010
enum AuthEndpoint {
1111
case login(socialLoginType: SocialLoginType, nickname: String?, token: String)
1212
case logout
13-
case withdraw
13+
case withdraw(withdrawReason: String)
1414
case reissue(refreshToken: String)
1515
case agreements(agreements: [TermsType: Bool])
1616
}
@@ -70,6 +70,8 @@ extension AuthEndpoint: Endpoint {
7070
parameters[agreement.key.termKey] = agreement.value
7171
}
7272
return parameters
73+
case .withdraw(let withdrawReason):
74+
return ["reasonOfWithdrawal": withdrawReason]
7375
default:
7476
return [:]
7577
}

Projects/DataSource/Sources/Repository/AuthRepository.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ final class AuthRepository: AuthRepositoryProtocol {
6060
}
6161

6262
// 탈퇴하기를 진행합니다.
63-
func withdraw() async throws {
64-
let endpoint = AuthEndpoint.withdraw
63+
func withdraw(reason: String) async throws {
64+
let endpoint = AuthEndpoint.withdraw(withdrawReason: reason)
6565
_ = try await networkService.request(endpoint: endpoint, type: String.self)
6666
try tokenManager.removeToken()
6767
try removeUserInfo()

Projects/Domain/Sources/DomainDependencyAssembler.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@ public struct DomainDependencyAssembler: DependencyAssemblerProtocol {
2828
return LogoutUseCase(authRepository: authRepository)
2929
}
3030

31-
DIContainer.shared.register(type: WithdrawUseCaseProtocol.self) { _ in
32-
return WithdrawUseCase(authRepository: authRepository)
33-
}
34-
3531
DIContainer.shared.register(type: RecommendedRoutineUseCaseProtocol.self) { container in
3632
guard let recommendedRoutineRepository = container.resolve(type: RecommendedRoutineRepositoryProtocol.self)
3733
else { fatalError("recommendedRoutineRepository 의존성이 등록되지 않았습니다.") }

Projects/Domain/Sources/Protocol/Repository/AuthRepositoryProtocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ public protocol AuthRepositoryProtocol {
2424
func logout() async throws
2525

2626
/// 계정 탈퇴를 진행합니다.
27-
func withdraw() async throws
27+
func withdraw(reason: String) async throws
2828
}

Projects/Domain/Sources/Protocol/UseCase/WithdrawUseCaseProtocol.swift

Lines changed: 0 additions & 11 deletions
This file was deleted.

Projects/Domain/Sources/UseCase/Auth/WithdrawUseCase.swift

Lines changed: 0 additions & 18 deletions
This file was deleted.

Projects/Presentation/Sources/Common/PresentationDependencyAssembler.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,5 +113,12 @@ public struct PresentationDependencyAssembler: DependencyAssemblerProtocol {
113113

114114
return RoutineListViewModel(routineRepository: routineRepository)
115115
}
116+
117+
DIContainer.shared.register(type: WithdrawViewModel.self) { container in
118+
guard let authRepository = container.resolve(type: AuthRepositoryProtocol.self)
119+
else { fatalError("authRepository 의존성이 등록되지 않았습니다.") }
120+
121+
return WithdrawViewModel(authRepository: authRepository)
122+
}
116123
}
117124
}

Projects/Presentation/Sources/Common/View/BitnagilAlert.swift

Lines changed: 29 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,54 +9,43 @@ import SnapKit
99
import UIKit
1010

1111
final class BitnagilAlert: UIViewController {
12-
enum AlertType {
13-
case withImage
14-
case plainText
15-
}
16-
1712
private enum Layout {
18-
static let contentViewHorizontalSpacing: CGFloat = 39
19-
static let contentViewHorizontalHeight: CGFloat = 154
20-
static let imageSize: CGFloat = 55
21-
static let imageTopSpacing: CGFloat = 24
22-
static let titleTopSpacing: CGFloat = 18
23-
static let titleHeight: CGFloat = 30
24-
static let contentTopSpacing: CGFloat = 2
25-
static let contentHeight: CGFloat = 18
13+
static let horizontalMargin: CGFloat = 24
14+
static let contentViewHorizontalSpacing: CGFloat = 44
15+
static let contentViewHorizontalHeight: CGFloat = 180
16+
static let titleTopSpacing: CGFloat = 20
17+
static let titleHeight: CGFloat = 28
18+
static let contentTopSpacing: CGFloat = 6
19+
static let contentHeight: CGFloat = 40
2620
static let buttonTopSpacing: CGFloat = 18
27-
static let buttonHorizontalSpacing: CGFloat = 20
28-
static let buttonHeight: CGFloat = 44
29-
static let confirmButtonLeadingSpacing: CGFloat = 8
30-
static let buttonBottomSpacing: CGFloat = 24
21+
static let buttonHeight: CGFloat = 48
22+
static let confirmButtonLeadingSpacing: CGFloat = 12
23+
static let buttonBottomSpacing: CGFloat = 20
3124
}
3225

3326
private let dimmedView = UIView()
3427
private let contentView = UIView()
35-
private let alertImageView = UIImageView()
3628
private let titleLabel = UILabel()
3729
private let contentLabel = UILabel()
3830
private let cancelButton = UIButton()
3931
private let confirmButton = UIButton()
40-
private let alertType: AlertType
4132
private var cancelHandler: (() -> Void)?
4233
private var confirmHandler: (() -> Void)?
4334

4435
init(
45-
alertType: AlertType,
4636
title: String,
4737
content: String,
4838
cancelButtonTitle: String,
4939
confirmButtonTitle: String,
5040
cancelHandler: (() -> Void)?,
5141
confirmHandler: (() -> Void)?
5242
) {
53-
self.alertType = alertType
5443
super.init(nibName: nil, bundle: nil)
5544

5645
self.cancelHandler = cancelHandler
5746
self.confirmHandler = confirmHandler
5847
titleLabel.text = title
59-
contentLabel.text = content
48+
contentLabel.attributedText = BitnagilFont(style: .body2, weight: .medium).attributedString(text: content)
6049
cancelButton.setTitle(cancelButtonTitle, for: .normal)
6150
confirmButton.setTitle(confirmButtonTitle, for: .normal)
6251

@@ -77,21 +66,22 @@ final class BitnagilAlert: UIViewController {
7766
dimmedView.alpha = 0.7
7867

7968
contentView.backgroundColor = .white
80-
contentView.layer.cornerRadius = 20
69+
contentView.layer.cornerRadius = 12
8170
contentView.layer.masksToBounds = true
8271

83-
alertImageView.image = BitnagilIcon.exclamationFilledIcon
84-
titleLabel.font = BitnagilFont(style: .title2, weight: .bold).font
85-
contentLabel.font = BitnagilFont(style: .caption1, weight: .regular).font
72+
titleLabel.font = BitnagilFont(style: .subtitle1, weight: .semiBold).font
73+
titleLabel.textColor = BitnagilColor.gray10
74+
75+
contentLabel.textColor = BitnagilColor.gray40
76+
contentLabel.numberOfLines = 2
8677

87-
cancelButton.backgroundColor = .white
88-
cancelButton.setTitleColor(BitnagilColor.navy500, for: .normal)
89-
confirmButton.backgroundColor = BitnagilColor.navy500
78+
cancelButton.backgroundColor = BitnagilColor.gray97
79+
cancelButton.setTitleColor(BitnagilColor.gray40, for: .normal)
80+
confirmButton.backgroundColor = BitnagilColor.gray10
9081
confirmButton.setTitleColor(.white, for: .normal)
9182
[cancelButton, confirmButton].forEach {
92-
$0.titleLabel?.font = BitnagilFont(style: .subtitle1, weight: .bold).font
93-
$0.layer.borderWidth = 1
94-
$0.layer.cornerRadius = 8
83+
$0.titleLabel?.font = BitnagilFont(style: .body2, weight: .medium).font
84+
$0.layer.cornerRadius = 12
9585
$0.layer.masksToBounds = true
9686
}
9787

@@ -129,44 +119,29 @@ final class BitnagilAlert: UIViewController {
129119
make.height.equalTo(Layout.contentViewHorizontalHeight).priority(.medium)
130120
}
131121

132-
if alertType == .withImage {
133-
contentView.addSubview(alertImageView)
134-
alertImageView.snp.makeConstraints { make in
135-
make.top.equalToSuperview().offset(Layout.imageTopSpacing)
136-
make.centerX.equalToSuperview()
137-
make.size.equalTo(Layout.imageSize)
138-
}
139-
140-
titleLabel.snp.makeConstraints { make in
141-
make.top.equalTo(alertImageView.snp.bottom).offset(Layout.titleTopSpacing)
142-
make.centerX.equalToSuperview()
143-
make.height.equalTo(Layout.titleHeight)
144-
}
145-
} else {
146-
titleLabel.snp.makeConstraints { make in
147-
make.top.equalToSuperview().offset(Layout.titleTopSpacing)
148-
make.centerX.equalToSuperview()
149-
make.height.equalTo(Layout.titleHeight)
150-
}
122+
titleLabel.snp.makeConstraints { make in
123+
make.top.equalToSuperview().offset(Layout.titleTopSpacing)
124+
make.horizontalEdges.equalToSuperview().inset(Layout.horizontalMargin)
125+
make.height.equalTo(Layout.titleHeight)
151126
}
152127

153128
contentLabel.snp.makeConstraints { make in
154129
make.top.equalTo(titleLabel.snp.bottom).offset(Layout.contentTopSpacing)
155-
make.centerX.equalToSuperview()
130+
make.horizontalEdges.equalToSuperview().inset(Layout.horizontalMargin)
156131
make.height.equalTo(Layout.contentHeight)
157132
}
158133

159134
cancelButton.snp.makeConstraints { make in
160135
make.top.equalTo(contentLabel.snp.bottom).offset(Layout.buttonTopSpacing)
161-
make.leading.equalToSuperview().offset(Layout.buttonHorizontalSpacing)
136+
make.leading.equalToSuperview().offset(Layout.horizontalMargin)
162137
make.height.equalTo(Layout.buttonHeight)
163138
make.bottom.equalToSuperview().inset(Layout.buttonBottomSpacing)
164139
}
165140

166141
confirmButton.snp.makeConstraints { make in
167142
make.top.equalTo(contentLabel.snp.bottom).offset(Layout.buttonTopSpacing)
168143
make.leading.equalTo(cancelButton.snp.trailing).offset(Layout.confirmButtonLeadingSpacing)
169-
make.trailing.equalToSuperview().inset(Layout.buttonHorizontalSpacing)
144+
make.trailing.equalToSuperview().inset(Layout.horizontalMargin)
170145
make.height.equalTo(Layout.buttonHeight)
171146
make.width.equalTo(cancelButton)
172147
make.bottom.equalToSuperview().inset(Layout.buttonBottomSpacing)
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//
2+
// BitnagilConfirmDialog.swift
3+
// Presentation
4+
//
5+
// Created by 최정인 on 9/16/25.
6+
//
7+
8+
import SnapKit
9+
import UIKit
10+
11+
final class BitnagilConfirmDialog: UIViewController {
12+
private enum Layout {
13+
static let horizontalMargin: CGFloat = 24
14+
static let contentViewWidth: CGFloat = 277
15+
static let contentViewHeight: CGFloat = 152
16+
static let titleLabelTopSpacing: CGFloat = 20
17+
static let titleLabelHeight: CGFloat = 28
18+
static let messageLabelTopSpacing: CGFloat = 6
19+
static let messageLabelHeight: CGFloat = 40
20+
static let confirmButtonBottomSpacing: CGFloat = 20
21+
}
22+
23+
private let dimmedView = UIView()
24+
private let contentView = UIView()
25+
private let titleLabel = UILabel()
26+
private let messageLabel = UILabel()
27+
private let confirmButton = UIButton()
28+
private var confirmHandler: (() -> Void)?
29+
30+
init(
31+
title: String,
32+
message: String,
33+
confirmHandler: (() -> Void)?
34+
) {
35+
super.init(nibName: nil, bundle: nil)
36+
self.confirmHandler = confirmHandler
37+
titleLabel.text = title
38+
messageLabel.text = message
39+
40+
configureAttribute()
41+
configureLayout()
42+
}
43+
44+
required init?(coder: NSCoder) {
45+
fatalError("init(coder:) has not been implemented")
46+
}
47+
48+
private func configureAttribute() {
49+
view.backgroundColor = .clear
50+
dimmedView.backgroundColor = .black
51+
dimmedView.alpha = 0.7
52+
53+
contentView.backgroundColor = .white
54+
contentView.layer.cornerRadius = 12
55+
contentView.layer.masksToBounds = true
56+
57+
titleLabel.font = BitnagilFont(style: .subtitle1, weight: .semiBold).font
58+
titleLabel.textColor = BitnagilColor.gray10
59+
60+
messageLabel.font = BitnagilFont(style: .body2, weight: .medium).font
61+
messageLabel.textColor = BitnagilColor.gray40
62+
messageLabel.numberOfLines = 2
63+
64+
confirmButton.setTitle("확인", for: .normal)
65+
confirmButton.setTitleColor(BitnagilColor.orange500, for: .normal)
66+
confirmButton.titleLabel?.font = BitnagilFont(style: .body2, weight: .semiBold).font
67+
confirmButton.titleLabel?.textAlignment = .right
68+
69+
confirmButton.addAction(
70+
UIAction { [weak self] _ in
71+
self?.confirmHandler?()
72+
self?.dismiss(animated: false)
73+
},
74+
for: .touchUpInside)
75+
}
76+
77+
private func configureLayout() {
78+
view.addSubview(dimmedView)
79+
view.addSubview(contentView)
80+
81+
contentView.addSubview(titleLabel)
82+
contentView.addSubview(messageLabel)
83+
contentView.addSubview(confirmButton)
84+
85+
dimmedView.snp.makeConstraints { make in
86+
make.edges.equalToSuperview()
87+
}
88+
89+
contentView.snp.makeConstraints { make in
90+
make.center.equalToSuperview()
91+
make.width.equalTo(Layout.contentViewWidth)
92+
make.height.equalTo(Layout.contentViewHeight)
93+
}
94+
95+
titleLabel.snp.makeConstraints { make in
96+
make.top.equalToSuperview().offset(Layout.titleLabelTopSpacing)
97+
make.horizontalEdges.equalToSuperview().inset(Layout.horizontalMargin)
98+
make.height.equalTo(Layout.titleLabelHeight)
99+
}
100+
101+
messageLabel.snp.makeConstraints { make in
102+
make.top.equalTo(titleLabel.snp.bottom).offset(Layout.messageLabelTopSpacing)
103+
make.horizontalEdges.equalToSuperview().inset(Layout.horizontalMargin)
104+
make.height.equalTo(Layout.messageLabelHeight)
105+
}
106+
107+
confirmButton.snp.makeConstraints { make in
108+
make.bottom.equalToSuperview().inset(Layout.confirmButtonBottomSpacing)
109+
make.trailing.equalToSuperview().inset(Layout.horizontalMargin)
110+
}
111+
}
112+
}

Projects/Presentation/Sources/Onboarding/Model/OnboardingChoiceProtocol.swift renamed to Projects/Presentation/Sources/Onboarding/Model/BitnagilChoiceProtocol.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//
2-
// OnboardingChoiceProtocol.swift
2+
// BitnagilChoiceProtocol.swift
33
// Presentation
44
//
55
// Created by 최정인 on 7/11/25.
66
//
77

8-
protocol OnboardingChoiceProtocol {
8+
protocol BitnagilChoiceProtocol {
99
var title: String { get }
1010
var subTitle: String? { get }
1111
}

0 commit comments

Comments
 (0)