- Created ErrorMessageParser utility for both iOS (Swift) and Android (Kotlin) - Parser detects JSON-formatted error messages and extracts user-friendly text - Identifies when data objects (not errors) are returned and provides generic messages - Updated all API error handling to pass raw error bodies instead of concatenating - Applied ErrorMessageParser across all ViewModels and screens on both platforms - Fixed ContractorApi and DocumentApi to not concatenate error bodies with messages - Updated ApiResultHandler to automatically parse all error messages - Error messages now show "Request failed. Please check your input and try again." instead of raw JSON 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
141 lines
4.9 KiB
Swift
141 lines
4.9 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(.blue.gradient)
|
|
|
|
Text("Join MyCrib")
|
|
.font(.largeTitle)
|
|
.fontWeight(.bold)
|
|
|
|
Text("Start managing your properties today")
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical)
|
|
}
|
|
.listRowBackground(Color.clear)
|
|
|
|
Section {
|
|
TextField("Username", text: $viewModel.username)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
.focused($focusedField, equals: .username)
|
|
.submitLabel(.next)
|
|
.onSubmit {
|
|
focusedField = .email
|
|
}
|
|
|
|
TextField("Email", text: $viewModel.email)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
.keyboardType(.emailAddress)
|
|
.focused($focusedField, equals: .email)
|
|
.submitLabel(.next)
|
|
.onSubmit {
|
|
focusedField = .password
|
|
}
|
|
} header: {
|
|
Text("Account Information")
|
|
}
|
|
|
|
Section {
|
|
SecureField("Password", text: $viewModel.password)
|
|
.focused($focusedField, equals: .password)
|
|
.submitLabel(.next)
|
|
.onSubmit {
|
|
focusedField = .confirmPassword
|
|
}
|
|
|
|
SecureField("Confirm Password", text: $viewModel.confirmPassword)
|
|
.focused($focusedField, equals: .confirmPassword)
|
|
.submitLabel(.go)
|
|
.onSubmit {
|
|
viewModel.register()
|
|
}
|
|
} header: {
|
|
Text("Security")
|
|
} footer: {
|
|
Text("Password must be secure")
|
|
}
|
|
|
|
if let errorMessage = viewModel.errorMessage {
|
|
Section {
|
|
HStack {
|
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
.foregroundColor(.red)
|
|
Text(errorMessage)
|
|
.foregroundColor(.red)
|
|
.font(.subheadline)
|
|
}
|
|
}
|
|
}
|
|
|
|
Section {
|
|
Button(action: viewModel.register) {
|
|
HStack {
|
|
Spacer()
|
|
if viewModel.isLoading {
|
|
ProgressView()
|
|
} else {
|
|
Text("Create Account")
|
|
.fontWeight(.semibold)
|
|
}
|
|
Spacer()
|
|
}
|
|
}
|
|
.disabled(viewModel.isLoading)
|
|
}
|
|
}
|
|
.navigationTitle("Create Account")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarLeading) {
|
|
Button("Cancel") {
|
|
dismiss()
|
|
}
|
|
}
|
|
}
|
|
.fullScreenCover(isPresented: $viewModel.isRegistered) {
|
|
VerifyEmailView(
|
|
onVerifySuccess: {
|
|
dismiss()
|
|
showVerifyEmail = false
|
|
},
|
|
onLogout: {
|
|
// Logout and return to login screen
|
|
TokenStorage.shared.clearToken()
|
|
DataCache.shared.clearLookups()
|
|
dismiss()
|
|
}
|
|
)
|
|
}
|
|
.handleErrors(
|
|
error: viewModel.errorMessage,
|
|
onRetry: { viewModel.register() }
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
RegisterView()
|
|
}
|