- SwiftUI + async/await architecture - Barcode scanning with AVFoundation - Product display with score ring, NOVA badge, nutrition - Alternatives with sort/filter - Auth (login/register) - Favorites & history - Account management - Dark theme - Connected to food.payfrit.com API (Open Food Facts proxy) Co-Authored-By: Claude <noreply@anthropic.com>
234 lines
9.3 KiB
Swift
234 lines
9.3 KiB
Swift
import SwiftUI
|
|
|
|
struct AccountScreen: View {
|
|
@EnvironmentObject var appState: AppState
|
|
@State private var showLogoutConfirm = false
|
|
@State private var showDeleteConfirm = false
|
|
@State private var isExporting = false
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Group {
|
|
if !appState.isAuthenticated {
|
|
// Not logged in
|
|
VStack(spacing: 20) {
|
|
Spacer()
|
|
Image(systemName: "person.crop.circle")
|
|
.font(.system(size: 80))
|
|
.foregroundColor(.gray)
|
|
|
|
Text("Your Account")
|
|
.font(.title2.bold())
|
|
|
|
Text("Log in to access your profile, export your data, and manage your account.")
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.horizontal, 40)
|
|
|
|
Button {
|
|
appState.showLoginSheet = true
|
|
} label: {
|
|
Text("Log In")
|
|
.font(.headline)
|
|
.foregroundColor(.black)
|
|
.frame(maxWidth: .infinity)
|
|
.padding()
|
|
.background(Color.green)
|
|
.cornerRadius(12)
|
|
}
|
|
.padding(.horizontal, 40)
|
|
|
|
Button {
|
|
appState.showRegisterSheet = true
|
|
} label: {
|
|
Text("Create Account")
|
|
.font(.headline)
|
|
.foregroundColor(.green)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
} else {
|
|
List {
|
|
// Profile Section
|
|
Section {
|
|
if let profile = appState.userProfile {
|
|
HStack(spacing: 16) {
|
|
ZStack {
|
|
Circle()
|
|
.fill(Color.green.opacity(0.2))
|
|
.frame(width: 60, height: 60)
|
|
Text(profile.name.prefix(1).uppercased())
|
|
.font(.title.bold())
|
|
.foregroundColor(.green)
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(profile.name)
|
|
.font(.headline)
|
|
Text(profile.email)
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
.padding(.vertical, 8)
|
|
}
|
|
}
|
|
|
|
// Premium Section
|
|
Section {
|
|
if appState.isPremium {
|
|
HStack {
|
|
Image(systemName: "star.fill")
|
|
.foregroundColor(.yellow)
|
|
Text("Premium Member")
|
|
.font(.headline)
|
|
Spacer()
|
|
Text("Active")
|
|
.foregroundColor(.green)
|
|
}
|
|
} else {
|
|
HStack {
|
|
Image(systemName: "star")
|
|
.foregroundColor(.yellow)
|
|
VStack(alignment: .leading) {
|
|
Text("Go Premium")
|
|
.font(.headline)
|
|
Text("Remove sponsored content")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
Spacer()
|
|
Image(systemName: "chevron.right")
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Data Section
|
|
Section("Your Data") {
|
|
Button {
|
|
exportData()
|
|
} label: {
|
|
HStack {
|
|
Image(systemName: "square.and.arrow.up")
|
|
Text("Export Data")
|
|
Spacer()
|
|
if isExporting {
|
|
ProgressView()
|
|
}
|
|
}
|
|
}
|
|
.disabled(isExporting)
|
|
}
|
|
|
|
// Account Actions
|
|
Section {
|
|
Button {
|
|
showLogoutConfirm = true
|
|
} label: {
|
|
HStack {
|
|
Image(systemName: "rectangle.portrait.and.arrow.right")
|
|
Text("Log Out")
|
|
}
|
|
}
|
|
|
|
Button(role: .destructive) {
|
|
showDeleteConfirm = true
|
|
} label: {
|
|
HStack {
|
|
Image(systemName: "trash")
|
|
Text("Delete Account")
|
|
}
|
|
}
|
|
}
|
|
|
|
// App Info
|
|
Section("About") {
|
|
HStack {
|
|
Text("Version")
|
|
Spacer()
|
|
Text("1.0.0")
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
Link(destination: URL(string: "https://food.payfrit.com/privacy")!) {
|
|
HStack {
|
|
Text("Privacy Policy")
|
|
Spacer()
|
|
Image(systemName: "arrow.up.right")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
|
|
Link(destination: URL(string: "https://food.payfrit.com/terms")!) {
|
|
HStack {
|
|
Text("Terms of Service")
|
|
Spacer()
|
|
Image(systemName: "arrow.up.right")
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.navigationTitle("Account")
|
|
.alert("Log Out", isPresented: $showLogoutConfirm) {
|
|
Button("Cancel", role: .cancel) {}
|
|
Button("Log Out", role: .destructive) {
|
|
logout()
|
|
}
|
|
} message: {
|
|
Text("Are you sure you want to log out?")
|
|
}
|
|
.alert("Delete Account", isPresented: $showDeleteConfirm) {
|
|
Button("Cancel", role: .cancel) {}
|
|
Button("Delete", role: .destructive) {
|
|
deleteAccount()
|
|
}
|
|
} message: {
|
|
Text("This action cannot be undone. All your data will be permanently deleted.")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func logout() {
|
|
Task {
|
|
try? await APIService.shared.logout()
|
|
await appState.clearAuth()
|
|
}
|
|
}
|
|
|
|
private func deleteAccount() {
|
|
Task {
|
|
do {
|
|
try await APIService.shared.deleteAccount()
|
|
await appState.clearAuth()
|
|
} catch {
|
|
appState.showError(error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func exportData() {
|
|
isExporting = true
|
|
Task {
|
|
do {
|
|
let url = try await APIService.shared.exportData()
|
|
await MainActor.run {
|
|
isExporting = false
|
|
UIApplication.shared.open(url)
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
isExporting = false
|
|
appState.showError(error.localizedDescription)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|