login fix
This commit is contained in:
parent
bbb79c25e2
commit
7ffbe04e37
@ -10,6 +10,8 @@ enum AppState: Equatable, Hashable {
|
||||
return true
|
||||
case (.login, .login):
|
||||
return true
|
||||
case (.signup, .signup):
|
||||
return true
|
||||
case (.dashboard, .dashboard):
|
||||
return true
|
||||
|
||||
@ -20,5 +22,6 @@ enum AppState: Equatable, Hashable {
|
||||
|
||||
case splashScreen
|
||||
case login
|
||||
case signup
|
||||
case dashboard
|
||||
}
|
||||
|
||||
@ -25,6 +25,10 @@ final class LoginViewModel: ViewModel {
|
||||
!email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !password.isEmpty && !isLoading
|
||||
}
|
||||
|
||||
func showSignup() {
|
||||
appStateManager.navigateTo(state: .signup)
|
||||
}
|
||||
|
||||
func login() async {
|
||||
guard canSubmit else { return }
|
||||
|
||||
|
||||
59
medmap/ViewModels/SignupViewModel.swift
Normal file
59
medmap/ViewModels/SignupViewModel.swift
Normal file
@ -0,0 +1,59 @@
|
||||
//
|
||||
// SignupViewModel.swift
|
||||
// medmap
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
final class SignupViewModel: ViewModel {
|
||||
|
||||
@Published var email = ""
|
||||
@Published var password = ""
|
||||
@Published var confirmationPassword = ""
|
||||
@Published private(set) var errorMessage: String?
|
||||
|
||||
private let api: MedMapAPI
|
||||
|
||||
init(api: MedMapAPI = MedMapAPI()) {
|
||||
self.api = api
|
||||
super.init()
|
||||
}
|
||||
|
||||
var canSubmit: Bool {
|
||||
!email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty &&
|
||||
password.count >= 8 &&
|
||||
password == confirmationPassword &&
|
||||
!isLoading
|
||||
}
|
||||
|
||||
func signup() async {
|
||||
guard canSubmit else { return }
|
||||
|
||||
errorMessage = nil
|
||||
isLoading = true
|
||||
defer { isLoading = false }
|
||||
|
||||
do {
|
||||
let response = try await api.signup(
|
||||
email: email.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
password: password,
|
||||
role: .patient
|
||||
)
|
||||
|
||||
guard response.role == .patient else {
|
||||
errorMessage = "Nao foi possivel criar uma conta de doente."
|
||||
return
|
||||
}
|
||||
|
||||
appStateManager.navigateTo(state: .dashboard)
|
||||
} catch {
|
||||
errorMessage = "Nao foi possivel criar a conta. Confirme os dados ou tente outro email."
|
||||
fluxlog("Patient signup failed: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
func showLogin() {
|
||||
appStateManager.navigateBack()
|
||||
}
|
||||
}
|
||||
@ -26,12 +26,17 @@ struct LoginView: View {
|
||||
|
||||
private var formPanel: some View {
|
||||
VStack(spacing: 0) {
|
||||
Image("splashLogo")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 120, height: 76)
|
||||
.accessibilityHidden(true)
|
||||
.padding(.top, 32)
|
||||
HStack {
|
||||
Image("splashLogo")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 120, height: 76)
|
||||
.accessibilityHidden(true)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 32)
|
||||
.padding(.horizontal, 32)
|
||||
|
||||
Spacer()
|
||||
|
||||
@ -93,7 +98,9 @@ struct LoginView: View {
|
||||
Spacer()
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text("Não tem conta? Criar conta")
|
||||
Button("Não tem conta? Criar conta") {
|
||||
viewModel.showSignup()
|
||||
}
|
||||
.font(.jakartaRegular14)
|
||||
.foregroundStyle(.white.opacity(0.88))
|
||||
|
||||
|
||||
171
medmap/Views/SignupView.swift
Normal file
171
medmap/Views/SignupView.swift
Normal file
@ -0,0 +1,171 @@
|
||||
//
|
||||
// SignupView.swift
|
||||
// medmap
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SignupView: View {
|
||||
|
||||
@StateObject private var viewModel = SignupViewModel()
|
||||
@FocusState private var focusedField: Field?
|
||||
|
||||
private enum Field {
|
||||
case email
|
||||
case password
|
||||
case confirmationPassword
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.appBackground.ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
Image("splashLogo")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 120, height: 76)
|
||||
.accessibilityHidden(true)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 32)
|
||||
.padding(.horizontal, 32)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .leading, spacing: 24) {
|
||||
Text("Criar nova conta")
|
||||
.font(.jakartaBold24)
|
||||
.foregroundStyle(.white)
|
||||
|
||||
VStack(spacing: 14) {
|
||||
field(
|
||||
placeholder: "Email",
|
||||
icon: "envelope",
|
||||
text: $viewModel.email,
|
||||
focusedField: .email,
|
||||
contentType: .emailAddress,
|
||||
keyboardType: .emailAddress
|
||||
)
|
||||
field(
|
||||
placeholder: "Palavra-passe (min. 8 caracteres)",
|
||||
icon: "lock",
|
||||
text: $viewModel.password,
|
||||
focusedField: .password,
|
||||
contentType: .newPassword,
|
||||
isSecure: true
|
||||
)
|
||||
field(
|
||||
placeholder: "Confirmar palavra-passe",
|
||||
icon: "lock",
|
||||
text: $viewModel.confirmationPassword,
|
||||
focusedField: .confirmationPassword,
|
||||
contentType: .newPassword,
|
||||
isSecure: true,
|
||||
submitLabel: .go
|
||||
) {
|
||||
Task { await viewModel.signup() }
|
||||
}
|
||||
}
|
||||
|
||||
if let errorMessage = viewModel.errorMessage {
|
||||
Text(errorMessage)
|
||||
.font(.jakartaRegular14)
|
||||
.foregroundStyle(Color.loginErrorText)
|
||||
.frame(maxWidth: .infinity)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
Button {
|
||||
focusedField = nil
|
||||
Task { await viewModel.signup() }
|
||||
} label: {
|
||||
Group {
|
||||
if viewModel.isLoading {
|
||||
ProgressView().tint(Color.loginButtonText)
|
||||
} else {
|
||||
Text("Registar").font(.jakartaBold16)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 46)
|
||||
}
|
||||
.foregroundStyle(Color.loginButtonText)
|
||||
.background(Color.loginButtonBackground)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
.disabled(!viewModel.canSubmit)
|
||||
.opacity(viewModel.canSubmit ? 1 : 0.55)
|
||||
}
|
||||
.padding(.horizontal, 32)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Button("Ja tem conta? Entrar") {
|
||||
viewModel.showLogin()
|
||||
}
|
||||
.font(.jakartaRegular14)
|
||||
.foregroundStyle(.white.opacity(0.88))
|
||||
|
||||
Text("MedMap")
|
||||
.font(.jakartaRegular12)
|
||||
.foregroundStyle(.white.opacity(0.6))
|
||||
}
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
|
||||
private func field(
|
||||
placeholder: String,
|
||||
icon: String,
|
||||
text: Binding<String>,
|
||||
focusedField: Field,
|
||||
contentType: UITextContentType,
|
||||
keyboardType: UIKeyboardType = .default,
|
||||
isSecure: Bool = false,
|
||||
submitLabel: SubmitLabel = .next,
|
||||
onSubmit: @escaping () -> Void = {}
|
||||
) -> some View {
|
||||
HStack(spacing: 10) {
|
||||
Image(systemName: icon)
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 18)
|
||||
ZStack(alignment: .leading) {
|
||||
if text.wrappedValue.isEmpty {
|
||||
Text(placeholder).foregroundStyle(Color.loginPlaceholder)
|
||||
}
|
||||
if isSecure {
|
||||
SecureField("", text: text)
|
||||
} else {
|
||||
TextField("", text: text)
|
||||
.keyboardType(keyboardType)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
}
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
.tint(.white)
|
||||
.font(.jakartaRegular16)
|
||||
.textContentType(contentType)
|
||||
.focused($focusedField, equals: focusedField)
|
||||
.submitLabel(submitLabel)
|
||||
.onSubmit(onSubmit)
|
||||
}
|
||||
.padding(.horizontal, 14)
|
||||
.frame(height: 46)
|
||||
.background(Color.loginFieldBackground)
|
||||
.overlay {
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(Color.loginFieldBorder, lineWidth: 1)
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||
.accessibilityLabel(placeholder)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
SignupView()
|
||||
}
|
||||
@ -93,6 +93,13 @@ struct medmapApp: App {
|
||||
.transition(.opacity)
|
||||
.animation(.easeInOut, value: 10)
|
||||
|
||||
case .signup:
|
||||
|
||||
SignupView()
|
||||
.navigationBarHidden(true)
|
||||
.transition(.opacity)
|
||||
.animation(.easeInOut, value: 10)
|
||||
|
||||
case .dashboard:
|
||||
|
||||
DashboardView()
|
||||
|
||||
@ -77,9 +77,20 @@ public class RestAuthController {
|
||||
})
|
||||
@SecurityRequirements
|
||||
@PostMapping("/signup")
|
||||
public ResponseEntity<LoginResponse> signup(@Valid @RequestBody SignupRequest request) {
|
||||
public ResponseEntity<LoginResponse> signup(@Valid @RequestBody SignupRequest request,
|
||||
HttpServletRequest httpRequest) {
|
||||
AppUser created = appUserService.register(request);
|
||||
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
context.setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||
created,
|
||||
null,
|
||||
created.getAuthorities()
|
||||
));
|
||||
SecurityContextHolder.setContext(context);
|
||||
HttpSession session = httpRequest.getSession(true);
|
||||
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("X-User-Id", created.getId().toString());
|
||||
headers.set("X-User-Role", created.getRole().name());
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user