Add auto-login after password reset on iOS and Android
- Add LOGGING_IN step to PasswordResetStep enum on both platforms - Auto-login with new credentials after successful password reset - Navigate directly to main app (or verification screen if unverified) - Show "Logging in..." state during auto-login process - Hide back button during auto-login to prevent interruption - Fall back to "Return to Login" if auto-login fails 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -289,9 +289,28 @@ fun App(
|
|||||||
}
|
}
|
||||||
val passwordResetViewModel: PasswordResetViewModel = viewModel(parentEntry) { PasswordResetViewModel() }
|
val passwordResetViewModel: PasswordResetViewModel = viewModel(parentEntry) { PasswordResetViewModel() }
|
||||||
|
|
||||||
|
// Set up auto-login callback
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
passwordResetViewModel.onLoginSuccess = { verified ->
|
||||||
|
isLoggedIn = true
|
||||||
|
isVerified = verified
|
||||||
|
onClearDeepLinkToken()
|
||||||
|
// Navigate directly to main app or verification screen
|
||||||
|
if (verified) {
|
||||||
|
navController.navigate(MainRoute) {
|
||||||
|
popUpTo<ForgotPasswordRoute> { inclusive = true }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navController.navigate(VerifyEmailRoute) {
|
||||||
|
popUpTo<ForgotPasswordRoute> { inclusive = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ResetPasswordScreen(
|
ResetPasswordScreen(
|
||||||
onPasswordResetSuccess = {
|
onPasswordResetSuccess = {
|
||||||
// Clear deep link token and navigate back to login after successful password reset
|
// Fallback: manual return to login (only shown if auto-login fails)
|
||||||
onClearDeepLinkToken()
|
onClearDeepLinkToken()
|
||||||
navController.navigate(LoginRoute) {
|
navController.navigate(LoginRoute) {
|
||||||
popUpTo<ForgotPasswordRoute> { inclusive = true }
|
popUpTo<ForgotPasswordRoute> { inclusive = true }
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ fun ResetPasswordScreen(
|
|||||||
var confirmPasswordVisible by remember { mutableStateOf(false) }
|
var confirmPasswordVisible by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val resetPasswordState by viewModel.resetPasswordState.collectAsState()
|
val resetPasswordState by viewModel.resetPasswordState.collectAsState()
|
||||||
|
val loginState by viewModel.loginState.collectAsState()
|
||||||
val currentStep by viewModel.currentStep.collectAsState()
|
val currentStep by viewModel.currentStep.collectAsState()
|
||||||
|
|
||||||
// Handle errors for password reset
|
// Handle errors for password reset
|
||||||
@@ -50,6 +51,7 @@ fun ResetPasswordScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val isLoading = resetPasswordState is ApiResult.Loading
|
val isLoading = resetPasswordState is ApiResult.Loading
|
||||||
|
val isLoggingIn = currentStep == com.example.casera.viewmodel.PasswordResetStep.LOGGING_IN
|
||||||
val isSuccess = currentStep == com.example.casera.viewmodel.PasswordResetStep.SUCCESS
|
val isSuccess = currentStep == com.example.casera.viewmodel.PasswordResetStep.SUCCESS
|
||||||
|
|
||||||
// Password validation
|
// Password validation
|
||||||
@@ -63,9 +65,12 @@ fun ResetPasswordScreen(
|
|||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(Res.string.auth_reset_title)) },
|
title = { Text(stringResource(Res.string.auth_reset_title)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
onNavigateBack?.let { callback ->
|
// Hide back button while logging in
|
||||||
IconButton(onClick = callback) {
|
if (!isLoggingIn) {
|
||||||
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(Res.string.common_back))
|
onNavigateBack?.let { callback ->
|
||||||
|
IconButton(onClick = callback) {
|
||||||
|
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(Res.string.common_back))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -239,15 +244,21 @@ fun ResetPasswordScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(56.dp),
|
.height(56.dp),
|
||||||
enabled = isFormValid && !isLoading,
|
enabled = isFormValid && !isLoading && !isLoggingIn,
|
||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
) {
|
) {
|
||||||
if (isLoading) {
|
if (isLoading || isLoggingIn) {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
strokeWidth = 2.dp
|
strokeWidth = 2.dp
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
if (isLoggingIn) "Logging in..." else "Resetting...",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
Icon(Icons.Default.LockReset, contentDescription = null)
|
Icon(Icons.Default.LockReset, contentDescription = null)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ enum class PasswordResetStep {
|
|||||||
REQUEST_CODE,
|
REQUEST_CODE,
|
||||||
VERIFY_CODE,
|
VERIFY_CODE,
|
||||||
RESET_PASSWORD,
|
RESET_PASSWORD,
|
||||||
|
LOGGING_IN,
|
||||||
SUCCESS
|
SUCCESS
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +30,12 @@ class PasswordResetViewModel(
|
|||||||
private val _resetPasswordState = MutableStateFlow<ApiResult<ResetPasswordResponse>>(ApiResult.Idle)
|
private val _resetPasswordState = MutableStateFlow<ApiResult<ResetPasswordResponse>>(ApiResult.Idle)
|
||||||
val resetPasswordState: StateFlow<ApiResult<ResetPasswordResponse>> = _resetPasswordState
|
val resetPasswordState: StateFlow<ApiResult<ResetPasswordResponse>> = _resetPasswordState
|
||||||
|
|
||||||
|
private val _loginState = MutableStateFlow<ApiResult<AuthResponse>>(ApiResult.Idle)
|
||||||
|
val loginState: StateFlow<ApiResult<AuthResponse>> = _loginState
|
||||||
|
|
||||||
|
// Callback for successful login after password reset
|
||||||
|
var onLoginSuccess: ((Boolean) -> Unit)? = null
|
||||||
|
|
||||||
private val _currentStep = MutableStateFlow(
|
private val _currentStep = MutableStateFlow(
|
||||||
if (deepLinkToken != null) PasswordResetStep.RESET_PASSWORD else PasswordResetStep.REQUEST_CODE
|
if (deepLinkToken != null) PasswordResetStep.RESET_PASSWORD else PasswordResetStep.REQUEST_CODE
|
||||||
)
|
)
|
||||||
@@ -80,6 +87,8 @@ class PasswordResetViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var _newPassword: String = ""
|
||||||
|
|
||||||
fun resetPassword(newPassword: String, confirmPassword: String) {
|
fun resetPassword(newPassword: String, confirmPassword: String) {
|
||||||
val token = _resetToken.value
|
val token = _resetToken.value
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
@@ -87,6 +96,8 @@ class PasswordResetViewModel(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_newPassword = newPassword
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_resetPasswordState.value = ApiResult.Loading
|
_resetPasswordState.value = ApiResult.Loading
|
||||||
// Note: confirmPassword is for UI validation only, not sent to API
|
// Note: confirmPassword is for UI validation only, not sent to API
|
||||||
@@ -98,7 +109,9 @@ class PasswordResetViewModel(
|
|||||||
)
|
)
|
||||||
_resetPasswordState.value = when (result) {
|
_resetPasswordState.value = when (result) {
|
||||||
is ApiResult.Success -> {
|
is ApiResult.Success -> {
|
||||||
_currentStep.value = PasswordResetStep.SUCCESS
|
// Password reset successful - now auto-login
|
||||||
|
_currentStep.value = PasswordResetStep.LOGGING_IN
|
||||||
|
autoLogin()
|
||||||
ApiResult.Success(result.data)
|
ApiResult.Success(result.data)
|
||||||
}
|
}
|
||||||
is ApiResult.Error -> result
|
is ApiResult.Error -> result
|
||||||
@@ -107,11 +120,46 @@ class PasswordResetViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun autoLogin() {
|
||||||
|
val username = _email.value
|
||||||
|
|
||||||
|
if (username.isEmpty()) {
|
||||||
|
// If we don't have the email (e.g., deep link flow), fall back to manual login
|
||||||
|
_currentStep.value = PasswordResetStep.SUCCESS
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_loginState.value = ApiResult.Loading
|
||||||
|
|
||||||
|
val loginResult = APILayer.login(LoginRequest(username = username, password = _newPassword))
|
||||||
|
|
||||||
|
when (loginResult) {
|
||||||
|
is ApiResult.Success -> {
|
||||||
|
val isVerified = loginResult.data.user.verified
|
||||||
|
// Initialize lookups
|
||||||
|
APILayer.initializeLookups()
|
||||||
|
_loginState.value = loginResult
|
||||||
|
// Call the login success callback
|
||||||
|
onLoginSuccess?.invoke(isVerified)
|
||||||
|
}
|
||||||
|
is ApiResult.Error -> {
|
||||||
|
// Auto-login failed, fall back to manual login
|
||||||
|
println("Auto-login failed: ${loginResult.message}")
|
||||||
|
_loginState.value = ApiResult.Idle
|
||||||
|
_currentStep.value = PasswordResetStep.SUCCESS
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
_currentStep.value = PasswordResetStep.SUCCESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun moveToNextStep() {
|
fun moveToNextStep() {
|
||||||
_currentStep.value = when (_currentStep.value) {
|
_currentStep.value = when (_currentStep.value) {
|
||||||
PasswordResetStep.REQUEST_CODE -> PasswordResetStep.VERIFY_CODE
|
PasswordResetStep.REQUEST_CODE -> PasswordResetStep.VERIFY_CODE
|
||||||
PasswordResetStep.VERIFY_CODE -> PasswordResetStep.RESET_PASSWORD
|
PasswordResetStep.VERIFY_CODE -> PasswordResetStep.RESET_PASSWORD
|
||||||
PasswordResetStep.RESET_PASSWORD -> PasswordResetStep.SUCCESS
|
PasswordResetStep.RESET_PASSWORD -> PasswordResetStep.LOGGING_IN
|
||||||
|
PasswordResetStep.LOGGING_IN -> PasswordResetStep.SUCCESS
|
||||||
PasswordResetStep.SUCCESS -> PasswordResetStep.SUCCESS
|
PasswordResetStep.SUCCESS -> PasswordResetStep.SUCCESS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,6 +169,7 @@ class PasswordResetViewModel(
|
|||||||
PasswordResetStep.REQUEST_CODE -> PasswordResetStep.REQUEST_CODE
|
PasswordResetStep.REQUEST_CODE -> PasswordResetStep.REQUEST_CODE
|
||||||
PasswordResetStep.VERIFY_CODE -> PasswordResetStep.REQUEST_CODE
|
PasswordResetStep.VERIFY_CODE -> PasswordResetStep.REQUEST_CODE
|
||||||
PasswordResetStep.RESET_PASSWORD -> PasswordResetStep.VERIFY_CODE
|
PasswordResetStep.RESET_PASSWORD -> PasswordResetStep.VERIFY_CODE
|
||||||
|
PasswordResetStep.LOGGING_IN -> PasswordResetStep.LOGGING_IN // Can't go back while logging in
|
||||||
PasswordResetStep.SUCCESS -> PasswordResetStep.SUCCESS
|
PasswordResetStep.SUCCESS -> PasswordResetStep.SUCCESS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17388,6 +17388,9 @@
|
|||||||
},
|
},
|
||||||
"Log in" : {
|
"Log in" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Logging in..." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Mark Task In Progress" : {
|
"Mark Task In Progress" : {
|
||||||
"comment" : "A button label that says \"Mark Task In Progress\".",
|
"comment" : "A button label that says \"Mark Task In Progress\".",
|
||||||
@@ -21300,6 +21303,9 @@
|
|||||||
"Reset Password" : {
|
"Reset Password" : {
|
||||||
"comment" : "The title of the screen where users can reset their passwords.",
|
"comment" : "The title of the screen where users can reset their passwords.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
|
"Resetting..." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Residences" : {
|
"Residences" : {
|
||||||
"comment" : "A tab label for the \"Residences\" section in the main tab view.",
|
"comment" : "A tab label for the \"Residences\" section in the main tab view.",
|
||||||
|
|||||||
@@ -336,7 +336,16 @@ struct LoginView: View {
|
|||||||
RegisterView()
|
RegisterView()
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showPasswordReset) {
|
.sheet(isPresented: $showPasswordReset) {
|
||||||
PasswordResetFlow(resetToken: resetToken)
|
PasswordResetFlow(resetToken: resetToken, onLoginSuccess: { isVerified in
|
||||||
|
// Update the shared authentication manager
|
||||||
|
AuthenticationManager.shared.login(verified: isVerified)
|
||||||
|
|
||||||
|
if isVerified {
|
||||||
|
// User is verified, call the success callback
|
||||||
|
self.onLoginSuccess?()
|
||||||
|
}
|
||||||
|
// If not verified, RootView will handle showing VerifyEmailView
|
||||||
|
})
|
||||||
}
|
}
|
||||||
.onChange(of: resetToken) { _, token in
|
.onChange(of: resetToken) { _, token in
|
||||||
if token != nil {
|
if token != nil {
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import SwiftUI
|
|||||||
struct PasswordResetFlow: View {
|
struct PasswordResetFlow: View {
|
||||||
@StateObject private var viewModel: PasswordResetViewModel
|
@StateObject private var viewModel: PasswordResetViewModel
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
var onLoginSuccess: ((Bool) -> Void)?
|
||||||
|
|
||||||
init(resetToken: String? = nil) {
|
init(resetToken: String? = nil, onLoginSuccess: ((Bool) -> Void)? = nil) {
|
||||||
_viewModel = StateObject(wrappedValue: PasswordResetViewModel(resetToken: resetToken))
|
_viewModel = StateObject(wrappedValue: PasswordResetViewModel(resetToken: resetToken))
|
||||||
|
self.onLoginSuccess = onLoginSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -15,13 +17,22 @@ struct PasswordResetFlow: View {
|
|||||||
ForgotPasswordView(viewModel: viewModel)
|
ForgotPasswordView(viewModel: viewModel)
|
||||||
case .verifyCode:
|
case .verifyCode:
|
||||||
VerifyResetCodeView(viewModel: viewModel)
|
VerifyResetCodeView(viewModel: viewModel)
|
||||||
case .resetPassword, .success:
|
case .resetPassword, .loggingIn, .success:
|
||||||
ResetPasswordView(viewModel: viewModel, onSuccess: {
|
ResetPasswordView(viewModel: viewModel, onSuccess: {
|
||||||
dismiss()
|
dismiss()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animation(.easeInOut, value: viewModel.currentStep)
|
.animation(.easeInOut, value: viewModel.currentStep)
|
||||||
|
.onAppear {
|
||||||
|
// Set up callback for auto-login success
|
||||||
|
viewModel.onLoginSuccess = { [self] isVerified in
|
||||||
|
// Dismiss the sheet first
|
||||||
|
dismiss()
|
||||||
|
// Then call the parent's login success handler
|
||||||
|
onLoginSuccess?(isVerified)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ enum PasswordResetStep: CaseIterable {
|
|||||||
case requestCode // Step 1: Enter email
|
case requestCode // Step 1: Enter email
|
||||||
case verifyCode // Step 2: Enter 6-digit code
|
case verifyCode // Step 2: Enter 6-digit code
|
||||||
case resetPassword // Step 3: Set new password
|
case resetPassword // Step 3: Set new password
|
||||||
case success // Final: Success confirmation
|
case loggingIn // Auto-login in progress
|
||||||
|
case success // Final: Success confirmation (only used if auto-login fails)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ViewModel for password reset flow.
|
/// ViewModel for password reset flow.
|
||||||
@@ -24,6 +25,9 @@ class PasswordResetViewModel: ObservableObject {
|
|||||||
@Published var currentStep: PasswordResetStep = .requestCode
|
@Published var currentStep: PasswordResetStep = .requestCode
|
||||||
@Published var resetToken: String?
|
@Published var resetToken: String?
|
||||||
|
|
||||||
|
// Callback for successful login after password reset
|
||||||
|
var onLoginSuccess: ((Bool) -> Void)?
|
||||||
|
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
init(resetToken: String? = nil) {
|
init(resetToken: String? = nil) {
|
||||||
// If we have a reset token from deep link, skip to password reset step
|
// If we have a reset token from deep link, skip to password reset step
|
||||||
@@ -141,9 +145,10 @@ class PasswordResetViewModel: ObservableObject {
|
|||||||
let result = try await APILayer.shared.resetPassword(request: request)
|
let result = try await APILayer.shared.resetPassword(request: request)
|
||||||
|
|
||||||
if result is ApiResultSuccess<ResetPasswordResponse> {
|
if result is ApiResultSuccess<ResetPasswordResponse> {
|
||||||
self.isLoading = false
|
// Password reset successful - now auto-login
|
||||||
self.successMessage = "Password reset successfully! You can now log in with your new password."
|
self.successMessage = "Password reset successfully! Logging you in..."
|
||||||
self.currentStep = .success
|
self.currentStep = .loggingIn
|
||||||
|
await self.autoLogin()
|
||||||
} else if let error = result as? ApiResultError {
|
} else if let error = result as? ApiResultError {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.errorMessage = ErrorMessageParser.parse(error.message)
|
self.errorMessage = ErrorMessageParser.parse(error.message)
|
||||||
@@ -155,6 +160,51 @@ class PasswordResetViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Auto-login after successful password reset
|
||||||
|
private func autoLogin() async {
|
||||||
|
// Use the email from the reset flow as the username
|
||||||
|
let username = email
|
||||||
|
|
||||||
|
guard !username.isEmpty else {
|
||||||
|
// If we don't have the email (e.g., deep link flow), fall back to manual login
|
||||||
|
self.isLoading = false
|
||||||
|
self.successMessage = "Password reset successfully! You can now log in with your new password."
|
||||||
|
self.currentStep = .success
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let loginResult = try await APILayer.shared.login(
|
||||||
|
request: LoginRequest(username: username, password: newPassword)
|
||||||
|
)
|
||||||
|
|
||||||
|
if let success = loginResult as? ApiResultSuccess<AuthResponse>,
|
||||||
|
let response = success.data {
|
||||||
|
let isVerified = response.user.verified
|
||||||
|
|
||||||
|
// Initialize lookups
|
||||||
|
_ = try? await APILayer.shared.initializeLookups()
|
||||||
|
|
||||||
|
self.isLoading = false
|
||||||
|
|
||||||
|
// Call the login success callback
|
||||||
|
self.onLoginSuccess?(isVerified)
|
||||||
|
} else if let error = loginResult as? ApiResultError {
|
||||||
|
// Auto-login failed, fall back to manual login
|
||||||
|
print("Auto-login failed: \(error.message)")
|
||||||
|
self.isLoading = false
|
||||||
|
self.successMessage = "Password reset successfully! You can now log in with your new password."
|
||||||
|
self.currentStep = .success
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Auto-login failed, fall back to manual login
|
||||||
|
print("Auto-login error: \(error.localizedDescription)")
|
||||||
|
self.isLoading = false
|
||||||
|
self.successMessage = "Password reset successfully! You can now log in with your new password."
|
||||||
|
self.currentStep = .success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Navigate to next step
|
/// Navigate to next step
|
||||||
func moveToNextStep() {
|
func moveToNextStep() {
|
||||||
switch currentStep {
|
switch currentStep {
|
||||||
@@ -163,6 +213,8 @@ class PasswordResetViewModel: ObservableObject {
|
|||||||
case .verifyCode:
|
case .verifyCode:
|
||||||
currentStep = .resetPassword
|
currentStep = .resetPassword
|
||||||
case .resetPassword:
|
case .resetPassword:
|
||||||
|
currentStep = .loggingIn
|
||||||
|
case .loggingIn:
|
||||||
currentStep = .success
|
currentStep = .success
|
||||||
case .success:
|
case .success:
|
||||||
break
|
break
|
||||||
@@ -178,7 +230,7 @@ class PasswordResetViewModel: ObservableObject {
|
|||||||
currentStep = .requestCode
|
currentStep = .requestCode
|
||||||
case .resetPassword:
|
case .resetPassword:
|
||||||
currentStep = .verifyCode
|
currentStep = .verifyCode
|
||||||
case .success:
|
case .loggingIn, .success:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,8 +182,11 @@ struct ResetPasswordView: View {
|
|||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading || viewModel.currentStep == .loggingIn {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
|
.padding(.trailing, 8)
|
||||||
|
Text(viewModel.currentStep == .loggingIn ? "Logging in..." : "Resetting...")
|
||||||
|
.fontWeight(.semibold)
|
||||||
} else {
|
} else {
|
||||||
Label("Reset Password", systemImage: "lock.shield.fill")
|
Label("Reset Password", systemImage: "lock.shield.fill")
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
@@ -191,9 +194,9 @@ struct ResetPasswordView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disabled(!isFormValid || viewModel.isLoading)
|
.disabled(!isFormValid || viewModel.isLoading || viewModel.currentStep == .loggingIn)
|
||||||
|
|
||||||
// Return to Login Button (shown after success)
|
// Return to Login Button (shown only if auto-login fails)
|
||||||
if viewModel.currentStep == .success {
|
if viewModel.currentStep == .success {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
viewModel.reset()
|
viewModel.reset()
|
||||||
@@ -217,8 +220,8 @@ struct ResetPasswordView: View {
|
|||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarLeading) {
|
ToolbarItem(placement: .navigationBarLeading) {
|
||||||
// Only show back button if not from deep link
|
// Only show back button if not from deep link and not logging in
|
||||||
if viewModel.resetToken == nil || viewModel.currentStep != .resetPassword {
|
if (viewModel.resetToken == nil || viewModel.currentStep != .resetPassword) && viewModel.currentStep != .loggingIn {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if viewModel.currentStep == .success {
|
if viewModel.currentStep == .success {
|
||||||
viewModel.reset()
|
viewModel.reset()
|
||||||
|
|||||||
Reference in New Issue
Block a user