This commit is contained in:
Daniel Arantes Loverde
2026-02-15 08:18:06 -03:00
parent d7b8b1864a
commit 0f5ad4c268
35 changed files with 4090 additions and 309 deletions

View File

@@ -3,73 +3,158 @@ import SwiftUI
struct CartView: View {
@Binding var appState: AppState
@State var openCheckout = false
@State var couponCode = ""
@State var appliedCouponCode: String? = nil
@State var discountValue: Double = 0
@State var deliveryFee: Double = 5
var body: some View {
VStack(spacing: 14) {
Text("Carrinho")
.font(AppTypography.heading1)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 20)
.padding(.top, 20)
ScrollView(showsIndicators: false) {
VStack(spacing: 16) {
Text("Meu Carrinho")
.font(AppTypography.heading1)
.foregroundStyle(AppColors.textPrimary)
.frame(maxWidth: .infinity, alignment: .center)
.padding(.top, 20)
if appState.cart.items.isEmpty {
VStack(spacing: 10) {
Text("Seu carrinho está vazio")
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
Text("Adicione produtos para continuar.")
.font(AppTypography.body)
.foregroundStyle(AppColors.textMuted)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
} else {
ScrollView(showsIndicators: false) {
if appState.cart.items.isEmpty {
VStack(spacing: 10) {
Text("Seu carrinho está vazio")
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
Text("Adicione produtos para continuar.")
.font(AppTypography.body)
.foregroundStyle(AppColors.textMuted)
}
.frame(maxWidth: .infinity, minHeight: 280, alignment: .center)
} else {
VStack(spacing: 12) {
if let storeName = appState.cart.storeName, storeName.isEmpty == false {
Text(storeName)
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 20)
}
ForEach(appState.cart.items) { item in
cartItemRow(item)
}
}
.padding(.bottom, 120)
.padding(.horizontal, 20)
couponSection
.padding(.horizontal, 20)
summarySection
.padding(.horizontal, 20)
}
}
.padding(.bottom, 120)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(AppColors.backgroundLight)
.safeAreaInset(edge: .bottom) {
if appState.cart.items.isEmpty == false {
VStack(spacing: 10) {
HStack {
Text("Total")
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
Spacer()
Text(formatCurrency(appState.cart.total))
.font(AppTypography.heading2)
.foregroundStyle(AppColors.primary)
}
.padding(.horizontal, 20)
.navigationDestination(isPresented: $openCheckout) {
CheckoutView(appState: $appState)
}
}
PrimaryButton(title: "Finalizar pedido", action: {})
.padding(.horizontal, 20)
private var subtotalValue: Double {
appState.cart.items.reduce(0) { $0 + (Double($1.quantity) * $1.unitPrice) }
}
private var totalValue: Double {
max(0, subtotalValue + deliveryFee - discountValue)
}
private var couponSection: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Cupom de Desconto")
.font(AppTypography.heading2)
.foregroundStyle(AppColors.textPrimary)
HStack(spacing: 10) {
HStack(spacing: 8) {
Image(systemName: "ticket")
.foregroundStyle(AppColors.textMuted)
TextField("Inserir cupom", text: $couponCode)
.appNoAutoCap()
}
.padding(.top, 10)
.padding(.bottom, 12)
.background(.ultraThinMaterial)
.padding(.horizontal, 12)
.frame(height: 50)
.background(AppColors.surface)
.overlay(
RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)
.stroke(AppColors.brandSoft, lineWidth: 1)
)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
Button("Aplicar") {
applyCoupon()
}
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textInverse)
.frame(width: 120, height: 50)
.background(AppColors.brandDark)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
.buttonStyle(.plain)
}
if let appliedCouponCode {
Text("Cupom aplicado: \(appliedCouponCode)")
.font(.caption)
.foregroundStyle(AppColors.primary)
}
}
}
private var summarySection: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Resumo de Valores")
.font(AppTypography.heading2)
.foregroundStyle(AppColors.textPrimary)
summaryRow(title: "Subtotal", value: formatCurrency(subtotalValue))
summaryRow(title: "Taxa de Entrega", value: formatCurrency(deliveryFee))
summaryRow(title: "Desconto", value: "-\(formatCurrency(discountValue))", valueColor: Color.red)
Divider()
summaryRow(title: "Total", value: formatCurrency(totalValue), highlighted: true)
Button {
openCheckout = true
} label: {
HStack(spacing: 10) {
Text("Ir para o Pagamento")
.font(AppTypography.heading2)
Image(systemName: "arrow.right")
.font(.system(size: 18, weight: .bold))
}
.foregroundStyle(AppColors.textInverse)
.frame(maxWidth: .infinity, minHeight: 54)
.background(AppColors.primary)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
.buttonStyle(.plain)
}
.padding(16)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusXL, style: .continuous))
}
private func summaryRow(title: String, value: String, valueColor: Color? = nil, highlighted: Bool = false) -> some View {
HStack {
Text(title)
.font(highlighted ? AppTypography.heading2 : AppTypography.body)
.foregroundStyle(AppColors.textMuted)
Spacer()
Text(value)
.font(highlighted ? AppTypography.heading1 : AppTypography.heading3)
.foregroundStyle(valueColor ?? AppColors.textPrimary)
}
}
private func cartItemRow(_ item: CartItemState) -> some View {
HStack(spacing: 12) {
VStack(alignment: .leading, spacing: 4) {
HStack(spacing: 14) {
AsyncStoreImage(imageURL: item.imageURL)
.frame(width: 78, height: 78)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
VStack(alignment: .leading, spacing: 6) {
Text(item.name)
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
@@ -87,13 +172,13 @@ struct CartView: View {
Spacer()
HStack(spacing: 8) {
HStack(spacing: 10) {
Button(action: { appState.cart.decrement(itemId: item.id) }) {
Image(systemName: "minus")
.font(.system(size: 13, weight: .bold))
.foregroundStyle(AppColors.textPrimary)
.frame(width: 28, height: 28)
.background(AppColors.brandSoft)
.frame(width: 32, height: 32)
.background(AppColors.backgroundLight)
.clipShape(Circle())
}
.buttonStyle(.plain)
@@ -107,18 +192,39 @@ struct CartView: View {
Image(systemName: "plus")
.font(.system(size: 13, weight: .bold))
.foregroundStyle(AppColors.textPrimary)
.frame(width: 28, height: 28)
.frame(width: 32, height: 32)
.background(AppColors.tertiary)
.clipShape(Circle())
}
.buttonStyle(.plain)
}
.padding(.horizontal, 8)
.padding(.vertical, 6)
.background(AppColors.backgroundLight)
.clipShape(Capsule())
}
.padding(.horizontal, 14)
.padding(.horizontal, 12)
.padding(.vertical, 12)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
.padding(.horizontal, 20)
}
private func applyCoupon() {
let normalized = couponCode.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
guard normalized.isEmpty == false else {
appliedCouponCode = nil
discountValue = 0
return
}
if normalized == "DESCONTO10" {
appliedCouponCode = normalized
discountValue = min(subtotalValue, subtotalValue * 0.1)
return
}
appliedCouponCode = nil
discountValue = 0
}
private func formatCurrency(_ value: Double) -> String {