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( when shouldShow: Bool, alignment: Alignment = .leading, @ViewBuilder placeholder: () -> Content ) -> some View { ZStack(alignment: alignment) { placeholder().opacity(shouldShow ? 1 : 0) self } } }