Not has address
This commit is contained in:
@@ -5,6 +5,8 @@ struct ProfileView: View {
|
||||
@Binding var selectedTab: MainTab
|
||||
let tokenStore: TokenStore
|
||||
@Binding var appState: AppState
|
||||
@State var openAddressesOnboarding = false
|
||||
@State var onboardingMessage: String? = nil
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
@@ -16,7 +18,7 @@ struct ProfileView: View {
|
||||
}
|
||||
|
||||
NavigationLink("Enderecos") {
|
||||
Text("Enderecos")
|
||||
AddressesView(message: nil, appState: $appState)
|
||||
}
|
||||
|
||||
NavigationLink("Ajuda") {
|
||||
@@ -26,6 +28,13 @@ struct ProfileView: View {
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
|
||||
NavigationLink(isActive: $openAddressesOnboarding) {
|
||||
AddressesView(message: onboardingMessage, appState: $appState)
|
||||
} label: {
|
||||
EmptyView()
|
||||
}
|
||||
.hidden()
|
||||
|
||||
Spacer()
|
||||
|
||||
SecondaryButton(title: "Sair") {
|
||||
@@ -39,6 +48,14 @@ struct ProfileView: View {
|
||||
.padding(.top, 24)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(AppColors.backgroundLight)
|
||||
.onAppear {
|
||||
guard let message = appState.address.onboardingMessage else {
|
||||
return
|
||||
}
|
||||
onboardingMessage = message
|
||||
appState.address.onboardingMessage = nil
|
||||
openAddressesOnboarding = true
|
||||
}
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
@@ -63,6 +80,268 @@ struct ProfileView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct AddressesView: View {
|
||||
let message: String?
|
||||
@Binding var appState: AppState
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@State var isLoading = false
|
||||
@State var errorMessage: String? = nil
|
||||
@State var addresses: [CustomerAddress] = []
|
||||
|
||||
let tabBarClearance: CGFloat = 96
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
AppColors.backgroundLight
|
||||
.ignoresSafeArea()
|
||||
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(spacing: 20) {
|
||||
header
|
||||
|
||||
if let message {
|
||||
Text(message)
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 14)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.background(AppColors.brandSoft)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
|
||||
}
|
||||
|
||||
VStack(spacing: 16) {
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
.padding(.top, 24)
|
||||
} else if let errorMessage {
|
||||
Text(errorMessage)
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.top, 24)
|
||||
} else if addresses.isEmpty {
|
||||
Text("Nenhum endereço cadastrado")
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
.padding(.top, 24)
|
||||
} else {
|
||||
ForEach(Array(addresses.enumerated()), id: \.offset) { index, address in
|
||||
AddressCard(item: addressToListItem(address, isPrimary: index == 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.top, 18)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
bottomOverlay
|
||||
.padding(.bottom, tabBarClearance)
|
||||
}
|
||||
}
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.toolbar(.hidden, for: .navigationBar)
|
||||
.onAppear {
|
||||
if isLoading == false, addresses.isEmpty {
|
||||
Task {
|
||||
await loadAddresses()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var header: some View {
|
||||
ZStack {
|
||||
Text("Meus Endereços")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bottomOverlay: some View {
|
||||
ZStack(alignment: .bottom) {
|
||||
Rectangle()
|
||||
.fill(AppColors.backgroundLight)
|
||||
.frame(height: 136)
|
||||
|
||||
Button(action: {}) {
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: "mappin.circle.fill")
|
||||
.font(.system(size: 24))
|
||||
Text("Adicionar novo endereço")
|
||||
.font(AppTypography.heading3)
|
||||
}
|
||||
.foregroundStyle(AppColors.textInverse)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 18)
|
||||
.background(AppColors.primary)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusXL, style: .continuous))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.bottom, 14)
|
||||
}
|
||||
}
|
||||
|
||||
func addressToListItem(_ address: CustomerAddress, isPrimary: Bool) -> AddressListItem {
|
||||
let title = address.label?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false ? (address.label ?? "Endereço") : "Endereço"
|
||||
let line1 = [address.address, address.number]
|
||||
.compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
.filter { $0.isEmpty == false }
|
||||
.joined(separator: ", ")
|
||||
let line2 = [address.neighborhood, address.city, address.state]
|
||||
.compactMap { $0?.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
.filter { $0.isEmpty == false }
|
||||
.joined(separator: ", ")
|
||||
let detail = [line1, line2]
|
||||
.filter { $0.isEmpty == false }
|
||||
.joined(separator: " - ")
|
||||
|
||||
return AddressListItem(
|
||||
title: title,
|
||||
detail: detail.isEmpty ? "Endereço sem detalhes" : detail,
|
||||
icon: iconName(for: title),
|
||||
isPrimary: isPrimary
|
||||
)
|
||||
}
|
||||
|
||||
func iconName(for label: String) -> String {
|
||||
let normalized = label.folding(options: [.diacriticInsensitive, .caseInsensitive], locale: .current)
|
||||
if normalized.contains("casa") {
|
||||
return "house.fill"
|
||||
}
|
||||
if normalized.contains("trabalho") {
|
||||
return "briefcase.fill"
|
||||
}
|
||||
return "mappin.and.ellipse"
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func loadAddresses() async {
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
|
||||
do {
|
||||
let service = ApiService()
|
||||
let response = try await service.profile()
|
||||
guard response.error == false else {
|
||||
errorMessage = response.message ?? "Não foi possível carregar os endereços."
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
addresses = response.result?.addressBook ?? []
|
||||
if let first = addresses.first {
|
||||
appState.address.selectedId = first.id
|
||||
appState.address.display = first.label ?? "Defina seu endereco"
|
||||
if let lat = first.latLong?.first, let lng = first.latLong?.dropFirst().first {
|
||||
appState.address.latitude = lat
|
||||
appState.address.longitude = lng
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
struct AddressCard: View {
|
||||
let item: AddressListItem
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 14) {
|
||||
icon
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(spacing: 10) {
|
||||
Text(item.title)
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
|
||||
if item.isPrimary {
|
||||
Text("PRINCIPAL")
|
||||
.font(AppTypography.overline)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
.background(AppColors.tertiary)
|
||||
.clipShape(Capsule())
|
||||
.lineLimit(1)
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
}
|
||||
}
|
||||
|
||||
Text(item.detail)
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
.lineLimit(2)
|
||||
}
|
||||
|
||||
Spacer(minLength: 8)
|
||||
|
||||
Rectangle()
|
||||
.fill(AppColors.backgroundLight)
|
||||
.frame(width: 1, height: 96)
|
||||
|
||||
VStack(spacing: 24) {
|
||||
Button(action: {}) {
|
||||
Image(systemName: "pencil")
|
||||
.font(.system(size: 22))
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
}
|
||||
|
||||
Button(action: {}) {
|
||||
Image(systemName: "trash")
|
||||
.font(.system(size: 22))
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
}
|
||||
}
|
||||
.frame(width: 40)
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 20)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusXL, style: .continuous))
|
||||
}
|
||||
|
||||
var icon: some View {
|
||||
Image(systemName: item.icon)
|
||||
.font(.system(size: 28))
|
||||
.foregroundStyle(item.isPrimary ? AppColors.primary : AppColors.textPrimary)
|
||||
.frame(width: 84, height: 84)
|
||||
.background(item.isPrimary ? AppColors.brandSoft : AppColors.backgroundLight)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
struct AddressListItem: Identifiable {
|
||||
let id = UUID()
|
||||
let title: String
|
||||
let detail: String
|
||||
let icon: String
|
||||
let isPrimary: Bool
|
||||
}
|
||||
|
||||
struct OrdersView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
|
||||
Reference in New Issue
Block a user