192 lines
No EOL
6.5 KiB
Swift
192 lines
No EOL
6.5 KiB
Swift
import SwiftUI
|
|
|
|
struct LoginView: View {
|
|
@EnvironmentObject var viewModel: PDSViewModel
|
|
|
|
@State private var serverURL = ""
|
|
@State private var password = ""
|
|
@State private var isLoggingIn = false
|
|
@State private var showingDebugInfo = false
|
|
@State private var debugLogs: [String] = []
|
|
|
|
var body: some View {
|
|
VStack(spacing: 20) {
|
|
Image(systemName: "server.rack")
|
|
.resizable()
|
|
.aspectRatio(contentMode: .fit)
|
|
.frame(width: 100, height: 100)
|
|
.foregroundColor(.blue)
|
|
|
|
Text("PDS Manager")
|
|
.font(.largeTitle)
|
|
.fontWeight(.bold)
|
|
|
|
VStack(alignment: .leading, spacing: 20) {
|
|
TextField("Server URL", text: $serverURL)
|
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
.autocapitalization(.none)
|
|
.disableAutocorrection(true)
|
|
.keyboardType(.URL)
|
|
.placeholder(when: serverURL.isEmpty) {
|
|
Text("example.com").foregroundColor(.gray.opacity(0.5))
|
|
}
|
|
|
|
SecureField("Admin Password", text: $password)
|
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
}
|
|
.padding(.horizontal, 40)
|
|
|
|
Button {
|
|
login()
|
|
} label: {
|
|
if isLoggingIn {
|
|
ProgressView()
|
|
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
|
.frame(maxWidth: .infinity)
|
|
.padding()
|
|
} else {
|
|
Text("Login")
|
|
.frame(maxWidth: .infinity)
|
|
.padding()
|
|
}
|
|
}
|
|
.background(Color.blue)
|
|
.foregroundColor(.white)
|
|
.cornerRadius(10)
|
|
.padding(.horizontal, 40)
|
|
.disabled(serverURL.isEmpty || password.isEmpty || isLoggingIn)
|
|
|
|
if let errorMessage = viewModel.errorMessage {
|
|
Text(errorMessage)
|
|
.foregroundColor(.red)
|
|
.font(.callout)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.horizontal, 40)
|
|
.padding(.vertical, 10)
|
|
.background(Color.red.opacity(0.1))
|
|
.cornerRadius(8)
|
|
}
|
|
|
|
Divider()
|
|
.padding(.vertical, 10)
|
|
.padding(.horizontal, 40)
|
|
|
|
Button {
|
|
enterDemoMode()
|
|
} label: {
|
|
HStack {
|
|
Image(systemName: "rectangle.fill.on.rectangle.fill.circle")
|
|
.imageScale(.medium)
|
|
Text("Enter Demo Mode")
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding()
|
|
}
|
|
.background(Color.green)
|
|
.foregroundColor(.white)
|
|
.cornerRadius(10)
|
|
.padding(.horizontal, 40)
|
|
|
|
Button("Show Debug Info") {
|
|
showingDebugInfo.toggle()
|
|
}
|
|
.font(.caption)
|
|
.foregroundColor(.secondary)
|
|
.padding(.top, 10)
|
|
|
|
if showingDebugInfo {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
ForEach(debugLogs, id: \.self) { log in
|
|
Text(log)
|
|
.font(.system(.caption, design: .monospaced))
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
.padding()
|
|
}
|
|
.frame(height: 200)
|
|
.background(Color.black.opacity(0.05))
|
|
.cornerRadius(8)
|
|
.padding(.horizontal, 40)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding(.top, 50)
|
|
.onAppear {
|
|
// Add some example connection info
|
|
debugLogs.append("Example URLs:")
|
|
debugLogs.append("bsky.social - Main Bluesky server")
|
|
debugLogs.append("bsky.atri.dad - Your local server")
|
|
debugLogs.append("localhost:3000 - Local development PDS")
|
|
}
|
|
}
|
|
|
|
private func enterDemoMode() {
|
|
debugLogs.append("Entering demo mode...")
|
|
viewModel.enableDemoMode()
|
|
}
|
|
|
|
private func login() {
|
|
isLoggingIn = true
|
|
|
|
// Add debug info
|
|
debugLogs.append("Attempting login to: \(serverURL)")
|
|
|
|
// Normalize the URL to ensure it has the correct format
|
|
var cleanURL = normalizeServerURL(serverURL)
|
|
debugLogs.append("Using final URL: \(cleanURL)")
|
|
|
|
Task {
|
|
await viewModel.login(serverURL: cleanURL, username: "admin", password: password)
|
|
isLoggingIn = false
|
|
|
|
if let error = viewModel.errorMessage {
|
|
debugLogs.append("Login failed: \(error)")
|
|
} else {
|
|
debugLogs.append("Login successful!")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func normalizeServerURL(_ url: String) -> String {
|
|
// Remove any leading/trailing whitespace
|
|
var cleanURL = url.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
// Strip any existing protocol prefix
|
|
if cleanURL.hasPrefix("http://") {
|
|
cleanURL = String(cleanURL.dropFirst(7))
|
|
debugLogs.append("Removed http:// prefix")
|
|
} else if cleanURL.hasPrefix("https://") {
|
|
cleanURL = String(cleanURL.dropFirst(8))
|
|
debugLogs.append("Removed https:// prefix")
|
|
}
|
|
|
|
// Remove trailing slash if present
|
|
if cleanURL.hasSuffix("/") {
|
|
cleanURL.removeLast()
|
|
debugLogs.append("Removed trailing slash")
|
|
}
|
|
|
|
// Add https:// prefix (always use HTTPS by default)
|
|
cleanURL = "https://\(cleanURL)"
|
|
debugLogs.append("Added https:// prefix")
|
|
|
|
return cleanURL
|
|
}
|
|
}
|
|
|
|
// Placeholder extension for TextField
|
|
extension View {
|
|
func placeholder<Content: View>(
|
|
when shouldShow: Bool,
|
|
alignment: Alignment = .leading,
|
|
@ViewBuilder placeholder: () -> Content
|
|
) -> some View {
|
|
ZStack(alignment: alignment) {
|
|
placeholder().opacity(shouldShow ? 1 : 0)
|
|
self
|
|
}
|
|
}
|
|
} |