RTSDA-iOS/Views/ContactFormView.swift
2025-02-03 16:15:57 -05:00

210 lines
7.5 KiB
Swift

import SwiftUI
struct ContactFormView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var viewModel = ContactFormViewModel()
@FocusState private var focusedField: Field?
var isModal: Bool = false
enum Field {
case firstName, lastName, email, phone, message
}
var body: some View {
NavigationStack {
Form {
Section {
Text("Use this form to get in touch with us for any reason - whether you have questions, need prayer, want to request Bible studies, learn more about our church, or would like to connect with our pastoral team.")
.foregroundColor(.secondary)
}
Section {
TextField("First Name (Required)", text: $viewModel.firstName)
.focused($focusedField, equals: .firstName)
.textContentType(.givenName)
TextField("Last Name (Required)", text: $viewModel.lastName)
.focused($focusedField, equals: .lastName)
.textContentType(.familyName)
TextField("Email (Required)", text: $viewModel.email)
.focused($focusedField, equals: .email)
.keyboardType(.emailAddress)
.textContentType(.emailAddress)
.autocapitalization(.none)
if !viewModel.email.isEmpty && !viewModel.isValidEmail(viewModel.email) {
Text("Please enter a valid email address")
.foregroundColor(.red)
}
TextField("Phone", text: $viewModel.phone)
.focused($focusedField, equals: .phone)
.keyboardType(.phonePad)
.textContentType(.telephoneNumber)
.onChange(of: viewModel.phone) { oldValue, newValue in
viewModel.phone = viewModel.formatPhoneNumber(newValue)
}
if !viewModel.phone.isEmpty && !viewModel.isValidPhone(viewModel.phone) {
Text("Please enter a valid phone number")
.foregroundColor(.red)
}
}
Section(header: Text("Message (Required)")) {
TextEditor(text: $viewModel.message)
.focused($focusedField, equals: .message)
.frame(minHeight: 100)
}
Section {
Button(action: {
Task {
focusedField = nil // Dismiss keyboard
await viewModel.submit()
}
}) {
HStack {
Spacer()
Text("Submit")
Spacer()
}
}
.disabled(!viewModel.isValid || viewModel.isSubmitting)
}
}
.navigationTitle("Contact Us")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
if isModal {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Done") {
focusedField = nil
dismiss()
}
}
}
}
.alert("Error", isPresented: .constant(viewModel.error != nil)) {
Button("OK") {
viewModel.error = nil
}
} message: {
Text(viewModel.error ?? "")
}
.alert("Success", isPresented: $viewModel.isSubmitted) {
Button("OK") {
viewModel.reset()
}
} message: {
Text("Thank you for your message! We'll get back to you soon.")
}
}
}
}
class ContactFormViewModel: ObservableObject {
@Published var firstName = ""
@Published var lastName = ""
@Published var email = ""
@Published var phone = ""
@Published var message = ""
@Published var error: String?
@Published var isSubmitting = false
@Published var isSubmitted = false
var isValid: Bool {
!firstName.isEmpty &&
!lastName.isEmpty &&
!email.isEmpty &&
isValidEmail(email) &&
!message.isEmpty &&
(phone.isEmpty || isValidPhone(phone))
}
func reset() {
// Reset all fields
firstName = ""
lastName = ""
email = ""
phone = ""
message = ""
// Reset state
error = nil
isSubmitting = false
isSubmitted = false
}
func formatPhoneNumber(_ value: String) -> String {
// Remove all non-digits
let digits = value.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
// Format the number
if digits.isEmpty {
return ""
} else if digits.count <= 3 {
return "(\(digits))"
} else if digits.count <= 6 {
return "(\(digits.prefix(3))) \(digits.dropFirst(3))"
} else {
let areaCode = digits.prefix(3)
let middle = digits.dropFirst(3).prefix(3)
let last = digits.dropFirst(6).prefix(4)
return "(\(areaCode)) \(middle)-\(last)"
}
}
func isValidEmail(_ email: String) -> Bool {
let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegex)
return emailPredicate.evaluate(with: email)
}
func isValidPhone(_ phone: String) -> Bool {
let phoneRegex = "^\\([0-9]{3}\\) [0-9]{3}-[0-9]{4}$"
let phonePredicate = NSPredicate(format:"SELF MATCHES %@", phoneRegex)
return phonePredicate.evaluate(with: phone)
}
@MainActor
func submit() async {
guard isValid else { return }
isSubmitting = true
error = nil
do {
let url = URL(string: "https://contact.rockvilletollandsda.church/api/contact")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let payload = [
"first_name": firstName,
"last_name": lastName,
"email": email,
"phone": phone,
"message": message
]
request.httpBody = try JSONSerialization.data(withJSONObject: payload)
let (_, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw URLError(.badServerResponse)
}
isSubmitted = true
reset()
} catch {
self.error = "There was an error submitting your message. Please try again."
print("Error submitting form:", error)
reset()
}
isSubmitting = false
}
}