mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
add UI and API call to delete account from ios app
This commit is contained in:
parent
75b22fbe67
commit
02f0e22cff
9 changed files with 132 additions and 42 deletions
|
|
@ -26,7 +26,7 @@ struct DebugMenuView: View {
|
|||
|
||||
Button(
|
||||
action: {
|
||||
authenticator.logout()
|
||||
authenticator.logout(dataService: dataService)
|
||||
dataService.switchAppEnvironment(appEnvironment: selectedEnvironment)
|
||||
},
|
||||
label: { Text("Apply Changes") }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
import Services
|
||||
import SwiftUI
|
||||
import Views
|
||||
|
||||
struct ManageAccountView: View {
|
||||
@EnvironmentObject var authenticator: Authenticator
|
||||
@EnvironmentObject var dataService: DataService
|
||||
|
||||
@State private var showDeleteAccountConfirmation = false
|
||||
@StateObject private var viewModel = ProfileContainerViewModel()
|
||||
|
||||
var body: some View {
|
||||
#if os(iOS)
|
||||
Form {
|
||||
innerBody
|
||||
}
|
||||
#elseif os(macOS)
|
||||
List {
|
||||
innerBody
|
||||
}
|
||||
.listStyle(InsetListStyle())
|
||||
#endif
|
||||
}
|
||||
|
||||
var innerBody: some View {
|
||||
Group {
|
||||
Section {
|
||||
ProfileCard(data: viewModel.profileCardData)
|
||||
.task {
|
||||
await viewModel.loadProfileData(dataService: dataService)
|
||||
}
|
||||
}
|
||||
Section {
|
||||
Button(
|
||||
action: {
|
||||
showDeleteAccountConfirmation = true
|
||||
},
|
||||
label: { Text("Delete Account") }
|
||||
)
|
||||
.alert(isPresented: $showDeleteAccountConfirmation) {
|
||||
Alert(
|
||||
title: Text("Are you sure you want to delete your account? This action can't be undone."),
|
||||
primaryButton: .destructive(Text("Delete Account")) {
|
||||
Task {
|
||||
await viewModel.deleteAccount(dataService: dataService, authenticator: authenticator)
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if let errorMessage = viewModel.deleteAccountErrorMessage {
|
||||
Text(errorMessage)
|
||||
.font(.appBody)
|
||||
.foregroundColor(.red)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import Views
|
|||
@MainActor final class ProfileContainerViewModel: ObservableObject {
|
||||
@Published var isLoading = false
|
||||
@Published var profileCardData = ProfileCardData()
|
||||
@Published var deleteAccountErrorMessage: String?
|
||||
|
||||
var appVersionString: String {
|
||||
if let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String {
|
||||
|
|
@ -31,6 +32,20 @@ import Views
|
|||
}
|
||||
}
|
||||
|
||||
func deleteAccount(dataService: DataService, authenticator: Authenticator) async {
|
||||
guard let currentViewer = dataService.currentViewer else {
|
||||
deleteAccountErrorMessage = "Unable to load account information."
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try await dataService.deleteAccount(userID: currentViewer.unwrappedUserID)
|
||||
authenticator.logout(dataService: dataService)
|
||||
} catch {
|
||||
deleteAccountErrorMessage = "We were unable to delete your account."
|
||||
}
|
||||
}
|
||||
|
||||
private func loadProfileCardData(viewer: Viewer) {
|
||||
profileCardData = ProfileCardData(
|
||||
name: viewer.unwrappedName,
|
||||
|
|
@ -106,14 +121,10 @@ struct ProfileView: View {
|
|||
}
|
||||
|
||||
Section(footer: Text(viewModel.appVersionString)) {
|
||||
if FeatureFlag.showAccountDeletion {
|
||||
NavigationLink(
|
||||
destination: ManageAccountView(handleAccountDeletion: {
|
||||
print("delete account")
|
||||
})
|
||||
) {
|
||||
Text("Manage Account")
|
||||
}
|
||||
NavigationLink(
|
||||
destination: ManageAccountView()
|
||||
) {
|
||||
Text("Manage Account")
|
||||
}
|
||||
|
||||
Text("Logout")
|
||||
|
|
@ -124,7 +135,7 @@ struct ProfileView: View {
|
|||
Alert(
|
||||
title: Text("Are you sure you want to logout?"),
|
||||
primaryButton: .destructive(Text("Confirm")) {
|
||||
authenticator.logout()
|
||||
authenticator.logout(dataService: dataService)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public final class RootViewModel: ObservableObject {
|
|||
|
||||
#if DEBUG
|
||||
if CommandLine.arguments.contains("--uitesting") {
|
||||
services.authenticator.logout()
|
||||
services.authenticator.logout(dataService: services.dataService)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ public final class Authenticator: ObservableObject {
|
|||
ValetKey.authToken.value()
|
||||
}
|
||||
|
||||
public func logout() {
|
||||
public func logout(dataService: DataService) {
|
||||
dataService.resetCoreData()
|
||||
clearCreds()
|
||||
Authenticator.unregisterIntercomUser?()
|
||||
isLoggedIn = false
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public final class DataService: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
private func resetCoreData() {
|
||||
func resetCoreData() {
|
||||
clearCoreData()
|
||||
|
||||
persistentContainer = PersistentContainer.make()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
import Foundation
|
||||
import Models
|
||||
import SwiftGraphQL
|
||||
|
||||
public extension DataService {
|
||||
func deleteAccount(userID: String) async throws {
|
||||
enum MutationResult {
|
||||
case success(id: String)
|
||||
case error(errorMessage: String)
|
||||
}
|
||||
|
||||
let selection = Selection<MutationResult, Unions.DeleteAccountResult> {
|
||||
try $0.on(
|
||||
deleteAccountError: .init {
|
||||
.error(errorMessage: (try $0.errorCodes().first ?? .forbidden).rawValue)
|
||||
},
|
||||
deleteAccountSuccess: .init {
|
||||
.success(id: try $0.userId())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let mutation = Selection.Mutation {
|
||||
try $0.deleteAccount(userId: userID, selection: selection)
|
||||
}
|
||||
|
||||
let path = appEnvironment.graphqlPath
|
||||
let headers = networker.defaultHeaders
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
send(mutation, to: path, headers: headers) { mutationResult in
|
||||
guard let payload = try? mutationResult.get() else {
|
||||
continuation.resume(throwing: BasicError.message(messageText: "failed to delete user"))
|
||||
return
|
||||
}
|
||||
|
||||
switch payload.data {
|
||||
case .success:
|
||||
continuation.resume()
|
||||
case .error:
|
||||
continuation.resume(throwing: BasicError.message(messageText: "failed to delete user"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import Foundation
|
|||
#endif
|
||||
|
||||
public enum FeatureFlag {
|
||||
public static let showAccountDeletion = false
|
||||
public static let enableSnoozeFromShareExtension = false
|
||||
public static let enableRemindersFromShareExtension = false
|
||||
public static let enableReadNow = false
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import SwiftUI
|
||||
|
||||
public struct ManageAccountView: View {
|
||||
let handleAccountDeletion: () -> Void
|
||||
@State private var showDeleteAccountConfirmation = false
|
||||
|
||||
public init(handleAccountDeletion: @escaping () -> Void) {
|
||||
self.handleAccountDeletion = handleAccountDeletion
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Button(
|
||||
action: {
|
||||
showDeleteAccountConfirmation = true
|
||||
},
|
||||
label: { Text("Delete my account") }
|
||||
)
|
||||
.alert(isPresented: $showDeleteAccountConfirmation) {
|
||||
Alert(
|
||||
title: Text("Are you sure you want to delete your account? This action can't be undone."),
|
||||
primaryButton: .destructive(Text("Delete Account")) {
|
||||
handleAccountDeletion()
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue