Files
LabWiseiOS/LabWise/ProfileView.swift
2026-04-10 01:44:02 -05:00

204 lines
7.1 KiB
Swift

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 }
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 trimmedName = displayName.trimmingCharacters(in: .whitespaces)
if !trimmedName.isEmpty && trimmedName != AppState.shared.currentUser?.name {
let updatedUser = try await authClient.updateName(trimmedName)
await MainActor.run {
AppState.shared.currentUser = updatedUser
}
}
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 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) {
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.")
}
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.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()
}
}
}