import SwiftUI import LabWiseKit @Observable final class ProfileViewModel { var profile: UserProfile? var isLoading = false var isSaving = false var isEditing = false var errorMessage: String? var showDeleteConfirm = false var isDeleting = false // Editable fields var displayName = "" var piFirstName = "" var bldgCode = "" var lab = "" var contact = "" private let profileClient = ProfileClient() private let authClient = AuthClient() func load() async { isLoading = true defer { isLoading = false } async let profileFetch = profileClient.get() async let userFetch = authClient.fetchCurrentUser() if let p = try? await profileFetch { profile = p populateFields(from: p) } if let user = try? await userFetch { await MainActor.run { AppState.shared.currentUser = user } } } func save() async { isSaving = true defer { isSaving = false } do { let trimmedName = displayName.trimmingCharacters(in: .whitespaces) if trimmedName != (AppState.shared.currentUser?.name ?? "") { let updatedUser = try await authClient.updateName(trimmedName) await MainActor.run { AppState.shared.currentUser = updatedUser } } let body = UserProfileUpsertBody( piFirstName: piFirstName.trimmingCharacters(in: .whitespaces).isEmpty ? nil : piFirstName.trimmingCharacters(in: .whitespaces), bldgCode: bldgCode.trimmingCharacters(in: .whitespaces).isEmpty ? nil : bldgCode.trimmingCharacters(in: .whitespaces), lab: lab.trimmingCharacters(in: .whitespaces).isEmpty ? nil : lab.trimmingCharacters(in: .whitespaces), contact: contact.trimmingCharacters(in: .whitespaces).isEmpty ? nil : contact.trimmingCharacters(in: .whitespaces) ) let updated = try await profileClient.upsert(body) profile = updated populateFields(from: updated) isEditing = false } catch { errorMessage = "Failed to save profile." } } func deleteAccount() async { isDeleting = true defer { isDeleting = false } do { try await authClient.deleteAccount() await MainActor.run { AppState.shared.signedOut() } } catch { errorMessage = "Failed to delete account. Please try again." } } 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") { if viewModel.isEditing { TextField("Name", text: $viewModel.displayName) } else { LabeledContent("Name", value: user.name ?? "—") } 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() } } } Section { Button(role: .destructive) { viewModel.showDeleteConfirm = true } label: { HStack { Spacer() Text("Delete Account") Spacer() } } .disabled(viewModel.isDeleting) } footer: { Text("Permanently deletes your account and all data including chemicals and protocols.") } } .refreshable { if !viewModel.isEditing { await viewModel.load() } } .navigationTitle("Profile") .toolbar { ToolbarItem(placement: .topBarTrailing) { if viewModel.isEditing { Button("Save") { Task { await viewModel.save() } } .disabled(viewModel.isSaving) } else { Button("Edit") { viewModel.displayName = appState.currentUser?.name ?? "" viewModel.isEditing = true } } } if viewModel.isEditing { ToolbarItem(placement: .topBarLeading) { Button("Cancel") { viewModel.isEditing = false viewModel.displayName = appState.currentUser?.name ?? "" 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 ?? "") } .alert("Delete Account?", isPresented: $viewModel.showDeleteConfirm) { Button("Cancel", role: .cancel) { } Button("Delete", role: .destructive) { Task { await viewModel.deleteAccount() } } } message: { Text("Are you sure? This will permanently delete your account and all your data. This action cannot be undone.") } } .task { await viewModel.load() } } }