Implement bank transfer app flows
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TransferReceiptView: View {
|
||||
let receipt: TransferReceiptData
|
||||
var onSaveContact: ((BankContact) -> Void)? = nil
|
||||
var onDone: () -> Void
|
||||
|
||||
@State private var contactSaved = false
|
||||
|
||||
private let primary = Color(red: 0.247, green: 0.396, blue: 0.678)
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
successHeader
|
||||
detailsCard
|
||||
Spacer(minLength: 32)
|
||||
doneButton
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.bottom, 32)
|
||||
}
|
||||
.background(Color(.systemGroupedBackground))
|
||||
.navigationTitle("Receipt")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Done") { onDone() }
|
||||
.foregroundStyle(primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sub-views
|
||||
|
||||
private var successHeader: some View {
|
||||
VStack(spacing: 12) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color.green.opacity(0.15))
|
||||
.frame(width: 80, height: 80)
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: 44))
|
||||
.foregroundStyle(.green)
|
||||
}
|
||||
.padding(.top, 32)
|
||||
|
||||
Text("Transfer Successful")
|
||||
.font(.title2.bold())
|
||||
|
||||
Text(amountText)
|
||||
.font(.system(size: 36, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(primary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
|
||||
private var detailsCard: some View {
|
||||
VStack(spacing: 0) {
|
||||
row(label: "From", value: receipt.fromAccountNumber)
|
||||
Divider().padding(.leading, 16)
|
||||
row(label: "Bank", value: receipt.fromBankName)
|
||||
Divider().padding(.leading, 16)
|
||||
row(label: "To Account", value: receipt.toAccountNumber)
|
||||
Divider().padding(.leading, 16)
|
||||
row(label: "Beneficiary", value: receipt.toAccountName.isEmpty ? "—" : receipt.toAccountName)
|
||||
if !receipt.reference.isEmpty {
|
||||
Divider().padding(.leading, 16)
|
||||
row(label: "Reference", value: receipt.reference)
|
||||
}
|
||||
if !receipt.date.isEmpty {
|
||||
Divider().padding(.leading, 16)
|
||||
row(label: "Date", value: receipt.date)
|
||||
}
|
||||
if !receipt.message.isEmpty && receipt.message != "Transfer successful" {
|
||||
Divider().padding(.leading, 16)
|
||||
row(label: "Note", value: receipt.message)
|
||||
}
|
||||
}
|
||||
.background(Color(.secondarySystemGroupedBackground))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
|
||||
private var doneButton: some View {
|
||||
VStack(spacing: 12) {
|
||||
if let onSave = onSaveContact {
|
||||
Button {
|
||||
onSave(makeContact())
|
||||
contactSaved = true
|
||||
} label: {
|
||||
Label(contactSaved ? "Contact Saved" : "Save Contact",
|
||||
systemImage: contactSaved ? "checkmark.circle.fill" : "person.badge.plus")
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 14)
|
||||
.background(contactSaved ? Color.green.opacity(0.15) : Color(.secondarySystemGroupedBackground))
|
||||
.foregroundStyle(contactSaved ? .green : primary)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
.disabled(contactSaved)
|
||||
}
|
||||
Button(action: onDone) {
|
||||
Text("Done")
|
||||
.font(.headline)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 14)
|
||||
.background(primary)
|
||||
.foregroundStyle(.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func makeContact() -> BankContact {
|
||||
let source = receipt.fromBankName.lowercased().contains("islamic") ? "MIB" : "BML"
|
||||
let displayName = receipt.toAccountName.isEmpty ? receipt.toAccountNumber : receipt.toAccountName
|
||||
return BankContact(
|
||||
id: "LOCAL_\(source)_\(receipt.toAccountNumber)",
|
||||
benefNo: receipt.toAccountNumber,
|
||||
benefName: displayName,
|
||||
benefNickName: nil,
|
||||
benefAccount: receipt.toAccountNumber,
|
||||
benefType: source,
|
||||
bankColor: nil,
|
||||
benefBankName: nil,
|
||||
bankCode: nil,
|
||||
benefStatus: "Active",
|
||||
transferCyDesc: receipt.currency,
|
||||
customerImgHash: nil,
|
||||
benefCategoryId: nil,
|
||||
profileId: nil,
|
||||
source: source
|
||||
)
|
||||
}
|
||||
|
||||
private func row(label: String, value: String) -> some View {
|
||||
HStack {
|
||||
Text(label)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(width: 110, alignment: .leading)
|
||||
Text(value)
|
||||
.font(.subheadline)
|
||||
.multilineTextAlignment(.trailing)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
|
||||
private var amountText: String {
|
||||
let f = NumberFormatter()
|
||||
f.numberStyle = .decimal
|
||||
f.minimumFractionDigits = 2
|
||||
f.maximumFractionDigits = 2
|
||||
f.usesGroupingSeparator = true
|
||||
return "\(receipt.currency) \(f.string(from: NSNumber(value: receipt.amount)) ?? "0.00")"
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
TransferReceiptView(receipt: TransferReceiptData(
|
||||
fromAccountNumber: "7701234567890",
|
||||
fromBankName: "Bank of Maldives",
|
||||
toAccountNumber: "7709876543210",
|
||||
toAccountName: "Ahmed Mohamed",
|
||||
amount: 1500.00,
|
||||
currency: "MVR",
|
||||
reference: "TXN20260607001",
|
||||
date: "2026-06-07 14:32:00",
|
||||
message: "Transfer successful"
|
||||
)) {}
|
||||
}
|
||||
Reference in New Issue
Block a user