import SwiftUI #if canImport(LCEssentials) import LCEssentials #endif struct SavedCardsView: View { @Binding var appState: AppState @Environment(\.dismiss) var dismiss @State private var cards: [SavedCard] = [] @State private var isLoading = false @State private var errorMessage: String? = nil @State private var openSwipeRowId: String? = nil @State private var deletingCardId: String? = nil @State private var showAddCard = false private var canDelete: Bool { cards.count > 1 } private let tabBarClearance: CGFloat = 96 var body: some View { LCENavigationView { content } .setLeftButton(image: AnyView(AppBackButtonIcon())) { dismiss() } .setTitle(text: Text("Meus Cartões").font(AppTypography.heading2).foregroundStyle(AppColors.textPrimary)) .setNavigationBarBackgroundColor(AppColors.backgroundLight) } private var content: some View { ZStack { AppColors.backgroundLight.ignoresSafeArea() ScrollView(showsIndicators: false) { VStack(spacing: 20) { VStack(spacing: 12) { if isLoading { ProgressView() .frame(maxWidth: .infinity) .padding(.top, 32) } else if let errorMessage { Text(errorMessage) .font(AppTypography.body) .foregroundStyle(AppColors.textMuted) .multilineTextAlignment(.center) .padding(.top, 32) } else if cards.isEmpty { Text("Nenhum cartão cadastrado") .font(AppTypography.body) .foregroundStyle(AppColors.textMuted) .padding(.top, 32) } else { ForEach(cards) { card in if canDelete { SwipeToDeleteAddressRow( rowId: card.id, openRowId: $openSwipeRowId, isDeleting: deletingCardId == card.id, onDelete: { deleteCard(card) } ) { cardRow(card) .appContentShape(Rectangle()) .onTapGesture { if openSwipeRowId == card.id { openSwipeRowId = nil } } } .id(card.id) .opacity(deletingCardId == card.id ? 0.6 : 1.0) .disabled(deletingCardId != nil) } else { cardRow(card) } } } } } .padding(.horizontal, 20) .padding(.top, 18) .padding(.bottom, tabBarClearance + 20) } VStack { Spacer() addCardButton .padding(.bottom, tabBarClearance) } } .navigationBarBackButtonHidden(true) .appHiddenNavigationBar() .sheet(isPresented: $showAddCard) { NavigationStack { AddCardFormView(appState: appState, isFirstCard: cards.isEmpty) { newCard in cards.append(newCard) } } } .task { await loadCards() } } private var addCardButton: some View { ZStack(alignment: .bottom) { Rectangle() .fill(AppColors.backgroundLight) .frame(height: 136) Button(action: { showAddCard = true }) { HStack(spacing: 12) { Image(systemName: "creditcard.fill") .font(.system(size: 20)) Text("Adicionar novo cartão") .font(AppTypography.heading3) } .foregroundStyle(AppColors.textInverse) .frame(maxWidth: .infinity) .padding(.vertical, 18) .background(AppColors.primary) .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusXL, style: .continuous)) } .buttonStyle(.plain) .padding(.horizontal, 20) .padding(.bottom, 14) } } private func cardRow(_ card: SavedCard) -> some View { HStack(spacing: 14) { ZStack { RoundedRectangle(cornerRadius: 10, style: .continuous) .fill(AppColors.backgroundLight) .frame(width: 52, height: 52) if let logo = brandLogoName(for: card.brand) { Image(logo) .resizable() .scaledToFit() .frame(width: 36, height: 24) } else { Image(systemName: "creditcard.fill") .font(.system(size: 22)) .foregroundStyle(AppColors.textMuted) } } VStack(alignment: .leading, spacing: 3) { HStack(spacing: 6) { Text(card.displayLabel) .font(AppTypography.heading3) .foregroundStyle(AppColors.textPrimary) if card.isDefault { Text("Principal") .font(AppTypography.caption) .foregroundStyle(AppColors.primary) .padding(.horizontal, 8) .padding(.vertical, 3) .background(AppColors.brandSoft) .clipShape(Capsule()) } } Text("Vence \(card.expiryLabel)") .font(AppTypography.caption) .foregroundStyle(AppColors.textMuted) if !canDelete { Text("Ao menos um cartão deve permanecer") .font(AppTypography.caption) .foregroundStyle(AppColors.textMuted.opacity(0.7)) } } Spacer(minLength: 0) } .padding(14) .background(AppColors.surface) .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)) } private func brandLogoName(for brand: String?) -> String? { switch brand?.lowercased() .trimmingCharacters(in: .whitespacesAndNewlines) .folding(options: .diacriticInsensitive, locale: .current) { case "visa": return "visacard_logo" case "mastercard", "master": return "mastercard_logo" case "amex", "american express", "americanexpress": return "amexcard_logo" case "hipercard": return "hipercard_logo" case "alelo": return "alelocard_logo" case "sodexo": return "sodexo_logo" default: return nil } } @MainActor private func loadCards() async { guard isLoading == false else { return } isLoading = true errorMessage = nil defer { isLoading = false } do { let response = try await ApiService().listCards() if response.error == false { cards = response.result ?? [] } else { errorMessage = response.message ?? "Erro ao carregar cartões." } } catch { errorMessage = "Não foi possível carregar seus cartões." } } private func deleteCard(_ card: SavedCard) { guard canDelete, deletingCardId == nil else { return } deletingCardId = card.id openSwipeRowId = nil Task { do { let response = try await ApiService().deleteCard(cardId: card.id) if response.error == false { cards.removeAll { $0.id == card.id } } else { SnackbarCenter.shared.show( title: response.message ?? "Não foi possível excluir o cartão.", style: .error, icon: "xmark.octagon.fill", duration: 3.0 ) } } catch { SnackbarCenter.shared.show( title: "Erro ao excluir cartão.", style: .error, icon: "xmark.octagon.fill", duration: 3.0 ) } deletingCardId = nil } } }