Merge pull request 'Load Terms of Use and Privacy Policy from Atomenta API' (#7) from feature/legal/api-links into main

Reviewed-on: Loverde-Company-LTDA/Pedi-Foods-Skip#7
This commit is contained in:
2026-07-09 10:11:23 -03:00
5 changed files with 168 additions and 62 deletions

View File

@@ -339,14 +339,6 @@
"comment" : "A prompt asking users to share their opinion about the delivery.", "comment" : "A prompt asking users to share their opinion about the delivery.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
}, },
"Conteúdo da política de privacidade..." : {
"comment" : "A placeholder text describing the content of the privacy policy.",
"isCommentAutoGenerated" : true
},
"Conteúdo dos termos de uso..." : {
"comment" : "A placeholder text describing the content of the terms of use.",
"isCommentAutoGenerated" : true
},
"Criar conta" : { "Criar conta" : {
"comment" : "A link that navigates to the registration screen.", "comment" : "A link that navigates to the registration screen.",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
@@ -748,6 +740,9 @@
"Não" : { "Não" : {
"comment" : "The label of a button in an alert that says \"No\".", "comment" : "The label of a button in an alert that says \"No\".",
"isCommentAutoGenerated" : true "isCommentAutoGenerated" : true
},
"Não foi possível carregar %@." : {
}, },
"Não recebeu o código?" : { "Não recebeu o código?" : {
"comment" : "A question displayed below the button to re-send the OTP.", "comment" : "A question displayed below the button to re-send the OTP.",

View File

@@ -39,3 +39,19 @@ enum ApiConfig {
} }
} }
} }
enum LegalDocument: Sendable {
case terms
case privacyPolicy
private var path: String {
switch self {
case .terms: return "api/public/pedi-foods-customer/terms"
case .privacyPolicy: return "api/public/pedi-foods-customer/privacy-policy"
}
}
var url: URL {
ApiConfig.baseURL.appendingPathComponent(path)
}
}

View File

@@ -1,22 +1,117 @@
import SwiftUI import SwiftUI
import WebKit
struct TermsOfUseView: View { private struct RemotePDFView: View {
@Environment(\.colorScheme) var colorScheme let url: URL
@Environment(\.dismiss) var dismiss @Binding var isLoading: Bool
@Binding var hasError: Bool
var body: some View { var body: some View {
ScrollView { RemotePDFRepresentable(url: url, isLoading: $isLoading, hasError: $hasError)
VStack(alignment: .leading, spacing: 16) { }
}
#if canImport(UIKit)
private struct RemotePDFRepresentable: UIViewRepresentable {
let url: URL
@Binding var isLoading: Bool
@Binding var hasError: Bool
func makeUIView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.navigationDelegate = context.coordinator
webView.load(URLRequest(url: url))
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) {}
func makeCoordinator() -> RemotePDFCoordinator {
RemotePDFCoordinator(isLoading: $isLoading, hasError: $hasError)
}
}
#elseif canImport(AppKit)
private struct RemotePDFRepresentable: NSViewRepresentable {
let url: URL
@Binding var isLoading: Bool
@Binding var hasError: Bool
func makeNSView(context: Context) -> WKWebView {
let webView = WKWebView()
webView.navigationDelegate = context.coordinator
webView.load(URLRequest(url: url))
return webView
}
func updateNSView(_ nsView: WKWebView, context: Context) {}
func makeCoordinator() -> RemotePDFCoordinator {
RemotePDFCoordinator(isLoading: $isLoading, hasError: $hasError)
}
}
#endif
private final class RemotePDFCoordinator: NSObject, WKNavigationDelegate {
@Binding var isLoading: Bool
@Binding var hasError: Bool
init(isLoading: Binding<Bool>, hasError: Binding<Bool>) {
self._isLoading = isLoading
self._hasError = hasError
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
isLoading = true
hasError = false
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
isLoading = false
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
isLoading = false
hasError = true
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
isLoading = false
hasError = true
}
}
private struct LegalDocumentScreen: View {
let title: String
let headerTitle: String
let document: LegalDocument
@Environment(\.colorScheme) var colorScheme
@Environment(\.dismiss) var dismiss
@State private var isLoading = true
@State private var hasError = false
var body: some View {
VStack(spacing: 0) {
screenHeader screenHeader
Text("Termos de Uso") .padding(24)
.font(AppTypography.heading1)
.foregroundStyle(AppColors.textPrimary) ZStack {
Text("Conteúdo dos termos de uso...") RemotePDFView(url: document.url, isLoading: $isLoading, hasError: $hasError)
if isLoading {
ProgressView()
}
if hasError {
VStack(spacing: 8) {
Text("Não foi possível carregar \(title.lowercased()).")
.font(AppTypography.body) .font(AppTypography.body)
.foregroundStyle(AppColors.textPrimary) .foregroundStyle(AppColors.textPrimary)
.multilineTextAlignment(.center)
} }
.padding(24) .padding(24)
} }
}
}
.background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea()) .background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea())
.appHiddenNavigationBar() .appHiddenNavigationBar()
.navigationBarBackButtonHidden(true) .navigationBarBackButtonHidden(true)
@@ -24,7 +119,7 @@ struct TermsOfUseView: View {
private var screenHeader: some View { private var screenHeader: some View {
ZStack { ZStack {
Text("Termos de Uso") Text(headerTitle)
.font(AppTypography.heading2) .font(AppTypography.heading2)
.foregroundStyle(AppColors.textPrimary) .foregroundStyle(AppColors.textPrimary)
HStack { HStack {
@@ -44,46 +139,14 @@ struct TermsOfUseView: View {
} }
} }
struct TermsOfUseView: View {
var body: some View {
LegalDocumentScreen(title: "os Termos de Uso", headerTitle: "Termos de Uso", document: .terms)
}
}
struct PrivacyPolicyView: View { struct PrivacyPolicyView: View {
@Environment(\.colorScheme) var colorScheme
@Environment(\.dismiss) var dismiss
var body: some View { var body: some View {
ScrollView { LegalDocumentScreen(title: "a Política de Privacidade", headerTitle: "Privacidade", document: .privacyPolicy)
VStack(alignment: .leading, spacing: 16) {
screenHeader
Text("Política de Privacidade")
.font(AppTypography.heading1)
.foregroundStyle(AppColors.textPrimary)
Text("Conteúdo da política de privacidade...")
.font(AppTypography.body)
.foregroundStyle(AppColors.textPrimary)
}
.padding(24)
}
.background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea())
.appHiddenNavigationBar()
.navigationBarBackButtonHidden(true)
}
private var screenHeader: some View {
ZStack {
Text("Privacidade")
.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)
}
.buttonStyle(.plain)
Spacer()
}
}
} }
} }

View File

@@ -90,6 +90,26 @@ struct ProfileView: View {
Text("Versão 1.0b") Text("Versão 1.0b")
.font(.system(size: 16, weight: .medium)) .font(.system(size: 16, weight: .medium))
.foregroundStyle(AppColors.textMuted) .foregroundStyle(AppColors.textMuted)
HStack(spacing: 6) {
NavigationLink {
TermsOfUseView()
} label: {
Text("Termos de Uso")
}
.buttonStyle(.plain)
Text("·")
NavigationLink {
PrivacyPolicyView()
} label: {
Text("Política de Privacidade")
}
.buttonStyle(.plain)
}
.font(.system(size: 14, weight: .medium))
.foregroundStyle(AppColors.textMuted)
.padding(.bottom, 12) .padding(.bottom, 12)
} }
} }

View File

@@ -0,0 +1,12 @@
import Testing
@testable import PediFoods
@Test("Terms URL points to PediFoods customer terms endpoint")
func termsURL() {
#expect(LegalDocument.terms.url.absoluteString == "https://atomenta.com.br/api/public/pedi-foods-customer/terms")
}
@Test("Privacy policy URL points to PediFoods customer privacy-policy endpoint")
func privacyPolicyURL() {
#expect(LegalDocument.privacyPolicy.url.absoluteString == "https://atomenta.com.br/api/public/pedi-foods-customer/privacy-policy")
}