feat(cards): implement full saved card management and transparent checkout
- ApiCardModels.swift: SavedCard, SaveCardPayload, CreditCardOrderPayload, etc. - ApiOrderModels.swift: add savedCardId, clientCpfCnpj, creditCard, creditCardHolderInfo to CreateOrderPayload; Codable for item/addon payloads - ApiService.swift: listCards, saveCard, updateCard, deleteCard, updateProfileCpf - AppState.ProfileState: add cpf field - CheckoutView: CardSelectionSheet (list saved cards + add new), rewrite PaymentCardView (submits order with card data), CardPaymentContext carries full order data - CheckoutView+Logic: buildCreateOrderPayload accepts card params, new loadSavedCards/confirmOrderWithSavedCard/handleOrderResponse helpers, redirect credit card confirm to CardSelectionSheet - OrdersView: CREDIT_CARD pending orders go to tracking (not card form, since card is now submitted with order) - UserProfileView: CPF field with mask, save via PATCH /api/customer/profile
This commit is contained in:
@@ -11,6 +11,7 @@ struct UserProfileView: View {
|
||||
@State var name: String = ""
|
||||
@State var email: String = ""
|
||||
@State var phone: String = ""
|
||||
@State var cpf: String = ""
|
||||
@State var profilePicture: String = ""
|
||||
@State var isSaving = false
|
||||
|
||||
@@ -97,6 +98,14 @@ struct UserProfileView: View {
|
||||
phone = masked
|
||||
}
|
||||
}
|
||||
|
||||
textFieldSection(title: "CPF", placeholder: "000.000.000-00", text: $cpf)
|
||||
.keyboardType(.numberPad)
|
||||
.onChange(of: cpf) { _, newValue in
|
||||
let digits = newValue.filter(\.isNumber)
|
||||
let masked = formatCPF(digits)
|
||||
if masked != newValue { cpf = masked }
|
||||
}
|
||||
.appNoAutoCap()
|
||||
}
|
||||
.padding(16)
|
||||
@@ -148,6 +157,15 @@ struct UserProfileView: View {
|
||||
email = appState.profile.email
|
||||
phone = formatPhoneForDisplay(appState.profile.phone)
|
||||
profilePicture = appState.profile.profilePicture
|
||||
cpf = formatCPF(appState.profile.cpf.filter(\.isNumber))
|
||||
}
|
||||
|
||||
private func formatCPF(_ digits: String) -> String {
|
||||
let d = String(digits.prefix(11))
|
||||
if d.count <= 3 { return d }
|
||||
if d.count <= 6 { return "\(d.prefix(3)).\(d.dropFirst(3))" }
|
||||
if d.count <= 9 { return "\(d.prefix(3)).\(d.dropFirst(3).prefix(3)).\(d.dropFirst(6))" }
|
||||
return "\(d.prefix(3)).\(d.dropFirst(3).prefix(3)).\(d.dropFirst(6).prefix(3))-\(d.dropFirst(9))"
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@@ -186,6 +204,14 @@ struct UserProfileView: View {
|
||||
appState.profile.email = customer?.email ?? cleanEmail
|
||||
appState.profile.phone = customer?.phoneNumber ?? normalizedPhone
|
||||
appState.profile.profilePicture = customer?.profilePicture ?? cleanPhoto
|
||||
|
||||
let cleanCpf = cpf.filter(\.isNumber)
|
||||
if cleanCpf.count == 11 {
|
||||
appState.profile.cpf = cleanCpf
|
||||
Task<Void, Never> {
|
||||
do { _ = try await ApiService().updateProfileCpf(cpf: cleanCpf) } catch {}
|
||||
}
|
||||
}
|
||||
SessionStateStore.setActiveUserKey(
|
||||
SessionStateStore.makeUserKey(profileId: appState.profile.id, email: appState.profile.email)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user