feat: add iOS support and harden receipt scanning

This commit is contained in:
Timo
2026-08-20 23:48:26 +02:00
parent f5e06c32b0
commit cf1b799e1b
107 changed files with 11755 additions and 122 deletions

View File

@@ -0,0 +1,110 @@
import SwiftUI
/// "Registrieren" screen. On success this does NOT sign the user in the
/// backend requires the emailed confirmation link to be opened first, exactly
/// like the web signup flow (see `AppState.signup(name:email:password:)`)
/// so success routes to `VerificationPendingView` via `onSignupSucceeded`,
/// not into `AppState`.
struct SignupView: View {
@EnvironmentObject private var appState: AppState
var onSignupSucceeded: (_ email: String) -> Void
var onSwitchToLogin: () -> Void
@State private var name = ""
@State private var email = ""
@State private var password = ""
@State private var isSubmitting = false
private var canSubmit: Bool {
!email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
&& !password.isEmpty
&& !isSubmitting
}
var body: some View {
ScrollView {
VStack(spacing: ZenithSpacing.md) {
Text("Konto erstellen")
.zenithHeadlineLgStyle()
.padding(.top, ZenithSpacing.lg)
VStack(spacing: ZenithSpacing.sm) {
ZenithTextField(
label: "Name (optional)",
text: $name,
textContentType: .name,
autocapitalization: .words
)
ZenithTextField(
label: "E-Mail",
text: $email,
keyboardType: .emailAddress,
textContentType: .username,
autocapitalization: .never,
autocorrectionDisabled: true
)
ZenithTextField(
label: "Passwort",
text: $password,
isSecure: true,
textContentType: .newPassword
)
}
if let error = appState.lastError {
Text(error)
.zenithBodySmStyle(color: .zenithError)
.multilineTextAlignment(.center)
}
Button {
Task { await submit() }
} label: {
Group {
if isSubmitting {
ProgressView()
.tint(.white)
} else {
Text("Konto erstellen")
}
}
.frame(maxWidth: .infinity)
}
.buttonStyle(.zenithPrimary)
.disabled(!canSubmit)
Button("Schon ein Konto? Anmelden") {
appState.lastError = nil
onSwitchToLogin()
}
.buttonStyle(.zenithPlain)
.padding(.top, ZenithSpacing.xs)
}
.padding(.horizontal, ZenithSpacing.md)
.padding(.bottom, ZenithSpacing.lg)
}
.scrollDismissesKeyboard(.interactively)
.background(Color.zenithBg)
}
private func submit() async {
guard canSubmit else { return }
isSubmitting = true
defer { isSubmitting = false }
let trimmedEmail = email.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
let succeeded = await appState.signup(
name: trimmedName.isEmpty ? nil : trimmedName,
email: trimmedEmail,
password: password
)
if succeeded {
onSignupSucceeded(trimmedEmail)
}
}
}