Repository navigation
Conversation
…creens Add EnterPhoneNumberScreen and EnterVerificationCodeScreen, generic over content that receives a PhoneNumberContentState or VerificationCodeContentState. The screens own the input state and the send, verify, resend and change-number actions; the content only draws. DefaultEnterPhoneNumberContent and DefaultEnterVerificationCodeContent hold the stock markup, and the existing views render them through the new screens. The verification code screen adds a resend button with a 60 second countdown. Verifying now goes through signIn(credentials:), so an MFA challenge reaches the resolution screen instead of being dropped. isLoading also covers sending the code, which previously showed no progress and allowed repeated taps.
There was a problem hiding this comment.
Code Review
This pull request refactors the phone authentication views by separating state management and business logic from the UI layout. It introduces EnterPhoneNumberScreen and EnterVerificationCodeScreen to handle state and actions, delegating rendering to customizable content views like DefaultEnterPhoneNumberContent and DefaultEnterVerificationCodeContent. A critical bug was identified in EnterVerificationCodeScreen where the navigation stack is cleared when multi-factor authentication is required but no MFA handler is configured.
| if case let .mfaRequired(mfaInfo) = outcome, let onMFA = mfaHandler { | ||
| onMFA(mfaInfo) | ||
| return | ||
| } | ||
| authService.navigator.clear() |
There was a problem hiding this comment.
If outcome is .mfaRequired but mfaHandler is nil, the code currently falls through and calls authService.navigator.clear(). This is a bug because the user is not actually signed in yet (MFA is still required), but the navigation stack is cleared as if sign-in succeeded. We should handle this case by reporting an error and keeping the user on the verification screen.
| if case let .mfaRequired(mfaInfo) = outcome, let onMFA = mfaHandler { | |
| onMFA(mfaInfo) | |
| return | |
| } | |
| authService.navigator.clear() | |
| switch outcome { | |
| case let .mfaRequired(mfaInfo): | |
| if let onMFA = mfaHandler { | |
| onMFA(mfaInfo) | |
| } else { | |
| let error = AuthServiceError.multiFactorAuth("Multi-factor authentication is required, but no MFA handler is configured.") | |
| errorMessage = error.localizedDescription | |
| reportError?(error) | |
| } | |
| case .signedIn: | |
| authService.navigator.clear() | |
| } |
Set the in-flight flag before starting each action's Task, so a quick second tap can't send two codes or overlap verify and resend. Only clear the navigator on .signedIn, so an MFA challenge with no handler doesn't drop the user back to the picker. Type the state closures as @mainactor and give both state structs public initializers, matching AuthMethodPickerState. Clear errorMessage when the input changes, document every field, disable Change number while loading, and show the resend countdown as its own text instead of a concatenated string.
Adds public
EnterPhoneNumberScreenandEnterVerificationCodeScreen, so apps can draw the phone sign-in screens themselves while the library keeps sending codes, signing in, navigating and reporting errors. Stacked on #1411.Changes
PhoneNumberContentStateorVerificationCodeContentState:Bindings for input, closures for actions, plusisValid,isLoadingand a localizederrorMessage.DefaultEnterPhoneNumberContentandDefaultEnterVerificationCodeContenthold the stock markup, and the existing internal views now render them through the screens.signIn(credentials:), so phone sign-in with MFA reaches the resolution screen instead of dropping the challenge.isLoadingnow covers sending the code, which previously showed no progress and allowed repeated taps.API Usage
Preview
errorMessageMaintainer note: Fixes internal CPRN-518