245 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			245 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
//
 | 
						||
//  RegistrationView.swift
 | 
						||
//  VolnahubApp
 | 
						||
//
 | 
						||
//  Created by cheykrym on 09/06/2025.
 | 
						||
//
 | 
						||
 | 
						||
import SwiftUI
 | 
						||
 | 
						||
struct RegistrationView: View {
 | 
						||
    @ObservedObject var viewModel: LoginViewModel
 | 
						||
    @Binding var isPresented: Bool
 | 
						||
    @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 = ""
 | 
						||
 | 
						||
    @State private var isLoading: Bool = false
 | 
						||
    @State private var showError: Bool = false
 | 
						||
    @State private var errorMessage: String = ""
 | 
						||
    @State private var isShowingTerms: Bool = false
 | 
						||
 | 
						||
    @FocusState private var focusedField: Field?
 | 
						||
 | 
						||
    private enum Field: Hashable {
 | 
						||
        case username
 | 
						||
        case password
 | 
						||
        case confirmPassword
 | 
						||
        case invite
 | 
						||
    }
 | 
						||
 | 
						||
    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 && viewModel.hasAcceptedTerms
 | 
						||
    }
 | 
						||
 | 
						||
    var body: some View {
 | 
						||
        NavigationView {
 | 
						||
            ScrollView {
 | 
						||
                ZStack(alignment: .top) {
 | 
						||
                    Color.clear
 | 
						||
                        .contentShape(Rectangle())
 | 
						||
                        .onTapGesture { focusedField = nil }
 | 
						||
 | 
						||
                    VStack(alignment: .leading, spacing: 16) {
 | 
						||
                        Group {
 | 
						||
                            HStack {
 | 
						||
                                TextField(NSLocalizedString("Логин", comment: "Логин"), text: $username)
 | 
						||
                                    .autocapitalization(.none)
 | 
						||
                                    .disableAutocorrection(true)
 | 
						||
                                    .focused($focusedField, equals: .username)
 | 
						||
                                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)
 | 
						||
                                    .focused($focusedField, equals: .password)
 | 
						||
                                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 > 128 {
 | 
						||
                                    password = String(newValue.prefix(128))
 | 
						||
                                }
 | 
						||
                            }
 | 
						||
 | 
						||
                            if !isPasswordValid && !password.isEmpty {
 | 
						||
                                Text(NSLocalizedString("Пароль должен быть от 8 до 128 символов", comment: "Пароль должен быть от 6 до 32 символов"))
 | 
						||
                                    .foregroundColor(.red)
 | 
						||
                                    .font(.caption)
 | 
						||
                            }
 | 
						||
 | 
						||
                            HStack {
 | 
						||
                                SecureField(NSLocalizedString("Подтверждение пароля", comment: "Подтверждение пароля"), text: $confirmPassword)
 | 
						||
                                    .autocapitalization(.none)
 | 
						||
                                    .focused($focusedField, equals: .confirmPassword)
 | 
						||
                                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)
 | 
						||
                                .focused($focusedField, equals: .invite)
 | 
						||
                        }
 | 
						||
 | 
						||
                        TermsAgreementCard(
 | 
						||
                            isAccepted: $viewModel.hasAcceptedTerms,
 | 
						||
                            openTerms: {
 | 
						||
                                viewModel.loadTermsIfNeeded()
 | 
						||
                                isShowingTerms = 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()
 | 
						||
                }
 | 
						||
            }
 | 
						||
            .navigationTitle(Text(NSLocalizedString("Регистрация", comment: "Регистрация")))
 | 
						||
            .toolbar {
 | 
						||
                ToolbarItem(placement: .navigationBarTrailing) {
 | 
						||
                    Button(action: dismissSheet) {
 | 
						||
                        Text(NSLocalizedString("Закрыть", comment: "Закрыть"))
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            }
 | 
						||
            .alert(isPresented: $showError) {
 | 
						||
                Alert(
 | 
						||
                    title: Text(NSLocalizedString("Ошибка регистрация", comment: "Ошибка")),
 | 
						||
                    message: Text(errorMessage),
 | 
						||
                    dismissButton: .default(Text(NSLocalizedString("OK", comment: "")))
 | 
						||
                )
 | 
						||
            }
 | 
						||
        }
 | 
						||
        .fullScreenCover(isPresented: $isShowingTerms) {
 | 
						||
            TermsFullScreenView(
 | 
						||
                isPresented: $isShowingTerms,
 | 
						||
                title: NSLocalizedString("Правила сервиса", comment: ""),
 | 
						||
                content: viewModel.termsContent,
 | 
						||
                isLoading: viewModel.isLoadingTerms,
 | 
						||
                errorMessage: viewModel.termsErrorMessage,
 | 
						||
                onRetry: {
 | 
						||
                    viewModel.reloadTerms()
 | 
						||
                }
 | 
						||
            )
 | 
						||
            .onAppear {
 | 
						||
                if viewModel.termsContent.isEmpty {
 | 
						||
                    viewModel.loadTermsIfNeeded()
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    private func registerUser() {
 | 
						||
        isLoading = true
 | 
						||
        errorMessage = ""
 | 
						||
        viewModel.registerUser(username: username, password: password, invite: inviteCode.isEmpty ? nil : inviteCode) { success, message in
 | 
						||
            isLoading = false
 | 
						||
            if success {
 | 
						||
                dismissSheet()
 | 
						||
            } else {
 | 
						||
                errorMessage = message ?? NSLocalizedString("Неизвестная ошибка.", comment: "")
 | 
						||
                showError = true
 | 
						||
            }
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    private func dismissSheet() {
 | 
						||
        focusedField = nil
 | 
						||
        viewModel.hasAcceptedTerms = false
 | 
						||
        isPresented = false
 | 
						||
        presentationMode.wrappedValue.dismiss()
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
 | 
						||
struct RegistrationView_Previews: PreviewProvider {
 | 
						||
    static var previews: some View {
 | 
						||
        let viewModel = LoginViewModel()
 | 
						||
        viewModel.isLoading = false // чтобы убрать спиннер
 | 
						||
        return RegistrationView(viewModel: viewModel, isPresented: .constant(true))
 | 
						||
    }
 | 
						||
}
 |