import SwiftUI struct ProfileView: View { @EnvironmentObject private var model: AppModel @State private var editingProfile = false @State private var changingPassword = false @State private var deletingAccount = false @State private var deletePassword = "" @State private var cardNumber = "" var body: some View { ScrollView { VStack(alignment: .leading, spacing: 16) { HStack { Text(model.text("Twój profil", "Your profile")).font(.largeTitle.bold()) Spacer() Button { editingProfile = true } label: { Image(systemName: "pencil.circle.fill").font(.title).foregroundStyle(KTheme.orange) } .accessibilityLabel(model.text("Edytuj profil", "Edit profile")) } VStack(alignment: .leading, spacing: 16) { ProfileValue(label: model.text("Imię i nazwisko", "Name"), value: model.user?.displayName ?? name) ProfileValue(label: "E-mail", value: model.user?.email ?? "—") ProfileValue(label: model.text("Telefon", "Phone"), value: model.user?.mobile ?? "—") ProfileValue(label: model.text("Kod pocztowy", "Postcode"), value: model.user?.postcode ?? "—") ProfileValue(label: model.text("Data urodzenia", "Date of birth"), value: model.profileForm().dob.nonEmpty ?? "—") ProfileValue(label: model.text("Numer karty", "Card number"), value: model.cardNumber.nonEmpty ?? "—") } .frame(maxWidth: .infinity, alignment: .leading) .kasztelanPanel() if model.cardNumber.isEmpty { cardAssignment } Button(model.text("Zmień hasło", "Change password")) { changingPassword = true } .buttonStyle(GlassButtonStyle()) Button(model.text("Wyloguj", "Sign out")) { Task { await model.logout() } } .buttonStyle(PrimaryButtonStyle()) Button(model.text("Usuń konto", "Delete account"), role: .destructive) { deletingAccount = true } .frame(maxWidth: .infinity, minHeight: 48) VStack(spacing: 12) { Link(model.text("Polityka prywatności", "Privacy policy"), destination: URL(string: "https://kasztelan.co.uk/privacy-policy/")!) Link(model.text("Informacje o usuwaniu konta", "Account deletion information"), destination: URL(string: "https://kasztelan.co.uk/delete-account/")!) Link(model.text("Regulamin", "Terms"), destination: URL(string: "https://kasztelan.co.uk/regulamin/")!) } .font(.subheadline.bold()) .foregroundStyle(KTheme.orange) .frame(maxWidth: .infinity) .padding(.top, 4) } .padding(20) } .background(AmbientBackground()) .toolbar { AppToolbar() } .toolbarBackground(.hidden, for: .navigationBar) .fullScreenCover(isPresented: $editingProfile) { EditProfileView() } .sheet(isPresented: $changingPassword) { ChangePasswordView() } .sheet(isPresented: $deletingAccount) { NavigationStack { VStack(spacing: 16) { Text(model.text("Usunięcie konta jest nieodwracalne. Potwierdź swoim hasłem.", "Deleting your account cannot be undone. Confirm with your password.")) KasztelanField(title: model.text("Hasło", "Password"), text: $deletePassword, secure: true, contentType: .password) Button(model.text("Usuń konto", "Delete account"), role: .destructive) { Task { if await model.deleteAccount(password: deletePassword) { deletingAccount = false } } } .buttonStyle(PrimaryButtonStyle()) Spacer() } .padding(20) .background(AmbientBackground()) .navigationTitle(model.text("Usuń konto", "Delete account")) .toolbar { ToolbarItem(placement: .cancellationAction) { Button(model.text("Anuluj", "Cancel")) { deletingAccount = false } } } } .presentationDetents([.medium]) } } private var name: String { let value = "\(model.user?.firstName ?? "") \(model.user?.lastName ?? "")".trimmingCharacters(in: .whitespaces) return value.nonEmpty ?? "—" } private var cardAssignment: some View { VStack(alignment: .leading, spacing: 10) { Text(model.text("Przypisz kartę", "Assign a card")).font(.headline) KasztelanField(title: model.text("13-cyfrowy numer karty", "13-digit card number"), text: $cardNumber, keyboard: .numberPad) Text(model.text("Po przypisaniu numeru nie można go zmienić.", "The number cannot be changed after assignment.")) .font(.caption).foregroundStyle(.secondary) Button(model.text("Przypisz", "Assign")) { Task { if await model.assignCard(cardNumber) { cardNumber = "" } } } .buttonStyle(PrimaryButtonStyle()) Button(model.text("Nie mam karty — wygeneruj cyfrową", "I don't have a card — create a digital one")) { Task { _ = await model.generateCard() } } .buttonStyle(GlassButtonStyle()) } .kasztelanPanel() } } private struct ProfileValue: View { let label: String let value: String var body: some View { VStack(alignment: .leading, spacing: 3) { Text(label).font(.caption).foregroundStyle(.secondary) Text(value.nonEmpty ?? "—").font(.title3.bold()) } .frame(maxWidth: .infinity, alignment: .leading) } } struct EditProfileView: View { @EnvironmentObject private var model: AppModel @Environment(\.dismiss) private var dismiss @State private var form = ProfileForm() var body: some View { NavigationStack { ScrollView { VStack(spacing: 12) { KasztelanField(title: model.text("Imię", "First name"), text: $form.firstName, contentType: .givenName) KasztelanField(title: model.text("Nazwisko", "Last name"), text: $form.lastName, contentType: .familyName) KasztelanField(title: "E-mail", text: $form.email, keyboard: .emailAddress, contentType: .emailAddress) KasztelanField(title: model.text("Telefon UK lub PL", "UK or Polish phone number"), text: $form.mobile, keyboard: .phonePad, contentType: .telephoneNumber) KasztelanField(title: model.text("Kod pocztowy UK", "UK postcode"), text: $form.postcode, contentType: .postalCode) KasztelanField(title: model.text("Data urodzenia: RRRR-MM-DD", "Date of birth: YYYY-MM-DD"), text: dateBinding, keyboard: .numberPad) Button(model.text("Zapisz", "Save")) { Task { if await model.saveProfile(form) { dismiss() } } } .buttonStyle(PrimaryButtonStyle()) } .frame(maxWidth: .infinity) .padding(20) } .background(AmbientBackground()) .navigationTitle(model.text("Edytuj profil", "Edit profile")) .toolbar { ToolbarItem(placement: .cancellationAction) { Button(model.text("Anuluj", "Cancel")) { dismiss() } } } .onAppear { form = model.profileForm() } } } private var dateBinding: Binding { Binding(get: { form.dob }, set: { form.dob = AuthView.formatDateInput($0) }) } } struct ChangePasswordView: View { @EnvironmentObject private var model: AppModel @Environment(\.dismiss) private var dismiss @State private var current = "" @State private var newPassword = "" @State private var confirmation = "" var body: some View { NavigationStack { VStack(spacing: 14) { KasztelanField(title: model.text("Obecne hasło", "Current password"), text: $current, secure: true, contentType: .password) KasztelanField(title: model.text("Nowe hasło", "New password"), text: $newPassword, secure: true, contentType: .newPassword) KasztelanField(title: model.text("Powtórz nowe hasło", "Repeat new password"), text: $confirmation, secure: true, contentType: .newPassword) Button(model.text("Zmień hasło", "Change password")) { guard newPassword == confirmation else { model.errorMessage = model.text("Nowe hasła nie są takie same.", "The new passwords do not match.") return } Task { if await model.changePassword(current: current, new: newPassword) { dismiss() } } } .buttonStyle(PrimaryButtonStyle()) Spacer() } .padding(20) .background(AmbientBackground()) .navigationTitle(model.text("Zmień hasło", "Change password")) .toolbar { ToolbarItem(placement: .cancellationAction) { Button(model.text("Anuluj", "Cancel")) { dismiss() } } } } .presentationDetents([.medium]) } }