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

@@ -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
}
}