add change password
This commit is contained in:
		
							parent
							
								
									ab5218f02a
								
							
						
					
					
						commit
						a588a33338
					
				@ -25,3 +25,7 @@ struct ErrorResponse: Decodable {
 | 
			
		||||
    let data: ErrorPayload?
 | 
			
		||||
    let detail: String?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct MessagePayload: Decodable {
 | 
			
		||||
    let message: String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -126,6 +126,43 @@ final class AuthService {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func changePassword(oldPassword: String, newPassword: String, completion: @escaping (Bool, String?) -> Void) {
 | 
			
		||||
        let payload = ChangePasswordRequestPayload(old_password: oldPassword, new_password: newPassword)
 | 
			
		||||
        guard let body = try? JSONEncoder().encode(payload) else {
 | 
			
		||||
            completion(false, NSLocalizedString("Не удалось сериализовать данные запроса.", comment: ""))
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        NetworkClient.shared.request(
 | 
			
		||||
            path: "/v1/auth/password/change",
 | 
			
		||||
            method: .post,
 | 
			
		||||
            body: body,
 | 
			
		||||
            requiresAuth: true
 | 
			
		||||
        ) { result in
 | 
			
		||||
            switch result {
 | 
			
		||||
            case .success(let response):
 | 
			
		||||
                do {
 | 
			
		||||
                    let decoder = JSONDecoder()
 | 
			
		||||
                    let apiResponse = try decoder.decode(APIResponse<MessagePayload>.self, from: response.data)
 | 
			
		||||
 | 
			
		||||
                    guard apiResponse.status == "fine" else {
 | 
			
		||||
                        let message = apiResponse.detail ?? NSLocalizedString("Не удалось обновить пароль.", comment: "")
 | 
			
		||||
                        completion(false, message)
 | 
			
		||||
                        return
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    completion(true, apiResponse.data.message)
 | 
			
		||||
                } catch {
 | 
			
		||||
                    completion(false, NSLocalizedString("Не удалось обработать ответ сервера.", comment: ""))
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            case .failure(let error):
 | 
			
		||||
                let message = self.changePasswordErrorMessage(for: error)
 | 
			
		||||
                completion(false, message)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func logoutCurrentUser(completion: @escaping (Bool, String?) -> Void) {
 | 
			
		||||
        guard let currentUser = UserDefaults.standard.string(forKey: "currentUser") else {
 | 
			
		||||
            completion(false, "Не найден текущий пользователь.")
 | 
			
		||||
@ -237,6 +274,47 @@ final class AuthService {
 | 
			
		||||
 | 
			
		||||
        return message
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private func changePasswordErrorMessage(for error: NetworkError) -> String {
 | 
			
		||||
        switch error {
 | 
			
		||||
        case .network(let err):
 | 
			
		||||
            return String(format: NSLocalizedString("Ошибка сети: %@", comment: ""), err.localizedDescription)
 | 
			
		||||
        case .server(let statusCode, let data):
 | 
			
		||||
            if let message = extractMessage(from: data) {
 | 
			
		||||
                return message
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            switch statusCode {
 | 
			
		||||
            case 401:
 | 
			
		||||
                return NSLocalizedString("Необходимо авторизоваться заново.", comment: "")
 | 
			
		||||
            case 403:
 | 
			
		||||
                return NSLocalizedString("Старый пароль указан неверно или совпадает с новым.", comment: "")
 | 
			
		||||
            case 422:
 | 
			
		||||
                return NSLocalizedString("Проверьте данные и повторите попытку.", comment: "")
 | 
			
		||||
            case 429:
 | 
			
		||||
                return NSLocalizedString("Слишком много попыток. Попробуйте позже.", comment: "")
 | 
			
		||||
            default:
 | 
			
		||||
                return String(format: NSLocalizedString("Ошибка сервера: %@", comment: ""), "\(statusCode)")
 | 
			
		||||
            }
 | 
			
		||||
        case .unauthorized:
 | 
			
		||||
            return NSLocalizedString("Необходимо авторизоваться заново.", comment: "")
 | 
			
		||||
        case .invalidURL, .noResponse:
 | 
			
		||||
            return NSLocalizedString("Некорректный ответ от сервера.", comment: "")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private func extractMessage(from data: Data?) -> String? {
 | 
			
		||||
        guard let data else { return nil }
 | 
			
		||||
        if let response = try? JSONDecoder().decode(ErrorResponse.self, from: data) {
 | 
			
		||||
            if let message = response.data?.message, !message.isEmpty {
 | 
			
		||||
                return message
 | 
			
		||||
            }
 | 
			
		||||
            if let detail = response.detail, !detail.isEmpty {
 | 
			
		||||
                return detail
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return nil
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private struct LoginRequest: Encodable {
 | 
			
		||||
@ -249,3 +327,8 @@ private struct RegisterRequest: Encodable {
 | 
			
		||||
    let password: String
 | 
			
		||||
    let invite: String?
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private struct ChangePasswordRequestPayload: Encodable {
 | 
			
		||||
    let old_password: String
 | 
			
		||||
    let new_password: String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -380,6 +380,9 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Не удалось загрузить чаты." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Не удалось обновить пароль." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Не удалось обработать данные чатов." : {
 | 
			
		||||
 | 
			
		||||
@ -422,6 +425,9 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Некорректный ответ от сервера." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Необходимо авторизоваться заново." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Нет аккаунта? Регистрация" : {
 | 
			
		||||
      "comment" : "Регистрация"
 | 
			
		||||
@ -449,6 +455,9 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Отправляем..." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Ошибка" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Ошибка авторизации" : {
 | 
			
		||||
 | 
			
		||||
@ -473,12 +482,21 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Пароли не совпадают" : {
 | 
			
		||||
      "comment" : "Пароли не совпадают"
 | 
			
		||||
    },
 | 
			
		||||
    "Пароли не совпадают." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Пароль" : {
 | 
			
		||||
      "comment" : "Пароль"
 | 
			
		||||
    },
 | 
			
		||||
    "Пароль должен быть от 8 до 128 символов" : {
 | 
			
		||||
      "comment" : "Пароль должен быть от 6 до 32 символов"
 | 
			
		||||
    },
 | 
			
		||||
    "Пароль обновлен" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Пароль успешно обновлен." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : {
 | 
			
		||||
      "comment" : "FAQ answer: reset password"
 | 
			
		||||
@ -520,6 +538,9 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Применить" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Проверьте данные и повторите попытку." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Произошла ошибка." : {
 | 
			
		||||
 | 
			
		||||
@ -564,6 +585,9 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Слишком много запросов." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Слишком много попыток. Попробуйте позже." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Сменить пароль" : {
 | 
			
		||||
 | 
			
		||||
@ -576,9 +600,15 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Старый пароль" : {
 | 
			
		||||
      "comment" : "Старый пароль"
 | 
			
		||||
    },
 | 
			
		||||
    "Старый пароль указан неверно или совпадает с новым." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Темы" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Ты шо ебанутый? А ниче тот факт что новый пароль должен отличаться от старого." : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Уведомления" : {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										34
									
								
								yobble/ViewModels/ChangePasswordViewModel.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								yobble/ViewModels/ChangePasswordViewModel.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
			
		||||
import Foundation
 | 
			
		||||
import Combine
 | 
			
		||||
 | 
			
		||||
final class ChangePasswordViewModel: ObservableObject {
 | 
			
		||||
    @Published private(set) var isLoading: Bool = false
 | 
			
		||||
    @Published var successMessage: String?
 | 
			
		||||
    @Published var errorMessage: String?
 | 
			
		||||
 | 
			
		||||
    private let authService: AuthService
 | 
			
		||||
 | 
			
		||||
    init(authService: AuthService = AuthService()) {
 | 
			
		||||
        self.authService = authService
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func changePassword(oldPassword: String, newPassword: String) {
 | 
			
		||||
        guard !isLoading else { return }
 | 
			
		||||
 | 
			
		||||
        isLoading = true
 | 
			
		||||
        successMessage = nil
 | 
			
		||||
        errorMessage = nil
 | 
			
		||||
 | 
			
		||||
        authService.changePassword(oldPassword: oldPassword, newPassword: newPassword) { [weak self] success, message in
 | 
			
		||||
            guard let self else { return }
 | 
			
		||||
            DispatchQueue.main.async {
 | 
			
		||||
                self.isLoading = false
 | 
			
		||||
                if success {
 | 
			
		||||
                    self.successMessage = message ?? NSLocalizedString("Пароль успешно обновлен.", comment: "")
 | 
			
		||||
                } else {
 | 
			
		||||
                    self.errorMessage = message ?? NSLocalizedString("Не удалось обновить пароль.", comment: "")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,31 +1,35 @@
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
struct ChangePasswordView: View {
 | 
			
		||||
    @StateObject private var viewModel = ChangePasswordViewModel()
 | 
			
		||||
    @State private var oldPassword = ""
 | 
			
		||||
    @State private var newPassword = ""
 | 
			
		||||
    @State private var confirmPassword = ""
 | 
			
		||||
    @State private var isOldPasswordVisible = false
 | 
			
		||||
    @State private var isNewPasswordVisible = false
 | 
			
		||||
    @State private var isConfirmPasswordVisible = false
 | 
			
		||||
    @State private var alertData: AlertData?
 | 
			
		||||
 | 
			
		||||
    private var isOldPasswordValid: Bool {
 | 
			
		||||
        return oldPassword.count >= 8 && oldPassword.count <= 128
 | 
			
		||||
        oldPassword.count >= 8 && oldPassword.count <= 128
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var isOldPasswordSame: Bool {
 | 
			
		||||
        return oldPassword == newPassword
 | 
			
		||||
        oldPassword == newPassword
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private var isNewPasswordValid: Bool {
 | 
			
		||||
        return newPassword.count >= 8 && newPassword.count <= 128
 | 
			
		||||
        newPassword.count >= 8 && newPassword.count <= 128
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private var isPasswordConfirmValid: Bool {
 | 
			
		||||
        return newPassword == confirmPassword
 | 
			
		||||
        newPassword == confirmPassword
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private var isButtonEnabled: Bool {
 | 
			
		||||
        isPasswordConfirmValid && !isOldPasswordSame && isNewPasswordValid && isOldPasswordValid && !viewModel.isLoading
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        Form {
 | 
			
		||||
            Section {
 | 
			
		||||
@ -86,12 +90,17 @@ struct ChangePasswordView: View {
 | 
			
		||||
 | 
			
		||||
                    if !newPassword.isEmpty {
 | 
			
		||||
                        let isAllValid = isNewPasswordValid && !isOldPasswordSame
 | 
			
		||||
                        
 | 
			
		||||
 | 
			
		||||
                        Image(systemName: isAllValid ? "checkmark.circle" : "xmark.circle")
 | 
			
		||||
                            .foregroundColor(isAllValid ? .green : .red)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if isOldPasswordSame && !newPassword.isEmpty {
 | 
			
		||||
                    Text(NSLocalizedString("Ты шо ебанутый? А ниче тот факт что новый пароль должен отличаться от старого.", comment: ""))
 | 
			
		||||
                        .font(.caption)
 | 
			
		||||
                        .foregroundColor(.red)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                HStack {
 | 
			
		||||
                    if isConfirmPasswordVisible {
 | 
			
		||||
                        TextField(NSLocalizedString("Подтверждение пароля", comment: "Подтверждение пароля"), text: $confirmPassword)
 | 
			
		||||
@ -121,27 +130,34 @@ struct ChangePasswordView: View {
 | 
			
		||||
                            .foregroundColor(isPasswordConfirmValid ? .green : .red)
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                
 | 
			
		||||
                
 | 
			
		||||
                if !confirmPassword.isEmpty && !isPasswordConfirmValid {
 | 
			
		||||
                    Text(NSLocalizedString("Пароли не совпадают.", comment: ""))
 | 
			
		||||
                        .font(.caption)
 | 
			
		||||
                        .foregroundColor(.red)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var isButtonEnabled: Bool {
 | 
			
		||||
                isPasswordConfirmValid && !isOldPasswordSame && isNewPasswordValid && isOldPasswordValid
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            Button(action: {
 | 
			
		||||
                // Действие для сохранения профиля
 | 
			
		||||
                print("oldPassword: \(oldPassword)")
 | 
			
		||||
                print("newPassword: \(newPassword)")
 | 
			
		||||
                print("confirmPassword: \(confirmPassword)")
 | 
			
		||||
                viewModel.changePassword(oldPassword: oldPassword, newPassword: newPassword)
 | 
			
		||||
            }) {
 | 
			
		||||
                Text(NSLocalizedString("Применить", comment: ""))
 | 
			
		||||
                    .foregroundColor(.white)
 | 
			
		||||
                    .padding()
 | 
			
		||||
                    .frame(maxWidth: .infinity)
 | 
			
		||||
                    .background(isButtonEnabled ? Color.blue : Color.gray)
 | 
			
		||||
                    .cornerRadius(8)
 | 
			
		||||
                if viewModel.isLoading {
 | 
			
		||||
                    ProgressView()
 | 
			
		||||
                        .progressViewStyle(CircularProgressViewStyle())
 | 
			
		||||
                        .padding()
 | 
			
		||||
                        .frame(maxWidth: .infinity)
 | 
			
		||||
                        .background(Color.gray.opacity(0.6))
 | 
			
		||||
                        .cornerRadius(8)
 | 
			
		||||
                } else {
 | 
			
		||||
                    Text(NSLocalizedString("Применить", comment: ""))
 | 
			
		||||
                        .foregroundColor(.white)
 | 
			
		||||
                        .padding()
 | 
			
		||||
                        .frame(maxWidth: .infinity)
 | 
			
		||||
                        .background(isButtonEnabled ? Color.blue : Color.gray)
 | 
			
		||||
                        .cornerRadius(8)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .disabled(!isButtonEnabled)
 | 
			
		||||
            .buttonStyle(PlainButtonStyle())
 | 
			
		||||
@ -149,5 +165,44 @@ struct ChangePasswordView: View {
 | 
			
		||||
            .listRowBackground(Color.clear)
 | 
			
		||||
        }
 | 
			
		||||
        .navigationTitle(NSLocalizedString("Изменение пароля", comment: ""))
 | 
			
		||||
        .onChange(of: viewModel.successMessage) { message in
 | 
			
		||||
            guard let message else { return }
 | 
			
		||||
            alertData = AlertData(kind: .success, message: message)
 | 
			
		||||
        }
 | 
			
		||||
        .onChange(of: viewModel.errorMessage) { message in
 | 
			
		||||
            guard let message else { return }
 | 
			
		||||
            alertData = AlertData(kind: .error, message: message)
 | 
			
		||||
        }
 | 
			
		||||
        .alert(item: $alertData) { data in
 | 
			
		||||
            Alert(
 | 
			
		||||
                title: Text(data.kind == .success
 | 
			
		||||
                             ? NSLocalizedString("Пароль обновлен", comment: "")
 | 
			
		||||
                             : NSLocalizedString("Ошибка", comment: "")),
 | 
			
		||||
                message: Text(data.message),
 | 
			
		||||
                dismissButton: .default(Text(NSLocalizedString("OK", comment: ""))) {
 | 
			
		||||
                    switch data.kind {
 | 
			
		||||
                    case .success:
 | 
			
		||||
                        oldPassword = ""
 | 
			
		||||
                        newPassword = ""
 | 
			
		||||
                        confirmPassword = ""
 | 
			
		||||
                        viewModel.successMessage = nil
 | 
			
		||||
                    case .error:
 | 
			
		||||
                        viewModel.errorMessage = nil
 | 
			
		||||
                    }
 | 
			
		||||
                    alertData = nil
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private struct AlertData: Identifiable {
 | 
			
		||||
    enum Kind {
 | 
			
		||||
        case success
 | 
			
		||||
        case error
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let id = UUID()
 | 
			
		||||
    let kind: Kind
 | 
			
		||||
    let message: String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user