a native ios client for gitbay

client ios swift

https://gitbay.org

gitbay/Views/SignInView.swift

ui-smoke
gitbay-ios/gitbay/Views/SignInView.swift history · blame · raw

110 lines · 3762 bytes

  1import SwiftUI
  2
  3/// Paste a token, name the instance, done. There is no browser flow and no
  4/// password anywhere in the system: tokens are minted over SSH on a
  5/// machine that has it.
  6struct SignInView: View {
  7
  8    @Environment(SessionStore.self) private var session
  9
 10    @State private var instanceURL = "gitbay.org"
 11    @State private var token = ""
 12    @State private var errorMessage: String?
 13    @State private var signingIn = false
 14    @FocusState private var tokenFieldFocused: Bool
 15
 16    var body: some View {
 17        Form {
 18            if let notice = session.signedOutMessage {
 19                Section {
 20                    Label(notice, systemImage: "key.slash")
 21                        .foregroundStyle(.secondary)
 22                }
 23            }
 24
 25            Section("Instance") {
 26                TextField("gitbay.org", text: $instanceURL)
 27                    .textContentType(.URL)
 28                    .keyboardType(.URL)
 29                    .autocorrectionDisabled()
 30                    .textInputAutocapitalization(.never)
 31            }
 32
 33            Section {
 34                SecureField("Paste a token", text: $token)
 35                    .autocorrectionDisabled()
 36                    .textInputAutocapitalization(.never)
 37                    .focused($tokenFieldFocused)
 38                    .submitLabel(.go)
 39                    .onSubmit { signIn() }
 40                    .privacySensitive()
 41            } header: {
 42                Text("Token")
 43            } footer: {
 44                VStack(alignment: .leading, spacing: 8) {
 45                    Text("Mint one over SSH on a machine that has your key:")
 46                    Text(verbatim: "gitbay auth token create --name iphone --scope full --ttl 90d")
 47                        .font(.gbMono(.caption))
 48                        .textSelection(.enabled)
 49                    Text("`--scope read` also works, but a read-only token cannot comment or merge.")
 50                }
 51            }
 52
 53            if let errorMessage {
 54                Section {
 55                    GBNotice(errorMessage)
 56                }
 57            }
 58
 59            Section {
 60                Button {
 61                    signIn()
 62                } label: {
 63                    if signingIn {
 64                        ProgressView()
 65                            .frame(maxWidth: .infinity)
 66                    } else {
 67                        Text("Sign In")
 68                            .frame(maxWidth: .infinity)
 69                    }
 70                }
 71                .disabled(signingIn || token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
 72            }
 73        }
 74        .navigationTitle("Sign in to gitbay")
 75        .onAppear { tokenFieldFocused = true }
 76    }
 77
 78    private func signIn() {
 79        signingIn = true
 80        errorMessage = nil
 81        Task {
 82            defer { signingIn = false }
 83            do {
 84                try await session.signIn(instanceURL: instanceURL, token: token)
 85                token = ""
 86            } catch let error as GitbayError {
 87                errorMessage = signInMessage(for: error)
 88            } catch let error as GitbayInstance.InvalidURL {
 89                errorMessage = error.errorDescription
 90            } catch {
 91                errorMessage = "Something went wrong. Please try again."
 92            }
 93        }
 94    }
 95
 96    /// Sign-in is the one place a 401 means "that token", not "your session".
 97    private func signInMessage(for error: GitbayError) -> String {
 98        if error.requiresReauthentication {
 99            return "That token was not accepted. Check it was pasted whole and has not expired."
100        }
101        return error.userFacingMessage
102    }
103}
104
105#Preview {
106    NavigationStack {
107        SignInView()
108    }
109    .environment(SessionStore())
110}