From 87261c4e39ad99edabfe171a3e44aeb3fb79e7fa Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Tue, 13 May 2025 14:24:34 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[#36]=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A9=94=EC=9D=B8=20=ED=99=94=EB=A9=B4=EC=97=90=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Mark-In/Sources/Feature/Main/MainView.swift | 9 ++- .../Sources/Feature/MyPage/MyPageView.swift | 80 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 Mark-In/Sources/Feature/MyPage/MyPageView.swift diff --git a/Mark-In/Sources/Feature/Main/MainView.swift b/Mark-In/Sources/Feature/Main/MainView.swift index a6afaab..0fdcc75 100644 --- a/Mark-In/Sources/Feature/Main/MainView.swift +++ b/Mark-In/Sources/Feature/Main/MainView.swift @@ -13,6 +13,7 @@ struct MainView: View { @State private var viewModel = MainViewModel() @State private var searchText: String = "" @State private var isAddMode: Bool = false + @State private var isPresentedMyPage: Bool = false var body: some View { ZStack { @@ -91,12 +92,18 @@ struct MainView: View { Spacer() Button { - // TODO: 구현 예정 + isPresentedMyPage = true } label: { Image(systemName: "person.circle.fill") .resizable() .frame(width: 22, height: 22) } + .popover( + isPresented: $isPresentedMyPage, + arrowEdge: .bottom + ) { + MyPageView() + } } } } diff --git a/Mark-In/Sources/Feature/MyPage/MyPageView.swift b/Mark-In/Sources/Feature/MyPage/MyPageView.swift new file mode 100644 index 0000000..57da846 --- /dev/null +++ b/Mark-In/Sources/Feature/MyPage/MyPageView.swift @@ -0,0 +1,80 @@ +// +// MyPageView.swift +// Mark-In +// +// Created by 이정동 on 5/13/25. +// + +import SwiftUI + +import DesignSystem + +private enum ViewConstants { + static let minContentWidth: CGFloat = 164 +} + +struct MyPageView: View { + @State private var contentWidth = ViewConstants.minContentWidth + + var body: some View { + VStack(spacing: 30) { + headerTitle + .padding(.top, 16) + + footerButtons + .padding(.bottom, 12) + } + .frame(width: contentWidth) + } + + private var headerTitle: some View { + VStack(spacing: 2) { + Text("User Name") + .font(.pretendard(size: 14, weight: .semiBold)) + .foregroundStyle(.markBlack) + + Text("asdfasdfasdf012345@gmail.com") + .font(.pretendard(size: 10, weight: .regular)) + .tint(.markBlack) + .fixedSize() + .padding(.horizontal, 8) + .background( + GeometryReader { geo in + Color.clear + .onAppear { + let width = geo.size.width + contentWidth = max(width, ViewConstants.minContentWidth) + } + } + ) + } + } + + private var footerButtons: some View { + VStack(spacing: 8) { + Button { + // TODO: 로그아웃 + } label: { + Text("로그아웃") + .font(.pretendard(size: 12, weight: .regular)) + .foregroundStyle(.markBlack) + } + + Divider() + .padding(.horizontal, 8) + + Button { + // TODO: 회원탈퇴 + } label: { + Text("회원 탈퇴") + .font(.pretendard(size: 12, weight: .regular)) + .foregroundStyle(.markRed) + } + } + .buttonStyle(.plain) + } +} + +#Preview { + MyPageView() +} From 81ee0df75f698a9f1cb8c87b948636b5b95d3aef Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Tue, 13 May 2025 17:08:39 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[#36]=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Feature/MyPage/MyPageView.swift | 3 +- .../Feature/MyPage/MyPageViewModel.swift | 81 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift diff --git a/Mark-In/Sources/Feature/MyPage/MyPageView.swift b/Mark-In/Sources/Feature/MyPage/MyPageView.swift index 57da846..6e2ca20 100644 --- a/Mark-In/Sources/Feature/MyPage/MyPageView.swift +++ b/Mark-In/Sources/Feature/MyPage/MyPageView.swift @@ -14,6 +14,7 @@ private enum ViewConstants { } struct MyPageView: View { + @State private var viewModel = MyPageViewModel() @State private var contentWidth = ViewConstants.minContentWidth var body: some View { @@ -53,7 +54,7 @@ struct MyPageView: View { private var footerButtons: some View { VStack(spacing: 8) { Button { - // TODO: 로그아웃 + viewModel.send(.logoutButtonTapped) } label: { Text("로그아웃") .font(.pretendard(size: 12, weight: .regular)) diff --git a/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift b/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift new file mode 100644 index 0000000..653abba --- /dev/null +++ b/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift @@ -0,0 +1,81 @@ +// +// MyPageViewModel.swift +// Mark-In +// +// Created by 이정동 on 5/13/25. +// + +import Foundation + +import FirebaseAuth +import GoogleSignIn + +@Observable +final class MyPageViewModel: Reducer { + struct State { + + } + + enum Action { + case logoutButtonTapped + case withdrawalButtonTapped + + case didSuccessLogout + case didFailLogout + } + + private let authUserManager: AuthUserManager + + private(set) var state: State = .init() + + init() { + self.authUserManager = DIContainer.shared.resolve() + } + + func send(_ action: Action) { + let effect = reduce(state: &state, action: action) + handleEffect(effect) + } + + func reduce(state: inout State, action: Action) -> Effect { + switch action { + case .logoutButtonTapped: + return .run { + // TODO: 이후 Auth 모듈을 분리하면서 코드 리팩토링 예정 + do { + try Auth.auth().signOut() + + if GIDSignIn.sharedInstance.currentUser != nil { + GIDSignIn.sharedInstance.signOut() + } + return .didSuccessLogout + } catch { + return .didFailLogout + } + } + + case .withdrawalButtonTapped: + return .none + + case .didSuccessLogout: + authUserManager.clear() + return .none + + // TODO: 로그아웃 실패에 대한 처리 필요 + case .didFailLogout: + return .none + } + } + + private func handleEffect(_ effect: Effect) { + switch effect { + case .none: + break + case .run(let action): + Task.detached { [weak self] in + let newAction = await action() + await self?.send(newAction) + } + } + } +} From 02cb48510baa617951a47b4c0fc66c109f32e7b7 Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Tue, 13 May 2025 21:48:46 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[#36]=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EB=A1=9C=EC=A7=81=20=EC=B4=88=EC=95=88=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Feature/MyPage/MyPageView.swift | 2 +- .../Feature/MyPage/MyPageViewModel.swift | 40 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Mark-In/Sources/Feature/MyPage/MyPageView.swift b/Mark-In/Sources/Feature/MyPage/MyPageView.swift index 6e2ca20..d633930 100644 --- a/Mark-In/Sources/Feature/MyPage/MyPageView.swift +++ b/Mark-In/Sources/Feature/MyPage/MyPageView.swift @@ -65,7 +65,7 @@ struct MyPageView: View { .padding(.horizontal, 8) Button { - // TODO: 회원탈퇴 + viewModel.send(.withdrawalButtonTapped) } label: { Text("회원 탈퇴") .font(.pretendard(size: 12, weight: .regular)) diff --git a/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift b/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift index 653abba..1611bb9 100644 --- a/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift +++ b/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift @@ -13,7 +13,7 @@ import GoogleSignIn @Observable final class MyPageViewModel: Reducer { struct State { - + var userState: AuthUserState = .init() } enum Action { @@ -22,6 +22,9 @@ final class MyPageViewModel: Reducer { case didSuccessLogout case didFailLogout + + case didSuccessWithdrawal + case didFailWithdrawal } private let authUserManager: AuthUserManager @@ -41,7 +44,7 @@ final class MyPageViewModel: Reducer { switch action { case .logoutButtonTapped: return .run { - // TODO: 이후 Auth 모듈을 분리하면서 코드 리팩토링 예정 + // TODO: 이후 Auth 모듈로 분리하면서 코드 리팩토링 예정 do { try Auth.auth().signOut() @@ -55,7 +58,23 @@ final class MyPageViewModel: Reducer { } case .withdrawalButtonTapped: - return .none + return .run { + // TODO: 이후 Auth 모듈로 분리하면서 코드 리팩토링 예정 + do { + // TODO: 재인증 과정 필요 + // try await Auth.auth().currentUser?.reauthenticate(with: credential) + try await Auth.auth().currentUser?.delete() + + if GIDSignIn.sharedInstance.currentUser != nil { + try await GIDSignIn.sharedInstance.disconnect() + GIDSignIn.sharedInstance.signOut() + } + + return .didSuccessWithdrawal + } catch { + return .didFailWithdrawal + } + } case .didSuccessLogout: authUserManager.clear() @@ -64,6 +83,14 @@ final class MyPageViewModel: Reducer { // TODO: 로그아웃 실패에 대한 처리 필요 case .didFailLogout: return .none + + case .didSuccessWithdrawal: + authUserManager.clear() + return .none + + // TODO: 회원탈퇴 실패에 대한 처리 필요 + case .didFailWithdrawal: + return .none } } @@ -79,3 +106,10 @@ final class MyPageViewModel: Reducer { } } } + +extension MyPageViewModel { + struct AuthUserState { + var name: String = "" + var email: String = "" + } +}