feat: add iOS support and harden receipt scanning
This commit is contained in:
110
app/ios/ScanReceipts/Features/Auth/SignupView.swift
Normal file
110
app/ios/ScanReceipts/Features/Auth/SignupView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user