add change password
This commit is contained in:
parent
ab5218f02a
commit
a588a33338
@ -25,3 +25,7 @@ struct ErrorResponse: Decodable {
|
|||||||
let data: ErrorPayload?
|
let data: ErrorPayload?
|
||||||
let detail: String?
|
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) {
|
func logoutCurrentUser(completion: @escaping (Bool, String?) -> Void) {
|
||||||
guard let currentUser = UserDefaults.standard.string(forKey: "currentUser") else {
|
guard let currentUser = UserDefaults.standard.string(forKey: "currentUser") else {
|
||||||
completion(false, "Не найден текущий пользователь.")
|
completion(false, "Не найден текущий пользователь.")
|
||||||
@ -237,6 +274,47 @@ final class AuthService {
|
|||||||
|
|
||||||
return message
|
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 {
|
private struct LoginRequest: Encodable {
|
||||||
@ -249,3 +327,8 @@ private struct RegisterRequest: Encodable {
|
|||||||
let password: String
|
let password: String
|
||||||
let invite: 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" : "Регистрация"
|
"comment" : "Регистрация"
|
||||||
@ -449,6 +455,9 @@
|
|||||||
},
|
},
|
||||||
"Отправляем..." : {
|
"Отправляем..." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Ошибка" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Ошибка авторизации" : {
|
"Ошибка авторизации" : {
|
||||||
|
|
||||||
@ -473,12 +482,21 @@
|
|||||||
},
|
},
|
||||||
"Пароли не совпадают" : {
|
"Пароли не совпадают" : {
|
||||||
"comment" : "Пароли не совпадают"
|
"comment" : "Пароли не совпадают"
|
||||||
|
},
|
||||||
|
"Пароли не совпадают." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Пароль" : {
|
"Пароль" : {
|
||||||
"comment" : "Пароль"
|
"comment" : "Пароль"
|
||||||
},
|
},
|
||||||
"Пароль должен быть от 8 до 128 символов" : {
|
"Пароль должен быть от 8 до 128 символов" : {
|
||||||
"comment" : "Пароль должен быть от 6 до 32 символов"
|
"comment" : "Пароль должен быть от 6 до 32 символов"
|
||||||
|
},
|
||||||
|
"Пароль обновлен" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Пароль успешно обновлен." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : {
|
"Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : {
|
||||||
"comment" : "FAQ answer: reset password"
|
"comment" : "FAQ answer: reset password"
|
||||||
@ -520,6 +538,9 @@
|
|||||||
},
|
},
|
||||||
"Применить" : {
|
"Применить" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Проверьте данные и повторите попытку." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Произошла ошибка." : {
|
"Произошла ошибка." : {
|
||||||
|
|
||||||
@ -564,6 +585,9 @@
|
|||||||
},
|
},
|
||||||
"Слишком много запросов." : {
|
"Слишком много запросов." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Слишком много попыток. Попробуйте позже." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Сменить пароль" : {
|
"Сменить пароль" : {
|
||||||
|
|
||||||
@ -576,9 +600,15 @@
|
|||||||
},
|
},
|
||||||
"Старый пароль" : {
|
"Старый пароль" : {
|
||||||
"comment" : "Старый пароль"
|
"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,29 +1,33 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct ChangePasswordView: View {
|
struct ChangePasswordView: View {
|
||||||
|
@StateObject private var viewModel = ChangePasswordViewModel()
|
||||||
@State private var oldPassword = ""
|
@State private var oldPassword = ""
|
||||||
@State private var newPassword = ""
|
@State private var newPassword = ""
|
||||||
@State private var confirmPassword = ""
|
@State private var confirmPassword = ""
|
||||||
@State private var isOldPasswordVisible = false
|
@State private var isOldPasswordVisible = false
|
||||||
@State private var isNewPasswordVisible = false
|
@State private var isNewPasswordVisible = false
|
||||||
@State private var isConfirmPasswordVisible = false
|
@State private var isConfirmPasswordVisible = false
|
||||||
|
@State private var alertData: AlertData?
|
||||||
|
|
||||||
private var isOldPasswordValid: Bool {
|
private var isOldPasswordValid: Bool {
|
||||||
return oldPassword.count >= 8 && oldPassword.count <= 128
|
oldPassword.count >= 8 && oldPassword.count <= 128
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isOldPasswordSame: Bool {
|
private var isOldPasswordSame: Bool {
|
||||||
return oldPassword == newPassword
|
oldPassword == newPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isNewPasswordValid: Bool {
|
private var isNewPasswordValid: Bool {
|
||||||
return newPassword.count >= 8 && newPassword.count <= 128
|
newPassword.count >= 8 && newPassword.count <= 128
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isPasswordConfirmValid: Bool {
|
private var isPasswordConfirmValid: Bool {
|
||||||
return newPassword == confirmPassword
|
newPassword == confirmPassword
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isButtonEnabled: Bool {
|
||||||
|
isPasswordConfirmValid && !isOldPasswordSame && isNewPasswordValid && isOldPasswordValid && !viewModel.isLoading
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -91,6 +95,11 @@ struct ChangePasswordView: View {
|
|||||||
.foregroundColor(isAllValid ? .green : .red)
|
.foregroundColor(isAllValid ? .green : .red)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if isOldPasswordSame && !newPassword.isEmpty {
|
||||||
|
Text(NSLocalizedString("Ты шо ебанутый? А ниче тот факт что новый пароль должен отличаться от старого.", comment: ""))
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
if isConfirmPasswordVisible {
|
if isConfirmPasswordVisible {
|
||||||
@ -121,27 +130,34 @@ struct ChangePasswordView: View {
|
|||||||
.foregroundColor(isPasswordConfirmValid ? .green : .red)
|
.foregroundColor(isPasswordConfirmValid ? .green : .red)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !confirmPassword.isEmpty && !isPasswordConfirmValid {
|
||||||
|
Text(NSLocalizedString("Пароли не совпадают.", comment: ""))
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var isButtonEnabled: Bool {
|
|
||||||
isPasswordConfirmValid && !isOldPasswordSame && isNewPasswordValid && isOldPasswordValid
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
// Действие для сохранения профиля
|
viewModel.changePassword(oldPassword: oldPassword, newPassword: newPassword)
|
||||||
print("oldPassword: \(oldPassword)")
|
|
||||||
print("newPassword: \(newPassword)")
|
|
||||||
print("confirmPassword: \(confirmPassword)")
|
|
||||||
}) {
|
}) {
|
||||||
Text(NSLocalizedString("Применить", comment: ""))
|
if viewModel.isLoading {
|
||||||
.foregroundColor(.white)
|
ProgressView()
|
||||||
.padding()
|
.progressViewStyle(CircularProgressViewStyle())
|
||||||
.frame(maxWidth: .infinity)
|
.padding()
|
||||||
.background(isButtonEnabled ? Color.blue : Color.gray)
|
.frame(maxWidth: .infinity)
|
||||||
.cornerRadius(8)
|
.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)
|
.disabled(!isButtonEnabled)
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
@ -149,5 +165,44 @@ struct ChangePasswordView: View {
|
|||||||
.listRowBackground(Color.clear)
|
.listRowBackground(Color.clear)
|
||||||
}
|
}
|
||||||
.navigationTitle(NSLocalizedString("Изменение пароля", comment: ""))
|
.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