Not has address
This commit is contained in:
173
pedi-foods/Sources/PediFoods/Views/Auth/RegistrationView.swift
Normal file
173
pedi-foods/Sources/PediFoods/Views/Auth/RegistrationView.swift
Normal file
@@ -0,0 +1,173 @@
|
||||
import SwiftUI
|
||||
|
||||
struct RegistrationView: View {
|
||||
@Binding var root: RootFlow
|
||||
@Binding var selectedTab: MainTab
|
||||
let tokenStore: TokenStore
|
||||
@Binding var appState: AppState
|
||||
@State var name = ""
|
||||
@State var email = ""
|
||||
@State var phone = ""
|
||||
@State var acceptedTerms = false
|
||||
@State var isLoading = false
|
||||
@State var errorMessage: String?
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
let navigate: (Route) -> Void
|
||||
|
||||
private var isFormValid: Bool { !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !phone.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && acceptedTerms }
|
||||
|
||||
@ViewBuilder private var logoImage: some View {
|
||||
#if os(Android)
|
||||
SwiftUI.Image("pedifoods")
|
||||
.resizable()
|
||||
#else
|
||||
SwiftUI.Image("pedifoods")
|
||||
.resizable()
|
||||
#endif
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
(colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea()
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
logoImage
|
||||
.scaledToFit()
|
||||
.frame(width: 120, height: 120)
|
||||
|
||||
Text("Crie sua conta")
|
||||
.font(AppTypography.heading1)
|
||||
.foregroundStyle(colorScheme == .dark ? Color.white : AppColors.textPrimary)
|
||||
|
||||
Text("Preencha os dados abaixo para começar.")
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(colorScheme == .dark ? Color.white : AppColors.textPrimary)
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
VStack(spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
LoginField(icon: "person", placeholder: "Ex: Maria Silva", text: $name)
|
||||
LoginField(icon: "envelope", placeholder: "seu@email.com", text: $email)
|
||||
LoginField(icon: "phone", placeholder: "(00) 00000-0000", text: $phone)
|
||||
.onChange(of: phone) { _, newValue in
|
||||
let masked = formatPhoneBR(newValue)
|
||||
if masked != newValue {
|
||||
phone = masked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
Toggle("", isOn: $acceptedTerms)
|
||||
.labelsHidden()
|
||||
.tint(AppColors.primary)
|
||||
|
||||
Group {
|
||||
Text("Ao se cadastrar, você concorda com nossos [Termos de Uso](app://terms) e [Política de Privacidade](app://policy)")
|
||||
.foregroundStyle(colorScheme == .dark ? Color.white : AppColors.textPrimary)
|
||||
.tint(AppColors.primary)
|
||||
.environment(\.openURL, OpenURLAction { url in
|
||||
guard url.scheme == "app" else { return .handled }
|
||||
|
||||
switch url.host {
|
||||
case "terms":
|
||||
navigate(.terms)
|
||||
return .handled
|
||||
case "policy":
|
||||
navigate(.policy)
|
||||
return .handled
|
||||
default:
|
||||
return .handled
|
||||
}
|
||||
})
|
||||
}
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 16)
|
||||
.padding(.bottom, 16)
|
||||
|
||||
PrimaryButton(title: "Cadastrar e Receber Código", image: Image(systemName: "arrow.right")) {
|
||||
registerAndRequestOtp()
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 6)
|
||||
.disabled(!isFormValid || isLoading)
|
||||
.opacity((!isFormValid || isLoading) ? 0.5 : 1.0)
|
||||
.tint(AppColors.tertiary)
|
||||
|
||||
HStack(spacing: 6) {
|
||||
Text("Já tem uma conta?")
|
||||
.foregroundStyle(colorScheme == .dark ? Color.white.opacity(0.8) : AppColors.textPrimary)
|
||||
NavigationLink("Entrar") {
|
||||
LoginEmailView(root: $root, selectedTab: $selectedTab, tokenStore: tokenStore, appState: $appState, navigate: navigate)
|
||||
}
|
||||
.foregroundStyle(AppColors.primary)
|
||||
}
|
||||
.font(AppTypography.body)
|
||||
.padding(.top, 16)
|
||||
|
||||
Spacer().frame(height: 12)
|
||||
}
|
||||
}
|
||||
.padding(.top, -40)
|
||||
}
|
||||
}
|
||||
|
||||
private func registerAndRequestOtp() {
|
||||
let sanitizedName = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let sanitizedEmail = email.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let normalizedPhone = normalizePhoneNumberForAPI(phone)
|
||||
guard !sanitizedName.isEmpty, !sanitizedEmail.isEmpty, !normalizedPhone.isEmpty else { return }
|
||||
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
|
||||
Task {
|
||||
do {
|
||||
let service = ApiService()
|
||||
let registration = try await service.registerCustomer(
|
||||
name: sanitizedName,
|
||||
email: sanitizedEmail,
|
||||
phoneNumber: normalizedPhone
|
||||
)
|
||||
if registration.error {
|
||||
await MainActor.run {
|
||||
isLoading = false
|
||||
let message = registration.message ?? "Nao foi possivel concluir o cadastro."
|
||||
errorMessage = message
|
||||
SnackbarCenter.shared.show(title: message, style: .error, icon: "xmark.octagon.fill", duration: 4.0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let otp = try await service.requestOtp(email: sanitizedEmail, phoneNumber: normalizedPhone)
|
||||
await MainActor.run {
|
||||
isLoading = false
|
||||
if otp.error {
|
||||
let message = otp.message ?? "Cadastro concluido, mas nao foi possivel enviar o codigo."
|
||||
errorMessage = message
|
||||
SnackbarCenter.shared.show(title: message, style: .warning, icon: "exclamationmark.triangle.fill", duration: 4.0)
|
||||
return
|
||||
}
|
||||
SnackbarCenter.shared.show(title: "Cadastro concluido. Codigo enviado.", style: .success, icon: "checkmark.seal.fill", duration: 3.0)
|
||||
appState.profile.email = sanitizedEmail
|
||||
appState.profile.phone = phone
|
||||
navigate(.otp(email: sanitizedEmail, phoneNumber: normalizedPhone))
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
isLoading = false
|
||||
let message = userFacingAuthErrorMessage(error)
|
||||
errorMessage = message
|
||||
SnackbarCenter.shared.show(title: message, style: .error, icon: "xmark.octagon.fill", duration: 4.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user