Payments
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user