a neat project
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add forgot password view

+76 -23
+28 -23
huntington/ContentView.swift
··· 16 16 private var client: HuntingtonClient { HuntingtonClient(session: session) } 17 17 18 18 var body: some View { 19 - TabView { 20 - Tab("Home", systemImage: "house") { 21 - HomeTab(accounts: accounts, transactions: transactions, 22 - isLoading: isLoading, errorMessage: errorMessage, 23 - isAuthenticated: session.isAuthenticated, 24 - onRefresh: loadData, onSignIn: { showLogin = true }) 25 - } 26 - Tab("Zelle", systemImage: "arrow.left.arrow.right") { 27 - ZelleTab() 28 - } 29 - Tab("Profile", systemImage: "person.circle") { 30 - ProfileTab(accounts: accounts, displayName: session.displayName, 31 - onSignOut: { session.signOut() }) 19 + Group { 20 + if session.isAuthenticated { 21 + TabView { 22 + Tab("Home", systemImage: "house") { 23 + HomeTab(accounts: accounts, transactions: transactions, 24 + isLoading: isLoading, errorMessage: errorMessage, 25 + onRefresh: loadData) 26 + } 27 + Tab("Zelle", systemImage: "arrow.left.arrow.right") { 28 + ZelleTab() 29 + } 30 + Tab("Profile", systemImage: "person.circle") { 31 + ProfileTab(accounts: accounts, displayName: session.displayName, 32 + onSignOut: { session.signOut() }) 33 + } 34 + } 35 + } else { 36 + NavigationStack { 37 + ContentUnavailableView("Not Signed In", systemImage: "lock", 38 + description: Text("Tap Sign In to connect your Huntington account.")) 39 + .toolbar { 40 + ToolbarItem(placement: .principal) { NeoWordmark() } 41 + ToolbarItem(placement: .primaryAction) { 42 + Button("Sign In") { showLogin = true } 43 + } 44 + } 45 + .navigationBarTitleDisplayMode(.inline) 46 + } 32 47 } 33 48 } 34 49 .overlay { ··· 115 130 let transactions: [Transaction] 116 131 let isLoading: Bool 117 132 let errorMessage: String? 118 - let isAuthenticated: Bool 119 133 let onRefresh: () async -> Void 120 - let onSignIn: () -> Void 121 134 122 135 var body: some View { 123 136 NavigationStack { ··· 126 139 ProgressView("Loading…") 127 140 } else if let error = errorMessage { 128 141 ContentUnavailableView(error, systemImage: "exclamationmark.triangle") 129 - } else if !isAuthenticated { 130 - ContentUnavailableView("Not Signed In", systemImage: "lock", 131 - description: Text("Tap Sign In to connect your Huntington account.")) 132 142 } else { 133 143 List { 134 144 if !accounts.isEmpty { ··· 156 166 .navigationBarTitleDisplayMode(.inline) 157 167 .toolbar { 158 168 ToolbarItem(placement: .principal) { NeoWordmark() } 159 - if !isAuthenticated { 160 - ToolbarItem(placement: .primaryAction) { 161 - Button("Sign In", action: onSignIn) 162 - } 163 - } 164 169 } 165 170 } 166 171 }
+4
huntington/HuntingtonSession.swift
··· 185 185 let cookieNames = cookieStorage.cookies?.map(\.name) ?? [] 186 186 guard cookieNames.contains("PD-ID") else { 187 187 print("[auth] pkmslogin failed (\(status)), body: \(String(data: data, encoding: .utf8)?.prefix(500) ?? "")") 188 + if let body = try? JSONSerialization.jsonObject(with: data) as? [String: Any], 189 + let op = body["operation"] as? String, op == "acct_locked" { 190 + throw HuntingtonError.authFailed("Your account is locked. You need to reset your password on the Huntington website before signing in.") 191 + } 188 192 if status == 200 || status == 302 { 189 193 throw HuntingtonError.authFailed("Incorrect username or password") 190 194 }
+44
huntington/LoginView.swift
··· 1 1 import SwiftUI 2 + import WebKit 2 3 3 4 struct LoginView: View { 4 5 var session: HuntingtonSession ··· 11 12 @State private var errorMessage: String? 12 13 @State private var deliveryOptions: [HuntingtonSession.OTPDeliveryOption] = [] 13 14 @State private var phase: Phase = .credentials 15 + @State private var showForgotPassword = false 14 16 15 17 enum Phase { case credentials, deliverySelection, codeEntry } 16 18 ··· 72 74 } 73 75 .contentShape(Rectangle()) 74 76 .onTapGesture { hideKeyboard() } 77 + .sheet(isPresented: $showForgotPassword) { 78 + ForgotPasswordSheet() 79 + } 75 80 } 76 81 } 77 82 ··· 109 114 .disabled(isLoading || username.isEmpty || password.isEmpty) 110 115 .padding(.horizontal, 24) 111 116 .padding(.top, 24) 117 + 118 + Button("Forgot Password?") { showForgotPassword = true } 119 + .font(.footnote) 120 + .foregroundStyle(.secondary) 121 + .padding(.top, 8) 112 122 } 113 123 114 124 @ViewBuilder ··· 237 247 to: nil, from: nil, for: nil) 238 248 } 239 249 } 250 + 251 + // MARK: - Forgot Password WebView 252 + 253 + struct ForgotPasswordView: UIViewRepresentable { 254 + private let url = URL(string: "https://onlinebanking.huntington.com/rol/Retail/SelfService/ForgotPassword/BeginForgotPasswordFlow/4")! 255 + 256 + func makeUIView(context: Context) -> WKWebView { 257 + let config = WKWebViewConfiguration() 258 + config.websiteDataStore = .nonPersistent() 259 + let webView = WKWebView(frame: .zero, configuration: config) 260 + webView.load(URLRequest(url: url)) 261 + return webView 262 + } 263 + 264 + func updateUIView(_ uiView: WKWebView, context: Context) {} 265 + } 266 + 267 + struct ForgotPasswordSheet: View { 268 + @Environment(\.dismiss) var dismiss 269 + 270 + var body: some View { 271 + NavigationStack { 272 + ForgotPasswordView() 273 + .ignoresSafeArea(edges: .bottom) 274 + .navigationTitle("Reset Password") 275 + .navigationBarTitleDisplayMode(.inline) 276 + .toolbar { 277 + ToolbarItem(placement: .cancellationAction) { 278 + Button("Done") { dismiss() } 279 + } 280 + } 281 + } 282 + } 283 + }