[2026-07-resubmission] Add guest browsing flow with App Attest session for App Review resubmission

Adds a pre-login public store locator (guest session via DeviceCheck/App
Attest, keychain-backed token storage) so the app no longer forces sign-in
before showing any content, plus updated support URL metadata.
This commit is contained in:
Daniel Arantes Loverde
2026-07-30 11:35:25 -03:00
parent 3e93196b92
commit 017bd7168f
21 changed files with 1140 additions and 136 deletions

View File

@@ -14,6 +14,8 @@ struct ProfileView: View {
@State var openAddressesOnboarding = false
@State var onboardingMessage: String? = nil
@State var showLogoutAlert = false
@State var showDeleteAccountAlert = false
@State var isDeletingAccount = false
@State private var openOrders = false
let tabBarClearance: CGFloat = 120
@@ -87,6 +89,25 @@ struct ProfileView: View {
.padding(.top, 10)
.padding(.horizontal, 20)
Button(action: { showDeleteAccountAlert = true }) {
HStack(spacing: 10) {
if isDeletingAccount {
ProgressView()
.tint(Color.red)
} else {
Image(systemName: "trash.fill")
.font(.system(size: 16, weight: .semibold))
}
Text("Excluir Conta")
.font(AppTypography.body)
}
.foregroundStyle(Color.red.opacity(0.7))
}
.buttonStyle(.plain)
.disabled(isDeletingAccount)
.padding(.top, 2)
.padding(.horizontal, 20)
Text("Versão 1.0b")
.font(.system(size: 16, weight: .medium))
.foregroundStyle(AppColors.textMuted)
@@ -130,6 +151,14 @@ struct ProfileView: View {
} message: {
Text("Tem certeza que deseja sair da sua conta?")
}
.alert("Excluir sua conta?", isPresented: $showDeleteAccountAlert) {
Button("Cancelar", role: .cancel) {}
Button("Excluir", role: .destructive) {
Task { await deleteAccount() }
}
} message: {
Text("Essa ação é permanente. Seus dados de perfil, endereços, cartões e favoritos serão excluídos e não poderão ser recuperados.")
}
.onAppear {
guard let message = appState.address.onboardingMessage else {
return
@@ -251,6 +280,78 @@ struct ProfileView: View {
appState = AppState()
root = .auth
}
@MainActor
private func deleteAccount() async {
guard isDeletingAccount == false else { return }
isDeletingAccount = true
defer { isDeletingAccount = false }
do {
let response = try await ApiService().deleteAccount()
guard response.error == false else {
SnackbarCenter.shared.show(
title: response.message ?? "Não foi possível excluir sua conta.",
style: .error,
icon: "xmark.octagon.fill",
duration: 3.5
)
return
}
logout()
} catch {
SnackbarCenter.shared.show(
title: "Não foi possível excluir sua conta. Tente novamente.",
style: .error,
icon: "xmark.octagon.fill",
duration: 3.5
)
}
}
}
struct ProfileLoggedOutView: View {
@Binding var root: RootFlow
var body: some View {
VStack(spacing: 18) {
Spacer()
Image(systemName: "person.crop.circle.badge.questionmark")
.font(.system(size: 56, weight: .regular))
.foregroundStyle(AppColors.textMuted)
Text("Entre na sua conta")
.font(AppTypography.heading2)
.foregroundStyle(AppColors.textPrimary)
Text("Faça login ou cadastre-se para ver seu perfil, pedidos e endereços.")
.font(AppTypography.body)
.foregroundStyle(AppColors.textMuted)
.multilineTextAlignment(.center)
.padding(.horizontal, 32)
Button {
root = .auth
} label: {
Text("Entrar ou Cadastrar")
.font(AppTypography.heading3)
.foregroundStyle(AppColors.textInverse)
.frame(maxWidth: .infinity)
.padding(.vertical, 14)
.background(AppColors.primary)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
}
.buttonStyle(.plain)
.padding(.horizontal, 32)
.padding(.top, 8)
Spacer()
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(AppColors.backgroundLight)
}
}
struct ProfileMenuRow: View {