import SwiftUI /// One team: its members and its repo grants. struct TeamView: View { @State private var model: TeamViewModel @State private var newMember = "" @State private var grantRepo = "" @State private var grantRole = "read" init(client: GitbayClient, org: String, team: String) { _model = State(initialValue: TeamViewModel(client: client, orgName: org, teamName: team)) } var body: some View { List { if let loaded = model.state.value { if let error = model.actionError { Section { GBNotice(error, .gbWarn) } } Section("Members") { ForEach(loaded.members ?? [], id: \.self) { user in Text(user) .font(.gbSans(.subheadline)) .swipeActions { Button("Remove", role: .destructive) { Task { await model.removeMember(user) } } } } HStack { TextField("Add org member", text: $newMember) .autocorrectionDisabled() .textInputAutocapitalization(.never) Button { let user = newMember.trimmingCharacters(in: .whitespaces) newMember = "" Task { await model.addMember(user) } } label: { Image(systemName: "plus.circle.fill") } .disabled(newMember.trimmingCharacters(in: .whitespaces).isEmpty || model.working) } } Section { ForEach(loaded.grants ?? []) { grant in HStack { Text(grant.repo) .font(.gbSans(.subheadline)) .lineLimit(1) Spacer() GBChip(grant.role, .secondary) } .swipeActions { Button("Revoke", role: .destructive) { Task { await model.revoke(grant) } } } } VStack(spacing: 8) { TextField("org/repo", text: $grantRepo) .autocorrectionDisabled() .textInputAutocapitalization(.never) HStack { Picker("Role", selection: $grantRole) { Text("read").tag("read") Text("write").tag("write") Text("admin").tag("admin") } .pickerStyle(.segmented) Button("Grant") { let repo = grantRepo.trimmingCharacters(in: .whitespaces) grantRepo = "" Task { await model.grant(repo: repo, role: grantRole) } } .disabled(!grantRepo.contains("/") || model.working) } } } header: { Text("Repository grants") } footer: { Text("A grant gives every team member the role on that org repository.") } } } .overlay { LoadStateOverlay(state: model.state) } .navigationTitle(model.teamName) .navigationBarTitleDisplayMode(.inline) .task { await model.load() } .refreshable { await model.load() } } }