import SwiftUI struct AccountScreen: View { @EnvironmentObject var appState: AppState @Environment(\.dismiss) var dismiss @State private var tierStatus: TierStatus? @State private var ledger: LedgerResponse? @State private var isLoading = true @State private var error: String? @State private var showingMyTasks = false @State private var showActivationInfo = false var body: some View { ScrollView { VStack(spacing: 16) { if isLoading { ProgressView() .padding(.top, 40) } else if let error = error { errorView(error) } else { if let tier = tierStatus { tierCard(tier) activationCard(tier) } if let ledger = ledger { earningsCard(ledger) } logoutButton } } .padding(16) } .navigationTitle("Account") .overlay(alignment: .bottomTrailing) { Button { showingMyTasks = true } label: { Image(systemName: "checkmark.circle.fill") .font(.title3) .padding(12) .background(Color.payfritGreen) .foregroundColor(.white) .clipShape(Circle()) .shadow(radius: 4) } .padding(.trailing, 16) .padding(.bottom, 16) } .navigationDestination(isPresented: $showingMyTasks) { MyTasksScreen() } .task { await loadData() } .refreshable { await loadData() } .alert("What is Activation?", isPresented: $showActivationInfo) { Button("Got it", role: .cancel) { } } message: { Text("Activation tracks your early earnings to help establish your account. Once you reach the activation cap, your account is fully activated and you can receive regular payouts. You can also complete activation early if you prefer.") } } // MARK: - Tier Card @ViewBuilder private func tierCard(_ tier: TierStatus) -> some View { VStack(alignment: .leading, spacing: 12) { HStack { Image(systemName: tier.tier >= 1 ? "checkmark.shield.fill" : "shield") .foregroundColor(tier.tier >= 1 ? .green : .secondary) .font(.title2) Text("Payout Status") .font(.headline) Spacer() } if tier.tier >= 1 { // Tier 1 unlocked HStack(spacing: 8) { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) Text("Tier 1 unlocked — Payouts enabled") .foregroundColor(.green) .font(.subheadline.weight(.medium)) } } else if !tier.stripe.hasAccount { // No account yet Text("Tier 1 is locked") .font(.subheadline) .foregroundColor(.secondary) Button { startStripeOnboarding() } label: { Label("Complete payout setup", systemImage: "arrow.right.circle.fill") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .tint(.payfritGreen) } else if tier.stripe.setupIncomplete { // Account exists but incomplete Text("Stripe needs more info to enable payouts.") .font(.subheadline) .foregroundColor(.secondary) Button { continueStripeOnboarding() } label: { Label("Continue payout setup", systemImage: "arrow.right.circle.fill") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .tint(.payfritGreen) } } .padding(16) .background(Color(.secondarySystemGroupedBackground)) .cornerRadius(12) } // MARK: - Activation Card @ViewBuilder private func activationCard(_ tier: TierStatus) -> some View { VStack(alignment: .leading, spacing: 12) { HStack { Image(systemName: tier.activation.isComplete ? "checkmark.circle.fill" : "star.circle") .foregroundColor(tier.activation.isComplete ? .green : .payfritGreen) .font(.title2) Text("Activation") .font(.headline) Spacer() } if tier.activation.isComplete { HStack(spacing: 8) { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) Text("Activation complete") .foregroundColor(.green) .font(.subheadline.weight(.medium)) } } else { // Progress bar VStack(alignment: .leading, spacing: 6) { ProgressView(value: tier.activation.progress) .tint(.payfritGreen) Text("\(tier.activation.balanceDollars) of \(tier.activation.capDollars) completed") .font(.subheadline) .foregroundColor(.secondary) } Button { earlyUnlock() } label: { Label("Complete activation now (optional)", systemImage: "bolt.fill") .frame(maxWidth: .infinity) } .buttonStyle(.bordered) Button { showActivationInfo = true } label: { Text("What is activation?") .font(.caption) .foregroundColor(.payfritGreen) } } } .padding(16) .background(Color(.secondarySystemGroupedBackground)) .cornerRadius(12) } // MARK: - Earnings Card @ViewBuilder private func earningsCard(_ ledger: LedgerResponse) -> some View { VStack(alignment: .leading, spacing: 12) { HStack { Image(systemName: "dollarsign.circle") .foregroundColor(.green) .font(.title2) Text("Earnings") .font(.headline) Spacer() VStack(alignment: .trailing) { Text(ledger.totals.totalNetDollars) .font(.title3.bold()) .foregroundColor(.green) Text("total earned") .font(.caption) .foregroundColor(.secondary) } } if !ledger.entries.isEmpty { Divider() ForEach(ledger.entries.prefix(20)) { entry in HStack { VStack(alignment: .leading, spacing: 2) { Text("Task #\(entry.taskId)") .font(.subheadline.weight(.medium)) Text(entry.createdAt) .font(.caption) .foregroundColor(.secondary) } Spacer() VStack(alignment: .trailing, spacing: 2) { Text(entry.netDollars) .font(.subheadline.weight(.medium)) .foregroundColor(.green) Text(entry.statusDisplay) .font(.caption2) .foregroundColor(entry.status == "transferred" ? .green : .secondary) .padding(.horizontal, 6) .padding(.vertical, 2) .background( entry.status == "transferred" ? Color.green.opacity(0.1) : Color(.systemGray5) ) .cornerRadius(4) } } .padding(.vertical, 2) } } else { Text("No earnings yet. Complete tasks to start earning!") .font(.subheadline) .foregroundColor(.secondary) } } .padding(16) .background(Color(.secondarySystemGroupedBackground)) .cornerRadius(12) } // MARK: - Logout private var logoutButton: some View { Button(role: .destructive) { Task { await APIService.shared.logout() await AuthStorage.shared.clearAuth() appState.clearAuth() } } label: { Label("Logout", systemImage: "rectangle.portrait.and.arrow.right") .frame(maxWidth: .infinity) } .buttonStyle(.bordered) .tint(.red) .padding(.top, 8) } // MARK: - Error private func errorView(_ msg: String) -> some View { VStack(spacing: 16) { Image(systemName: "exclamationmark.circle") .font(.system(size: 48)) .foregroundColor(.red) Text(msg) .multilineTextAlignment(.center) Button("Retry") { Task { await loadData() } } .buttonStyle(.borderedProminent) } .padding() } // MARK: - Actions private func loadData() async { isLoading = true error = nil // Load tier status (required) do { tierStatus = try await APIService.shared.getTierStatus() } catch { self.error = error.localizedDescription isLoading = false return } // Load ledger (optional — don't block screen if it fails) do { ledger = try await APIService.shared.getLedger() } catch { ledger = LedgerResponse() } isLoading = false } private func startStripeOnboarding() { Task { do { _ = try await APIService.shared.createStripeAccount() let urlStr = try await APIService.shared.getOnboardingLink() if let url = URL(string: urlStr) { await MainActor.run { #if os(iOS) UIApplication.shared.open(url) #endif } } // Refresh on return try? await Task.sleep(nanoseconds: 2_000_000_000) await loadData() } catch { self.error = error.localizedDescription } } } private func continueStripeOnboarding() { Task { do { let urlStr = try await APIService.shared.getOnboardingLink() if let url = URL(string: urlStr) { await MainActor.run { #if os(iOS) UIApplication.shared.open(url) #endif } } try? await Task.sleep(nanoseconds: 2_000_000_000) await loadData() } catch { self.error = error.localizedDescription } } } private func earlyUnlock() { Task { do { let urlStr = try await APIService.shared.getEarlyUnlockUrl() if let url = URL(string: urlStr) { await MainActor.run { #if os(iOS) UIApplication.shared.open(url) #endif } } try? await Task.sleep(nanoseconds: 2_000_000_000) await loadData() } catch { self.error = error.localizedDescription } } } }