- Fix RegisterView to call AuthenticationManager.login() after email verification so user is properly transitioned to home screen instead of returning to login - Fix ResidencesListView to load data when authentication state becomes true, ensuring residences load after registration/login - Add accessibility identifier to verification code field for UI testing - Add NSAppTransportSecurity exceptions for localhost/127.0.0.1 for local dev - Add comprehensive XCUITest suite for registration flow including: - Form validation tests (empty fields, invalid email, mismatched passwords) - Full registration and verification flow test - Logout from verification screen test - Helper scripts for test user cleanup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
163 lines
6.6 KiB
Swift
163 lines
6.6 KiB
Swift
import SwiftUI
|
|
import ComposeApp
|
|
|
|
struct RegisterView: View {
|
|
@StateObject private var viewModel = RegisterViewModel()
|
|
@Environment(\.dismiss) var dismiss
|
|
@FocusState private var focusedField: Field?
|
|
@State private var showVerifyEmail = false
|
|
|
|
enum Field {
|
|
case username, email, password, confirmPassword
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationView {
|
|
Form {
|
|
Section {
|
|
VStack(spacing: 16) {
|
|
Image(systemName: "person.badge.plus")
|
|
.font(.system(size: 60))
|
|
.foregroundStyle(Color.appPrimary.gradient)
|
|
|
|
Text("Join MyCrib")
|
|
.font(.largeTitle)
|
|
.fontWeight(.bold)
|
|
|
|
Text("Start managing your properties today")
|
|
.font(.subheadline)
|
|
.foregroundColor(Color.appTextSecondary)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical)
|
|
}
|
|
.listRowBackground(Color.clear)
|
|
|
|
Section {
|
|
TextField("Username", text: $viewModel.username)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
.textContentType(.username)
|
|
.focused($focusedField, equals: .username)
|
|
.submitLabel(.next)
|
|
.onSubmit {
|
|
focusedField = .email
|
|
}
|
|
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.registerUsernameField)
|
|
|
|
TextField("Email", text: $viewModel.email)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
.keyboardType(.emailAddress)
|
|
.textContentType(.emailAddress)
|
|
.focused($focusedField, equals: .email)
|
|
.submitLabel(.next)
|
|
.onSubmit {
|
|
focusedField = .password
|
|
}
|
|
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.registerEmailField)
|
|
} header: {
|
|
Text("Account Information")
|
|
}
|
|
.listRowBackground(Color.appBackgroundSecondary)
|
|
|
|
Section {
|
|
// Using .newPassword enables iOS Strong Password generation
|
|
// iOS will automatically offer to save to iCloud Keychain after successful registration
|
|
SecureField("Password", text: $viewModel.password)
|
|
.textContentType(.newPassword)
|
|
.focused($focusedField, equals: .password)
|
|
.submitLabel(.next)
|
|
.onSubmit {
|
|
focusedField = .confirmPassword
|
|
}
|
|
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.registerPasswordField)
|
|
|
|
SecureField("Confirm Password", text: $viewModel.confirmPassword)
|
|
.textContentType(.newPassword)
|
|
.focused($focusedField, equals: .confirmPassword)
|
|
.submitLabel(.go)
|
|
.onSubmit {
|
|
viewModel.register()
|
|
}
|
|
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.registerConfirmPasswordField)
|
|
} header: {
|
|
Text("Security")
|
|
} footer: {
|
|
Text("Tap the password field for a strong password suggestion")
|
|
}
|
|
.listRowBackground(Color.appBackgroundSecondary)
|
|
|
|
if let errorMessage = viewModel.errorMessage {
|
|
Section {
|
|
HStack {
|
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
.foregroundColor(Color.appError)
|
|
Text(errorMessage)
|
|
.foregroundColor(Color.appError)
|
|
.font(.subheadline)
|
|
}
|
|
}
|
|
.listRowBackground(Color.appBackgroundSecondary)
|
|
}
|
|
|
|
Section {
|
|
Button(action: viewModel.register) {
|
|
HStack {
|
|
Spacer()
|
|
if viewModel.isLoading {
|
|
ProgressView()
|
|
} else {
|
|
Text("Create Account")
|
|
.fontWeight(.semibold)
|
|
}
|
|
Spacer()
|
|
}
|
|
}
|
|
.disabled(viewModel.isLoading)
|
|
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.registerButton)
|
|
}
|
|
.listRowBackground(Color.appBackgroundSecondary)
|
|
}
|
|
.listStyle(.plain)
|
|
.scrollContentBackground(.hidden)
|
|
.background(Color.appBackgroundPrimary)
|
|
.navigationTitle("Create Account")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarLeading) {
|
|
Button("Cancel") {
|
|
dismiss()
|
|
}
|
|
.accessibilityIdentifier(AccessibilityIdentifiers.Authentication.registerCancelButton)
|
|
}
|
|
}
|
|
.fullScreenCover(isPresented: $viewModel.isRegistered) {
|
|
VerifyEmailView(
|
|
onVerifySuccess: {
|
|
// User has verified their email - mark as authenticated
|
|
// This will update RootView to show the main app
|
|
AuthenticationManager.shared.login()
|
|
showVerifyEmail = false
|
|
dismiss()
|
|
},
|
|
onLogout: {
|
|
// Logout and return to login screen
|
|
TokenStorage.shared.clearToken()
|
|
DataCache.shared.clearLookups()
|
|
dismiss()
|
|
}
|
|
)
|
|
}
|
|
.handleErrors(
|
|
error: viewModel.errorMessage,
|
|
onRetry: { viewModel.register() }
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
RegisterView()
|
|
}
|