diff --git a/Sources/Screens/ForgotPassword/ForgotPassword.swift b/Sources/Screens/ForgotPassword/ForgotPassword.swift index d25043a..22497ae 100644 --- a/Sources/Screens/ForgotPassword/ForgotPassword.swift +++ b/Sources/Screens/ForgotPassword/ForgotPassword.swift @@ -10,9 +10,10 @@ import SwiftUI -struct ForgotPassword: View { - @State private var isLoading = false +struct ForgotPassword: View, FormValidatable, ErrorPresentable { + @StateObject private var forgotPasswordStore = ForgotPasswordStore() @State private var email: String = "" + @State private var isProcessing: Bool = false private let onCompletion: (() -> Void)? private let onSignIn: (() -> Void)? @@ -35,21 +36,19 @@ struct ForgotPassword: View { .voTextField(width: VOMetrics.formWidth) .textInputAutocapitalization(.never) .autocorrectionDisabled() - .disabled(isLoading) + .disabled(isProcessing) Button { - isLoading = true - DispatchQueue.main.asyncAfter(deadline: .now() + 3) { - isLoading = false - onCompletion?() + if isValid() { + performSendResetPasswordEmail() } } label: { VOButtonLabel( "Send Recovery Instructions", - isLoading: isLoading, + isLoading: isProcessing, progressViewTint: .white ) } - .voPrimaryButton(width: VOMetrics.formWidth, isDisabled: isLoading) + .voPrimaryButton(width: VOMetrics.formWidth, isDisabled: isProcessing) HStack { Text("Password recovered?") .voFormHintText() @@ -59,7 +58,7 @@ struct ForgotPassword: View { Text("Sign In") .voFormHintLabel() } - .disabled(isLoading) + .disabled(isProcessing) } } .toolbar { @@ -72,6 +71,34 @@ struct ForgotPassword: View { } } } + .voErrorSheet(isPresented: $errorIsPresented, message: errorMessage) + } + + private func performSendResetPasswordEmail() { + withErrorHandling { + _ = try await forgotPasswordStore.sendResetPasswordEmail(.init(email: email)) + return true + } before: { + isProcessing = true + } success: { + onCompletion?() + } failure: { message in + errorMessage = message + errorIsPresented = true + } anyways: { + isProcessing = false + } + } + + // MARK: - ErrorPresentable + + @State var errorIsPresented: Bool = false + @State var errorMessage: String? + + // MARK: - FormValidatable + + func isValid() -> Bool { + !email.isEmpty } } diff --git a/Sources/Screens/ForgotPassword/ForgotPasswordStore.swift b/Sources/Screens/ForgotPassword/ForgotPasswordStore.swift index fe40f06..8feb8d6 100644 --- a/Sources/Screens/ForgotPassword/ForgotPasswordStore.swift +++ b/Sources/Screens/ForgotPassword/ForgotPasswordStore.swift @@ -9,5 +9,15 @@ // AGPL-3.0-only in the root of this repository. import Combine +import Foundation +import VoltaserveCore -class ForgotPasswordStore: ObservableObject {} +class ForgotPasswordStore: ObservableObject { + private var accountClient: VOAccount = .init(baseURL: Config.production.idpURL) + + // MARK: - Update + + func sendResetPasswordEmail(_ options: VOAccount.SendResetPasswordEmailOptions) async throws { + try await accountClient.sendResetPasswordEmail(options) + } +} diff --git a/Voltaserve.xcodeproj/project.pbxproj b/Voltaserve.xcodeproj/project.pbxproj index ed5431b..c86b43a 100644 --- a/Voltaserve.xcodeproj/project.pbxproj +++ b/Voltaserve.xcodeproj/project.pbxproj @@ -8,10 +8,6 @@ /* Begin PBXBuildFile section */ 644C380F2C8EA8E4008E8F0F /* VoltaserveCore in Frameworks */ = {isa = PBXBuildFile; productRef = 644C380E2C8EA8E4008E8F0F /* VoltaserveCore */; }; - 64562B6C2C69EC2A00E10D97 /* .swift-format in Resources */ = {isa = PBXBuildFile; fileRef = 64562B6A2C69EC2A00E10D97 /* .swift-format */; }; - 64562B6D2C69EC2A00E10D97 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 64562B682C69EC2A00E10D97 /* .gitignore */; }; - 64562B6F2C69EC2A00E10D97 /* .swift-version in Resources */ = {isa = PBXBuildFile; fileRef = 64562B692C69EC2A00E10D97 /* .swift-version */; }; - 6476FD3A2C9BD9CB00C1BC38 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 6476FD392C9BD9C700C1BC38 /* .swiftlint.yml */; }; 649066E22C5E35AD00BA3CCD /* VoltaserveApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649066E12C5E35AD00BA3CCD /* VoltaserveApp.swift */; }; 649066E42C5E35AD00BA3CCD /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649066E32C5E35AD00BA3CCD /* ContentView.swift */; }; 649066E62C5E35AE00BA3CCD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 649066E52C5E35AE00BA3CCD /* Assets.xcassets */; }; @@ -25,11 +21,6 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 6446B6212C7F61FD00868B3E /* Voltaserve-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Voltaserve-Info.plist"; sourceTree = ""; }; - 64562B682C69EC2A00E10D97 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; - 64562B692C69EC2A00E10D97 /* .swift-version */ = {isa = PBXFileReference; lastKnownFileType = text; path = ".swift-version"; sourceTree = ""; }; - 64562B6A2C69EC2A00E10D97 /* .swift-format */ = {isa = PBXFileReference; lastKnownFileType = text; path = ".swift-format"; sourceTree = ""; }; - 6476FD392C9BD9C700C1BC38 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = ""; }; 649066DE2C5E35AD00BA3CCD /* Voltaserve.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Voltaserve.app; sourceTree = BUILT_PRODUCTS_DIR; }; 649066E12C5E35AD00BA3CCD /* VoltaserveApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoltaserveApp.swift; sourceTree = ""; }; 649066E32C5E35AD00BA3CCD /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -160,6 +151,7 @@ SignIn/SignIn.swift, SignIn/TokenStore.swift, SignUp/SignUp.swift, + SignUp/SignUpPasswordHint.swift, SignUp/SignUpStore.swift, Snapshot/SnapshotFeatures.swift, Snapshot/SnapshotList.swift, @@ -233,11 +225,6 @@ 649066D52C5E35AD00BA3CCD = { isa = PBXGroup; children = ( - 6476FD392C9BD9C700C1BC38 /* .swiftlint.yml */, - 6446B6212C7F61FD00868B3E /* Voltaserve-Info.plist */, - 64562B682C69EC2A00E10D97 /* .gitignore */, - 64562B692C69EC2A00E10D97 /* .swift-version */, - 64562B6A2C69EC2A00E10D97 /* .swift-format */, 649066E52C5E35AE00BA3CCD /* Assets.xcassets */, 64D0D97A2C8120E800B96180 /* Fonts */, 649066E72C5E35AE00BA3CCD /* Preview Content */, @@ -361,12 +348,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 64562B6C2C69EC2A00E10D97 /* .swift-format in Resources */, - 64562B6D2C69EC2A00E10D97 /* .gitignore in Resources */, 64D0D97B2C8120E800B96180 /* IBM Plex Sans Var-Roman.ttf in Resources */, 64D0D97C2C8120E800B96180 /* Unbounded-VariableFont_wght.ttf in Resources */, - 6476FD3A2C9BD9CB00C1BC38 /* .swiftlint.yml in Resources */, - 64562B6F2C69EC2A00E10D97 /* .swift-version in Resources */, 649066E92C5E35AE00BA3CCD /* Preview Assets.xcassets in Resources */, 649066E62C5E35AE00BA3CCD /* Assets.xcassets in Resources */, 64F083822CA87E0A00CCB644 /* IBM Plex Sans Var-Italic.ttf in Resources */,