diff --git a/LabWise.xcodeproj/xcuserdata/adipu.xcuserdatad/xcschemes/xcschememanagement.plist b/LabWise.xcodeproj/xcuserdata/adipu.xcuserdatad/xcschemes/xcschememanagement.plist
index 54c14a0..a9c03c8 100644
--- a/LabWise.xcodeproj/xcuserdata/adipu.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/LabWise.xcodeproj/xcuserdata/adipu.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,7 +7,7 @@
LabWise.xcscheme_^#shared#^_
orderHint
- 0
+ 1
diff --git a/LabWise/AddChemicalView.swift b/LabWise/AddChemicalView.swift
index 5d7f58d..ef54e89 100644
--- a/LabWise/AddChemicalView.swift
+++ b/LabWise/AddChemicalView.swift
@@ -120,9 +120,9 @@ final class AddChemicalViewModel {
func loadProfile() async {
guard !isEditing else { return }
if let profile = try? await profileClient.get() {
- piFirstName = profile.piFirstName
- bldgCode = profile.bldgCode
- lab = profile.lab
+ piFirstName = profile.piFirstName ?? ""
+ bldgCode = profile.bldgCode ?? ""
+ lab = profile.lab ?? ""
contact = profile.contact ?? ""
}
}
diff --git a/LabWise/InventoryView.swift b/LabWise/InventoryView.swift
index 883bbc8..51ff921 100644
--- a/LabWise/InventoryView.swift
+++ b/LabWise/InventoryView.swift
@@ -61,11 +61,16 @@ struct InventoryView: View {
ProgressView("Loading...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else if viewModel.chemicals.isEmpty {
- ContentUnavailableView(
- "No Chemicals",
- systemImage: "flask",
- description: Text("Add your first chemical using the + button.")
- )
+ ScrollView {
+ ContentUnavailableView(
+ "No Chemicals",
+ systemImage: "flask",
+ description: Text("Add your first chemical using the + button.")
+ )
+ }
+ .refreshable {
+ await viewModel.loadChemicals()
+ }
} else {
List {
ForEach(viewModel.chemicals) { chemical in
diff --git a/LabWise/ProfileView.swift b/LabWise/ProfileView.swift
index f5bd985..9e480c6 100644
--- a/LabWise/ProfileView.swift
+++ b/LabWise/ProfileView.swift
@@ -24,12 +24,18 @@ final class ProfileViewModel {
func load() async {
isLoading = true
defer { isLoading = false }
- do {
- let p = try await profileClient.get()
+
+ async let profileFetch = profileClient.get()
+ async let userFetch = authClient.fetchCurrentUser()
+
+ if let p = try? await profileFetch {
profile = p
populateFields(from: p)
- } catch {
- // Profile may not exist yet — that's OK
+ }
+ if let user = try? await userFetch {
+ await MainActor.run {
+ AppState.shared.currentUser = user
+ }
}
}
@@ -38,17 +44,17 @@ final class ProfileViewModel {
defer { isSaving = false }
do {
let trimmedName = displayName.trimmingCharacters(in: .whitespaces)
- if !trimmedName.isEmpty && trimmedName != AppState.shared.currentUser?.name {
+ 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,
- bldgCode: bldgCode,
- lab: lab,
- contact: contact.isEmpty ? nil : contact
+ 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
@@ -80,9 +86,9 @@ final class ProfileViewModel {
}
private func populateFields(from p: UserProfile) {
- piFirstName = p.piFirstName
- bldgCode = p.bldgCode
- lab = p.lab
+ piFirstName = p.piFirstName ?? ""
+ bldgCode = p.bldgCode ?? ""
+ lab = p.lab ?? ""
contact = p.contact ?? ""
}
}
@@ -125,6 +131,18 @@ struct ProfileView: View {
}
}
+ Section {
+ Button(role: .destructive) {
+ Task { await viewModel.signOut() }
+ } label: {
+ HStack {
+ Spacer()
+ Text("Sign Out")
+ Spacer()
+ }
+ }
+ }
+
Section {
Button(role: .destructive) {
viewModel.showDeleteConfirm = true
@@ -139,17 +157,10 @@ struct ProfileView: View {
} 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()
- }
- }
+ }
+ .refreshable {
+ if !viewModel.isEditing {
+ await viewModel.load()
}
}
.navigationTitle("Profile")
@@ -173,9 +184,9 @@ struct ProfileView: View {
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.piFirstName = p.piFirstName ?? ""
+ viewModel.bldgCode = p.bldgCode ?? ""
+ viewModel.lab = p.lab ?? ""
viewModel.contact = p.contact ?? ""
}
}
diff --git a/LabWise/ScanView.swift b/LabWise/ScanView.swift
index 73b82c3..d49be6e 100644
--- a/LabWise/ScanView.swift
+++ b/LabWise/ScanView.swift
@@ -499,9 +499,9 @@ final class LabelScannerViewModel {
func loadProfile() async {
if let profile = try? await profileClient.get() {
- piFirstName = profile.piFirstName
- bldgCode = profile.bldgCode
- lab = profile.lab
+ piFirstName = profile.piFirstName ?? ""
+ bldgCode = profile.bldgCode ?? ""
+ lab = profile.lab ?? ""
contact = profile.contact ?? ""
}
}
diff --git a/LabWiseKit/Sources/LabWiseKit/APIClient.swift b/LabWiseKit/Sources/LabWiseKit/APIClient.swift
index 3f860b9..5170c30 100644
--- a/LabWiseKit/Sources/LabWiseKit/APIClient.swift
+++ b/LabWiseKit/Sources/LabWiseKit/APIClient.swift
@@ -44,6 +44,7 @@ public final class APIClient: Sendable {
var req = URLRequest(url: url)
req.httpMethod = method
req.setValue(contentType, forHTTPHeaderField: "Content-Type")
+ req.setValue(baseURL.absoluteString, forHTTPHeaderField: "Origin")
if let body {
do {
diff --git a/LabWiseKit/Sources/LabWiseKit/Auth/AuthClient.swift b/LabWiseKit/Sources/LabWiseKit/Auth/AuthClient.swift
index 3d4b318..b4ccb7f 100644
--- a/LabWiseKit/Sources/LabWiseKit/Auth/AuthClient.swift
+++ b/LabWiseKit/Sources/LabWiseKit/Auth/AuthClient.swift
@@ -335,13 +335,13 @@ public final class AuthClient: Sendable {
// MARK: - Update Name
private struct UpdateNameBody: Encodable, Sendable {
- let name: String
+ let name: String?
}
- /// Update the authenticated user's display name.
+ /// Update the authenticated user's display name. Pass an empty string to clear it.
public func updateName(_ name: String) async throws -> AuthUser {
- let body = UpdateNameBody(name: name)
- _ = try await api.postRaw("/api/auth/update-user", body: body)
+ let body = UpdateNameBody(name: name.isEmpty ? nil : name)
+ _ = try await api.postRaw("/api/account/name", body: body)
return try await fetchCurrentUser()
}
diff --git a/LabWiseKit/Sources/LabWiseKit/Models/UserProfile.swift b/LabWiseKit/Sources/LabWiseKit/Models/UserProfile.swift
index 9f52411..f5d50d5 100644
--- a/LabWiseKit/Sources/LabWiseKit/Models/UserProfile.swift
+++ b/LabWiseKit/Sources/LabWiseKit/Models/UserProfile.swift
@@ -3,9 +3,9 @@ import Foundation
/// Profile data returned by the server uses snake_case keys.
public struct UserProfile: Codable, Sendable {
public var userId: String
- public var piFirstName: String
- public var bldgCode: String
- public var lab: String
+ public var piFirstName: String?
+ public var bldgCode: String?
+ public var lab: String?
public var contact: String?
enum CodingKeys: String, CodingKey {
@@ -18,9 +18,9 @@ public struct UserProfile: Codable, Sendable {
public init(
userId: String = "",
- piFirstName: String = "",
- bldgCode: String = "",
- lab: String = "",
+ piFirstName: String? = nil,
+ bldgCode: String? = nil,
+ lab: String? = nil,
contact: String? = nil
) {
self.userId = userId
@@ -33,9 +33,9 @@ public struct UserProfile: Codable, Sendable {
/// Request body for POST /api/profile — server expects snake_case keys.
public struct UserProfileUpsertBody: Codable, Sendable {
- public var piFirstName: String
- public var bldgCode: String
- public var lab: String
+ public var piFirstName: String?
+ public var bldgCode: String?
+ public var lab: String?
public var contact: String?
enum CodingKeys: String, CodingKey {
@@ -45,7 +45,7 @@ public struct UserProfileUpsertBody: Codable, Sendable {
case contact
}
- public init(piFirstName: String, bldgCode: String, lab: String, contact: String? = nil) {
+ public init(piFirstName: String? = nil, bldgCode: String? = nil, lab: String? = nil, contact: String? = nil) {
self.piFirstName = piFirstName
self.bldgCode = bldgCode
self.lab = lab