214 lines
9.1 KiB
Swift
214 lines
9.1 KiB
Swift
//
|
||
// RegistrationView.swift
|
||
// VolnahubApp
|
||
//
|
||
// Created by cheykrym on 09/06/2025.
|
||
//
|
||
|
||
import SwiftUI
|
||
|
||
struct RegistrationView: View {
|
||
@ObservedObject var viewModel: LoginViewModel
|
||
@Environment(\.presentationMode) private var presentationMode
|
||
|
||
@State private var username: String = ""
|
||
@State private var password: String = ""
|
||
@State private var confirmPassword: String = ""
|
||
@State private var inviteCode: String = ""
|
||
@AppStorage("isDarkMode") private var isDarkMode: Bool = true
|
||
|
||
@State private var isLoading: Bool = false
|
||
@State private var showError: Bool = false
|
||
@State private var errorMessage: String = ""
|
||
|
||
private var isUsernameValid: Bool {
|
||
let pattern = "^[A-Za-z0-9_]{3,32}$"
|
||
return username.range(of: pattern, options: .regularExpression) != nil
|
||
}
|
||
|
||
private var isPasswordValid: Bool {
|
||
password.count >= 8 && password.count <= 128
|
||
}
|
||
|
||
private var isConfirmPasswordValid: Bool {
|
||
confirmPassword == password && !confirmPassword.isEmpty
|
||
}
|
||
|
||
private var isFormValid: Bool {
|
||
isUsernameValid && isPasswordValid && isConfirmPasswordValid
|
||
}
|
||
|
||
var body: some View {
|
||
NavigationView {
|
||
|
||
ScrollView {
|
||
ZStack {
|
||
Color.clear
|
||
.contentShape(Rectangle())
|
||
.onTapGesture {
|
||
hideKeyboard()
|
||
}
|
||
|
||
VStack(alignment: .leading, spacing: 16) {
|
||
|
||
Group {
|
||
|
||
HStack {
|
||
TextField(NSLocalizedString("Логин", comment: "Логин"), text: $username)
|
||
.autocapitalization(.none)
|
||
.disableAutocorrection(true)
|
||
Spacer()
|
||
if !username.isEmpty {
|
||
Image(systemName: isUsernameValid ? "checkmark.circle" : "xmark.circle")
|
||
.foregroundColor(isUsernameValid ? .green : .red)
|
||
}
|
||
}
|
||
.padding()
|
||
.background(Color(.secondarySystemBackground))
|
||
.cornerRadius(8)
|
||
.autocapitalization(.none)
|
||
.disableAutocorrection(true)
|
||
.onChange(of: username) { newValue in
|
||
if newValue.count > 32 {
|
||
username = String(newValue.prefix(32))
|
||
}
|
||
}
|
||
|
||
if !isUsernameValid && !username.isEmpty {
|
||
Text(NSLocalizedString("Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)", comment: "Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)"))
|
||
.foregroundColor(.red)
|
||
.font(.caption)
|
||
}
|
||
|
||
HStack {
|
||
SecureField(NSLocalizedString("Пароль", comment: "Пароль"), text: $password)
|
||
.autocapitalization(.none)
|
||
Spacer()
|
||
if !password.isEmpty {
|
||
Image(systemName: isPasswordValid ? "checkmark.circle" : "xmark.circle")
|
||
.foregroundColor(isPasswordValid ? .green : .red)
|
||
}
|
||
}
|
||
.padding()
|
||
.background(Color(.secondarySystemBackground))
|
||
.cornerRadius(8)
|
||
.autocapitalization(.none)
|
||
.onChange(of: password) { newValue in
|
||
if newValue.count > 32 {
|
||
password = String(newValue.prefix(32))
|
||
}
|
||
}
|
||
|
||
if !isPasswordValid && !password.isEmpty {
|
||
Text(NSLocalizedString("Пароль должен быть от 8 до 128 символов", comment: "Пароль должен быть от 6 до 32 символов"))
|
||
.foregroundColor(.red)
|
||
.font(.caption)
|
||
}
|
||
|
||
HStack {
|
||
SecureField(NSLocalizedString("Подтверждение пароля", comment: "Подтверждение пароля"), text: $confirmPassword)
|
||
.autocapitalization(.none)
|
||
Spacer()
|
||
if !confirmPassword.isEmpty {
|
||
Image(systemName: isConfirmPasswordValid ? "checkmark.circle" : "xmark.circle")
|
||
.foregroundColor(isConfirmPasswordValid ? .green : .red)
|
||
}
|
||
}
|
||
.padding()
|
||
.background(Color(.secondarySystemBackground))
|
||
.cornerRadius(8)
|
||
.autocapitalization(.none)
|
||
.onChange(of: confirmPassword) { newValue in
|
||
if newValue.count > 32 {
|
||
confirmPassword = String(newValue.prefix(32))
|
||
}
|
||
}
|
||
|
||
if !isConfirmPasswordValid && !confirmPassword.isEmpty {
|
||
Text(NSLocalizedString("Пароли не совпадают", comment: "Пароли не совпадают"))
|
||
.foregroundColor(.red)
|
||
.font(.caption)
|
||
}
|
||
|
||
TextField(NSLocalizedString("Инвайт-код (необязательно)", comment: "Инвайт-код"), text: $inviteCode)
|
||
.padding()
|
||
.background(Color(.secondarySystemBackground))
|
||
.cornerRadius(8)
|
||
.autocapitalization(.none)
|
||
.disableAutocorrection(true)
|
||
}
|
||
|
||
Button(action: registerUser) {
|
||
if isLoading {
|
||
ProgressView()
|
||
.padding()
|
||
.frame(maxWidth: .infinity)
|
||
.background(Color.gray.opacity(0.6))
|
||
.cornerRadius(8)
|
||
} else {
|
||
Text(NSLocalizedString("Зарегистрироваться", comment: "Зарегистрироваться"))
|
||
.foregroundColor(.white)
|
||
.padding()
|
||
.frame(maxWidth: .infinity)
|
||
.background(isFormValid ? Color.blue : Color.gray.opacity(0.6))
|
||
.cornerRadius(8)
|
||
}
|
||
}
|
||
.disabled(!isFormValid)
|
||
.padding(.bottom)
|
||
}
|
||
.padding()
|
||
}
|
||
.navigationBarItems(trailing:
|
||
Button(action: {
|
||
presentationMode.wrappedValue.dismiss()
|
||
}) {
|
||
Text(NSLocalizedString("Закрыть", comment: "Закрыть"))
|
||
}
|
||
)
|
||
.navigationTitle(Text(NSLocalizedString("Регистрация", comment: "Регистрация")))
|
||
.alert(isPresented: $showError) {
|
||
Alert(
|
||
title: Text(NSLocalizedString("Ошибка регистрация", comment: "Ошибка")),
|
||
message: Text(errorMessage),
|
||
dismissButton: .default(Text(NSLocalizedString("OK", comment: "")))
|
||
)
|
||
}
|
||
}
|
||
.onTapGesture {
|
||
hideKeyboard()
|
||
}
|
||
}
|
||
.onTapGesture {
|
||
hideKeyboard()
|
||
}
|
||
}
|
||
|
||
private func registerUser() {
|
||
isLoading = true
|
||
errorMessage = ""
|
||
viewModel.registerUser(username: username, password: password, invite: inviteCode.isEmpty ? nil : inviteCode) { success, message in
|
||
isLoading = false
|
||
if success {
|
||
presentationMode.wrappedValue.dismiss()
|
||
} else {
|
||
errorMessage = message ?? NSLocalizedString("Неизвестная ошибка.", comment: "")
|
||
showError = true
|
||
}
|
||
}
|
||
}
|
||
|
||
private func hideKeyboard() {
|
||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||
}
|
||
}
|
||
|
||
|
||
struct RegistrationView_Previews: PreviewProvider {
|
||
static var previews: some View {
|
||
let viewModel = LoginViewModel()
|
||
viewModel.isLoading = false // чтобы убрать спиннер
|
||
return RegistrationView(viewModel: viewModel)
|
||
}
|
||
}
|