diff --git a/Mark-In/Sources/Feature/Main/MainView.swift b/Mark-In/Sources/Feature/Main/MainView.swift index 001f2a1..38fd640 100644 --- a/Mark-In/Sources/Feature/Main/MainView.swift +++ b/Mark-In/Sources/Feature/Main/MainView.swift @@ -12,6 +12,8 @@ import DesignSystem struct MainView: View { @State private var viewModel = MainViewModel() @State private var searchText: String = "" + + @State private var isPresentedMyPage: Bool = false var body: some View { ZStack { @@ -93,12 +95,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..d633930 --- /dev/null +++ b/Mark-In/Sources/Feature/MyPage/MyPageView.swift @@ -0,0 +1,81 @@ +// +// 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 viewModel = MyPageViewModel() + @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 { + viewModel.send(.logoutButtonTapped) + } label: { + Text("로그아웃") + .font(.pretendard(size: 12, weight: .regular)) + .foregroundStyle(.markBlack) + } + + Divider() + .padding(.horizontal, 8) + + Button { + viewModel.send(.withdrawalButtonTapped) + } label: { + Text("회원 탈퇴") + .font(.pretendard(size: 12, weight: .regular)) + .foregroundStyle(.markRed) + } + } + .buttonStyle(.plain) + } +} + +#Preview { + MyPageView() +} diff --git a/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift b/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift new file mode 100644 index 0000000..1611bb9 --- /dev/null +++ b/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift @@ -0,0 +1,115 @@ +// +// MyPageViewModel.swift +// Mark-In +// +// Created by 이정동 on 5/13/25. +// + +import Foundation + +import FirebaseAuth +import GoogleSignIn + +@Observable +final class MyPageViewModel: Reducer { + struct State { + var userState: AuthUserState = .init() + } + + enum Action { + case logoutButtonTapped + case withdrawalButtonTapped + + case didSuccessLogout + case didFailLogout + + case didSuccessWithdrawal + case didFailWithdrawal + } + + 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 .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() + return .none + + // TODO: 로그아웃 실패에 대한 처리 필요 + case .didFailLogout: + return .none + + case .didSuccessWithdrawal: + authUserManager.clear() + return .none + + // TODO: 회원탈퇴 실패에 대한 처리 필요 + case .didFailWithdrawal: + 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) + } + } + } +} + +extension MyPageViewModel { + struct AuthUserState { + var name: String = "" + var email: String = "" + } +}