feat: полноценное Pulse приложение с TabBar
- Auth: переключено на Pulse API (api.digital-home.site) вместо health - TabBar: Главная, Задачи, Привычки, Здоровье, Финансы - Models: TaskModels, HabitModels, FinanceModels, обновлённые AuthModels - Services: APIService (Pulse API), HealthAPIService (health отдельно) - Dashboard: обзор дня с задачами, привычками, readiness, балансом - Tasks: список, фильтр, создание, выполнение, удаление - Habits: список с прогресс-баром, отметка выполнения, стрики - Health: бывший DashboardView, HealthKit sync через health API key - Finance: баланс, список транзакций, добавление расхода/дохода - Health данные через x-api-key вместо JWT токена health сервиса
This commit is contained in:
@@ -9,9 +9,6 @@ struct LoginView: View {
|
||||
@State private var errorMessage = ""
|
||||
@State private var showPassword = false
|
||||
@State private var isRegistering = false
|
||||
@State private var forgotEmail = ""
|
||||
@State private var showForgotSheet = false
|
||||
@State private var forgotSent = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -20,9 +17,9 @@ struct LoginView: View {
|
||||
|
||||
VStack(spacing: 32) {
|
||||
VStack(spacing: 8) {
|
||||
Text("🫀").font(.system(size: 60))
|
||||
Text("Pulse Health").font(.largeTitle.bold()).foregroundColor(.white)
|
||||
Text(isRegistering ? "Создать аккаунт" : "Персональный дашборд здоровья")
|
||||
Text("⚡").font(.system(size: 60))
|
||||
Text("Pulse").font(.largeTitle.bold()).foregroundColor(.white)
|
||||
Text(isRegistering ? "Создать аккаунт" : "Управление жизнью")
|
||||
.font(.subheadline).foregroundColor(.white.opacity(0.6))
|
||||
}.padding(.top, 60)
|
||||
|
||||
@@ -86,15 +83,6 @@ struct LoginView: View {
|
||||
.cornerRadius(12)
|
||||
.disabled(isLoading || email.isEmpty || password.isEmpty)
|
||||
|
||||
// Forgot password (only on login)
|
||||
if !isRegistering {
|
||||
Button(action: { showForgotSheet = true }) {
|
||||
Text("Забыли пароль?")
|
||||
.font(.footnote)
|
||||
.foregroundColor(Color(hex: "00d4aa"))
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle login/register
|
||||
HStack {
|
||||
Text(isRegistering ? "Уже есть аккаунт?" : "Нет аккаунта?")
|
||||
@@ -114,9 +102,6 @@ struct LoginView: View {
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.sheet(isPresented: $showForgotSheet) {
|
||||
ForgotPasswordView(isPresented: $showForgotSheet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,10 +109,12 @@ struct LoginView: View {
|
||||
isLoading = true; errorMessage = ""
|
||||
Task {
|
||||
do {
|
||||
let response = try await APIService.shared.login(email: email.trimmingCharacters(in: .whitespaces), password: password)
|
||||
let profile = try? await APIService.shared.getProfile(token: response.token)
|
||||
let response = try await APIService.shared.login(
|
||||
email: email.trimmingCharacters(in: .whitespaces),
|
||||
password: password
|
||||
)
|
||||
await MainActor.run {
|
||||
authManager.login(token: response.token, name: response.user.name, apiKey: profile?.apiKey ?? "")
|
||||
authManager.login(token: response.token, user: response.user)
|
||||
}
|
||||
} catch let error as APIError {
|
||||
await MainActor.run { errorMessage = error.errorDescription ?? "Ошибка"; isLoading = false }
|
||||
@@ -141,10 +128,13 @@ struct LoginView: View {
|
||||
isLoading = true; errorMessage = ""
|
||||
Task {
|
||||
do {
|
||||
let response = try await APIService.shared.register(email: email.trimmingCharacters(in: .whitespaces), password: password, name: name)
|
||||
let profile = try? await APIService.shared.getProfile(token: response.token)
|
||||
let response = try await APIService.shared.register(
|
||||
email: email.trimmingCharacters(in: .whitespaces),
|
||||
password: password,
|
||||
name: name
|
||||
)
|
||||
await MainActor.run {
|
||||
authManager.login(token: response.token, name: response.user.name, apiKey: profile?.apiKey ?? "")
|
||||
authManager.login(token: response.token, user: response.user)
|
||||
}
|
||||
} catch let error as APIError {
|
||||
await MainActor.run { errorMessage = error.errorDescription ?? "Ошибка"; isLoading = false }
|
||||
@@ -154,49 +144,3 @@ struct LoginView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ForgotPasswordView: View {
|
||||
@Binding var isPresented: Bool
|
||||
@State private var email = ""
|
||||
@State private var isSent = false
|
||||
@State private var isLoading = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(hex: "0a0a1a").ignoresSafeArea()
|
||||
VStack(spacing: 24) {
|
||||
Text("Сброс пароля").font(.title2.bold()).foregroundColor(.white)
|
||||
if isSent {
|
||||
VStack(spacing: 12) {
|
||||
Text("✅").font(.system(size: 50))
|
||||
Text("Письмо отправлено!\nПроверьте \(email)")
|
||||
.foregroundColor(.white).multilineTextAlignment(.center)
|
||||
}
|
||||
Button("Закрыть") { isPresented = false }
|
||||
.padding().foregroundColor(Color(hex: "00d4aa"))
|
||||
} else {
|
||||
Text("Введите email и мы отправим ссылку для сброса пароля")
|
||||
.foregroundColor(.white.opacity(0.6)).multilineTextAlignment(.center)
|
||||
TextField("Email", text: $email)
|
||||
.keyboardType(.emailAddress).autocapitalization(.none)
|
||||
.padding().background(Color.white.opacity(0.1)).cornerRadius(12).foregroundColor(.white)
|
||||
Button(action: send) {
|
||||
if isLoading { ProgressView().tint(.black) }
|
||||
else { Text("Отправить").font(.headline).foregroundColor(.black) }
|
||||
}
|
||||
.frame(maxWidth: .infinity).padding()
|
||||
.background(Color(hex: "00d4aa")).cornerRadius(12).disabled(email.isEmpty || isLoading)
|
||||
Button("Отмена") { isPresented = false }.foregroundColor(.white.opacity(0.5))
|
||||
}
|
||||
}.padding(32)
|
||||
}
|
||||
}
|
||||
|
||||
func send() {
|
||||
isLoading = true
|
||||
Task {
|
||||
try? await APIService.shared.forgotPassword(email: email)
|
||||
await MainActor.run { isSent = true; isLoading = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user