add UI and API call to delete account from ios app

This commit is contained in:
Satindar Dhillon 2022-06-23 13:55:12 -07:00
parent 75b22fbe67
commit 02f0e22cff
9 changed files with 132 additions and 42 deletions

View file

@ -26,7 +26,7 @@ struct DebugMenuView: View {
Button(
action: {
authenticator.logout()
authenticator.logout(dataService: dataService)
dataService.switchAppEnvironment(appEnvironment: selectedEnvironment)
},
label: { Text("Apply Changes") }

View file

@ -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)
}
}
}
}

View file

@ -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()
)

View file

@ -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
}

View file

@ -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

View file

@ -95,7 +95,7 @@ public final class DataService: ObservableObject {
}
}
private func resetCoreData() {
func resetCoreData() {
clearCoreData()
persistentContainer = PersistentContainer.make()

View file

@ -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"))
}
}
}
}
}

View file

@ -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

View file

@ -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()
)
}
}
}