editing all fields works and app is actually functional with profile edits
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
<key>LabWise.xcscheme_^#shared#^_</key>
|
<key>LabWise.xcscheme_^#shared#^_</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>orderHint</key>
|
<key>orderHint</key>
|
||||||
<integer>0</integer>
|
<integer>1</integer>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -120,9 +120,9 @@ final class AddChemicalViewModel {
|
|||||||
func loadProfile() async {
|
func loadProfile() async {
|
||||||
guard !isEditing else { return }
|
guard !isEditing else { return }
|
||||||
if let profile = try? await profileClient.get() {
|
if let profile = try? await profileClient.get() {
|
||||||
piFirstName = profile.piFirstName
|
piFirstName = profile.piFirstName ?? ""
|
||||||
bldgCode = profile.bldgCode
|
bldgCode = profile.bldgCode ?? ""
|
||||||
lab = profile.lab
|
lab = profile.lab ?? ""
|
||||||
contact = profile.contact ?? ""
|
contact = profile.contact ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,11 +61,16 @@ struct InventoryView: View {
|
|||||||
ProgressView("Loading...")
|
ProgressView("Loading...")
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
} else if viewModel.chemicals.isEmpty {
|
} else if viewModel.chemicals.isEmpty {
|
||||||
|
ScrollView {
|
||||||
ContentUnavailableView(
|
ContentUnavailableView(
|
||||||
"No Chemicals",
|
"No Chemicals",
|
||||||
systemImage: "flask",
|
systemImage: "flask",
|
||||||
description: Text("Add your first chemical using the + button.")
|
description: Text("Add your first chemical using the + button.")
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
.refreshable {
|
||||||
|
await viewModel.loadChemicals()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
List {
|
List {
|
||||||
ForEach(viewModel.chemicals) { chemical in
|
ForEach(viewModel.chemicals) { chemical in
|
||||||
|
|||||||
@@ -24,12 +24,18 @@ final class ProfileViewModel {
|
|||||||
func load() async {
|
func load() async {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
defer { isLoading = false }
|
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
|
profile = p
|
||||||
populateFields(from: 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 }
|
defer { isSaving = false }
|
||||||
do {
|
do {
|
||||||
let trimmedName = displayName.trimmingCharacters(in: .whitespaces)
|
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)
|
let updatedUser = try await authClient.updateName(trimmedName)
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
AppState.shared.currentUser = updatedUser
|
AppState.shared.currentUser = updatedUser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let body = UserProfileUpsertBody(
|
let body = UserProfileUpsertBody(
|
||||||
piFirstName: piFirstName,
|
piFirstName: piFirstName.trimmingCharacters(in: .whitespaces).isEmpty ? nil : piFirstName.trimmingCharacters(in: .whitespaces),
|
||||||
bldgCode: bldgCode,
|
bldgCode: bldgCode.trimmingCharacters(in: .whitespaces).isEmpty ? nil : bldgCode.trimmingCharacters(in: .whitespaces),
|
||||||
lab: lab,
|
lab: lab.trimmingCharacters(in: .whitespaces).isEmpty ? nil : lab.trimmingCharacters(in: .whitespaces),
|
||||||
contact: contact.isEmpty ? nil : contact
|
contact: contact.trimmingCharacters(in: .whitespaces).isEmpty ? nil : contact.trimmingCharacters(in: .whitespaces)
|
||||||
)
|
)
|
||||||
let updated = try await profileClient.upsert(body)
|
let updated = try await profileClient.upsert(body)
|
||||||
profile = updated
|
profile = updated
|
||||||
@@ -80,9 +86,9 @@ final class ProfileViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func populateFields(from p: UserProfile) {
|
private func populateFields(from p: UserProfile) {
|
||||||
piFirstName = p.piFirstName
|
piFirstName = p.piFirstName ?? ""
|
||||||
bldgCode = p.bldgCode
|
bldgCode = p.bldgCode ?? ""
|
||||||
lab = p.lab
|
lab = p.lab ?? ""
|
||||||
contact = p.contact ?? ""
|
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 {
|
Section {
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
viewModel.showDeleteConfirm = true
|
viewModel.showDeleteConfirm = true
|
||||||
@@ -139,17 +157,10 @@ struct ProfileView: View {
|
|||||||
} footer: {
|
} footer: {
|
||||||
Text("Permanently deletes your account and all data including chemicals and protocols.")
|
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")
|
.navigationTitle("Profile")
|
||||||
@@ -173,9 +184,9 @@ struct ProfileView: View {
|
|||||||
viewModel.isEditing = false
|
viewModel.isEditing = false
|
||||||
viewModel.displayName = appState.currentUser?.name ?? ""
|
viewModel.displayName = appState.currentUser?.name ?? ""
|
||||||
if let p = viewModel.profile {
|
if let p = viewModel.profile {
|
||||||
viewModel.piFirstName = p.piFirstName
|
viewModel.piFirstName = p.piFirstName ?? ""
|
||||||
viewModel.bldgCode = p.bldgCode
|
viewModel.bldgCode = p.bldgCode ?? ""
|
||||||
viewModel.lab = p.lab
|
viewModel.lab = p.lab ?? ""
|
||||||
viewModel.contact = p.contact ?? ""
|
viewModel.contact = p.contact ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -499,9 +499,9 @@ final class LabelScannerViewModel {
|
|||||||
|
|
||||||
func loadProfile() async {
|
func loadProfile() async {
|
||||||
if let profile = try? await profileClient.get() {
|
if let profile = try? await profileClient.get() {
|
||||||
piFirstName = profile.piFirstName
|
piFirstName = profile.piFirstName ?? ""
|
||||||
bldgCode = profile.bldgCode
|
bldgCode = profile.bldgCode ?? ""
|
||||||
lab = profile.lab
|
lab = profile.lab ?? ""
|
||||||
contact = profile.contact ?? ""
|
contact = profile.contact ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ public final class APIClient: Sendable {
|
|||||||
var req = URLRequest(url: url)
|
var req = URLRequest(url: url)
|
||||||
req.httpMethod = method
|
req.httpMethod = method
|
||||||
req.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
req.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
||||||
|
req.setValue(baseURL.absoluteString, forHTTPHeaderField: "Origin")
|
||||||
|
|
||||||
if let body {
|
if let body {
|
||||||
do {
|
do {
|
||||||
|
|||||||
@@ -335,13 +335,13 @@ public final class AuthClient: Sendable {
|
|||||||
// MARK: - Update Name
|
// MARK: - Update Name
|
||||||
|
|
||||||
private struct UpdateNameBody: Encodable, Sendable {
|
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 {
|
public func updateName(_ name: String) async throws -> AuthUser {
|
||||||
let body = UpdateNameBody(name: name)
|
let body = UpdateNameBody(name: name.isEmpty ? nil : name)
|
||||||
_ = try await api.postRaw("/api/auth/update-user", body: body)
|
_ = try await api.postRaw("/api/account/name", body: body)
|
||||||
return try await fetchCurrentUser()
|
return try await fetchCurrentUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import Foundation
|
|||||||
/// Profile data returned by the server uses snake_case keys.
|
/// Profile data returned by the server uses snake_case keys.
|
||||||
public struct UserProfile: Codable, Sendable {
|
public struct UserProfile: Codable, Sendable {
|
||||||
public var userId: String
|
public var userId: String
|
||||||
public var piFirstName: String
|
public var piFirstName: String?
|
||||||
public var bldgCode: String
|
public var bldgCode: String?
|
||||||
public var lab: String
|
public var lab: String?
|
||||||
public var contact: String?
|
public var contact: String?
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
@@ -18,9 +18,9 @@ public struct UserProfile: Codable, Sendable {
|
|||||||
|
|
||||||
public init(
|
public init(
|
||||||
userId: String = "",
|
userId: String = "",
|
||||||
piFirstName: String = "",
|
piFirstName: String? = nil,
|
||||||
bldgCode: String = "",
|
bldgCode: String? = nil,
|
||||||
lab: String = "",
|
lab: String? = nil,
|
||||||
contact: String? = nil
|
contact: String? = nil
|
||||||
) {
|
) {
|
||||||
self.userId = userId
|
self.userId = userId
|
||||||
@@ -33,9 +33,9 @@ public struct UserProfile: Codable, Sendable {
|
|||||||
|
|
||||||
/// Request body for POST /api/profile — server expects snake_case keys.
|
/// Request body for POST /api/profile — server expects snake_case keys.
|
||||||
public struct UserProfileUpsertBody: Codable, Sendable {
|
public struct UserProfileUpsertBody: Codable, Sendable {
|
||||||
public var piFirstName: String
|
public var piFirstName: String?
|
||||||
public var bldgCode: String
|
public var bldgCode: String?
|
||||||
public var lab: String
|
public var lab: String?
|
||||||
public var contact: String?
|
public var contact: String?
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
@@ -45,7 +45,7 @@ public struct UserProfileUpsertBody: Codable, Sendable {
|
|||||||
case contact
|
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.piFirstName = piFirstName
|
||||||
self.bldgCode = bldgCode
|
self.bldgCode = bldgCode
|
||||||
self.lab = lab
|
self.lab = lab
|
||||||
|
|||||||
Reference in New Issue
Block a user