krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.4.0: Hutch/Views/Auth/AuthView.swift · raw
1import SwiftUI
2
3/// Token entry screen shown when the user is not authenticated.
4struct TokenEntryView: View {
5 private let createAccountURL = URL(string: "https://meta.sr.ht/register")!
6 private let personalAccessTokensURL = URL(string: "https://meta.sr.ht/oauth2/personal-token")!
7
8 @Environment(AppState.self) private var appState
9 @State private var token = ""
10 @State private var isConnecting = false
11 @State private var errorMessage: String?
12
13 var body: some View {
14 NavigationStack {
15 Form {
16 Section {
17 Text("Enter your SourceHut personal access token to connect.")
18 .themedRow()
19 } header: {
20 Text("Welcome to Hutch")
21 } footer: {
22 Text("Hutch stores your SourceHut personal access token securely in the iOS keychain.")
23 }
24
25 Section {
26 SecureField("Personal Access Token", text: $token)
27 .textContentType(.password)
28 .autocorrectionDisabled()
29 .textInputAutocapitalization(.never)
30 .disabled(isConnecting)
31 .themedRow()
32 } header: {
33 Text("Token")
34 }
35
36 if let errorMessage {
37 Section {
38 Label {
39 Text(errorMessage)
40 } icon: {
41 Image(systemName: "exclamationmark.triangle.fill")
42 .foregroundStyle(.red)
43 }
44 .foregroundStyle(.red)
45 .themedRow()
46 }
47 }
48
49 Section {
50 Button {
51 connect()
52 } label: {
53 HStack {
54 Text("Connect")
55 if isConnecting {
56 Spacer()
57 ProgressView()
58 }
59 }
60 }
61 .disabled(tokenTrimmed.isEmpty || isConnecting)
62 .themedRow()
63 }
64
65 Section {
66 Link(destination: createAccountURL) {
67 Label("Create SourceHut account", systemImage: "person.badge.plus")
68 }
69 .themedRow()
70
71 Link(destination: personalAccessTokensURL) {
72 Label("Create Personal Access Token", systemImage: "key")
73 }
74 .themedRow()
75 } header: {
76 Text("Need an account?")
77 } footer: {
78 Text("These links open SourceHut in your browser. After signing up, create a Personal Access Token there and paste it here.")
79 }
80 }
81 .themedList()
82 .navigationTitle("Hutch")
83 }
84 }
85
86 private var tokenTrimmed: String {
87 token.trimmingCharacters(in: .whitespacesAndNewlines)
88 }
89
90 private func connect() {
91 errorMessage = nil
92 isConnecting = true
93 Task {
94 do {
95 try await appState.connect(with: tokenTrimmed)
96 } catch {
97 errorMessage = error.userFacingMessage
98 }
99 isConnecting = false
100 }
101 }
102}