Ban Yaro Go — Phase 1 Foundation
SwiftUI/SwiftData iOS-Client, redet mit https://banyaro.app FastAPI-Backend. Bundle-ID app.banyaro.ios, Xcode-26-Projekt mit synchronisierten Ordnern. Drin: - APIClient (URLSession + Bearer + convertFromSnakeCase Decoder) - KeychainStore + AuthSession (@Observable) für persistenten Login - LoginView, MainTabView, SettingsView (mit Logout) - RoutesListView + RouteDetailView mit MapKit-Polyline aus preview_track - DogsListView mit Foto-Avatar - App-Icon (Pfote auf Banyaro-Amber)
This commit is contained in:
commit
81681130e6
20 changed files with 1129 additions and 0 deletions
78
BanYaroGo/Views/LoginView.swift
Normal file
78
BanYaroGo/Views/LoginView.swift
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import SwiftUI
|
||||
|
||||
struct LoginView: View {
|
||||
@Environment(AuthSession.self) private var auth
|
||||
|
||||
@State private var email = ""
|
||||
@State private var password = ""
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 24) {
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "pawprint.fill")
|
||||
.font(.system(size: 80))
|
||||
.foregroundStyle(Color.accentColor)
|
||||
|
||||
VStack(spacing: 6) {
|
||||
Text("Ban Yaro Go")
|
||||
.font(.largeTitle.bold())
|
||||
Text("Melde dich mit deinem banyaro-Account an.")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
VStack(spacing: 12) {
|
||||
TextField("E-Mail", text: $email)
|
||||
.textContentType(.emailAddress)
|
||||
.keyboardType(.emailAddress)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
.padding()
|
||||
.background(.background.secondary, in: RoundedRectangle(cornerRadius: 12))
|
||||
|
||||
SecureField("Passwort", text: $password)
|
||||
.textContentType(.password)
|
||||
.padding()
|
||||
.background(.background.secondary, in: RoundedRectangle(cornerRadius: 12))
|
||||
}
|
||||
|
||||
if let error = auth.errorMessage {
|
||||
Text(error)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.red)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
Button {
|
||||
Task {
|
||||
await auth.login(
|
||||
email: email.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
password: password
|
||||
)
|
||||
}
|
||||
} label: {
|
||||
Group {
|
||||
if auth.isLoggingIn {
|
||||
ProgressView().tint(.white)
|
||||
} else {
|
||||
Text("Login").bold()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 50)
|
||||
}
|
||||
.background(Color.accentColor, in: RoundedRectangle(cornerRadius: 12))
|
||||
.foregroundStyle(.white)
|
||||
.disabled(auth.isLoggingIn || email.isEmpty || password.isEmpty)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 28)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
LoginView()
|
||||
.environment(AuthSession())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue