add change password

This commit is contained in:
cheykrym 2025-10-07 01:51:15 +03:00
parent ab5218f02a
commit a588a33338
5 changed files with 234 additions and 28 deletions

View File

@ -25,3 +25,7 @@ struct ErrorResponse: Decodable {
let data: ErrorPayload?
let detail: String?
}
struct MessagePayload: Decodable {
let message: String
}

View File

@ -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
}

View File

@ -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" : "Старый пароль"
},
"Старый пароль указан неверно или совпадает с новым." : {
},
"Темы" : {
},
"Ты шо ебанутый? А ниче тот факт что новый пароль должен отличаться от старого." : {
},
"Уведомления" : {

View 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: "")
}
}
}
}
}

View File

@ -1,29 +1,33 @@
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 {
@ -91,6 +95,11 @@ struct ChangePasswordView: View {
.foregroundColor(isAllValid ? .green : .red)
}
}
if isOldPasswordSame && !newPassword.isEmpty {
Text(NSLocalizedString("Ты шо ебанутый? А ниче тот факт что новый пароль должен отличаться от старого.", comment: ""))
.font(.caption)
.foregroundColor(.red)
}
HStack {
if isConfirmPasswordVisible {
@ -121,21 +130,27 @@ 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)
}) {
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()
@ -143,11 +158,51 @@ struct ChangePasswordView: View {
.background(isButtonEnabled ? Color.blue : Color.gray)
.cornerRadius(8)
}
}
.disabled(!isButtonEnabled)
.buttonStyle(PlainButtonStyle())
.listRowInsets(EdgeInsets())
.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
}