chahinebrini c1edef8abd feat(magic): RebreakMagic device-binding + DNS profile
- backend: /api/magic/{register,devices,profile,release} + AdGuard provisioning + 24h cooldown
- prisma: magic_binding_fields migration (additive on UserDevice)
- mac-app: Phase 2 - Login + MacRegistration + Profile install
- marketing: landing section + /download/rebreakmagic + DMG
- lyra: forbidden phrases + RebreakMagic coach guidance
2026-06-02 09:15:19 +02:00

121 lines
4.0 KiB
Swift

import SwiftUI
struct LoginView: View {
@State private var email = ""
@State private var password = ""
@State private var isLoading = false
@State private var errorMessage: String?
let onSuccess: (AuthSession) -> Void
var body: some View {
VStack(spacing: 24) {
// Logo + Header
VStack(spacing: 12) {
Image(systemName: "shield.checkered")
.font(.system(size: 64))
.foregroundStyle(.blue)
Text("ReBreak Magic")
.font(.title.bold())
Text("Bitte mit deinem ReBreak-Account anmelden")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.padding(.top, 40)
// Form
VStack(spacing: 16) {
VStack(alignment: .leading, spacing: 6) {
Text("Email")
.font(.caption)
.foregroundStyle(.secondary)
TextField("name@example.com", text: $email)
.textFieldStyle(.roundedBorder)
.textContentType(.emailAddress)
.autocorrectionDisabled()
}
VStack(alignment: .leading, spacing: 6) {
Text("Passwort")
.font(.caption)
.foregroundStyle(.secondary)
SecureField("••••••••", text: $password)
.textFieldStyle(.roundedBorder)
.textContentType(.password)
}
if let error = errorMessage {
HStack(spacing: 8) {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundStyle(.red)
Text(error)
.font(.caption)
.foregroundStyle(.red)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
Button(action: handleSignIn) {
HStack {
if isLoading {
ProgressView()
.controlSize(.small)
.tint(.white)
}
Text(isLoading ? "Anmeldung läuft..." : "Anmelden")
.fontWeight(.medium)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 8)
}
.buttonStyle(.borderedProminent)
.disabled(email.isEmpty || password.isEmpty || isLoading)
}
.padding(.horizontal, 40)
.frame(maxWidth: 400)
Spacer()
// Signup Link
HStack(spacing: 4) {
Text("Noch kein Account?")
.font(.caption)
.foregroundStyle(.secondary)
Link("Jetzt registrieren →", destination: URL(string: "https://rebreak.org/signup")!)
.font(.caption)
}
.padding(.bottom, 20)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(nsColor: .windowBackgroundColor))
}
private func handleSignIn() {
Task {
isLoading = true
errorMessage = nil
do {
let session = try await AuthService.shared.signIn(email: email, password: password)
onSuccess(session)
} catch {
errorMessage = error.localizedDescription
}
isLoading = false
}
}
}
#Preview {
LoginView { session in
print("Logged in: \(session.email)")
}
.frame(width: 720, height: 600)
}