import SwiftUI import LabWiseKit @Observable final class ProfileViewModel { var profile: UserProfile? var isLoading = false var isSaving = false var isEditing = false var errorMessage: String? // Editable fields var piFirstName = "" var bldgCode = "" var lab = "" var contact = "" private let profileClient = ProfileClient() private let authClient = AuthClient() func load() async { isLoading = true defer { isLoading = false } do { let p = try await profileClient.get() profile = p populateFields(from: p) } catch { // Profile may not exist yet — that's OK } } func save() async { isSaving = true defer { isSaving = false } do { let body = UserProfileUpsertBody( piFirstName: piFirstName, bldgCode: bldgCode, lab: lab, contact: contact.isEmpty ? nil : contact ) let updated = try await profileClient.upsert(body) profile = updated populateFields(from: updated) isEditing = false } catch { errorMessage = "Failed to save profile." } } func signOut() async { try? await authClient.signOut() await MainActor.run { AppState.shared.signedOut() } } private func populateFields(from p: UserProfile) { piFirstName = p.piFirstName bldgCode = p.bldgCode lab = p.lab contact = p.contact ?? "" } } struct ProfileView: View { @Environment(AppState.self) private var appState @State private var viewModel = ProfileViewModel() var body: some View { NavigationStack { Form { if let user = appState.currentUser { Section("Account") { LabeledContent("Email", value: user.email) } } Section("Lab Info") { if viewModel.isEditing { TextField("PI First Name", text: $viewModel.piFirstName) TextField("Building Code", text: $viewModel.bldgCode) TextField("Lab", text: $viewModel.lab) TextField("Contact", text: $viewModel.contact) } else { LabeledContent("PI First Name", value: viewModel.piFirstName.isEmpty ? "—" : viewModel.piFirstName) LabeledContent("Building Code", value: viewModel.bldgCode.isEmpty ? "—" : viewModel.bldgCode) LabeledContent("Lab", value: viewModel.lab.isEmpty ? "—" : viewModel.lab) LabeledContent("Contact", value: viewModel.contact.isEmpty ? "—" : viewModel.contact) } } Section { Link(destination: URL(string: "https://labwise.wahwa.com/privacy")!) { Text("Privacy Policy") } } Section { Button(role: .destructive) { Task { await viewModel.signOut() } } label: { HStack { Spacer() Text("Sign Out") Spacer() } } } } .navigationTitle("Profile") .toolbar { ToolbarItem(placement: .topBarTrailing) { if viewModel.isEditing { Button("Save") { Task { await viewModel.save() } } .disabled(viewModel.isSaving) } else { Button("Edit") { viewModel.isEditing = true } } } if viewModel.isEditing { ToolbarItem(placement: .topBarLeading) { Button("Cancel") { viewModel.isEditing = false if let p = viewModel.profile { viewModel.piFirstName = p.piFirstName viewModel.bldgCode = p.bldgCode viewModel.lab = p.lab viewModel.contact = p.contact ?? "" } } } } } .alert("Error", isPresented: .constant(viewModel.errorMessage != nil)) { Button("OK") { viewModel.errorMessage = nil } } message: { Text(viewModel.errorMessage ?? "") } } .task { await viewModel.load() } } }