Parse RoleID from myBusinesses API. CashCollectionSheet now shows role-appropriate messaging: staff keeps cash, admin deposits to business. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
153 lines
5.4 KiB
Swift
153 lines
5.4 KiB
Swift
import SwiftUI
|
|
|
|
struct ProfileScreen: View {
|
|
@EnvironmentObject var appState: AppState
|
|
@Environment(\.dismiss) private var dismiss
|
|
@State private var appearAnimation = false
|
|
@State private var avatarURLLoaded: URL?
|
|
|
|
private var displayName: String {
|
|
appState.userName ?? "Worker"
|
|
}
|
|
|
|
private var avatarURL: URL? {
|
|
if let loaded = avatarURLLoaded { return loaded }
|
|
guard let urlString = appState.userPhotoUrl, !urlString.isEmpty else { return nil }
|
|
return URL(string: urlString)
|
|
}
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(spacing: 20) {
|
|
// Avatar section
|
|
VStack(spacing: 8) {
|
|
if let url = avatarURL {
|
|
AsyncImage(url: url) { phase in
|
|
switch phase {
|
|
case .success(let image):
|
|
image
|
|
.resizable()
|
|
.scaledToFill()
|
|
case .failure:
|
|
placeholderAvatar
|
|
default:
|
|
ProgressView()
|
|
.frame(width: 80, height: 80)
|
|
}
|
|
}
|
|
.frame(width: 80, height: 80)
|
|
.clipShape(Circle())
|
|
.overlay(Circle().stroke(Color.payfritGreen.opacity(0.3), lineWidth: 2))
|
|
.shadow(color: .black.opacity(0.1), radius: 6, y: 3)
|
|
} else {
|
|
placeholderAvatar
|
|
}
|
|
|
|
Text(displayName)
|
|
.font(.title3)
|
|
.fontWeight(.bold)
|
|
.foregroundColor(.primary)
|
|
|
|
Text(appState.roleName)
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.top, 16)
|
|
.scaleEffect(appearAnimation ? 1 : 0.9)
|
|
.opacity(appearAnimation ? 1 : 0)
|
|
|
|
// Info section
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
Text("Account Information")
|
|
.font(.subheadline)
|
|
.fontWeight(.semibold)
|
|
.foregroundColor(.payfritGreen)
|
|
.padding(.horizontal, 16)
|
|
|
|
VStack(spacing: 0) {
|
|
infoRow(icon: "person.fill", label: "Name", value: displayName)
|
|
Divider().padding(.leading, 54)
|
|
infoRow(icon: "briefcase.fill", label: "Role", value: appState.roleName)
|
|
}
|
|
.background(Color(.secondarySystemGroupedBackground))
|
|
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
|
|
.padding(.horizontal, 16)
|
|
}
|
|
.opacity(appearAnimation ? 1 : 0)
|
|
.offset(y: appearAnimation ? 0 : 15)
|
|
|
|
// Note
|
|
Text("Profile information is managed through your employer. Contact your manager if you need to update your details.")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.horizontal, 24)
|
|
.padding(.top, 8)
|
|
.opacity(appearAnimation ? 1 : 0)
|
|
|
|
Spacer()
|
|
.frame(height: 20)
|
|
}
|
|
}
|
|
.background(Color(.systemGroupedBackground).ignoresSafeArea())
|
|
.navigationTitle("Profile")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.task {
|
|
do {
|
|
if let urlString = try await APIService.shared.getAvatarUrl(),
|
|
let url = URL(string: urlString) {
|
|
avatarURLLoaded = url
|
|
}
|
|
} catch {
|
|
// Avatar load failed
|
|
}
|
|
}
|
|
.onAppear {
|
|
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
|
|
appearAnimation = true
|
|
}
|
|
}
|
|
}
|
|
|
|
private var placeholderAvatar: some View {
|
|
Image(systemName: "person.circle.fill")
|
|
.resizable()
|
|
.foregroundStyle(.linearGradient(
|
|
colors: [Color.payfritGreen.opacity(0.6), Color.payfritGreen.opacity(0.3)],
|
|
startPoint: .top,
|
|
endPoint: .bottom
|
|
))
|
|
.frame(width: 80, height: 80)
|
|
.shadow(color: .black.opacity(0.1), radius: 6, y: 3)
|
|
}
|
|
|
|
private func infoRow(icon: String, label: String, value: String) -> some View {
|
|
HStack(spacing: 14) {
|
|
Image(systemName: icon)
|
|
.font(.subheadline)
|
|
.foregroundColor(.payfritGreen)
|
|
.frame(width: 24)
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(label)
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
Text(value)
|
|
.font(.subheadline)
|
|
.foregroundColor(.primary)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding(.horizontal, 16)
|
|
.padding(.vertical, 12)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
NavigationStack {
|
|
ProfileScreen()
|
|
.environmentObject(AppState())
|
|
}
|
|
}
|