Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// ForgotEffect.swift
// TorqueNet
//
// Created by MAC on 13/04/2026.
//

enum ForgotEffect {
case successDialog
case showError(String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// ForgotIntent.swift
// TorqueNet
//
// Created by MAC on 10/04/2026.
//

enum ForgotIntent{
case onEmailChanged(String)
case forgotPassword
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// ForgotPasswordState.swift
// TorqueNet
//
// Created by MAC on 10/04/2026.
//

struct ForgotPasswordState{
var email:String = ""
var dialogEntity = DialogEntity()
var isShowAlertDialog = false
var isForgotPasswordEnable: Bool = false
var forgotPasswordErrors = [String: String]()
var forgotPasswordState: FetchState = FetchState.good
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ struct ForgotPasswordView: View {
@EnvironmentObject var router: Router
@StateObject var forgotPasswordViewModel = ForgotPasswordViewModel()

@State private var email: String = ""

var body: some View {
VStack(spacing: 0) {
Expand All @@ -31,7 +30,6 @@ struct ForgotPasswordView: View {
.padding(.horizontal,12)
Spacer()

// Card Section
VStack(spacing: 20) {
Text("Forgot Password?")
.font(.custom("Exo2-Bold", size: 20))
Expand All @@ -45,64 +43,36 @@ struct ForgotPasswordView: View {
InputFieldView(
description: "Email",
placeHolder: "johndoe@gmail.com",
text: $email,
text: $forgotPasswordViewModel.uiState.email,
foregroundColor: Color.theme.onSurfaceColor,
keyboardType: .emailAddress,
errorMessage: forgotPasswordViewModel.forgotPasswordErrors["email"] ?? "",
errorMessage: forgotPasswordViewModel.uiState.forgotPasswordErrors["email"] ?? "",
inputFieldStyle: .outlined,
onTextChange: { text in
forgotPasswordViewModel.updateEmail(value: text)
forgotPasswordViewModel.onAction(_intent: .onEmailChanged(text))
}
)


CustomButtonView(
buttonName:"Send",
isDisabled: !forgotPasswordViewModel.isForgotPasswordEnable,
isDisabled: !forgotPasswordViewModel.uiState.isForgotPasswordEnable,
onTap: {
Task{
await forgotPasswordViewModel.forgotPassword(
onSuccess: {
forgotPasswordViewModel.updateIsShowAlertDialog(value: true)
forgotPasswordViewModel.updateDialogEntity(
value: DialogEntity(
title: "Request Password Change Successful!",
message: "Please check your email for password reset link and proceed to login.",
icon: "",
confirmButtonText: "Proceed",
dismissButtonText: "",
onConfirm: {
forgotPasswordViewModel.updateIsShowAlertDialog(value: false)
router.push(.login)
},
onDismiss: {
forgotPasswordViewModel.updateIsShowAlertDialog(value: false)
}
)
)

}, onFailure:{error in
forgotPasswordViewModel.updateIsShowAlertDialog(value: true)
forgotPasswordViewModel.updateDialogEntity(
value: DialogEntity(
title: "Forgot Password Request Failed.",
message: error,
icon: "",
confirmButtonText: "",
dismissButtonText: "Okay",
onConfirm: {
forgotPasswordViewModel.updateIsShowAlertDialog(value: false)
},
onDismiss: {
forgotPasswordViewModel.updateIsShowAlertDialog(value: false)
}
)
)
}
)
}
forgotPasswordViewModel.onAction(_intent: .forgotPassword)
}
)

HStack {
Text("Just Remembered your password?")
.foregroundColor(Color.theme.onSurfaceColor)
Button("Sign In") {
router.push(.login)
}
.foregroundColor(Color.theme.primaryColor)
.fontWeight(.semibold)
}
.font(.custom("Exo2-Regular", size: 14))
.padding(.top, 8)
}
.padding(.vertical, 50)
.padding(.horizontal, 16)
Expand All @@ -115,7 +85,70 @@ struct ForgotPasswordView: View {
)
}
.background(Color.theme.surfaceColor)
.overlay {
CustomAlertDialogView(
isPresented: $forgotPasswordViewModel.uiState.isShowAlertDialog,
title: forgotPasswordViewModel.uiState.dialogEntity.title,
text: forgotPasswordViewModel.uiState.dialogEntity.message,
confirmButtonText: forgotPasswordViewModel.uiState.dialogEntity.confirmButtonText,
dismissButtonText: forgotPasswordViewModel.uiState.dialogEntity.dismissButtonText,
imageName: forgotPasswordViewModel.uiState.dialogEntity.icon,
onDismiss: {
if let onDismiss = forgotPasswordViewModel.uiState.dialogEntity.onDismiss {
onDismiss()
}
},
onConfirmation: {
if let onConfirm = forgotPasswordViewModel.uiState.dialogEntity.onConfirm {
onConfirm()
}
}
)
}
.fullScreenProgressOverlay(isShowing: forgotPasswordViewModel.uiState.forgotPasswordState == .isLoading )
.ignoresSafeArea(edges: .all)
.onReceive(forgotPasswordViewModel.$effect) { effect in
guard let effect = effect else { return }
switch effect {
case .successDialog:
forgotPasswordViewModel.updateIsShowAlertDialog(value: true)
forgotPasswordViewModel.updateDialogEntity(
value: DialogEntity(
title: "Request Password Change Successful!",
message: "Please check your email for password reset link and proceed to login.",
icon: "",
confirmButtonText: "Proceed",
dismissButtonText: "",
onConfirm: {
forgotPasswordViewModel.updateIsShowAlertDialog(value: false)
router.push(.login)
},
onDismiss: {
forgotPasswordViewModel.updateIsShowAlertDialog(value: false)
}
)
)
case .showError(let message):
forgotPasswordViewModel.updateIsShowAlertDialog(value: true)
forgotPasswordViewModel.updateDialogEntity(
value: DialogEntity(
title: "Forgot Password Request Failed.",
message: message,
icon: "",
confirmButtonText: "",
dismissButtonText: "Okay",
onConfirm: {
forgotPasswordViewModel.updateIsShowAlertDialog(value: false)
},
onDismiss: {
forgotPasswordViewModel.updateIsShowAlertDialog(value: false)
}
)
)
forgotPasswordViewModel.effect = nil

}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,62 +10,63 @@ import FirebaseAuth

@MainActor
class ForgotPasswordViewModel:ObservableObject {
@Published var dialogEntity = DialogEntity()
@Published var isShowAlertDialog = false
@Published var email: String = ""
@Published var isForgotPasswordEnable: Bool = false
@Published var forgotPasswordErrors = [String: String]()
@Published var forgotPasswordState: FetchState = FetchState.good

@Published var uiState = ForgotPasswordState()
@Published var effect: ForgotEffect?
let authUseCase: AuthUseCase = AuthUseCase(authRepository: AuthenticationRepositoryImpl.shared)

func onAction(_intent:ForgotIntent){
switch _intent {
case .onEmailChanged(let email):
uiState.email = email
let error = ValidatorUtils.shared.validateEmail(email: uiState.email)
updateForgotErrors(key: "email", value: error)
case .forgotPassword:
Task{
await handleForgotPassword()
}
}

}

func validateIfForgotPasswordIsEnabled(){
var isFormValid = true

if !forgotPasswordErrors.values.allSatisfy({ $0.isEmpty }) || email.isEmpty{
if !uiState.forgotPasswordErrors.values.allSatisfy({ $0.isEmpty }) || uiState.email.isEmpty{
isFormValid = false
}

isForgotPasswordEnable = isFormValid
uiState.isForgotPasswordEnable = isFormValid
}

func updateForgotErrors(key: String, value: String) {
forgotPasswordErrors[key] = value
uiState.forgotPasswordErrors[key] = value
validateIfForgotPasswordIsEnabled()
}

func updateEmail(value: String) {
email = value
let error = ValidatorUtils.shared.validateEmail(email: email)
updateForgotErrors(key: "email", value: error)
}


func updateDialogEntity(value: DialogEntity) {
dialogEntity = value
uiState.dialogEntity = value
}

func updateIsShowAlertDialog(value: Bool) {
isShowAlertDialog = value
uiState.isShowAlertDialog = value
}

func forgotPassword(
onSuccess: () -> Void,
onFailure: (String) -> Void
) async {
forgotPasswordState = .isLoading

private func handleForgotPassword() async {
uiState.forgotPasswordState = .isLoading

var result: Result<Bool, FirebaseAuthError>
result = await authUseCase.executeForgotPassword(email: email)
result = await authUseCase.executeForgotPassword(email: uiState.email)

switch result {
case .success(let authDataResult):
onSuccess()
forgotPasswordState = .good
case .success:
uiState.forgotPasswordState = .good
effect = .successDialog

case .failure(let error):
forgotPasswordState = .error(error.description)
onFailure(error.description)
uiState.forgotPasswordState = .error(error.description)
effect = .showError(error.description)
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// LoginEffect.swift
// TorqueNet
//
// Created by MAC on 10/04/2026.
//

enum LoginEffect {
case navigateToDashboard
case showError(String)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// LoginIntent.swift
// TorqueNet
//
// Created by MAC on 10/04/2026.
//

enum LoginIntent {
case onEmailChange(String)
case onPasswordChange(String)
case onRememberMe(Bool)
case login
}
Loading
Loading