181 lines
9.3 KiB
Swift
181 lines
9.3 KiB
Swift
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<String> {
|
|
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])
|
|
}
|
|
}
|