feat(cards): add input masks and char limits to card form fields
- Card number: groups of 4 digits (0000 0000 0000 0000), max 16 digits - Expiry: MM/AAAA mask, max 6 digits - CVV: digits only, max 4 - CPF: 000.000.000-00 mask, max 11 digits - canSubmit validates stripped digit counts
This commit is contained in:
@@ -997,10 +997,10 @@ struct PaymentCardView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var canSubmit: Bool {
|
var canSubmit: Bool {
|
||||||
holderName.isEmpty == false &&
|
holderName.trimmingCharacters(in: .whitespaces).isEmpty == false &&
|
||||||
cardNumber.filter(\.isNumber).count >= 13 &&
|
cardNumber.filter(\.isNumber).count >= 13 &&
|
||||||
expiry.count >= 4 &&
|
expiry.filter(\.isNumber).count == 6 &&
|
||||||
cvv.count >= 3 &&
|
cvv.filter(\.isNumber).count >= 3 &&
|
||||||
cpf.filter(\.isNumber).count == 11
|
cpf.filter(\.isNumber).count == 11
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1051,15 +1051,49 @@ struct PaymentCardView: View {
|
|||||||
VStack(spacing: 10) {
|
VStack(spacing: 10) {
|
||||||
labeledField("Número do Cartão", placeholder: "0000 0000 0000 0000", text: $cardNumber)
|
labeledField("Número do Cartão", placeholder: "0000 0000 0000 0000", text: $cardNumber)
|
||||||
.keyboardType(.numberPad)
|
.keyboardType(.numberPad)
|
||||||
|
.onChange(of: cardNumber) { _, v in
|
||||||
|
let d = String(v.filter(\.isNumber).prefix(16))
|
||||||
|
let masked = stride(from: 0, to: d.count, by: 4)
|
||||||
|
.map { i -> String in
|
||||||
|
let start = d.index(d.startIndex, offsetBy: i)
|
||||||
|
let end = d.index(start, offsetBy: min(4, d.count - i))
|
||||||
|
return String(d[start..<end])
|
||||||
|
}.joined(separator: " ")
|
||||||
|
if masked != v { cardNumber = masked }
|
||||||
|
}
|
||||||
|
|
||||||
labeledField("Nome no Cartão", placeholder: "Como impresso no cartão", text: $holderName)
|
labeledField("Nome no Cartão", placeholder: "Como impresso no cartão", text: $holderName)
|
||||||
|
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
labeledField("Validade", placeholder: "MM/AAAA", text: $expiry)
|
labeledField("Validade", placeholder: "MM/AAAA", text: $expiry)
|
||||||
.keyboardType(.numberPad)
|
.keyboardType(.numberPad)
|
||||||
|
.onChange(of: expiry) { _, v in
|
||||||
|
let d = String(v.filter(\.isNumber).prefix(6))
|
||||||
|
let masked: String
|
||||||
|
if d.count <= 2 { masked = d }
|
||||||
|
else { masked = "\(d.prefix(2))/\(d.dropFirst(2))" }
|
||||||
|
if masked != v { expiry = masked }
|
||||||
|
}
|
||||||
|
|
||||||
labeledField("CVV", placeholder: "•••", text: $cvv)
|
labeledField("CVV", placeholder: "•••", text: $cvv)
|
||||||
.keyboardType(.numberPad)
|
.keyboardType(.numberPad)
|
||||||
|
.onChange(of: cvv) { _, v in
|
||||||
|
let d = String(v.filter(\.isNumber).prefix(4))
|
||||||
|
if d != v { cvv = d }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
labeledField("CPF do Titular", placeholder: "000.000.000-00", text: $cpf)
|
labeledField("CPF do Titular", placeholder: "000.000.000-00", text: $cpf)
|
||||||
.keyboardType(.numberPad)
|
.keyboardType(.numberPad)
|
||||||
|
.onChange(of: cpf) { _, v in
|
||||||
|
let d = String(v.filter(\.isNumber).prefix(11))
|
||||||
|
let masked: String
|
||||||
|
if d.count <= 3 { masked = d }
|
||||||
|
else if d.count <= 6 { masked = "\(d.prefix(3)).\(d.dropFirst(3))" }
|
||||||
|
else if d.count <= 9 { masked = "\(d.prefix(3)).\(d.dropFirst(3).prefix(3)).\(d.dropFirst(6))" }
|
||||||
|
else { masked = "\(d.prefix(3)).\(d.dropFirst(3).prefix(3)).\(d.dropFirst(6).prefix(3))-\(d.dropFirst(9))" }
|
||||||
|
if masked != v { cpf = masked }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(14)
|
.padding(14)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user