import SwiftUI /// `POST /api/auth/change-password` (see `Networking/AccountAPI.swift`) /// rotates EVERY session for the account on success, including this /// device's — and the new session is only delivered as a Set-Cookie, which /// this Bearer-token client never sees. There is no fresh token to recover /// from that response, so trying to "stay logged in" here would leave the /// app holding a dead token. Instead: show a brief confirmation, then log /// out locally and let the user sign back in with the new password. This is /// a deliberate consequence of how the backend's session rotation works, /// not an arbitrary UX choice — don't "fix" this into a silent success. struct ChangePasswordView: View { @EnvironmentObject private var appState: AppState @State private var currentPassword = "" @State private var newPassword = "" @State private var confirmPassword = "" @State private var isSubmitting = false @State private var errorMessage: String? @State private var showSuccessAlert = false private var passwordsMatch: Bool { !newPassword.isEmpty && newPassword == confirmPassword } private var canSubmit: Bool { !currentPassword.isEmpty && passwordsMatch && !isSubmitting } var body: some View { ScrollView { VStack(alignment: .leading, spacing: ZenithSpacing.md) { ZenithTextField( label: "Aktuelles Passwort", text: $currentPassword, placeholder: "Aktuelles Passwort", isSecure: true, textContentType: .password ) ZenithDivider() ZenithTextField( label: "Neues Passwort", text: $newPassword, placeholder: "Neues Passwort", isSecure: true, textContentType: .newPassword ) ZenithTextField( label: "Neues Passwort bestätigen", text: $confirmPassword, placeholder: "Neues Passwort bestätigen", isSecure: true, textContentType: .newPassword ) if !confirmPassword.isEmpty && !passwordsMatch { Text("Die Passwörter stimmen nicht überein.") .zenithBodySmStyle(color: .zenithError) } if let errorMessage { Text(errorMessage) .zenithBodySmStyle(color: .zenithError) } Button { Task { await submit() } } label: { if isSubmitting { ProgressView() .tint(.white) } else { Text("Passwort ändern") } } .buttonStyle(.zenithPrimary) .disabled(!canSubmit) .padding(.top, ZenithSpacing.sm) } .padding(ZenithSpacing.sm) } .background(Color.zenithBg) .navigationTitle("Passwort ändern") .navigationBarTitleDisplayMode(.inline) .alert("Passwort geändert", isPresented: $showSuccessAlert) { Button("OK") { Task { await appState.logout() } } } message: { Text("Passwort geändert. Bitte melde dich mit dem neuen Passwort erneut an.") } } private func submit() async { errorMessage = nil isSubmitting = true defer { isSubmitting = false } do { try await AccountAPI.changePassword(currentPassword: currentPassword, newPassword: newPassword) showSuccessAlert = true } catch { errorMessage = error.localizedDescription } } }