@@ -106,6 +106,28 @@ struct LoginSlide: View {
106106 Text ( viewModel. loginStatus)
107107 . font ( . subheadline)
108108 . foregroundStyle ( . secondary)
109+
110+ // 2FA Input (shown when needed, below progress)
111+ if viewModel. loginViewModel. needVerificationCode {
112+ TextField ( " Verification Code " , text: $viewModel. loginViewModel. verificationCode)
113+ . textFieldStyle ( . roundedBorder)
114+ . autocapitalization ( . none)
115+ . keyboardType ( . numberPad)
116+ . padding ( . top)
117+
118+ Button ( action: {
119+ submitTwoFactorCode ( )
120+ } ) {
121+ Text ( " Verify " )
122+ . font ( . headline)
123+ . foregroundStyle ( . white)
124+ . frame ( maxWidth: . infinity)
125+ . padding ( )
126+ . background ( . blue. gradient)
127+ . cornerRadius ( 15 )
128+ }
129+ . disabled ( viewModel. loginViewModel. isVerificationCodeSubmitting)
130+ }
109131 }
110132 . padding ( )
111133 . background ( Color ( UIColor . secondarySystemGroupedBackground) )
@@ -220,16 +242,44 @@ struct LoginSlide: View {
220242 isLoggingIn = false
221243 viewModel. nextStep ( )
222244 }
245+ } catch is CancellationError {
246+ await MainActor . run {
247+ isLoggingIn = false
248+ }
223249 } catch {
224250 await MainActor . run {
225- viewModel. errorMessage = error. localizedDescription
226- viewModel. showError = true
251+ // If 2FA is needed, don't show error - the 2FA input will be shown
252+ if !viewModel. loginViewModel. needVerificationCode {
253+ viewModel. errorMessage = error. localizedDescription
254+ viewModel. showError = true
255+ }
227256 isLoggingIn = false
228257 }
229258 }
230259 }
231260 }
232261
262+ private func submitTwoFactorCode( ) {
263+ viewModel. loginViewModel. submitVerificationCode ( )
264+
265+ // Wait for authentication to complete
266+ Task {
267+ try ? await Task . sleep ( nanoseconds: 2_000_000_000 ) // 2 seconds
268+
269+ await MainActor . run {
270+ if DataManager . shared. model. session != nil {
271+ // Authentication succeeded
272+ isLoggingIn = false
273+ viewModel. nextStep ( )
274+ } else {
275+ // Authentication failed
276+ viewModel. errorMessage = " Failed to verify 2FA code "
277+ viewModel. showError = true
278+ }
279+ }
280+ }
281+ }
282+
233283 private func handleSideStoreImport( url: URL ) {
234284 guard url. startAccessingSecurityScopedResource ( ) else {
235285 viewModel. errorMessage = " Could not access the file "
@@ -273,75 +323,6 @@ struct LoginSlide: View {
273323 }
274324}
275325
276- // MARK: - 2FA Slide
277- struct TwoFactorSlide : View {
278- @ObservedObject var viewModel : WizardViewModel
279- @State private var twoFactorCode : String = " "
280-
281- var body : some View {
282- VStack ( spacing: 30 ) {
283- Spacer ( )
284-
285- Image ( systemName: " lock.shield.fill " )
286- . resizable ( )
287- . frame ( width: 80 , height: 80 )
288- . foregroundStyle ( . blue. gradient)
289-
290- VStack ( spacing: 10 ) {
291- Text ( " Enter 2FA Code " )
292- . font ( . system( size: 28 , weight: . bold) )
293- . foregroundStyle ( . primary)
294-
295- Text ( " Enter the verification code sent to your trusted devices " )
296- . font ( . subheadline)
297- . foregroundStyle ( . secondary)
298- . multilineTextAlignment ( . center)
299- . padding ( . horizontal)
300- }
301-
302- TextField ( " 6-digit code " , text: $twoFactorCode)
303- . textFieldStyle ( . roundedBorder)
304- . keyboardType ( . numberPad)
305- . multilineTextAlignment ( . center)
306- . font ( . system( size: 24 , weight: . bold, design: . monospaced) )
307- . padding ( . horizontal, 40 )
308-
309- Button ( action: {
310- submitTwoFactorCode ( )
311- } ) {
312- Text ( " Verify " )
313- . font ( . headline)
314- . foregroundStyle ( . white)
315- . frame ( maxWidth: . infinity)
316- . padding ( )
317- . background ( . blue. gradient)
318- . cornerRadius ( 15 )
319- }
320- . padding ( . horizontal, 40 )
321- . disabled ( twoFactorCode. count != 6 )
322-
323- Spacer ( )
324- }
325- . background ( Color ( UIColor . systemGroupedBackground) )
326- }
327-
328- private func submitTwoFactorCode( ) {
329- Task {
330- do {
331- try await viewModel. loginViewModel. verifyTwoFactorCode ( twoFactorCode)
332- await MainActor . run {
333- viewModel. nextStep ( )
334- }
335- } catch {
336- await MainActor . run {
337- viewModel. errorMessage = error. localizedDescription
338- viewModel. showError = true
339- }
340- }
341- }
342- }
343- }
344-
345326// MARK: - Apps List Slide
346327struct AppsListSlide : View {
347328 @ObservedObject var viewModel : WizardViewModel
@@ -631,9 +612,6 @@ struct WizardView: View {
631612 LoginSlide ( viewModel: viewModel)
632613 . tag ( WizardStep . login)
633614
634- TwoFactorSlide ( viewModel: viewModel)
635- . tag ( WizardStep . twoFactor)
636-
637615 AppsListSlide ( viewModel: viewModel)
638616 . tag ( WizardStep . apps)
639617
0 commit comments