Fix login

This commit is contained in:
Daniel Arantes Loverde
2026-02-21 18:51:39 -03:00
parent 0f5ad4c268
commit 3a3dc7217b
17 changed files with 1296 additions and 91 deletions

View File

@@ -201,16 +201,3 @@ struct AddressListItem: Identifiable {
let icon: String
let isPrimary: Bool
}
struct OrdersView: View {
var body: some View {
VStack(spacing: 16) {
Text("Pedidos")
.font(AppTypography.heading1)
Text("Historico vazio")
.foregroundStyle(AppColors.secondary)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(AppColors.backgroundLight)
}
}

View File

@@ -211,21 +211,6 @@ extension CheckoutView {
func handleConfirmPaymentTap() async {
guard canConfirmPayment else { return }
if useInAppPayment == false {
SnackbarCenter.shared.show(title: "Pagamento presencial selecionado.", style: .info, icon: "creditcard.fill", duration: 2.0)
return
}
if paymentMethod == .creditCard {
openCardPayment = true
return
}
guard paymentMethod == .pix else {
SnackbarCenter.shared.show(title: "Método de pagamento não suportado no app.", style: .warning, icon: "exclamationmark.triangle.fill", duration: 2.0)
return
}
guard let storeId = appState.cart.storeId, storeId.isEmpty == false else {
SnackbarCenter.shared.show(title: "Loja do pedido não encontrada.", style: .error, icon: "xmark.octagon.fill", duration: 2.0)
return
@@ -233,7 +218,13 @@ extension CheckoutView {
await refreshSelectedCustomerAddress()
let payloadBuildResult = buildCreateOrderPayload(paymentMethod: .pix)
let effectivePaymentMethod = paymentMethod
if useInAppPayment && availableInAppPaymentMethods.contains(effectivePaymentMethod) == false {
SnackbarCenter.shared.show(title: "Método de pagamento não suportado no app.", style: .warning, icon: "exclamationmark.triangle.fill", duration: 2.0)
return
}
let payloadBuildResult = buildCreateOrderPayload(paymentMethod: effectivePaymentMethod)
guard case .success(let payload) = payloadBuildResult else {
let message: String
if case .failure(let reason) = payloadBuildResult {
@@ -252,7 +243,7 @@ extension CheckoutView {
let response = try await ApiService().createOrder(storeId: storeId, payload: payload)
if response.error {
SnackbarCenter.shared.show(
title: response.message ?? "Não foi possível gerar o pagamento PIX.",
title: response.message ?? "Não foi possível criar o pedido.",
style: .error,
icon: "xmark.octagon.fill",
duration: 3.0
@@ -265,23 +256,52 @@ extension CheckoutView {
return
}
let pixPayload = result.payment?.pix ?? result.paymentPayload
guard let copyPaste = pixPayload?.copyPaste, copyPaste.isEmpty == false else {
let orderSnapshot = result.asPublicOrderResult()
SessionStateStore.saveTrackedOrder(orderSnapshot)
let orderId = result.id ?? UUID().uuidString
if useInAppPayment == false {
orderTrackingContext = OrderTrackingContext(orderId: orderId, shortId: result.shortId)
return
}
if effectivePaymentMethod == .creditCard {
cardPaymentContext = CardPaymentContext(
orderId: orderId,
shortId: result.shortId,
total: totalValue
)
return
}
let pixFromPayment = result.payment?.pix
let pixFromPayload = result.paymentPayload
let copyPaste = (pixFromPayment?.copyPaste?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false)
? pixFromPayment?.copyPaste
: pixFromPayload?.copyPaste
let qrCodeImage = (pixFromPayment?.qrCodeImage?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false)
? pixFromPayment?.qrCodeImage
: pixFromPayload?.qrCodeImage
let expirationDate = (pixFromPayment?.expirationDate?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false)
? pixFromPayment?.expirationDate
: pixFromPayload?.expirationDate
guard let copyPaste, copyPaste.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false else {
SnackbarCenter.shared.show(title: "Código PIX não retornado pela API.", style: .error, icon: "xmark.octagon.fill", duration: 3.0)
return
}
let orderId = result.id ?? UUID().uuidString
pixPaymentContext = PixPaymentContext(
id: orderId,
orderId: orderId,
shortId: result.shortId,
copyPaste: copyPaste,
qrCodeImageBase64: pixPayload?.qrCodeImage,
expirationDate: pixPayload?.expirationDate
qrCodeImageBase64: qrCodeImage,
expirationDate: expirationDate
)
} catch {
SnackbarCenter.shared.show(title: "Não foi possível gerar o pagamento PIX.", style: .error, icon: "xmark.octagon.fill", duration: 3.0)
SnackbarCenter.shared.show(title: "Não foi possível criar o pedido.", style: .error, icon: "xmark.octagon.fill", duration: 3.0)
}
}

View File

@@ -24,7 +24,8 @@ struct CheckoutView: View {
@State var lastAcceptedAddressState: AddressState? = nil
@State var isSubmittingOrder = false
@State var pixPaymentContext: PixPaymentContext? = nil
@State var openCardPayment = false
@State var cardPaymentContext: CardPaymentContext? = nil
@State var orderTrackingContext: OrderTrackingContext? = nil
var paymentConfig: StorePaymentMethodsInfo? { storeInfo?.paymentMethods }
@@ -151,10 +152,17 @@ struct CheckoutView: View {
}
}
.navigationDestination(item: $pixPaymentContext) { context in
PaymentPixView(context: context)
PaymentPixView(context: context) {
orderTrackingContext = OrderTrackingContext(orderId: context.orderId, shortId: context.shortId)
}
}
.navigationDestination(isPresented: $openCardPayment) {
PaymentCardView(total: totalValue)
.navigationDestination(item: $cardPaymentContext) { context in
PaymentCardView(context: context) {
orderTrackingContext = OrderTrackingContext(orderId: context.orderId, shortId: context.shortId)
}
}
.navigationDestination(item: $orderTrackingContext) { context in
OrderTrackingView(orderId: context.orderId, initialShortId: context.shortId)
}
}
@@ -529,9 +537,26 @@ struct PixPaymentContext: Identifiable, Hashable {
let expirationDate: String?
}
struct OrderTrackingContext: Identifiable, Hashable {
var id: String { orderId }
let orderId: String
let shortId: String?
}
struct CardPaymentContext: Identifiable, Hashable {
var id: String { orderId }
let orderId: String
let shortId: String?
let total: Double
}
struct PaymentPixView: View {
let context: PixPaymentContext
var onOpenTracking: (() -> Void)? = nil
@Environment(\.dismiss) var dismiss
@State var tracker = OrderRealtimeTracker()
@State var latestOrder: PublicOrderResult? = nil
@State var hasOpenedTracking = false
private var qrImageSource: String? {
guard let raw = context.qrCodeImageBase64?.trimmingCharacters(in: .whitespacesAndNewlines),
@@ -601,7 +626,11 @@ struct PaymentPixView: View {
}
Button("Já realizei o pagamento") {
dismiss()
if latestOrder?.isPaymentConfirmed == true {
openTrackingOnce()
} else {
SnackbarCenter.shared.show(title: "Pagamento ainda não confirmado.", style: .info, icon: "clock.fill", duration: 2.0)
}
}
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textMuted)
@@ -612,6 +641,25 @@ struct PaymentPixView: View {
.background(AppColors.backgroundLight)
.navigationTitle("Pagamento via PIX")
.navigationBarTitleDisplayMode(.inline)
.task {
tracker.onOrderUpdated = { updated in
latestOrder = updated
if updated.isPaymentConfirmed {
openTrackingOnce()
}
}
tracker.start(orderId: context.orderId, jwt: DefaultTokenStore().jwt)
}
.onDisappear {
tracker.stop()
}
}
private func openTrackingOnce() {
guard hasOpenedTracking == false else { return }
hasOpenedTracking = true
onOpenTracking?()
dismiss()
}
private func copyToClipboard(_ value: String) {
@@ -625,12 +673,16 @@ struct PaymentPixView: View {
}
struct PaymentCardView: View {
let total: Double
let context: CardPaymentContext
var onOpenTracking: (() -> Void)? = nil
@Environment(\.dismiss) var dismiss
@State var cardHolderName = ""
@State var cardNumber = ""
@State var expiry = ""
@State var cvv = ""
@State var tracker = OrderRealtimeTracker()
@State var latestOrder: PublicOrderResult? = nil
@State var hasOpenedTracking = false
var body: some View {
ScrollView(showsIndicators: false) {
@@ -643,7 +695,7 @@ struct PaymentCardView: View {
Text("Total do Pedido")
.font(AppTypography.caption)
.foregroundStyle(AppColors.textMuted)
Text(formatCurrency(total))
Text(formatCurrency(context.total))
.font(AppTypography.heading2)
.foregroundStyle(AppColors.textPrimary)
}
@@ -670,11 +722,19 @@ struct PaymentCardView: View {
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
PrimaryButton(title: "Salvar e Pagar") {
SnackbarCenter.shared.show(title: "Fluxo de cartão em construção.", style: .info, icon: "creditcard.fill", duration: 2.0)
if latestOrder?.isPaymentConfirmed == true {
openTrackingOnce()
} else {
SnackbarCenter.shared.show(title: "Pagamento ainda não confirmado.", style: .info, icon: "clock.fill", duration: 2.0)
}
}
Button("Apenas Pagar") {
dismiss()
if latestOrder?.isPaymentConfirmed == true {
openTrackingOnce()
} else {
SnackbarCenter.shared.show(title: "Aguardando confirmação do pagamento.", style: .info, icon: "clock.fill", duration: 2.0)
}
}
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textMuted)
@@ -688,6 +748,25 @@ struct PaymentCardView: View {
.background(AppColors.backgroundLight)
.navigationTitle("Pagamento")
.navigationBarTitleDisplayMode(.inline)
.task {
tracker.onOrderUpdated = { updated in
latestOrder = updated
if updated.isPaymentConfirmed {
openTrackingOnce()
}
}
tracker.start(orderId: context.orderId, jwt: DefaultTokenStore().jwt)
}
.onDisappear {
tracker.stop()
}
}
private func openTrackingOnce() {
guard hasOpenedTracking == false else { return }
hasOpenedTracking = true
onOpenTracking?()
dismiss()
}
private func labeledField(_ label: String, placeholder: String, text: Binding<String>) -> some View {

View File

@@ -78,6 +78,10 @@ extension HomeView {
@MainActor
func resolveCoordinates(forceRefresh: Bool) async -> (Double, Double)? {
guard hasConfiguredAddress() else {
return nil
}
if !forceRefresh {
if let lat = appState.address.latitude, let lng = appState.address.longitude {
return (lat, lng)
@@ -87,9 +91,6 @@ extension HomeView {
appState.address.longitude = cached.1
return cached
}
if hasConfiguredAddress() == false {
return nil
}
}
let coordinate = await LocationService.shared.requestLocationAsync(timeoutSeconds: 3)
if let coordinate {
@@ -103,9 +104,6 @@ extension HomeView {
if appState.address.selectedId != nil {
return true
}
if appState.address.latitude != nil, appState.address.longitude != nil {
return true
}
let normalized = appState.address.display
.trimmingCharacters(in: .whitespacesAndNewlines)

View File

@@ -0,0 +1,263 @@
import SwiftUI
struct OrderTrackingView: View {
let orderId: String
let initialShortId: String?
@State var isLoading = true
@State var errorMessage: String? = nil
@State var order: PublicOrderResult? = nil
@State var tracker = OrderRealtimeTracker()
var body: some View {
ScrollView(showsIndicators: false) {
VStack(spacing: 16) {
headerCard
if isLoading {
ProgressView()
.padding(.top, 20)
}
if let errorMessage, errorMessage.isEmpty == false {
Text(errorMessage)
.font(AppTypography.body)
.foregroundStyle(Color.red)
.multilineTextAlignment(.center)
.padding(.horizontal, 20)
}
if let order {
timelineCard(order)
}
footerPlaceholderCard
}
.padding(.horizontal, 20)
.padding(.top, 14)
.padding(.bottom, UIDevice.bottomNotch + 24)
}
.background(AppColors.backgroundLight)
.navigationTitle("Pedido \(displayOrderTitle)")
.navigationBarTitleDisplayMode(.inline)
.task {
await loadInitialOrder()
tracker.onOrderUpdated = { updated in
order = updated
isLoading = false
errorMessage = nil
}
tracker.start(orderId: orderId, jwt: DefaultTokenStore().jwt)
}
.onDisappear {
tracker.stop()
}
}
private var displayOrderTitle: String {
if let short = order?.shortId, short.isEmpty == false { return "#\(short)" }
if let initialShortId, initialShortId.isEmpty == false { return "#\(initialShortId)" }
return "#\(orderId.prefix(6))"
}
private var headerCard: some View {
VStack(alignment: .leading, spacing: 6) {
Text(statusTitle)
.font(AppTypography.heading1)
.foregroundStyle(AppColors.textPrimary)
Text("Pedido ID #\(order?.shortId ?? initialShortId ?? orderId)")
.font(AppTypography.body)
.foregroundStyle(AppColors.textMuted)
if let order, order.isInDeliveryRoute, let otp = order.displayOtpCode {
Text("Seu código do pedido: \(otp) - Informe esse número ao motoboy")
.font(AppTypography.body)
.foregroundStyle(AppColors.textPrimary)
.multilineTextAlignment(.leading)
.padding(.top, 2)
}
HStack {
Spacer()
Text(statusPill)
.font(.system(size: 13, weight: .bold))
.foregroundStyle(AppColors.primary)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(AppColors.brandSoft)
.clipShape(Capsule())
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(16)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
private func timelineCard(_ order: PublicOrderResult) -> some View {
let events: [PublicOrderTimelineEvent] = {
if order.timeline.isEmpty == false { return order.timeline }
return [
PublicOrderTimelineEvent(
status: order.status ?? order.paymentStatus ?? "PENDING",
message: nil,
time: order.updatedAt ?? order.createdAt
)
]
}()
return VStack(alignment: .leading, spacing: 12) {
Text("Acompanhamento")
.font(AppTypography.heading2)
.foregroundStyle(AppColors.textPrimary)
VStack(alignment: .leading, spacing: 0) {
ForEach(Array(events.enumerated()), id: \.offset) { index, event in
timelineRow(event: event, isLast: index == events.count - 1)
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(16)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
private func timelineRow(event: PublicOrderTimelineEvent, isLast: Bool) -> some View {
HStack(alignment: .top, spacing: 12) {
VStack(spacing: 0) {
Circle()
.fill(AppColors.tertiary)
.frame(width: 20, height: 20)
.overlay(
Image(systemName: "checkmark")
.font(.system(size: 11, weight: .bold))
.foregroundStyle(.white)
)
if isLast == false {
Rectangle()
.fill(AppColors.tertiary.opacity(0.45))
.frame(width: 2, height: 30)
}
}
VStack(alignment: .leading, spacing: 2) {
Text(humanReadableStatus(event.status ?? event.message ?? "Atualizado"))
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
Text(formatTime(event.time) ?? "")
.font(AppTypography.caption)
.foregroundStyle(AppColors.textMuted)
}
Spacer()
}
}
private var footerPlaceholderCard: some View {
VStack(spacing: 10) {
RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)
.fill(Color.black.opacity(0.9))
.frame(height: 180)
.overlay(
Image(systemName: "shippingbox.fill")
.font(.system(size: 56, weight: .bold))
.foregroundStyle(AppColors.tertiary)
)
Text("Acompanhe seu pedido em tempo real")
.font(AppTypography.body)
.foregroundStyle(AppColors.textMuted)
}
.frame(maxWidth: .infinity)
.padding(16)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
private var statusTitle: String {
humanReadableStatus(order?.status ?? "Aguardando")
}
private var statusPill: String {
let value = order?.paymentStatus ?? order?.status ?? "PENDING"
return humanReadableStatus(value)
}
private func humanReadableStatus(_ raw: String) -> String {
let normalized = raw.uppercased()
if normalized.contains("PAYMENT_PENDING") || normalized == "PENDING" {
return "Aguardando pagamento"
}
if normalized.contains("CONFIRMED") {
return "Pagamento confirmado"
}
if normalized.contains("PREPAR") {
return "Em preparo"
}
if normalized.contains("OUT_FOR_DELIVERY") || normalized.contains("EM_ROTA") || normalized.contains("ROTA") {
return "Em rota de entrega"
}
if normalized.contains("COMPLETED") || normalized.contains("DELIVERED") {
return "Pedido entregue"
}
if normalized.contains("CANCEL") {
return "Pedido cancelado"
}
return raw.capitalized
}
private func formatTime(_ isoValue: String?) -> String? {
guard let isoValue, isoValue.isEmpty == false else { return nil }
let iso = ISO8601DateFormatter()
iso.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
var date: Date? = iso.date(from: isoValue)
if date == nil {
iso.formatOptions = [.withInternetDateTime]
date = iso.date(from: isoValue)
}
if date == nil {
let fallback = DateFormatter()
fallback.locale = Locale(identifier: "pt_BR")
fallback.dateFormat = "yyyy-MM-dd HH:mm:ss"
date = fallback.date(from: isoValue)
}
guard let date else { return nil }
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "pt_BR")
formatter.dateFormat = "HH:mm"
return formatter.string(from: date)
}
@MainActor
private func loadInitialOrder() async {
if let cached = SessionStateStore.loadTrackedOrder(orderId: orderId) {
order = cached
isLoading = false
}
do {
let response = try await ApiService().publicOrder(orderId: orderId)
if response.error {
errorMessage = response.message ?? "Não foi possível carregar o pedido."
} else if let result = response.result {
order = result
SessionStateStore.saveTrackedOrder(result)
errorMessage = nil
}
} catch {
if order == nil {
errorMessage = "Não foi possível carregar o pedido."
}
}
isLoading = false
}
}

View File

@@ -0,0 +1,205 @@
import SwiftUI
struct OrdersView: View {
@State var isLoading = false
@State var errorMessage: String? = nil
@State var orders: [AppOrderSummary] = []
var body: some View {
ScrollView(showsIndicators: false) {
VStack(alignment: .leading, spacing: 14) {
Text("Histórico de Pedidos")
.font(AppTypography.heading1)
.foregroundStyle(AppColors.textPrimary)
.padding(.top, 6)
if isLoading {
ProgressView()
.frame(maxWidth: .infinity)
.padding(.top, 24)
} else if let errorMessage, errorMessage.isEmpty == false {
Text(errorMessage)
.font(AppTypography.body)
.foregroundStyle(Color.red)
.multilineTextAlignment(.center)
.frame(maxWidth: .infinity)
.padding(.top, 24)
} else if orders.isEmpty {
Text("Nenhum pedido encontrado.")
.font(AppTypography.body)
.foregroundStyle(AppColors.textMuted)
.padding(.top, 24)
} else {
ForEach(orders) { order in
NavigationLink {
OrderTrackingView(orderId: order.id, initialShortId: order.shortId)
} label: {
orderRow(order)
}
.buttonStyle(.plain)
}
}
}
.padding(.horizontal, 20)
.padding(.bottom, UIDevice.bottomNotch + 18)
}
.background(AppColors.backgroundLight)
.navigationTitle("Meus Pedidos")
.navigationBarTitleDisplayMode(.inline)
.task {
await loadOrders()
}
}
private func orderRow(_ order: AppOrderSummary) -> some View {
HStack(spacing: 12) {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(AppColors.brandSoft)
.frame(width: 44, height: 44)
.overlay(
Image(systemName: "bag.fill")
.foregroundStyle(AppColors.primary)
)
VStack(alignment: .leading, spacing: 4) {
Text("Pedido #\(order.shortId ?? order.id)")
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
Text(humanReadableStatus(order.status ?? order.paymentStatus ?? "Pendente"))
.font(AppTypography.caption)
.foregroundStyle(AppColors.textMuted)
}
Spacer()
VStack(alignment: .trailing, spacing: 4) {
Text(formatCurrency(order.total ?? 0))
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textPrimary)
if let createdAt = formatDate(order.createdAt) {
Text(createdAt)
.font(AppTypography.caption)
.foregroundStyle(AppColors.textMuted)
}
}
}
.padding(14)
.background(AppColors.surface)
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
}
private func humanReadableStatus(_ raw: String) -> String {
let normalized = raw.uppercased()
if normalized.contains("PAYMENT_PENDING") || normalized == "PENDING" { return "Aguardando pagamento" }
if normalized.contains("CONFIRMED") { return "Confirmado" }
if normalized.contains("OUT_FOR_DELIVERY") || normalized.contains("ROTA") { return "Em rota" }
if normalized.contains("COMPLETED") || normalized.contains("DELIVERED") { return "Entregue" }
if normalized.contains("CANCEL") { return "Cancelado" }
return raw.capitalized
}
private func formatCurrency(_ value: Double) -> String {
String(format: "R$ %.2f", value).replacingOccurrences(of: ".", with: ",")
}
private func formatDate(_ isoValue: String?) -> String? {
guard let isoValue, isoValue.isEmpty == false else { return nil }
let iso = ISO8601DateFormatter()
iso.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
var date = iso.date(from: isoValue)
if date == nil {
iso.formatOptions = [.withInternetDateTime]
date = iso.date(from: isoValue)
}
guard let date else { return nil }
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "pt_BR")
formatter.dateFormat = "dd/MM HH:mm"
return formatter.string(from: date)
}
@MainActor
private func loadOrders() async {
isLoading = true
errorMessage = nil
let cachedTracked = SessionStateStore.loadTrackedOrders()
if cachedTracked.isEmpty == false {
let mapped = cachedTracked.map {
AppOrderSummary.fromTracked($0)
}
orders = mergeOrders(apiOrders: orders, trackedOrders: mapped)
}
do {
let response = try await ApiService().listOrders()
if response.error {
errorMessage = response.message ?? "Não foi possível carregar os pedidos."
} else {
let remote = response.result ?? []
orders = mergeOrders(apiOrders: remote, trackedOrders: orders)
}
} catch {
if orders.isEmpty {
errorMessage = "Não foi possível carregar os pedidos."
}
}
isLoading = false
}
private func mergeOrders(apiOrders: [AppOrderSummary], trackedOrders: [AppOrderSummary]) -> [AppOrderSummary] {
var map: [String: AppOrderSummary] = [:]
for item in trackedOrders { map[item.id] = item }
for item in apiOrders { map[item.id] = item }
return map.values.sorted { lhs, rhs in
let left = lhs.updatedAt ?? lhs.createdAt ?? ""
let right = rhs.updatedAt ?? rhs.createdAt ?? ""
return left > right
}
}
}
extension AppOrderSummary {
static func fromTracked(_ tracked: PublicOrderResult) -> AppOrderSummary {
AppOrderSummary(
id: tracked.id,
shortId: tracked.shortId,
total: tracked.total,
status: tracked.status,
paymentStatus: tracked.paymentStatus,
paymentMethod: tracked.paymentMethod,
deliveryType: tracked.deliveryType,
storeName: tracked.storeName,
createdAt: tracked.createdAt,
updatedAt: tracked.updatedAt
)
}
init(
id: String,
shortId: String?,
total: Double?,
status: String?,
paymentStatus: String?,
paymentMethod: String?,
deliveryType: String?,
storeName: String?,
createdAt: String?,
updatedAt: String?
) {
self.id = id
self.shortId = shortId
self.total = total
self.status = status
self.paymentStatus = paymentStatus
self.paymentMethod = paymentMethod
self.deliveryType = deliveryType
self.storeName = storeName
self.createdAt = createdAt
self.updatedAt = updatedAt
}
}

View File

@@ -192,6 +192,7 @@ struct ProfileView: View {
private func logout() {
tokenStore.clear()
SessionStateStore.clearActiveUser()
SessionStateStore.clearTrackedOrders()
AppContentCache.shared.invalidate()
AppImageCache.shared.invalidateAll()
appState = AppState()