import SwiftUI import VoiceCatCore struct AccountsView: View { @Bindable var session: SessionState @State private var showCreateAccount = false @State private var newUsername = "" @State private var newPassword = "" @State private var accountToDelete: Account? @State private var showResetPassword = false @State private var resetForAccount: Account? @State private var resetPassword = "" var body: some View { List { ForEach(session.accounts, id: \.username) { account in AccountRowView(account: account) .swipeActions(edge: .trailing) { Button(role: .destructive) { accountToDelete = account } label: { Label("Delete", systemImage: "trash") } Button { resetForAccount = account showResetPassword = true } label: { Label("Reset PW", systemImage: "key") } .tint(.orange) } } } .navigationTitle("Accounts") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .primaryAction) { Button { showCreateAccount = true } label: { Image(systemName: "plus") } .accessibilityLabel("Create account") } } .refreshable { session.fetchAccountList() } .onAppear { session.fetchAccountList() } .confirmationDialog("Delete account?", isPresented: Binding( get: { accountToDelete != nil }, set: { if !$0 { accountToDelete = nil } } )) { if let a = accountToDelete { Button("Delete \(a.username)", role: .destructive) { session.deleteAccount(username: a.username) accountToDelete = nil } } Button("Cancel", role: .cancel) { accountToDelete = nil } } .sheet(isPresented: $showCreateAccount) { CreateAccountSheet(session: session) } .alert("Reset Password", isPresented: $showResetPassword) { SecureField("New password", text: $resetPassword) .accessibilityLabel("New password for account") Button("Reset") { if let a = resetForAccount, !resetPassword.isEmpty { session.resetPassword(username: a.username, newPassword: resetPassword) } resetPassword = "" resetForAccount = nil } Button("Cancel", role: .cancel) { resetPassword = "" resetForAccount = nil } } message: { Text("Enter a new password for \(resetForAccount?.username ?? "").") } } } private struct AccountRowView: View { let account: Account private var joinedDate: String { let date = Date(timeIntervalSince1970: Double(account.createdAtUnixMs) / 1000) return date.formatted(.dateTime.year().month().day()) } var body: some View { VStack(alignment: .leading, spacing: 2) { HStack { Text(account.username) .fontWeight(.medium) if account.isAdmin { Text("admin") .font(.caption2) .padding(.horizontal, 6).padding(.vertical, 2) .background(.orange.opacity(0.2), in: Capsule()) .foregroundStyle(.orange) } } Text("Created \(joinedDate)") .font(.caption) .foregroundStyle(.secondary) } .accessibilityElement(children: .combine) .accessibilityLabel("\(account.username)\(account.isAdmin ? ", administrator" : ""), created \(joinedDate)") } } private struct CreateAccountSheet: View { @Bindable var session: SessionState @Environment(\.dismiss) private var dismiss @State private var username = "" @State private var password = "" var body: some View { NavigationStack { Form { Section { TextField("Username", text: $username) .textContentType(.username) .autocorrectionDisabled() .textInputAutocapitalization(.never) .accessibilityLabel("Username") SecureField("Password", text: $password) .textContentType(.newPassword) .accessibilityLabel("Password") } } .navigationTitle("Create Account") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .confirmationAction) { Button("Create") { session.createAccount(username: username, password: password) dismiss() } .disabled(username.isEmpty || password.isEmpty) } } } } }