From 99228d03b52f3bf13fe194d32c9fb5b8221a9dfb Mon Sep 17 00:00:00 2001 From: Trey t Date: Sun, 9 Nov 2025 18:41:47 -0600 Subject: [PATCH] Update login and password reset UI across iOS and Android MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add email/username login support - Android: Update LoginScreen with email keyboard type - iOS: Update LoginView with email keyboard support - Refactor iOS password reset screens to use native SwiftUI components - Convert ForgotPasswordView to use Form with Sections - Convert VerifyResetCodeView to use Form with Sections - Convert ResetPasswordView to use Form with Sections - Use Label components for error/success messages - Add navigation titles and improve iOS-native appearance 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../example/mycrib/ui/screens/LoginScreen.kt | 11 +- iosApp/iosApp/Login/LoginView.swift | 3 +- .../PasswordReset/ForgotPasswordView.swift | 214 +++++------ .../PasswordReset/ResetPasswordView.swift | 350 ++++++++---------- .../PasswordReset/VerifyResetCodeView.swift | 192 +++++----- 5 files changed, 340 insertions(+), 430 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/LoginScreen.kt b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/LoginScreen.kt index c0f6d95..b64545a 100644 --- a/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/LoginScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/example/mycrib/ui/screens/LoginScreen.kt @@ -7,10 +7,13 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp @@ -84,13 +87,17 @@ fun LoginScreen( OutlinedTextField( value = username, onValueChange = { username = it }, - label = { Text("Username") }, + label = { Text("Username or Email") }, leadingIcon = { Icon(Icons.Default.Person, contentDescription = null) }, modifier = Modifier.fillMaxWidth(), singleLine = true, - shape = RoundedCornerShape(12.dp) + shape = RoundedCornerShape(12.dp), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ) ) OutlinedTextField( diff --git a/iosApp/iosApp/Login/LoginView.swift b/iosApp/iosApp/Login/LoginView.swift index 9d9cddf..98f59c8 100644 --- a/iosApp/iosApp/Login/LoginView.swift +++ b/iosApp/iosApp/Login/LoginView.swift @@ -41,9 +41,10 @@ struct LoginView: View { .listRowBackground(Color.clear) Section { - TextField("Username", text: $viewModel.username) + TextField("Username or Email", text: $viewModel.username) .textInputAutocapitalization(.never) .autocorrectionDisabled() + .keyboardType(.emailAddress) .focused($focusedField, equals: .username) .submitLabel(.next) .onSubmit { diff --git a/iosApp/iosApp/PasswordReset/ForgotPasswordView.swift b/iosApp/iosApp/PasswordReset/ForgotPasswordView.swift index 7cc772b..0bde637 100644 --- a/iosApp/iosApp/PasswordReset/ForgotPasswordView.swift +++ b/iosApp/iosApp/PasswordReset/ForgotPasswordView.swift @@ -7,150 +7,106 @@ struct ForgotPasswordView: View { var body: some View { NavigationView { - ZStack { - Color(.systemGroupedBackground) - .ignoresSafeArea() + Form { + // Header Section + Section { + VStack(spacing: 12) { + Image(systemName: "key.fill") + .font(.system(size: 60)) + .foregroundStyle(.blue.gradient) + .padding(.vertical) - ScrollView { - VStack(spacing: 24) { - Spacer().frame(height: 20) + Text("Forgot Password?") + .font(.title2) + .fontWeight(.bold) - // Header - VStack(spacing: 12) { - Image(systemName: "key.fill") - .font(.system(size: 60)) - .foregroundStyle(.blue.gradient) - .padding(.bottom, 8) - - Text("Forgot Password?") - .font(.title) - .fontWeight(.bold) - - Text("Enter your email address and we'll send you a code to reset your password") - .font(.subheadline) - .foregroundColor(.secondary) - .multilineTextAlignment(.center) - .padding(.horizontal) - } - - // Email Input - VStack(alignment: .leading, spacing: 12) { - Text("Email Address") - .font(.headline) - .padding(.horizontal) - - TextField("Enter your email", text: $viewModel.email) - .textInputAutocapitalization(.never) - .autocorrectionDisabled() - .keyboardType(.emailAddress) - .textFieldStyle(.roundedBorder) - .frame(height: 44) - .padding(.horizontal) - .focused($isEmailFocused) - .submitLabel(.go) - .onSubmit { - viewModel.requestPasswordReset() - } - .onChange(of: viewModel.email) { _, _ in - viewModel.clearError() - } - - Text("We'll send a 6-digit verification code to this address") - .font(.caption) - .foregroundColor(.secondary) - .padding(.horizontal) - } - - // Error Message - if let errorMessage = viewModel.errorMessage { - HStack(spacing: 12) { - Image(systemName: "exclamationmark.triangle.fill") - .foregroundColor(.red) - Text(errorMessage) - .foregroundColor(.red) - .font(.subheadline) - } - .padding() - .background(Color.red.opacity(0.1)) - .cornerRadius(12) - .padding(.horizontal) - } - - // Success Message - if let successMessage = viewModel.successMessage { - HStack(spacing: 12) { - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.green) - Text(successMessage) - .foregroundColor(.green) - .font(.subheadline) - } - .padding() - .background(Color.green.opacity(0.1)) - .cornerRadius(12) - .padding(.horizontal) - } - - // Send Code Button - Button(action: { - viewModel.requestPasswordReset() - }) { - HStack { - if viewModel.isLoading { - ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: .white)) - } else { - Image(systemName: "envelope.fill") - Text("Send Reset Code") - .fontWeight(.semibold) - } - } - .frame(maxWidth: .infinity) - .frame(height: 50) - .background( - !viewModel.email.isEmpty && !viewModel.isLoading - ? Color.blue - : Color.gray.opacity(0.3) - ) - .foregroundColor(.white) - .cornerRadius(12) - } - .disabled(viewModel.email.isEmpty || viewModel.isLoading) - .padding(.horizontal) - - Spacer().frame(height: 20) - - // Help Text - Text("Remember your password?") + Text("Enter your email address and we'll send you a verification code") .font(.subheadline) .foregroundColor(.secondary) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity) + .padding(.vertical) + } + .listRowBackground(Color.clear) - Button(action: { - dismiss() - }) { - Text("Back to Login") - .font(.subheadline) - .fontWeight(.semibold) + // Email Input Section + Section { + TextField("Email Address", text: $viewModel.email) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .keyboardType(.emailAddress) + .focused($isEmailFocused) + .submitLabel(.go) + .onSubmit { + viewModel.requestPasswordReset() + } + .onChange(of: viewModel.email) { _, _ in + viewModel.clearError() + } + } header: { + Text("Email") + } footer: { + Text("We'll send a 6-digit verification code to this address") + } + + // Error/Success Messages + if let errorMessage = viewModel.errorMessage { + Section { + Label { + Text(errorMessage) + .foregroundColor(.red) + } icon: { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.red) } } } - } - .navigationBarTitleDisplayMode(.inline) - .navigationBarBackButtonHidden(true) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { + + if let successMessage = viewModel.successMessage { + Section { + Label { + Text(successMessage) + .foregroundColor(.green) + } icon: { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + } + } + } + + // Send Code Button + Section { + Button(action: { + viewModel.requestPasswordReset() + }) { + HStack { + Spacer() + if viewModel.isLoading { + ProgressView() + } else { + Label("Send Reset Code", systemImage: "envelope.fill") + .fontWeight(.semibold) + } + Spacer() + } + } + .disabled(viewModel.email.isEmpty || viewModel.isLoading) + Button(action: { dismiss() }) { - HStack(spacing: 4) { - Image(systemName: "chevron.left") - .font(.system(size: 16)) - Text("Back") - .font(.subheadline) + HStack { + Spacer() + Text("Back to Login") + .foregroundColor(.secondary) + Spacer() } } } } + .navigationTitle("Reset Password") + .navigationBarTitleDisplayMode(.inline) .onAppear { isEmailFocused = true } diff --git a/iosApp/iosApp/PasswordReset/ResetPasswordView.swift b/iosApp/iosApp/PasswordReset/ResetPasswordView.swift index 956a5da..eff63ad 100644 --- a/iosApp/iosApp/PasswordReset/ResetPasswordView.swift +++ b/iosApp/iosApp/PasswordReset/ResetPasswordView.swift @@ -13,210 +13,180 @@ struct ResetPasswordView: View { } var body: some View { - ZStack { - Color(.systemGroupedBackground) - .ignoresSafeArea() - - ScrollView { - VStack(spacing: 24) { - Spacer().frame(height: 20) - - // Header + NavigationView { + Form { + // Header Section + Section { VStack(spacing: 12) { Image(systemName: "lock.rotation") .font(.system(size: 60)) .foregroundStyle(.blue.gradient) - .padding(.bottom, 8) + .padding(.vertical) Text("Set New Password") - .font(.title) + .font(.title2) .fontWeight(.bold) Text("Create a strong password to secure your account") .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) - .padding(.horizontal) } + .frame(maxWidth: .infinity) + .padding(.vertical) + } + .listRowBackground(Color.clear) - // Password Requirements - GroupBox { - VStack(alignment: .leading, spacing: 8) { - Text("Password Requirements") - .font(.subheadline) - .fontWeight(.semibold) - - HStack(spacing: 8) { - Image(systemName: viewModel.newPassword.count >= 8 ? "checkmark.circle.fill" : "circle") - .foregroundColor(viewModel.newPassword.count >= 8 ? .green : .secondary) - Text("At least 8 characters") - .font(.caption) - } - - HStack(spacing: 8) { - Image(systemName: hasLetter ? "checkmark.circle.fill" : "circle") - .foregroundColor(hasLetter ? .green : .secondary) - Text("Contains letters") - .font(.caption) - } - - HStack(spacing: 8) { - Image(systemName: hasNumber ? "checkmark.circle.fill" : "circle") - .foregroundColor(hasNumber ? .green : .secondary) - Text("Contains numbers") - .font(.caption) - } - - HStack(spacing: 8) { - Image(systemName: passwordsMatch ? "checkmark.circle.fill" : "circle") - .foregroundColor(passwordsMatch ? .green : .secondary) - Text("Passwords match") - .font(.caption) - } + // Password Requirements + Section { + VStack(alignment: .leading, spacing: 8) { + HStack(spacing: 8) { + Image(systemName: viewModel.newPassword.count >= 8 ? "checkmark.circle.fill" : "circle") + .foregroundColor(viewModel.newPassword.count >= 8 ? .green : .secondary) + Text("At least 8 characters") + .font(.caption) } - .padding(.vertical, 4) - } - .padding(.horizontal) - // New Password Input - VStack(alignment: .leading, spacing: 12) { - Text("New Password") - .font(.headline) - .padding(.horizontal) - - HStack { - if isNewPasswordVisible { - TextField("Enter new password", text: $viewModel.newPassword) - .textInputAutocapitalization(.never) - .autocorrectionDisabled() - .focused($focusedField, equals: .newPassword) - .submitLabel(.next) - .onSubmit { - focusedField = .confirmPassword - } - } else { - SecureField("Enter new password", text: $viewModel.newPassword) - .focused($focusedField, equals: .newPassword) - .submitLabel(.next) - .onSubmit { - focusedField = .confirmPassword - } - } - - Button(action: { - isNewPasswordVisible.toggle() - }) { - Image(systemName: isNewPasswordVisible ? "eye.slash.fill" : "eye.fill") - .foregroundColor(.secondary) - } - .buttonStyle(.plain) + HStack(spacing: 8) { + Image(systemName: hasLetter ? "checkmark.circle.fill" : "circle") + .foregroundColor(hasLetter ? .green : .secondary) + Text("Contains letters") + .font(.caption) } - .textFieldStyle(.roundedBorder) - .frame(height: 44) - .padding(.horizontal) - .onChange(of: viewModel.newPassword) { _, _ in - viewModel.clearError() + + HStack(spacing: 8) { + Image(systemName: hasNumber ? "checkmark.circle.fill" : "circle") + .foregroundColor(hasNumber ? .green : .secondary) + Text("Contains numbers") + .font(.caption) + } + + HStack(spacing: 8) { + Image(systemName: passwordsMatch ? "checkmark.circle.fill" : "circle") + .foregroundColor(passwordsMatch ? .green : .secondary) + Text("Passwords match") + .font(.caption) } } + } header: { + Text("Password Requirements") + } - // Confirm Password Input - VStack(alignment: .leading, spacing: 12) { - Text("Confirm Password") - .font(.headline) - .padding(.horizontal) - - HStack { - if isConfirmPasswordVisible { - TextField("Re-enter new password", text: $viewModel.confirmPassword) - .textInputAutocapitalization(.never) - .autocorrectionDisabled() - .focused($focusedField, equals: .confirmPassword) - .submitLabel(.go) - .onSubmit { - viewModel.resetPassword() - } - } else { - SecureField("Re-enter new password", text: $viewModel.confirmPassword) - .focused($focusedField, equals: .confirmPassword) - .submitLabel(.go) - .onSubmit { - viewModel.resetPassword() - } - } - - Button(action: { - isConfirmPasswordVisible.toggle() - }) { - Image(systemName: isConfirmPasswordVisible ? "eye.slash.fill" : "eye.fill") - .foregroundColor(.secondary) - } - .buttonStyle(.plain) + // New Password Input + Section { + HStack { + if isNewPasswordVisible { + TextField("Enter new password", text: $viewModel.newPassword) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .focused($focusedField, equals: .newPassword) + .submitLabel(.next) + .onSubmit { + focusedField = .confirmPassword + } + } else { + SecureField("Enter new password", text: $viewModel.newPassword) + .focused($focusedField, equals: .newPassword) + .submitLabel(.next) + .onSubmit { + focusedField = .confirmPassword + } } - .textFieldStyle(.roundedBorder) - .frame(height: 44) - .padding(.horizontal) - .onChange(of: viewModel.confirmPassword) { _, _ in - viewModel.clearError() + + Button(action: { + isNewPasswordVisible.toggle() + }) { + Image(systemName: isNewPasswordVisible ? "eye.slash.fill" : "eye.fill") + .foregroundColor(.secondary) } + .buttonStyle(.plain) } + .onChange(of: viewModel.newPassword) { _, _ in + viewModel.clearError() + } + } header: { + Text("New Password") + } - // Error Message - if let errorMessage = viewModel.errorMessage { - HStack(spacing: 12) { - Image(systemName: "exclamationmark.triangle.fill") - .foregroundColor(.red) + // Confirm Password Input + Section { + HStack { + if isConfirmPasswordVisible { + TextField("Re-enter new password", text: $viewModel.confirmPassword) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .focused($focusedField, equals: .confirmPassword) + .submitLabel(.go) + .onSubmit { + viewModel.resetPassword() + } + } else { + SecureField("Re-enter new password", text: $viewModel.confirmPassword) + .focused($focusedField, equals: .confirmPassword) + .submitLabel(.go) + .onSubmit { + viewModel.resetPassword() + } + } + + Button(action: { + isConfirmPasswordVisible.toggle() + }) { + Image(systemName: isConfirmPasswordVisible ? "eye.slash.fill" : "eye.fill") + .foregroundColor(.secondary) + } + .buttonStyle(.plain) + } + .onChange(of: viewModel.confirmPassword) { _, _ in + viewModel.clearError() + } + } header: { + Text("Confirm Password") + } + + // Error/Success Messages + if let errorMessage = viewModel.errorMessage { + Section { + Label { Text(errorMessage) .foregroundColor(.red) - .font(.subheadline) + } icon: { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.red) } - .padding() - .background(Color.red.opacity(0.1)) - .cornerRadius(12) - .padding(.horizontal) } + } - // Success Message - if let successMessage = viewModel.successMessage { - HStack(spacing: 12) { - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.green) + if let successMessage = viewModel.successMessage { + Section { + Label { Text(successMessage) .foregroundColor(.green) - .font(.subheadline) .multilineTextAlignment(.center) + } icon: { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) } - .padding() - .background(Color.green.opacity(0.1)) - .cornerRadius(12) - .padding(.horizontal) } + } - // Reset Password Button + // Reset Password Button + Section { Button(action: { viewModel.resetPassword() }) { HStack { + Spacer() if viewModel.isLoading { ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: .white)) } else { - Image(systemName: "lock.shield.fill") - Text("Reset Password") + Label("Reset Password", systemImage: "lock.shield.fill") .fontWeight(.semibold) } + Spacer() } - .frame(maxWidth: .infinity) - .frame(height: 50) - .background( - isFormValid && !viewModel.isLoading - ? Color.blue - : Color.gray.opacity(0.3) - ) - .foregroundColor(.white) - .cornerRadius(12) } .disabled(!isFormValid || viewModel.isLoading) - .padding(.horizontal) // Return to Login Button (shown after success) if viewModel.currentStep == .success { @@ -224,42 +194,44 @@ struct ResetPasswordView: View { viewModel.reset() onSuccess() }) { - Text("Return to Login") - .font(.subheadline) - .fontWeight(.semibold) - } - .padding(.top, 8) - } - - Spacer().frame(height: 20) - } - } - } - .navigationBarBackButtonHidden(true) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - // Only show back button if not from deep link - if viewModel.resetToken == nil || viewModel.currentStep != .resetPassword { - Button(action: { - if viewModel.currentStep == .success { - viewModel.reset() - onSuccess() - } else { - viewModel.moveToPreviousStep() - } - }) { - HStack(spacing: 4) { - Image(systemName: viewModel.currentStep == .success ? "xmark" : "chevron.left") - .font(.system(size: 16)) - Text(viewModel.currentStep == .success ? "Close" : "Back") - .font(.subheadline) + HStack { + Spacer() + Text("Return to Login") + .fontWeight(.semibold) + Spacer() + } } } } } - } - .onAppear { - focusedField = .newPassword + .navigationTitle("Reset Password") + .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(true) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + // Only show back button if not from deep link + if viewModel.resetToken == nil || viewModel.currentStep != .resetPassword { + Button(action: { + if viewModel.currentStep == .success { + viewModel.reset() + onSuccess() + } else { + viewModel.moveToPreviousStep() + } + }) { + HStack(spacing: 4) { + Image(systemName: viewModel.currentStep == .success ? "xmark" : "chevron.left") + .font(.system(size: 16)) + Text(viewModel.currentStep == .success ? "Close" : "Back") + .font(.subheadline) + } + } + } + } + } + .onAppear { + focusedField = .newPassword + } } } diff --git a/iosApp/iosApp/PasswordReset/VerifyResetCodeView.swift b/iosApp/iosApp/PasswordReset/VerifyResetCodeView.swift index 131d8e3..d46818d 100644 --- a/iosApp/iosApp/PasswordReset/VerifyResetCodeView.swift +++ b/iosApp/iosApp/PasswordReset/VerifyResetCodeView.swift @@ -6,23 +6,18 @@ struct VerifyResetCodeView: View { @Environment(\.dismiss) var dismiss var body: some View { - ZStack { - Color(.systemGroupedBackground) - .ignoresSafeArea() - - ScrollView { - VStack(spacing: 24) { - Spacer().frame(height: 20) - - // Header + NavigationView { + Form { + // Header Section + Section { VStack(spacing: 12) { Image(systemName: "envelope.badge.fill") .font(.system(size: 60)) .foregroundStyle(.blue.gradient) - .padding(.bottom, 8) + .padding(.vertical) Text("Check Your Email") - .font(.title) + .font(.title2) .fontWeight(.bold) Text("We sent a 6-digit code to") @@ -33,115 +28,91 @@ struct VerifyResetCodeView: View { .font(.subheadline) .fontWeight(.semibold) .foregroundColor(.primary) - .padding(.horizontal) } + .frame(maxWidth: .infinity) + .padding(.vertical) + } + .listRowBackground(Color.clear) - // Info Card - GroupBox { - HStack(spacing: 12) { - Image(systemName: "clock.fill") - .foregroundColor(.orange) - .font(.title2) - - Text("Code expires in 15 minutes") - .font(.subheadline) - .foregroundColor(.primary) - .fontWeight(.semibold) - } - .padding(.vertical, 4) + // Info Section + Section { + Label { + Text("Code expires in 15 minutes") + .fontWeight(.semibold) + } icon: { + Image(systemName: "clock.fill") + .foregroundColor(.orange) } - .padding(.horizontal) + } - // Code Input - VStack(alignment: .leading, spacing: 12) { - Text("Verification Code") - .font(.headline) - .padding(.horizontal) - - TextField("000000", text: $viewModel.code) - .font(.system(size: 32, weight: .semibold, design: .rounded)) - .multilineTextAlignment(.center) - .keyboardType(.numberPad) - .textFieldStyle(.roundedBorder) - .frame(height: 60) - .padding(.horizontal) - .focused($isCodeFocused) - .onChange(of: viewModel.code) { _, newValue in - // Limit to 6 digits - if newValue.count > 6 { - viewModel.code = String(newValue.prefix(6)) - } - // Only allow numbers - viewModel.code = newValue.filter { $0.isNumber } - viewModel.clearError() + // Code Input Section + Section { + TextField("000000", text: $viewModel.code) + .font(.system(size: 32, weight: .semibold, design: .rounded)) + .multilineTextAlignment(.center) + .keyboardType(.numberPad) + .focused($isCodeFocused) + .onChange(of: viewModel.code) { _, newValue in + // Limit to 6 digits + if newValue.count > 6 { + viewModel.code = String(newValue.prefix(6)) } + // Only allow numbers + viewModel.code = newValue.filter { $0.isNumber } + viewModel.clearError() + } + } header: { + Text("Verification Code") + } footer: { + Text("Enter the 6-digit code from your email") + } - Text("Enter the 6-digit code from your email") - .font(.caption) - .foregroundColor(.secondary) - .padding(.horizontal) - } - - // Error Message - if let errorMessage = viewModel.errorMessage { - HStack(spacing: 12) { - Image(systemName: "exclamationmark.triangle.fill") - .foregroundColor(.red) + // Error/Success Messages + if let errorMessage = viewModel.errorMessage { + Section { + Label { Text(errorMessage) .foregroundColor(.red) - .font(.subheadline) + } icon: { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.red) } - .padding() - .background(Color.red.opacity(0.1)) - .cornerRadius(12) - .padding(.horizontal) } + } - // Success Message - if let successMessage = viewModel.successMessage { - HStack(spacing: 12) { - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.green) + if let successMessage = viewModel.successMessage { + Section { + Label { Text(successMessage) .foregroundColor(.green) - .font(.subheadline) + } icon: { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) } - .padding() - .background(Color.green.opacity(0.1)) - .cornerRadius(12) - .padding(.horizontal) } + } - // Verify Button + // Verify Button + Section { Button(action: { viewModel.verifyResetCode() }) { HStack { + Spacer() if viewModel.isLoading { ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: .white)) } else { - Image(systemName: "checkmark.shield.fill") - Text("Verify Code") + Label("Verify Code", systemImage: "checkmark.shield.fill") .fontWeight(.semibold) } + Spacer() } - .frame(maxWidth: .infinity) - .frame(height: 50) - .background( - viewModel.code.count == 6 && !viewModel.isLoading - ? Color.blue - : Color.gray.opacity(0.3) - ) - .foregroundColor(.white) - .cornerRadius(12) } .disabled(viewModel.code.count != 6 || viewModel.isLoading) - .padding(.horizontal) + } - Spacer().frame(height: 20) - - // Help Section + // Help Section + Section { VStack(spacing: 12) { Text("Didn't receive the code?") .font(.subheadline) @@ -162,29 +133,32 @@ struct VerifyResetCodeView: View { .font(.caption) .foregroundColor(.secondary) .multilineTextAlignment(.center) - .padding(.horizontal, 32) + } + .frame(maxWidth: .infinity) + } + .listRowBackground(Color.clear) + } + .navigationTitle("Verify Code") + .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(true) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button(action: { + viewModel.moveToPreviousStep() + }) { + HStack(spacing: 4) { + Image(systemName: "chevron.left") + .font(.system(size: 16)) + Text("Back") + .font(.subheadline) + } } } } - } - .navigationBarBackButtonHidden(true) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button(action: { - viewModel.moveToPreviousStep() - }) { - HStack(spacing: 4) { - Image(systemName: "chevron.left") - .font(.system(size: 16)) - Text("Back") - .font(.subheadline) - } - } + .onAppear { + isCodeFocused = true } } - .onAppear { - isCodeFocused = true - } } }