gitbay/Views/SignInView.swift

v1.7.0
gitbay-ios/gitbay/Views/SignInView.swift history · blame · raw

119 lines · 4366 bytes

  1import SwiftUI
  2
  3/// Paste a token, name the instance, done. There is no browser flow and no
  4/// password in the app: tokens are minted on the instance's web settings
  5/// page or over SSH.
  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                    if let tokenPage = (try? GitbayInstance(url: instanceURL))?.tokenPageURL {
 46                        Link(destination: tokenPage) {
 47                            Text("Create one with full scope under Settings → API tokens")
 48                                .multilineTextAlignment(.leading)
 49                                .frame(maxWidth: .infinity, alignment: .leading)
 50                        }
 51                        Text("Or over SSH on a machine that has your key:")
 52                    } else {
 53                        Text("Create one over SSH on a machine that has your key:")
 54                    }
 55                    Text(verbatim: "gitbay auth token create --name iphone --scope full --ttl 90d")
 56                        .font(.gbMono(.caption))
 57                        .textSelection(.enabled)
 58                    Text("Full scope can comment and merge, and on an admin account, administer the instance. A read-only token cannot comment or merge.")
 59                }
 60            }
 61
 62            if let errorMessage {
 63                Section {
 64                    GBNotice(errorMessage)
 65                }
 66            }
 67
 68            Section {
 69                Button {
 70                    signIn()
 71                } label: {
 72                    if signingIn {
 73                        ProgressView()
 74                            .frame(maxWidth: .infinity)
 75                    } else {
 76                        Text("Sign In")
 77                            .frame(maxWidth: .infinity)
 78                    }
 79                }
 80                .disabled(signingIn || token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
 81            }
 82        }
 83        .navigationTitle("Sign in to gitbay")
 84        .onAppear { tokenFieldFocused = true }
 85    }
 86
 87    private func signIn() {
 88        signingIn = true
 89        errorMessage = nil
 90        Task {
 91            defer { signingIn = false }
 92            do {
 93                try await session.signIn(instanceURL: instanceURL, token: token)
 94                token = ""
 95            } catch let error as GitbayError {
 96                errorMessage = signInMessage(for: error)
 97            } catch let error as GitbayInstance.InvalidURL {
 98                errorMessage = error.errorDescription
 99            } catch {
100                errorMessage = "Something went wrong. Please try again."
101            }
102        }
103    }
104
105    /// Sign-in is the one place a 401 means "that token", not "your session".
106    private func signInMessage(for error: GitbayError) -> String {
107        if error.requiresReauthentication {
108            return "That token was not accepted. Check it was pasted whole and has not expired."
109        }
110        return error.userFacingMessage
111    }
112}
113
114#Preview {
115    NavigationStack {
116        SignInView()
117    }
118    .environment(SessionStore())
119}