- Flatten project structure: remove Models/, Services/, ViewModels/, Views/ subdirs - Replace APIService actor with simpler Api class, IS_DEV flag controls dev vs prod URL - Rewrite BeaconScanner to use CoreLocation (CLBeaconRegion ranging) instead of CoreBluetooth — iOS blocks iBeacon data from CBCentralManager - Add SVG logo on login page with proper scaling (was showing green square) - Make login page scrollable, add "enter 6-digit code" OTP instruction - Fix text input visibility (white on white) with .foregroundColor(.primary) - Add diagonal orange DEV ribbon banner (lower-left corner), gated on Api.IS_DEV - Update app icon: logo 10% larger, wifi icon closer - Add en.lproj/InfoPlist.strings for display name localization - Fix scan flash: keep isScanning=true until enrichment completes - Add Podfile with SVGKit, Kingfisher, CocoaLumberjack dependencies Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
142 lines
4.5 KiB
Swift
142 lines
4.5 KiB
Swift
import SwiftUI
|
|
import Kingfisher
|
|
|
|
struct BusinessListView: View {
|
|
@Binding var hasAutoSelected: Bool
|
|
var onBusinessSelected: (Business) -> Void
|
|
var onLogout: () -> Void
|
|
|
|
@State private var businesses: [Business] = []
|
|
@State private var isLoading = false
|
|
@State private var errorMessage: String?
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
VStack {
|
|
if isLoading {
|
|
Spacer()
|
|
ProgressView("Loading businesses...")
|
|
Spacer()
|
|
} else if let error = errorMessage {
|
|
Spacer()
|
|
Text(error)
|
|
.foregroundColor(.errorRed)
|
|
.multilineTextAlignment(.center)
|
|
.padding()
|
|
addBusinessButton
|
|
Spacer()
|
|
} else if businesses.isEmpty {
|
|
Spacer()
|
|
Text("No businesses found")
|
|
.foregroundColor(.secondary)
|
|
addBusinessButton
|
|
Spacer()
|
|
} else {
|
|
List(businesses) { business in
|
|
Button {
|
|
onBusinessSelected(business)
|
|
} label: {
|
|
businessRow(business)
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
.listStyle(.plain)
|
|
|
|
addBusinessButton
|
|
}
|
|
}
|
|
.navigationTitle("Select Business")
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Button("Log Out") {
|
|
logout()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.onAppear {
|
|
loadBusinesses()
|
|
}
|
|
}
|
|
|
|
private func businessRow(_ business: Business) -> some View {
|
|
HStack(spacing: 12) {
|
|
if let ext = business.headerImageExtension, !ext.isEmpty {
|
|
let imageUrl = URL(string: "https://dev.payfrit.com/uploads/businesses/\(business.businessId)/header.\(ext)")
|
|
KFImage(imageUrl)
|
|
.resizable()
|
|
.placeholder {
|
|
Image(systemName: "building.2")
|
|
.font(.title2)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
.scaledToFill()
|
|
.frame(width: 48, height: 48)
|
|
.clipShape(Circle())
|
|
} else {
|
|
Image(systemName: "building.2")
|
|
.font(.title2)
|
|
.foregroundColor(.secondary)
|
|
.frame(width: 48, height: 48)
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(business.name)
|
|
.font(.body.weight(.medium))
|
|
.foregroundColor(.primary)
|
|
Text("Tap to configure beacons")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Image(systemName: "chevron.right")
|
|
.foregroundColor(.secondary)
|
|
.font(.caption)
|
|
}
|
|
.padding(.vertical, 4)
|
|
}
|
|
|
|
private var addBusinessButton: some View {
|
|
Button {
|
|
if let url = URL(string: "https://dev.payfrit.com/portal/index.html") {
|
|
UIApplication.shared.open(url)
|
|
}
|
|
} label: {
|
|
Text("Add Business via Portal")
|
|
.font(.callout)
|
|
.foregroundColor(.payfritGreen)
|
|
}
|
|
.padding()
|
|
}
|
|
|
|
private func loadBusinesses() {
|
|
isLoading = true
|
|
errorMessage = nil
|
|
|
|
Task {
|
|
do {
|
|
let result = try await Api.shared.listBusinesses()
|
|
businesses = result
|
|
isLoading = false
|
|
|
|
if businesses.count == 1 && !hasAutoSelected {
|
|
hasAutoSelected = true
|
|
onBusinessSelected(businesses[0])
|
|
}
|
|
} catch {
|
|
isLoading = false
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
}
|
|
|
|
private func logout() {
|
|
UserDefaults.standard.removeObject(forKey: "token")
|
|
UserDefaults.standard.removeObject(forKey: "userId")
|
|
UserDefaults.standard.removeObject(forKey: "firstName")
|
|
Api.shared.setAuthToken(nil)
|
|
onLogout()
|
|
}
|
|
}
|