Payments
This commit is contained in:
@@ -56,11 +56,54 @@ extension StoreDetailView {
|
||||
return "R$ --"
|
||||
}
|
||||
|
||||
var isStoreOpen: Bool {
|
||||
info?.isOpen ?? true
|
||||
}
|
||||
|
||||
var summaryCardHeight: CGFloat {
|
||||
summaryCardBaseHeight + (isStoreOpen ? 0 : closedBannerHeight)
|
||||
}
|
||||
|
||||
var closedStoreBannerText: String {
|
||||
let label = info?.statusLabel?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
if label.isEmpty {
|
||||
return "Loja fechada • Consulte o horário de abertura"
|
||||
}
|
||||
let normalized = label.lowercased()
|
||||
if normalized.hasPrefix("fechado") {
|
||||
let cleaned = label.replacingOccurrences(of: "Fechado", with: "")
|
||||
.replacingOccurrences(of: "fechado", with: "")
|
||||
.trimmingCharacters(in: CharacterSet(charactersIn: " -:•"))
|
||||
if cleaned.isEmpty == false {
|
||||
return "Loja fechada • \(cleaned)"
|
||||
}
|
||||
}
|
||||
return "Loja fechada • \(label)"
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func loadStoreData() async {
|
||||
func loadStoreData(forceRefresh: Bool = false) async {
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
|
||||
let infoCacheKey = "store-info:\(storeId)"
|
||||
let catalogCacheKey = "store-catalog:\(storeId)"
|
||||
|
||||
if forceRefresh == false,
|
||||
let cachedInfo: StoreInfoResult = AppContentCache.shared.value(for: infoCacheKey, as: StoreInfoResult.self),
|
||||
let cachedCatalog: [StoreCatalogCategory] = AppContentCache.shared.value(for: catalogCacheKey, as: [StoreCatalogCategory].self) {
|
||||
info = cachedInfo
|
||||
categories = cachedCatalog
|
||||
selectedCategoryId = cachedCatalog.first?.id
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
if forceRefresh {
|
||||
AppContentCache.shared.invalidate(prefix: "store-info:\(storeId)")
|
||||
AppContentCache.shared.invalidate(prefix: "store-catalog:\(storeId)")
|
||||
}
|
||||
|
||||
do {
|
||||
async let infoRequest = ApiService().storeInfo(storeId: storeId)
|
||||
async let catalogRequest = ApiService().storeCatalog(storeId: storeId)
|
||||
@@ -80,6 +123,10 @@ extension StoreDetailView {
|
||||
info = infoResponse.result
|
||||
categories = catalogResponse.result ?? []
|
||||
selectedCategoryId = categories.first?.id
|
||||
if let info = infoResponse.result {
|
||||
AppContentCache.shared.set(info, for: infoCacheKey, ttl: 300)
|
||||
}
|
||||
AppContentCache.shared.set(categories, for: catalogCacheKey, ttl: 300)
|
||||
isLoading = false
|
||||
} catch {
|
||||
isLoading = false
|
||||
@@ -96,15 +143,16 @@ extension StoreDetailView {
|
||||
}
|
||||
|
||||
func resolvedURL(_ raw: String?) -> String? {
|
||||
guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
raw.isEmpty == false else { return nil }
|
||||
guard var normalized = raw?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
normalized.isEmpty == false else { return nil }
|
||||
|
||||
let lower = raw.lowercased()
|
||||
if lower.hasPrefix("http://") || lower.hasPrefix("https://") {
|
||||
return raw
|
||||
normalized = normalized.replacingOccurrences(of: "\\/", with: "/")
|
||||
let lower = normalized.lowercased()
|
||||
if lower.hasPrefix("http://") || lower.hasPrefix("https://") || lower.hasPrefix("data:image") {
|
||||
return normalized
|
||||
}
|
||||
let base = ApiConfig.baseURL.absoluteString.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
|
||||
let path = raw.hasPrefix("/") ? raw : "/\(raw)"
|
||||
let path = normalized.hasPrefix("/") ? normalized : "/\(normalized)"
|
||||
return "\(base)\(path)"
|
||||
}
|
||||
|
||||
@@ -113,6 +161,23 @@ extension StoreDetailView {
|
||||
return String(format: "R$ %.2f", value).replacingOccurrences(of: ".", with: ",")
|
||||
}
|
||||
|
||||
func listPriceLabel(for product: StoreCatalogProduct, in category: StoreCatalogCategory) -> String {
|
||||
guard product.type?.lowercased() == "pizza", product.pizzaPrices.isEmpty == false else {
|
||||
return formatCurrency(product.price)
|
||||
}
|
||||
|
||||
if let firstSizeId = category.pizzaConfig?.sizes.first?.id,
|
||||
let firstSizePrice = product.pizzaPrices[firstSizeId] {
|
||||
return "A partir de \(formatCurrency(firstSizePrice))"
|
||||
}
|
||||
|
||||
if let fallback = product.pizzaPrices.sorted(by: { $0.key < $1.key }).first?.value {
|
||||
return "A partir de \(formatCurrency(fallback))"
|
||||
}
|
||||
|
||||
return formatCurrency(product.price)
|
||||
}
|
||||
|
||||
var topSectionHeight: CGFloat {
|
||||
cardTopInset + summaryCardHeight
|
||||
}
|
||||
@@ -146,7 +211,53 @@ extension StoreDetailView {
|
||||
.reduce(0) { $0 + $1.quantity }
|
||||
}
|
||||
|
||||
func quantityInCart(for item: StoreCatalogListItem) -> Int {
|
||||
if item.isPizzaSummary {
|
||||
let ids = Set(item.pizzaProductIds)
|
||||
return appState.cart.items
|
||||
.filter { $0.storeId == storeId && ids.contains($0.productId) }
|
||||
.reduce(0) { $0 + $1.quantity }
|
||||
}
|
||||
return quantityInCart(for: item.product.id)
|
||||
}
|
||||
|
||||
func listItems(for category: StoreCatalogCategory) -> [StoreCatalogListItem] {
|
||||
if category.isPizzaCategory {
|
||||
guard let first = category.products.first else { return [] }
|
||||
let representativeImage = category.products
|
||||
.compactMap(\.image)
|
||||
.first { $0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false }
|
||||
|
||||
return [
|
||||
StoreCatalogListItem(
|
||||
id: "\(category.id)::pizza-summary",
|
||||
product: first,
|
||||
title: "Pizza de varios sabores",
|
||||
description: "Escolha o tamanho da sua fome",
|
||||
imageURL: representativeImage ?? first.image,
|
||||
isPizzaSummary: true,
|
||||
pizzaCategoryId: category.id,
|
||||
pizzaProductIds: category.products.map(\.id)
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
return category.products.map { product in
|
||||
StoreCatalogListItem(
|
||||
id: product.id,
|
||||
product: product,
|
||||
title: product.name,
|
||||
description: product.description,
|
||||
imageURL: product.image
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func requestAddToCart(_ item: CartItemState) {
|
||||
guard isStoreOpen else {
|
||||
SnackbarCenter.shared.show(title: "Loja fechada no momento.", style: .warning, icon: "moon.zzz.fill", duration: 2.0)
|
||||
return
|
||||
}
|
||||
pendingCartAction = .add
|
||||
if shouldAskForStoreSwitch(for: storeId) {
|
||||
pendingCartItem = item
|
||||
@@ -158,6 +269,10 @@ extension StoreDetailView {
|
||||
}
|
||||
|
||||
func requestSetCartItem(_ item: CartItemState) {
|
||||
guard isStoreOpen || item.quantity <= 0 else {
|
||||
SnackbarCenter.shared.show(title: "Loja fechada no momento.", style: .warning, icon: "moon.zzz.fill", duration: 2.0)
|
||||
return
|
||||
}
|
||||
pendingCartAction = .set
|
||||
if shouldAskForStoreSwitch(for: storeId) && item.quantity > 0 {
|
||||
pendingCartItem = item
|
||||
@@ -169,6 +284,10 @@ extension StoreDetailView {
|
||||
}
|
||||
|
||||
func requestOpenProductSheet(_ product: StoreCatalogProduct) {
|
||||
guard isStoreOpen else {
|
||||
SnackbarCenter.shared.show(title: "Loja fechada no momento.", style: .warning, icon: "moon.zzz.fill", duration: 2.0)
|
||||
return
|
||||
}
|
||||
pendingCartAction = .openProductSheet
|
||||
if shouldAskForStoreSwitch(for: storeId) {
|
||||
pendingCartItem = nil
|
||||
@@ -179,6 +298,22 @@ extension StoreDetailView {
|
||||
selectedProduct = product
|
||||
}
|
||||
|
||||
func requestOpenPizzaSheet(categoryId: String) {
|
||||
guard isStoreOpen else {
|
||||
SnackbarCenter.shared.show(title: "Loja fechada no momento.", style: .warning, icon: "moon.zzz.fill", duration: 2.0)
|
||||
return
|
||||
}
|
||||
pendingCartAction = .openPizzaSheet
|
||||
if shouldAskForStoreSwitch(for: storeId) {
|
||||
pendingCartItem = nil
|
||||
pendingProductSheet = nil
|
||||
pendingPizzaCategoryId = categoryId
|
||||
showSwitchStoreAlert = true
|
||||
return
|
||||
}
|
||||
selectedPizzaCategoryId = categoryId
|
||||
}
|
||||
|
||||
func applyAddToCart(_ item: CartItemState) {
|
||||
if appState.cart.storeId == nil {
|
||||
appState.cart.storeId = storeId
|
||||
@@ -224,4 +359,5 @@ enum CartAction {
|
||||
case add
|
||||
case set
|
||||
case openProductSheet
|
||||
case openPizzaSheet
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user