gitbay/Views/Orgs/TeamView.swift

5411753023a5e2958f17b523903abce380941107
gitbay-ios/gitbay/Views/Orgs/TeamView.swift history · blame · raw

103 lines · 4351 bytes

  1import SwiftUI
  2
  3/// One team: its members and its repo grants.
  4struct TeamView: View {
  5
  6    @State private var model: TeamViewModel
  7    @State private var newMember = ""
  8    @State private var grantRepo = ""
  9    @State private var grantRole = "read"
 10
 11    init(client: GitbayClient, org: String, team: String) {
 12        _model = State(initialValue: TeamViewModel(client: client, orgName: org, teamName: team))
 13    }
 14
 15    var body: some View {
 16        List {
 17            if let loaded = model.state.value {
 18                if let error = model.actionError {
 19                    Section {
 20                        Label(error, systemImage: "hand.raised")
 21                            .foregroundStyle(.orange)
 22                            .font(.subheadline)
 23                    }
 24                }
 25
 26                Section("Members") {
 27                    ForEach(loaded.members ?? [], id: \.self) { user in
 28                        Text(user)
 29                            .font(.subheadline)
 30                            .swipeActions {
 31                                Button("Remove", role: .destructive) {
 32                                    Task { await model.removeMember(user) }
 33                                }
 34                            }
 35                    }
 36                    HStack {
 37                        TextField("Add org member", text: $newMember)
 38                            .autocorrectionDisabled()
 39                            .textInputAutocapitalization(.never)
 40                        Button {
 41                            let user = newMember.trimmingCharacters(in: .whitespaces)
 42                            newMember = ""
 43                            Task { await model.addMember(user) }
 44                        } label: {
 45                            Image(systemName: "plus.circle.fill")
 46                        }
 47                        .disabled(newMember.trimmingCharacters(in: .whitespaces).isEmpty
 48                            || model.working)
 49                    }
 50                }
 51
 52                Section {
 53                    ForEach(loaded.grants ?? []) { grant in
 54                        HStack {
 55                            Text(grant.repo)
 56                                .font(.subheadline)
 57                                .lineLimit(1)
 58                            Spacer()
 59                            Text(grant.role)
 60                                .font(.caption)
 61                                .padding(.horizontal, 6)
 62                                .padding(.vertical, 2)
 63                                .background(.quaternary, in: Capsule())
 64                        }
 65                        .swipeActions {
 66                            Button("Revoke", role: .destructive) {
 67                                Task { await model.revoke(grant) }
 68                            }
 69                        }
 70                    }
 71                    VStack(spacing: 8) {
 72                        TextField("org/repo", text: $grantRepo)
 73                            .autocorrectionDisabled()
 74                            .textInputAutocapitalization(.never)
 75                        HStack {
 76                            Picker("Role", selection: $grantRole) {
 77                                Text("read").tag("read")
 78                                Text("write").tag("write")
 79                                Text("admin").tag("admin")
 80                            }
 81                            .pickerStyle(.segmented)
 82                            Button("Grant") {
 83                                let repo = grantRepo.trimmingCharacters(in: .whitespaces)
 84                                grantRepo = ""
 85                                Task { await model.grant(repo: repo, role: grantRole) }
 86                            }
 87                            .disabled(!grantRepo.contains("/") || model.working)
 88                        }
 89                    }
 90                } header: {
 91                    Text("Repository grants")
 92                } footer: {
 93                    Text("A grant gives every team member the role on that org repository.")
 94                }
 95            }
 96        }
 97        .overlay { LoadStateOverlay(state: model.state) }
 98        .navigationTitle(model.teamName)
 99        .navigationBarTitleDisplayMode(.inline)
100        .task { await model.load() }
101        .refreshable { await model.load() }
102    }
103}